source: 2015/26/ohjaajat/HillbillyRun/HillbillyRun/HillbillyRun/HillbillyRun/HillbillyRun.cs @ 6220

Revision 6220, 22.1 KB checked in by sieerinn, 5 years ago (diff)

Ryömijöitä paranneltu.

Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using Jypeli;
5using Jypeli.Assets;
6using Jypeli.Controls;
7using Jypeli.Effects;
8using Jypeli.Widgets;
9
10/* Muistutuksia.
11 *
12 * Layerit
13 *   3 -
14 *   2 -
15 *   1 -
16 *   0 -
17 *  -1 - Näkyvä maanpinta.
18 *  -2 - Talot taustalla.
19 * 
20 * CollisionIgnoreGroupit
21 *  3 - Ryömivät ja liekit. //TODO: lisätä noidatkin tänne
22 *  2 - Kärry.
23 *
24*/
25
26
27//Luokka maajussien erikoisuuksille.
28class HillBilly : PlatformCharacter
29{
30    private IntMeter lifeCounter = new IntMeter(2, 0, 2);
31    public IntMeter Life
32    {
33        set { lifeCounter = value; }
34        get { return lifeCounter; }
35    }
36
37
38
39    public HillBilly(double leveys, double korkeus)
40        : base(leveys, korkeus)
41    {
42
43    }
44}
45
46/// <summary>
47/// An MMO where you go to war with mages in the lava kingdom.
48/// </summary>
49public class HillbillyRun : PhysicsGame
50{
51    private List<HillBilly> players = new List<HillBilly>();
52    private List<HillBilly> startingPlayers = new List<HillBilly>();
53
54    //private List<double> playerPositionsX = new List<double>();
55    //private List<double> playerPositionsY = new List<double>();
56
57    private const double PLAYER_SPEED = 180;
58    private const double TILE_SIZE = 70;
59
60    private double cameraTargetX; // Sijainti jossa kameran pitäisi olla.
61    private Vector cameraTarget;
62
63    private double cameraOffset = 400; // TODO: Tämän voisi ehkä laskea jostain (esim. ikkunan leveydestä kolmasosa tai jotain).
64    private double cameraSpeed = 2.0; // Kameran liikkumisnopeus.
65
66    # region Images
67    private Image[] groundImages = LoadImages("ground");    //Näitä ei tosin kaikkia kenties jaksa tehdä erilaisiksi levelistä riippuen
68    private Image[] groundTopImages = LoadImages("ground_top");
69    private Image[] foregroundDecorations = LoadImages("viljaa");
70    private Image[] blockImages = LoadImages("box2");
71
72    private Image[] houseImages = LoadImages("house", "houseburned");
73
74    private Image cartImage = LoadImage("cart");
75    private Image milkImage = LoadImage("tonkkaitem");
76
77    private Image cartWheelImage = LoadImage("cartwheel");
78
79    private Image smokeImage1 = LoadImage("smoke1");
80    private Image smokeImage2 = LoadImage("smoke2");
81
82    private Image forestBackgroundImage = LoadImage("forestbackground");
83
84    private Animation crawl;
85    private Animation blaze;
86
87    private Animation pitchforkAttack;
88    private Animation pitchforkWalk;
89    private Animation milkThrow;
90    private Animation milkWalk;
91    private Animation normalWalk;
92    private Animation playerIdle;
93    private Animation playerJump;
94    private Animation playerFall;
95    #endregion
96
97    private double leftCamLimit;
98    private double rightCamLimit;
99    private double bottomCamLimit;
100
101    private int levelNumber = 0;
102
103    public override void Begin()
104    {
105        IntroSequence();
106        //MainMenu();
107        //StartGame();
108    }
109
110    #region Intro
111    void IntroSequence()
112    {
113        Level.Background.Color = Color.Black;
114
115        Keyboard.Listen(Key.Escape, ButtonState.Pressed, SkipIntro, "Skip");
116
117        ShowMadeWithJypeli();
118
119        Timer.SingleShot(4.0, delegate // TODO get rid of fixed amount of seconds
120        {
121            ShowBackgroundStory();
122        });
123
124
125
126    }
127
128    void ShowMadeWithJypeli()
129    {
130        Image madewithImage = LoadImage("madewithjypeli");
131
132        double w = Window.Width / 4;
133        double h = w * (madewithImage.Height / (double)madewithImage.Width); // take aspect ratio from the picture
134
135        GameObject madewithjypeliObject = new GameObject(w, h);
136        madewithjypeliObject.Image = madewithImage;
137        Add(madewithjypeliObject);
138
139        // Tässä oli jotain epätehokasta feidailua tuolle Made with Jypeli -kuvalle. Ei tunnu päivittyvän joka framella tai on muuten vain tosi raskas...
140
141        //fadeTimer = new Timer();
142        //fadeTimer.Interval = 0.5;
143        //fadeTimer.Timeout += delegate
144        //{
145        //    madewithjypeliObject.Image = LerpImage(madewithjypeliObject.Image, Color.Black, 0.6);
146        //};
147        //fadeTimer.Start(5);
148
149        Timer.SingleShot(1.2, delegate
150        {
151            madewithjypeliObject.Destroy();
152        });
153
154    }
155
156    Image LerpImage(Image original, Color targetColor, double ratio)
157    {
158        Image newImage = new Image(original.Width, original.Height, Color.Transparent);
159        for (int y = 0; y < original.Height; y++)
160        {
161            for (int x = 0; x < original.Width; x++)
162            {
163                newImage[y, x] = Color.Lerp(original[y, x], targetColor, ratio);
164            }
165        }
166        return newImage;
167    }
168
169    void ShowBackgroundStory()
170    {
171        ShowStoryText(new Queue<string>(new[] { "Once upon a time...", "...blaa blaa blaa.", "Blaa." }));
172    }
173
174    void ShowStoryText(Queue<string> messages)
175    {
176        // Mennään alkuvalikkoon jos ei ole enää viestejä.
177        if (messages.Count == 0)
178        {
179            MainMenu();
180            return;
181        }
182
183        // Näytetään nykyinen viesti.
184        var storyLabel = new Label(messages.Dequeue()) { TextColor = Color.Black, TextScale = new Vector(3, 3) };
185        Add(storyLabel);
186
187        var textTimer = new Timer { Interval = 0.05 };
188        textTimer.Timeout += () => storyLabel.TextColor = Color.Lerp(storyLabel.TextColor, Color.White, 0.05);
189        textTimer.Start();
190
191        // Näytetään seuraava viesti muutaman sekunnin päästä.
192        Timer.SingleShot(4, delegate { storyLabel.Destroy(); ShowStoryText(messages); });
193    }
194
195    void SkipIntro()
196    {
197        ClearTimers();
198        ClearAll();
199        Keyboard.Clear();
200        MainMenu();
201    }
202
203    void MainMenu()
204    {
205        Level.Background.Color = Color.DarkBlue;
206
207        MultiSelectWindow mainmenu = new MultiSelectWindow("Main menu",
208"Start game", "Credits", "Exit");
209        mainmenu.AddItemHandler(0, StartGame);
210        mainmenu.AddItemHandler(1, ShowCredits);
211        mainmenu.AddItemHandler(2, Exit);
212        mainmenu.DefaultCancel = 2;
213        Add(mainmenu);
214    }
215
216    void ShowCredits()
217    {
218        MessageWindow win = new MessageWindow("This game was made in one week by\nSimo Rinne,\nEmma Heikura,\n and Jouni Potila\n using University of Jyväskylä's\n game programming library Jypeli");
219        win.Closed += delegate
220        {
221            ClearAll();
222            MainMenu();
223        };
224        Add(win);
225    }
226    #endregion
227
228    void StartGame()
229    {
230        Initializing();
231        CreateLevel();
232        ScreenSettings();
233
234        // Taustatestailua.
235        GameObject forestBackground = new GameObject(Level.Width, Level.Height);
236        forestBackground.Image = forestBackgroundImage;
237        forestBackground.TextureWrapSize = new Vector(5, 1);
238        Add(forestBackground, -3);
239        Layers[-3].RelativeTransition = new Vector(0.5, 1.0);
240        Level.Background.CreateGradient(Color.Black, Color.SkyBlue);
241
242        // Testailen tässä kärryn luomista.
243        //double size = 0.7;
244        //PhysicsObject cart = new PhysicsObject(400 * size, 80 * size);
245        //cart.X = -1600;
246        //cart.Y = 200;
247        //cart.Image = cartImage;
248        //cart.CollisionIgnoreGroup = 2;
249        //Add(cart);
250        //PhysicsObject cartWheel = new PhysicsObject(160 * size, 160 * size, Shape.Circle);
251        //cartWheel.Image = cartWheelImage;
252        //cartWheel.Position = cart.Position + new Vector(-110, -30) * size;
253        //cartWheel.CollisionIgnoreGroup = 2;
254        //Add(cartWheel);
255        //AxleJoint joint = new AxleJoint(cart, cartWheel);
256        //Add(joint);
257    }
258
259    void Initializing()
260    {
261        crawl = LoadAnimation("crawl");
262        blaze = LoadAnimation("flame");
263        pitchforkAttack = LoadAnimation("attack");
264        pitchforkWalk = LoadAnimation("hanko");
265        milkThrow = LoadAnimation("heitto");
266        milkWalk = LoadAnimation("tonkka");
267        normalWalk = LoadAnimation("pwalk");
268        playerIdle = LoadAnimation("idle");
269        playerJump = LoadAnimation("jump");
270        playerFall = LoadAnimation("fall");
271    }
272
273    void ScreenSettings()
274    {
275        Window.Width = 1800;
276        Window.Height = 900;
277
278        leftCamLimit = Level.Left + Window.Width / 2.0;
279        rightCamLimit = Level.Right - Window.Width / 2.0;
280        bottomCamLimit = Level.Bottom + Window.Height / 2.0;
281
282        Camera.X = cameraTargetX = leftCamLimit;
283
284        Timer cameraTimer = new Timer();
285        cameraTimer.Interval = 1 / 30.0;
286        cameraTimer.Timeout += UpdateCamera;
287        cameraTimer.Start();
288    }
289
290    #region LevelCreation
291    void CreateLevel()
292    {
293        Gravity = new Vector(0, -1000);
294
295        ColorTileMap level = ColorTileMap.FromLevelAsset("level" + levelNumber);
296        level.SetTileMethod(Color.Black, CreateGroundTop);
297        level.SetTileMethod(Color.Brown, CreateGround);
298
299        level.SetTileMethod(Color.Cyan, CreateHouse);
300        level.SetTileMethod(Color.PaintDotNetBlue, CreateBurnedHouse);
301
302        level.SetTileMethod(Color.Gold, CreatePlayer);
303        level.SetTileMethod(Color.Harlequin, CreateCart);
304        level.SetTileMethod(Color.White, CreateBlockObject);
305        level.SetTileMethod(Color.DarkGray, CreateMilk);
306        level.SetTileMethod(Color.Gray, CreateCrawly);
307        level.SetTileMethod(Color.Red, CreateDummy, Color.Red);    //TODO: CreateWitch
308        level.SetTileMethod(Color.Rose, CreateFlame);
309        //level.SetTileMethod(Color.Azure, CreateDummy, Color.Azure);   //TODO: CreateSmoke
310        //level.SetTileMethod(Color.Orange, CreateDummy, Color.Orange); //TODO: CreateTombstone
311        level.Optimize(Color.Black, Color.Brown);
312        level.Execute(TILE_SIZE, TILE_SIZE);
313
314
315        //TileMap level = TileMap.FromLevelAsset("level" + levelNumber);
316        //level.SetTileMethod('P', CreatePlayer);
317        //level.SetTileMethod('#', CreateGroundTop);
318        //level.SetTileMethod('%', CreateGround);
319        //level.Optimize('#', '%');
320        //level.Execute(TILE_SIZE, TILE_SIZE);
321        //Level.CreateBorders(true);
322
323        SetControls();
324    }
325
326    void CreateFlame(Vector position, double width, double height)
327    {
328        PhysicsObject flame = PhysicsObject.CreateStaticObject(width, height * 2);
329        //flame.Image = flameImage;
330        flame.Color = Color.Red;
331        double hieman = height * 0.3;
332        flame.Position = position - new Vector(0, hieman);
333        flame.CollisionIgnoreGroup = 3;
334        flame.Tag = "burn";
335        flame.Animation = new Animation(blaze) { FPS = RandomGen.NextInt(20, 26) };
336        flame.Animation.Step(RandomGen.NextInt(0, 12));
337        flame.Animation.Resume();
338        Add(flame);
339
340        Smoke savu = new Smoke();
341        savu.ParticleImage = smokeImage1;
342        savu.OuterParticleImage = smokeImage2;
343        savu.MaxLifetime = 1.0;
344        savu.MaxScale = 200;
345        savu.MinScale = 10;
346        savu.Position = flame.Position;
347        Wind = new Vector(-10, 0);
348        Add(savu);
349    }
350
351    void CreateBlockObject(Vector position, double width, double height)
352    {
353        // Säädin vähän tätä jotta laatikko näkyy hyvin. Pitää ehkä tehdä laatikolle ihan oma metodi.
354
355        PhysicsObject block = PhysicsObject.CreateStaticObject(width, height);
356        block.IsVisible = false;
357        //block.Image = blockImages[levelNumber];
358        block.Position = position;
359        block.Y -= height / 2.0;
360        Add(block, 1);
361
362        GameObject visibleBlock = new GameObject(width, height * 1.5);
363        visibleBlock.Position = position;
364        visibleBlock.Image = blockImages[levelNumber];
365        visibleBlock.Y -= height / 2.0;
366        Add(visibleBlock, -1);
367    }
368
369    void CreateMilk(Vector position, double width, double height)
370    {
371        double size = 30;
372        double ratio = milkImage.Height / milkImage.Width;
373        PhysicsObject milk = new PhysicsObject(size, size * ratio);
374        milk.Image = milkImage;
375        milk.Position = position;
376        milk.Tag = "milk";
377        Add(milk);
378    }
379
380    void CreateCrawly(Vector position, double width, double height)
381    {
382        PlatformCharacter crawly = new PlatformCharacter(width * 2, height * 0.6);
383        crawly.Position = position;
384        crawly.Color = Color.Gray;
385        crawly.Animation = new Animation(crawl);
386        crawly.Animation.FPS = RandomGen.NextInt(18, 24);
387        crawly.Animation.Step(RandomGen.NextInt(0, 16));
388        crawly.Animation.Resume();
389        crawly.Tag = "burn";
390        crawly.CollisionIgnoreGroup = 3;
391        Add(crawly, 1);
392
393        GameObject flame = new GameObject(width * 1.4, height * 2);
394        flame.Animation = new Animation(blaze) { FPS = RandomGen.NextInt(20, 26) };
395        flame.Animation.Step(RandomGen.NextInt(0, 12));
396        flame.Animation.Resume();
397        Add(flame);
398
399        // Pakko liikutella näin koska lapsioliona liekki näkyisi ryömijän päällä.
400        Timer flameMover = new Timer { Interval = 0.05 };
401        flameMover.Timeout += delegate
402        {
403            double hieman = height * 0.75;
404            flame.Position = crawly.Position + new Vector(0, hieman);
405        };
406        flameMover.Start();
407
408        PlatformWandererBrain brain = new PlatformWandererBrain();
409        brain.Speed = 20;
410        brain.FallsOffPlatforms = true;
411
412        crawly.Brain = brain;
413
414        // Sammuu törmätessään maitoon.
415        AddCollisionHandler(crawly, "milkparticle", delegate(PhysicsObject c, PhysicsObject particle)
416        {
417            particle.Destroy();
418            crawly.Brain = null;
419            crawly.Tag = "";
420            flame.Destroy();
421        });
422    }
423
424    void CreateHouse(Vector position, double width, double height)
425    {
426        // Näkyvä talo
427        GameObject house = new GameObject(width * 10, height * 8);
428        house.Image = houseImages[0];
429        house.Position = position - new Vector(0, height * 2.5);
430        Add(house, -2);
431
432        //Seisottava tasanne
433        PhysicsObject platform = PhysicsObject.CreateStaticObject(house.Width - (TILE_SIZE * 3.25), 1);     //Tätä kohtaa on nyt vähän hakemalla haettu.
434        platform.IsVisible = false;
435        platform.Position = new Vector(house.X + (TILE_SIZE * 0.5), house.Top);
436        Add(platform);
437    }
438
439    void CreateBurnedHouse(Vector position, double width, double height)
440    {
441        GameObject house = new GameObject(width * 10, height * 8);
442        house.Image = houseImages[1];
443        house.Position = position - new Vector(0, height * 2.5);
444        Add(house, -2);
445
446        PhysicsObject platform = PhysicsObject.CreateStaticObject(house.Width * 0.25, 1);
447        platform.IsVisible = false;
448        platform.Position = new Vector(house.X + platform.Width * 0.5, house.Top);
449        Add(platform);
450    }
451
452    private void CreateCart(Vector position, double width, double height)
453    {
454        double size = 0.7;
455        PhysicsObject cart = new PhysicsObject(400 * size, 80 * size);
456        cart.Position = position;
457        cart.Image = cartImage;
458        cart.CollisionIgnoreGroup = 2;
459        Add(cart);
460        PhysicsObject cartWheel = new PhysicsObject(160 * size, 160 * size, Shape.Circle);
461        cartWheel.Image = cartWheelImage;
462        cartWheel.Position = cart.Position + new Vector(-110, -30) * size;
463        cartWheel.CollisionIgnoreGroup = 2;
464        Add(cartWheel);
465        AxleJoint joint = new AxleJoint(cart, cartWheel);
466        Add(joint);
467    }
468
469    private void CreateDummy(Vector position, double width, double height, Color color)
470    {
471        PhysicsObject dummy = PhysicsObject.CreateStaticObject(width, height);
472        dummy.Position = position;
473        dummy.Color = color;
474        Add(dummy);
475    }
476
477    void CreatePlayer(Vector position, double width, double height)
478    {
479        HillBilly player = new HillBilly(width, height * 2);
480        player.Shape = Shape.Rectangle;
481        player.Position = position + new Vector(0, height * 0.5);
482        player.Color = Color.White;
483        player.AnimWalk = new Animation(normalWalk);
484        player.AnimIdle = new Animation(playerIdle);
485        player.AnimJump = new Animation(playerJump);
486        player.AnimFall = new Animation(playerFall);
487        player.AnimFall.StopOnLastFrame = true;
488        players.Add(player);
489        startingPlayers.Add(player);
490        Add(player);
491        AddCollisionHandler(player, "burn", delegate(PhysicsObject p, PhysicsObject t)
492        {
493            HillBilly billy = p as HillBilly;
494            billy.Life.Value--;
495
496        });
497
498        player.Life.LowerLimit += delegate
499        {
500            if (players.Count < 2)
501            {
502                Loss();
503            }
504            players.Remove(player); player.Destroy();
505        };
506
507        AddCollisionHandler(player, "milk", CollectMilk);
508    }
509
510    void CreateGroundTop(Vector position, double width, double height)
511    {
512        // Puolet pienempi näkymätön palikka alla johon törmää.
513        PhysicsObject ground = PhysicsObject.CreateStaticObject(width, height / 2.0);
514        ground.IsVisible = false;
515        ground.Position = position - new Vector(0.0, TILE_SIZE / 4.0);
516        Add(ground);
517
518        // Maanpinnan näkyvä osa.
519        GameObject visibleGround = new GameObject(width, height);
520        visibleGround.Image = groundImages[levelNumber];
521        visibleGround.Position = position;
522        visibleGround.TextureWrapSize = new Vector(width / TILE_SIZE, height / TILE_SIZE);
523        Add(visibleGround, -1);
524
525        int probability = RandomGen.NextInt(100);
526        bool wheat = probability < 20;
527
528        if (wheat)
529        {
530            GameObject wheatBlock = new GameObject(width * 1.5, height * 2);
531            wheatBlock.Image = foregroundDecorations[0];
532            wheatBlock.X = visibleGround.X;
533            wheatBlock.Bottom = visibleGround.Bottom;
534            wheatBlock.TextureWrapSize = new Vector(width / TILE_SIZE, height / TILE_SIZE);
535            Add(wheatBlock, 2);
536        }
537    }
538
539    void CreateGround(Vector position, double width, double height)
540    {
541        PhysicsObject ground = PhysicsObject.CreateStaticObject(width, height);
542        ground.Image = groundTopImages[levelNumber];
543        ground.Position = position;
544        ground.TextureWrapSize = new Vector(width / TILE_SIZE, height / TILE_SIZE);
545        Add(ground);
546    }
547    #endregion
548
549    void SetControls()
550    {
551        Keyboard.Listen(Key.A, ButtonState.Down, delegate { startingPlayers[0].Walk(-PLAYER_SPEED); }, "Player 1 moves left");
552        Keyboard.Listen(Key.D, ButtonState.Down, delegate { startingPlayers[0].Walk(PLAYER_SPEED); }, "Player 1 moves right");
553        Keyboard.Listen(Key.W, ButtonState.Down, delegate { startingPlayers[0].Jump(1000); }, "Player 1 jumps");
554
555        Keyboard.Listen(Key.Left, ButtonState.Down, delegate { startingPlayers[1].Walk(-PLAYER_SPEED); }, "Player 2 moves left");
556        Keyboard.Listen(Key.Right, ButtonState.Down, delegate { startingPlayers[1].Walk(PLAYER_SPEED); }, "Player 2 moves right");
557        Keyboard.Listen(Key.Up, ButtonState.Down, delegate { startingPlayers[1].Jump(1000); }, "Player 2 jumps");
558
559        Keyboard.Listen(Key.F1, ButtonState.Pressed, ShowControlHelp, "Show help");
560        Keyboard.Listen(Key.Escape, ButtonState.Pressed, ConfirmExit, "Exit game");
561    }
562
563    void Loss()
564    {
565        //TODO: Sumthin' flashy for our fallen friends.
566        Exit();
567    }
568
569    void CollectMilk(PhysicsObject player, PhysicsObject milk)
570    {
571        HillBilly billy = player as HillBilly;
572        if (billy == null)
573            return;
574
575        milk.Destroy();
576        billy.AnimWalk = new Animation(milkWalk);
577        billy.AnimIdle = new Animation(milkWalk.CurrentFrame);
578    }
579
580    #region Camera
581    void UpdateCamera()
582    {
583        double minY = players.Min(p => p.Y);
584        double maxY = players.Max(p => p.Y);
585        double minX = players.Min(p => p.X) + cameraOffset;
586
587        Vector minPosition = new Vector(Math.Max(minX, cameraTargetX), minY);
588        Vector maxPosition = new Vector(minX, maxY);
589
590        cameraTarget = (minPosition + maxPosition) * 0.5;
591        cameraTarget.X = Math.Max(cameraTargetX, minX);     //Lellllll.
592
593        //cameraTarget.Y = Math.Max(cameraTarget.X, Level.Bottom + Window.Height/2.0);
594
595        cameraTargetX = Math.Max(cameraTargetX, minX);
596
597        cameraTarget.X = Math.Min(cameraTargetX, rightCamLimit);
598        cameraTarget.Y = Math.Max(cameraTarget.Y, bottomCamLimit);
599
600        double windowMax = Camera.ScreenToWorld(new Vector(Window.Width / 2.0, 0)).X;
601        double windowMin = Camera.ScreenToWorld(new Vector(-Window.Width / 2.0, 0)).X;
602        foreach (var player in players)
603        {
604            player.Left = Math.Max(player.Left, windowMin);
605            player.Right = Math.Min(player.Right, windowMax);
606        }
607    }
608
609    protected override void Update(Time time)
610    {
611        Camera.Position += (cameraTarget - Camera.Position) * time.SinceLastUpdate.TotalSeconds * cameraSpeed;
612        //Camera.X += (cameraTargetX - Camera.X) * time.SinceLastUpdate.TotalSeconds * cameraSpeed;
613
614
615        base.Update(time);
616    }
617
618    /// <summary>
619    /// Yritän leikkiä kameralla. Vielä varmaan hetken pidempään.
620    /// </summary>
621    /// <param name="gameTime"></param>
622    /*
623    protected override void Update(Microsoft.Xna.Framework.GameTime gameTime)
624    {
625        foreach(HillBilly player in players)
626        {
627            playerPositionsX.Add(player.Position.X);
628            playerPositionsY.Add(player.Position.Y);
629        }
630
631        double maxX = playerPositionsX.Max();
632        double maxY = playerPositionsY.Max();
633        double minX = playerPositionsX.Min();
634        double minY = playerPositionsY.Min();
635
636        Vector minPosition = new Vector(minX, minY);
637        Vector maxPosition = new Vector(maxX, maxY);
638
639        Camera.Position = (minPosition + maxPosition) * 0.5;
640
641        playerPositionsX.Clear();
642        playerPositionsY.Clear();
643
644        base.Update(gameTime);
645    }
646     */
647    #endregion
648}
Note: See TracBrowser for help on using the repository browser.