// $Id$ using System; using System.Collections; using System.IO; using System.Drawing; using Lisp; public class ImageRegion { public String ImageFile; public Rectangle Region; } public class Attribute { /// solid tile that is indestructible by Tux public const int SOLID = 0x0001; /// uni-directional solid tile public const int UNISOLID = 0x0002; /// a brick that can be destroyed by jumping under it public const int BRICK = 0x0004; /// the level should be finished when touching a goaltile. /// /// if data is 0 then the endsequence should be /// triggered, if data is 1 then we can finish /// the level instantly. /// public const int GOAL = 0x0008; /// slope tile public const int SLOPE = 0x0010; /// Bonusbox, content is stored in data public const int FULLBOX = 0x0020; /// Tile is a coin public const int COIN = 0x0040; /// an ice brick that makes tux sliding more than usual public const int ICE = 0x0100; /// a water tile in which tux starts to swim public const int WATER = 0x0200; /// a tile that hurts the player if he touches it public const int HURTS = 0x0400; /// for lava: WATER, HURTS, FIRE public const int FIRE = 0x0800; // TODO: Find out why are worldmap tile attributes stored in data(s) // worldmap flags public const int WORLDMAP_NORTH = 0x0001; public const int WORLDMAP_SOUTH = 0x0002; public const int WORLDMAP_EAST = 0x0004; public const int WORLDMAP_WEST = 0x0008; public const int WORLDMAP_STOP = 0x0010; } public class Tile { public int ID; public bool Hidden; public int NextTile; public int Attributes; public int Data; public float AnimFps; public string OneWayString; public ArrayList Images = new ArrayList(); public ArrayList EditorImages = new ArrayList(); public Tile() { ID = -1; NextTile = -1; AnimFps = 1; } public bool HasAttribute (int Attrib) { return (Attributes & Attrib) != 0; } public void SetAttribute (int Attrib, bool Value) { if (Value) Attributes |= Attrib; else Attributes &= (~Attrib); //NOTE: "~" stands for bitwise negation } public bool HasWMAttribute (int Attrib) { return (Data & Attrib) != 0; } public void SetWMAttribute (int Attrib, bool Value) { if (Value) Data |= Attrib; else Data &= (~Attrib); //NOTE: "~" stands for bitwise negation } public void Write(LispWriter writer) { writer.StartList("tile"); writer.Write("id", ID); WriteTileImages(writer, "images", Images); if(HasAttribute(Attribute.SOLID)) writer.Write("solid", true); if(HasAttribute(Attribute.UNISOLID)) writer.Write("unisolid", true); if(HasAttribute(Attribute.ICE)) writer.Write("ice", true); if(HasAttribute(Attribute.WATER)) writer.Write("water", true); if(HasAttribute(Attribute.SLOPE)) writer.Write("slope-type", Data); if(HasAttribute(Attribute.HURTS)) writer.Write("hurts", true); if(HasAttribute(Attribute.FIRE)) writer.Write("fire", true); if(HasAttribute(Attribute.COIN)) writer.Write("coin", true); if(HasAttribute(Attribute.FULLBOX)) writer.Write("fullbox", true); if(HasAttribute(Attribute.BRICK)) writer.Write("brick", true); if(HasAttribute(Attribute.GOAL)) writer.Write("goal", true); if(Hidden) writer.Write("hidden", true); if(NextTile >= 0) writer.Write("next-tile", NextTile); if(EditorImages != null) WriteTileImages(writer, "editor-images", EditorImages); if(Data != 0) writer.Write("data", Data); if(Images.Count > 1) { if(AnimFps == 1.0) AnimFps = 40; writer.Write("anim-fps", AnimFps); } if(!String.IsNullOrEmpty(OneWayString)) { writer.Write("one-way", OneWayString); } writer.EndList("tile"); } public void Parse(Lisp.Parser parser) { int d = parser.Depth; while(parser.Parse() && parser.Depth >= d) { if(parser.Depth == d+1) { if(parser.Type != Parser.LispType.SYMBOL) throw new Exception("expected SYMBOL at single tile deserialization level, but found \"" + parser.StringValue + "\""); string symbol = parser.SymbolValue; parser.Parse(); switch(symbol) { case "id": ID = parser.IntegerValue; break; case "images": ParseTileImages(parser, Images); break; case "editor-images": ParseTileImages(parser, EditorImages); break; case "anim-fps": AnimFps = parser.FloatValue; break; case "one-way": OneWayString = parser.StringValue; break; case "data": Data = parser.IntegerValue; break; case "next-tile": NextTile = parser.IntegerValue; break; case "hidden": Hidden = parser.BoolValue; break; case "solid": SetAttribute(Attribute.SOLID, parser.BoolValue); break; case "unisolid": SetAttribute(Attribute.UNISOLID, parser.BoolValue); break; case "ice": SetAttribute(Attribute.ICE, parser.BoolValue); break; case "water": SetAttribute(Attribute.WATER, parser.BoolValue); break; case "slope-type": SetAttribute(Attribute.SLOPE, true); Data = parser.IntegerValue; break; case "hurts": SetAttribute(Attribute.HURTS, parser.BoolValue); break; case "fire": SetAttribute(Attribute.FIRE, parser.BoolValue); break; case "brick": SetAttribute(Attribute.BRICK, parser.BoolValue); break; case "fullbox": SetAttribute(Attribute.FULLBOX, parser.BoolValue); break; case "coin": SetAttribute(Attribute.COIN, parser.BoolValue); break; case "goal": SetAttribute(Attribute.GOAL, parser.BoolValue); break; //Worldmap attributes section - these are stored in Data case "north": SetWMAttribute(Attribute.WORLDMAP_NORTH, parser.BoolValue); break; case "south": SetWMAttribute(Attribute.WORLDMAP_SOUTH, parser.BoolValue); break; case "west": SetWMAttribute(Attribute.WORLDMAP_WEST, parser.BoolValue); break; case "east": SetWMAttribute(Attribute.WORLDMAP_EAST, parser.BoolValue); break; case "stop": SetWMAttribute(Attribute.WORLDMAP_STOP, parser.BoolValue); break; default: Console.WriteLine("Unknown tile element " + symbol); break; } } } } private void ParseTileImages(Lisp.Parser parser, ArrayList ImagesList) { if(parser.Type == Parser.LispType.END_LIST) return; int d = parser.Depth; do { ImageRegion region = new ImageRegion(); if(parser.Type == Parser.LispType.STRING) { region.ImageFile = parser.StringValue; } else if(parser.Type == Parser.LispType.START_LIST) { ParseImageRegion(parser, region); } else { throw new Exception("unexpected lisp data: " + parser.Type); } ImagesList.Add(region); } while(parser.Parse() && parser.Depth >= d); } private void WriteTileImages(LispWriter writer, string ListName, ArrayList ImagesList) { if(ImagesList.Count > 0) { writer.StartList(ListName); foreach(ImageRegion region in ImagesList) { if(region.Region.Width != 0) { writer.WriteVerbatimLine( String.Format("(region \"{0}\" {1} {2} {3} {4})", region.ImageFile, region.Region.Left, region.Region.Top, region.Region.Width, region.Region.Height)); } else { writer.WriteVerbatimLine( "\"" + region.ImageFile + "\""); } } writer.EndList(ListName); } else { Console.WriteLine("no images on tile " + ID); } } private void ParseImageRegion(Lisp.Parser parser, ImageRegion region) { parser.Parse(); if(parser.Type != Parser.LispType.SYMBOL) throw new Exception("expected symbol"); if(parser.SymbolValue != "region") throw new Exception("expected region symbol"); parser.Parse(); if(parser.Type != Parser.LispType.STRING) throw new Exception("expected string"); region.ImageFile = parser.StringValue; parser.Parse(); if(parser.Type != Parser.LispType.INTEGER) throw new Exception("expected integer"); region.Region.X = parser.IntegerValue; parser.Parse(); if(parser.Type != Parser.LispType.INTEGER) throw new Exception("expected integer"); region.Region.Y = parser.IntegerValue; parser.Parse(); if(parser.Type != Parser.LispType.INTEGER) throw new Exception("expected integer"); region.Region.Width = parser.IntegerValue; parser.Parse(); if(parser.Type != Parser.LispType.INTEGER) throw new Exception("expected integer"); region.Region.Height = parser.IntegerValue; parser.Parse(); if(parser.Type != Parser.LispType.END_LIST) throw new Exception("expected END_LIST"); } }