This file is indexed.

/usr/share/games/lightyears/code/tutor.py is in lightyears 1.4-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# 
# 20,000 Light Years Into Space
# This game is licensed under GPL v2, and copyright (C) Jack Whitham 2006-07.
# 
# 4 hours remaining.
# For a new player, the game is confusing and difficult to understand.
# Solution: tutorial mode.
# Can it be done?

import pygame , time
from pygame.locals import *

import stats , extra , resource
from primitives import *
from map_items import *


__tutor = None

def On(width):
    global __tutor
    __tutor = Tutor_Memory(width)

    Message(None, "welcome",
        "Welcome to 20,000 Light Years Into Space!",
        "You are playing the interactive tutorial. As you play, " +
        "information boxes like this one will appear on your screen. " +
        "When each message appears, read it, and then follow the " +
        "instructions that it provides.\n" +
        "To proceed, select the City, which is in the centre of the map.",
        False)


def City_Selected():
    Message("welcome", "citysel",
        "Your City",
        "To win the game, you must upgrade the city. Upgrades " +
        "raise the Technology Level of the City. Currently, the City " +
        "is at level 1 - you can see this on the right hand side of " +
        "the screen ('Tech Level'). When the City's Tech Level " + 
        "reaches " + str(DIFFICULTY.CITY_MAX_TECH_LEVEL) + ", you win.\n" +
        "You can upgrade the City at any time, but you should wait " +
        "until you have secured some more supplies of steam.\n" +
        "Now click on the structure to the right of the City.",
        False)

def Steam_Maker_Selected(first):
    if ( first ):
        Message("citysel", "steamsel",
            "The First Steam Maker",
            "You have just selected a Steam Maker. " +
            "Steam Makers are the " +
            "source of your steam supply. You'll need " +
            "to build more of these " +
            "in order to win the game, and you'll need " +
            "to connect them to your " +
            "city via Pipes.\n" +
            "Steam Makers have to be built on top of Steam " +
            "Sources. These are producing clouds of steam - " +
            "there are about ten of them, " +
            "dotted around the map.\n" +
            "Click 'Build Node', then click on one of the Steam " +
            "Sources. Ideally, you should choose one that's near the City.",
            False)
    else:
        Message("steamsel", "newsteam",
            "The New Steam Maker",
            "You've just built a new Steam Maker. " +
            "Or, more accurately, you've planned the construction " +
            "of a new Steam Maker. It won't actually be built until " +
            "it is connected by some Pipes to your City.\n" +
            "Your next job is to connect your Steam Maker to your City. " +
            "For this, you'll need to build a Pipe.\n" +
            "Click 'Build Pipe', then click first on the City, and " +
            "then on the new Steam Maker.",
            False)

def Pipe_Added():
    Message("newsteam", "building",
        "Building", 
        "Now you've planned the beginnings of your network. " +
        "You have two Steam Makers, one operational, the other " +
        "under construction, and Pipes linking them to the City.\n" +
        "Please wait while the new Steam Maker is " +
        "built. While construction is in progress, try clicking on the " +
        "Pipes and the Steam Makers. You'll see some information about " +
        "them, including the progress of construction!",
        False)

def All_Nodes_Finished():
    Message("building", "nodesready",
        "Building Complete!", 
        "Great! Your City is now supplied with Steam from two sources.\n" +
        "You can safely upgrade it now. Click 'Upgrade', and then " +
        "click on the City. The upgrade will begin immediately.",
        False)

def City_Upgrade_Running():
    Message("nodesready", "upgraderunning",
        "Upgrade In Progress",
        "The City upgrade is now in progress. As soon as you started " +
        "the upgrade, two things happened:\n" +
        "- You got an extra Work Unit. Now, two of your buildings " +
        "can be built simultaneously. Currently, one Work Unit is being " +
        "used to upgrade the City. The other one is spare.\n" +
        "- The City's steam requirement increased. Note the figures for " +
        "Supply and Demand on the right hand side. Demand has just " +
        "gone up. Fortunately, as you have two Steam Makers, Supply will " +
        "be able to match it.\n" +
        "Now you should strengthen your network. Later in the game, you'll " +
        "be under attack in a variety of nasty ways.\n" +
        "To do that, create a new Node somewhere between the two Steam " +
        "Makers. Nodes are just connection points. They store steam, " +
        "but they don't produce it or consume it.\n" +
        "Click 'Build Node' and then click on the position of " +
        "your new node.",
        False)
        
def Node_Selected():
    Message("upgraderunning", "makinglinks",
        "Making New Links",
        "Your network's strength depends on the number of links. " +
        "Generally, the more routes between two points, the better. The only " +
        "disadvantage of adding new routes is that they consume Work Units " +
        "during construction and repair. Don't worry about that for now.\n" +
        "Now build three new Pipes, each running " +
        "from your new Node: one to the City, and two for the two " +
        "Steam Makers. These connections make your network stronger. " + 
        "Wait for these to be built.",
        False)

def Number_Of_Pipes_Is(pipe_count):
    if ( pipe_count >= 5 ):
        Message("makinglinks", "networkbasics",
            "Almost There...",
            "Excellent. Your network is now strong enough to withstand " +
            "attacks. You are almost ready to begin playing for real! " +
            "But before you do, you have to understand how steam flows. " +
            "Please click on one of the pipes.",
            False)

def Pipe_Selected():
    Message("networkbasics", "networkbasics2",
        "Steam is Water..",
        "To understand the network, it helps to imagine that the steam " +
        "is just (liquid) water. The City is like a drain: it is draining " +
        "water out of the system. The Steam Makers are like taps: they are " +
        "adding water to the system. In both cases, the flow rate " +
        "depends only on the amount of Upgrades you have applied.\n" +
        "Flow rates are given in Flow Units (U). Flow rates are represented " +
        "by the green dots that move along the pipes. The movement of the " +
        "dots is proportional to the velocity of the flow.\n" +
        "Now click on one of the nodes.",
        False)

def Any_Node_Selected():
    Message("networkbasics2", "networkbasics3",
        "Pressure..",
        "Water always finds it's own level. If you have two water tanks " +
        "and you connect them with a pipe, the water level will try to " +
        "equalise. The same effect happens here, but with steam.\n" +
        "All of the Nodes are steam storage tanks. The 'level' is " +
        "steam pressure. It is constantly trying to equalise.\n" +
        "Pressure is given in Pressure Units (P). " +
        "Now you've selected a Node, you can see its pressure on the right " +
        "hand side. Pressure is limited: to increase the limit, you can " +
        "upgrade the node, but there's no need to do that yet.\n" +
        "You lose the game if the pressure at the City falls too low (below " +
        str(PRESSURE_DANGER) + " P) for more than a certain " +
        "period of time (" + str(DIFFICULTY.GRACE_TIME) + " days, in " +
        "Beginner mode). To avoid that, ensure that Supply matches Demand.\n" +
        "We're almost done. Please click on a pipe again.",False)

def Pipe_Selected_2():
    Message("networkbasics3", "networkbasics4",
        "Rules Of Thumb",
        "The steam pressures in your Nodes will never equalise, because " +
        "steam is being added and removed from the network. However, you " +
        "may wonder why pressure and flow vary so much.\n" +
        "The answer is Resistance. Each pipe has only a limited capacity. " +
        "There's a limit to the rate at which steam can move, imposed " +
        "by each pipe. Resistance is a hidden property: you can't see " +
        "it, but it affects the game. Longer pipes have more resistance " +
        "than short ones.\n" +
        "All of this will reduce to a few rules of thumb.\n" +
        "- Build one Steam Maker per City Upgrade.\n" +
        "- Make lots of Pipes.\n" +
        "- Don't do an Upgrade unless the steam pressure at your City " +
        "is stable.\n" +
        "Now you're ready to experience an attack. Please click on your " +
        "City.", False)

def City_Selected_2():
    Message("networkbasics4", "attack",
        "Alien Attack",
        "The Aliens are coming!\n" +
        "You can't fight the aliens: all you can do is rebuild " +
        "your network. They'll try to destroy your Nodes and Pipes: " +
        "they'll only be able to put your Nodes out of action, but " +
        "they can destroy your Pipes.\n" +
        "The attack will last for two minutes. If sound is enabled, " +
        "you will hear an alarm " +
        "before each wave of alien attackers.\n" +
        "When you're ready for them, " +
        "click on the planet's surface.", False)

def Nothing_Selected():
    Message("attack", "running",
        "Alien Attack",
        "Remember:\n" +
        "- Rebuild Pipes that are destroyed by aliens.\n" +
        "- You can add new Pipes, Nodes, and Steam Makers.\n" +
        "- Your goal is always to upgrade the City.\n" +
        "The Aliens disappear after 2 minutes. Good luck." , True)


def Aliens_Gone():
    Message("running", "ended",
        "You Survived",
        "Good work! You survived the attack.\n" +
        "Now you are ready to play for real. Now click " +
        "'Exit to Main Menu' and begin your first game!\n" +
        "Good luck, and have fun.", True)




def Notify_Select(item):
    global __tutor
    if ( __tutor == None ):
        return

    if ( isinstance(item, Node) ):
        Any_Node_Selected()


    if ( isinstance(item, City_Node) ):
        City_Selected()
        City_Selected_2()
    elif ( isinstance(item, Well_Node) ):
        first = item.tutor_special
        Steam_Maker_Selected(first) # note change of terminology :(
    elif ( isinstance(item, Node) ):
        Node_Selected()
    elif ( isinstance(item, Pipe) ):
        Pipe_Selected()
        Pipe_Selected_2()
    elif ( item == None ):
        Nothing_Selected()

def Notify_Add_Pipe():
    global __tutor
    if ( __tutor == None ):
        return

    Pipe_Added()

def Notify_Add_Node(n):
    global __tutor
    if ( __tutor == None ):
        return

    #Node_Added(n)

def Examine_Game(g):
    global __tutor
    if ( __tutor == None ):
        return

    # test 1 - are all nodes finished?
    all_finished = True
    for n in g.net.node_list:
        all_finished = all_finished and ( not n.Needs_Work() )

    if ( all_finished ):
        All_Nodes_Finished()

    # test 2 - has the city begun an upgrade?
    if ( g.net.hub.city_upgrade != 0 ):
        City_Upgrade_Running()

    # test 3 - number of pipes.
    pipe_count = 0
    for p in g.net.pipe_list:
        if (( not p.Is_Destroyed() ) and ( not p.Needs_Work() )):
            pipe_count += 1

    # test 4 - season
    if ( g.season in [ SEASON_STORM , SEASON_QUAKE ] ):
        Aliens_Gone()
        g.game_running = False

    Number_Of_Pipes_Is(pipe_count)

def Off():
    global __tutor
    __tutor = None

def Message(previous_msg_name, this_msg_name, 
            title, text, sf):
    global __tutor
    t = __tutor
    if ( t != None ):
        t.Add_Message((previous_msg_name, this_msg_name,
                title, text, sf))

def Draw(screen, g):
    global __tutor
    t = __tutor
    if ( t != None ):
        t.Draw(screen, g)

def Permit_Season_Change():
    global __tutor
    t = __tutor
    if ( t != None ):
        return t.Permit_Season_Change()
    else:
        return True

def Frozen():
    return False

def Active():
    global __tutor
    return ( __tutor != None )

def Has_Changed():
    global __tutor
    t = __tutor
    if ( t != None ):
        x = t.update
        t.update = False
        return x
    else:
        return False


class Tutor_Memory:
    def __init__(self, w):
        self.current_msg_name = None
        self.current_msg_surf = None
        self.current_msg_popup = False
        self.width = w
        self.update = False
        self.permit_season_change = False

    def Add_Message(self,(previous_msg_name, this_msg_name,
                title, text, sf)):

        if ( self.current_msg_name == previous_msg_name ):
            self.current_msg_name = this_msg_name
            self.current_msg_surf = self.__Draw(title, text)
            self.current_msg_popup = True
            self.update = True
            self.permit_season_change = sf

    def Permit_Season_Change(self):
        return self.permit_season_change

    def Draw(self, screen, g):
        if ( self.current_msg_popup ):
            r = self.current_msg_surf.get_rect()
            r.top = r.left = 30
            screen.blit(self.current_msg_surf, r)

    def __Draw(self, title, text):
        height = 10
        (surf, height) = self.__Draw_H(title, text, height)
        (surf, height) = self.__Draw_H(title, text, height)
        return surf
    
    def __Draw_H(self, title, text, height):
        width = self.width
        margin = 10
        fs1 = 12
        fs2 = 14
        newline_gap = 12

        surf = pygame.Surface((width, height))
        bbox = surf.get_rect()
        extra.Tile_Texture(surf, "006metal.jpg", bbox)
        
        tsurf = stats.Get_Font(fs1).render(title, True, (250,250,200))
        tsurf_r = tsurf.get_rect()
        tsurf_r.center = bbox.center
        tsurf_r.top = margin

        surf.blit(tsurf, tsurf_r.topleft)

        y = tsurf_r.bottom + margin
        # line edging for title
        extra.Line_Edging(surf, Rect(0,0,width,y), True)

        y += margin
        x = margin
        height = y
        
        while ( len(text) != 0 ):
            newline = False
            i = text.find(' ')
            j = text.find("\n")

            if (( j >= 0 ) and ( j < i )):
                i = j
                newline = True

            if ( i < 0 ):
                i = len(text)

            word = text[ : i ] + " "
            text = text[ i + 1 : ].lstrip()

            tsurf = stats.Get_Font(fs2).render(word, True, (250,200,250))
            tsurf_r = tsurf.get_rect()
            tsurf_r.topleft = (x,y)
            if ( tsurf_r.right > ( width - 5 )):
                # Wrap.
                y += tsurf_r.height
                x = margin
                tsurf_r.topleft = (x,y)

            surf.blit(tsurf, tsurf_r.topleft)
            x = tsurf_r.right
            height = tsurf_r.bottom + margin

            if ( newline ):
                x = margin
                y = tsurf_r.bottom + newline_gap

        # line edging for rest of box
        extra.Line_Edging(surf, bbox, True)

        return (surf, height)