Store "one-way" worldmap attribute, because AnMaster said, that it's meaningfull
[supertux.git] / tools / tilemanager / Tile.cs
1 //  $Id$
2 using System;
3 using System.Collections;
4 using System.IO;
5 using System.Drawing;
6 using Lisp;
7
8 public class ImageRegion {
9     public String ImageFile;
10     public Rectangle Region;
11 }
12
13 public class Attribute {
14         /// <summary>solid tile that is indestructible by Tux</summary>
15         public const int SOLID     = 0x0001;
16         /// <summary>uni-directional solid tile</summary>
17         public const int UNISOLID  = 0x0002;
18         /// <summary>a brick that can be destroyed by jumping under it</summary>
19         public const int BRICK     = 0x0004;
20         /// <summary>the level should be finished when touching a goaltile.</summary>
21         /// <remarks>
22         /// if <see cref="Data">data</see> is 0 then the endsequence should be
23         /// triggered, if <see cref="Data">data</see> is 1 then we can finish
24         /// the level instantly.
25         /// </remarks>
26         public const int GOAL      = 0x0008;
27         /// <summary>slope tile</summary>
28         public const int SLOPE     = 0x0010;
29         /// <summary>Bonusbox, content is stored in <see cref="Data">data</see></summary>
30         public const int FULLBOX   = 0x0020;
31         /// <summary>Tile is a coin</summary>
32         public const int COIN      = 0x0040;
33         /// <summary>an ice brick that makes tux sliding more than usual</summary>
34         public const int ICE       = 0x0100;
35         /// <summary>a water tile in which tux starts to swim</summary>
36         public const int WATER     = 0x0200;
37         /// <summary>a tile that hurts the player if he touches it</summary>
38         public const int HURTS     = 0x0400;
39         /// <summary>for lava: WATER, HURTS, FIRE</summary>
40         public const int FIRE      = 0x0800;
41
42
43         // TODO: Find out why are worldmap tile attributes stored in data(s)
44         // worldmap flags
45         public const int WORLDMAP_NORTH = 0x0001;
46         public const int WORLDMAP_SOUTH = 0x0002;
47         public const int WORLDMAP_EAST  = 0x0004;
48         public const int WORLDMAP_WEST  = 0x0008;
49
50         public const int WORLDMAP_STOP  = 0x0010;
51 }
52
53 public class Tile {
54         public int ID;
55         public bool Hidden;
56         public int NextTile;
57         public int Attributes;
58         public int Data;
59         public float AnimFps;
60         public string OneWayString;
61         public ArrayList Images = new ArrayList();
62         public ArrayList EditorImages = new ArrayList();
63
64         public Tile() {
65                 ID = -1;
66                 NextTile = -1;
67                 AnimFps = 1;
68         }
69
70         public bool HasAttribute (int Attrib)
71         {
72                 return (Attributes & Attrib) != 0;
73         }
74
75         public void SetAttribute (int Attrib, bool Value)
76         {
77                 if (Value)
78                         Attributes |= Attrib;
79                 else
80                         Attributes &= (~Attrib);        //NOTE: "~" stands for bitwise negation
81         }
82
83         public bool HasWMAttribute (int Attrib)
84         {
85                 return (Data & Attrib) != 0;
86         }
87
88         public void SetWMAttribute (int Attrib, bool Value)
89         {
90                 if (Value)
91                         Data |= Attrib;
92                 else
93                         Data &= (~Attrib);      //NOTE: "~" stands for bitwise negation
94         }
95
96     public void Write(LispWriter writer) {
97         writer.StartList("tile");
98         writer.Write("id", ID);
99
100         WriteTileImages(writer, "images", Images);
101
102         if(HasAttribute(Attribute.SOLID))
103             writer.Write("solid", true);
104         if(HasAttribute(Attribute.UNISOLID))
105             writer.Write("unisolid", true);
106         if(HasAttribute(Attribute.ICE))
107             writer.Write("ice", true);
108         if(HasAttribute(Attribute.WATER))
109             writer.Write("water", true);
110         if(HasAttribute(Attribute.SLOPE))
111             writer.Write("slope-type", Data);
112         if(HasAttribute(Attribute.HURTS))
113             writer.Write("hurts", true);
114         if(HasAttribute(Attribute.FIRE))
115             writer.Write("fire", true);
116         if(HasAttribute(Attribute.COIN))
117             writer.Write("coin", true);
118         if(HasAttribute(Attribute.FULLBOX))
119             writer.Write("fullbox", true);
120         if(HasAttribute(Attribute.BRICK))
121             writer.Write("brick", true);
122         if(HasAttribute(Attribute.GOAL))
123             writer.Write("goal", true);
124
125         if(Hidden)
126             writer.Write("hidden", true);
127         if(NextTile >= 0)
128             writer.Write("next-tile", NextTile);
129         if(EditorImages != null)
130             WriteTileImages(writer, "editor-images", EditorImages);
131         if(Data != 0)
132             writer.Write("data", Data);
133         if(Images.Count > 1) {
134             if(AnimFps == 1.0)
135               AnimFps = 40;
136             writer.Write("anim-fps", AnimFps);
137         }
138         if(!String.IsNullOrEmpty(OneWayString)) {
139                 writer.Write("one-way", OneWayString);
140         }
141         writer.EndList("tile");
142     }
143
144     public void Parse(Lisp.Parser parser) {
145         int d = parser.Depth;
146         while(parser.Parse() && parser.Depth >= d) {
147             if(parser.Depth == d+1) {
148                 if(parser.Type != Parser.LispType.SYMBOL)
149                     throw new Exception("expected SYMBOL at single tile deserialization level, but found \"" + parser.StringValue + "\"");
150                 string symbol = parser.SymbolValue;
151                 parser.Parse();
152                 switch(symbol) {
153                     case "id":
154                         ID = parser.IntegerValue;
155                     break;
156                     case "images":
157                         ParseTileImages(parser, Images);
158                         break;
159                     case "editor-images":
160                         ParseTileImages(parser, EditorImages);
161                          break;
162                     case "anim-fps":
163                         AnimFps = parser.FloatValue;
164                         break;
165                     case "one-way":
166                         OneWayString = parser.StringValue;
167                         break;
168                     case "data":
169                         Data = parser.IntegerValue;
170                         break;
171                     case "next-tile":
172                         NextTile = parser.IntegerValue;
173                         break;
174                     case "hidden":
175                         Hidden = parser.BoolValue;
176                         break;
177                     case "solid":
178                         SetAttribute(Attribute.SOLID, parser.BoolValue);
179                         break;
180                     case "unisolid":
181                         SetAttribute(Attribute.UNISOLID, parser.BoolValue);
182                         break;
183                     case "ice":
184                         SetAttribute(Attribute.ICE, parser.BoolValue);
185                         break;
186                     case "water":
187                         SetAttribute(Attribute.WATER, parser.BoolValue);
188                         break;
189                     case "slope-type":
190                         SetAttribute(Attribute.SLOPE, true);
191                         Data = parser.IntegerValue;
192                         break;
193                     case "hurts":
194                         SetAttribute(Attribute.HURTS, parser.BoolValue);
195                         break;
196                     case "fire":
197                         SetAttribute(Attribute.FIRE, parser.BoolValue);
198                         break;
199                     case "brick":
200                         SetAttribute(Attribute.BRICK, parser.BoolValue);
201                         break;
202                     case "fullbox":
203                         SetAttribute(Attribute.FULLBOX, parser.BoolValue);
204                         break;
205                     case "coin":
206                         SetAttribute(Attribute.COIN, parser.BoolValue);
207                         break;
208                     case "goal":
209                         SetAttribute(Attribute.GOAL, parser.BoolValue);
210                         break;
211
212                 //Worldmap attributes section - these are stored in Data
213                     case "north":
214                         SetWMAttribute(Attribute.WORLDMAP_NORTH, parser.BoolValue);
215                         break;
216                     case "south":
217                         SetWMAttribute(Attribute.WORLDMAP_SOUTH, parser.BoolValue);
218                         break;
219                     case "west":
220                         SetWMAttribute(Attribute.WORLDMAP_WEST, parser.BoolValue);
221                         break;
222                     case "east":
223                         SetWMAttribute(Attribute.WORLDMAP_EAST, parser.BoolValue);
224                         break;
225                     case "stop":
226                         SetWMAttribute(Attribute.WORLDMAP_STOP, parser.BoolValue);
227                         break;
228                     default:
229                         Console.WriteLine("Unknown tile element " + symbol);
230                         break;
231                 }
232             }
233         }
234     }
235
236     private void ParseTileImages(Lisp.Parser parser, ArrayList ImagesList) {
237         if(parser.Type == Parser.LispType.END_LIST)
238             return;
239
240         int d = parser.Depth;
241         do {
242             ImageRegion region = new ImageRegion();
243             if(parser.Type == Parser.LispType.STRING) {
244                 region.ImageFile = parser.StringValue;
245             } else if(parser.Type == Parser.LispType.START_LIST) {
246                 ParseImageRegion(parser, region);
247             } else {
248                 throw new Exception("unexpected lisp data: " + parser.Type);
249             }
250             ImagesList.Add(region);
251         } while(parser.Parse() && parser.Depth >= d);
252     }
253
254     private void WriteTileImages(LispWriter writer, string ListName, ArrayList ImagesList) {
255         if(ImagesList.Count > 0) {
256             writer.StartList(ListName);
257             foreach(ImageRegion region in ImagesList) {
258                 if(region.Region.Width != 0) {
259                     writer.WriteVerbatimLine(
260                             String.Format("(region \"{0}\" {1} {2} {3} {4})",
261                                 region.ImageFile, region.Region.Left,
262                                 region.Region.Top, region.Region.Width,
263                                 region.Region.Height));
264                 } else {
265                     writer.WriteVerbatimLine(
266                             "\"" + region.ImageFile + "\"");
267                 }
268             }
269             writer.EndList(ListName);
270         } else {
271             Console.WriteLine("no images on tile " + ID);
272         }
273     }
274
275     private void ParseImageRegion(Lisp.Parser parser, ImageRegion region) {
276         parser.Parse();
277         if(parser.Type != Parser.LispType.SYMBOL)
278             throw new Exception("expected symbol");
279         if(parser.SymbolValue != "region")
280             throw new Exception("expected region symbol");
281         parser.Parse();
282         if(parser.Type != Parser.LispType.STRING)
283             throw new Exception("expected string");
284         region.ImageFile = parser.StringValue;
285
286         parser.Parse();
287         if(parser.Type != Parser.LispType.INTEGER)
288             throw new Exception("expected integer");
289         region.Region.X = parser.IntegerValue;
290
291         parser.Parse();
292         if(parser.Type != Parser.LispType.INTEGER)
293             throw new Exception("expected integer");
294         region.Region.Y = parser.IntegerValue;
295
296         parser.Parse();
297         if(parser.Type != Parser.LispType.INTEGER)
298             throw new Exception("expected integer");
299         region.Region.Width = parser.IntegerValue;
300
301         parser.Parse();
302         if(parser.Type != Parser.LispType.INTEGER)
303             throw new Exception("expected integer");
304         region.Region.Height = parser.IntegerValue;
305
306         parser.Parse();
307         if(parser.Type != Parser.LispType.END_LIST)
308             throw new Exception("expected END_LIST");
309     }
310 }