Support for more editor-images or editor-images with regions
[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 ArrayList Images = new ArrayList();
61         public ArrayList EditorImages = new ArrayList();
62
63         public Tile() {
64                 ID = -1;
65                 NextTile = -1;
66                 AnimFps = 1;
67         }
68
69         public bool HasAttribute (int Attrib)
70         {
71                 return (Attributes & Attrib) != 0;
72         }
73
74         public void SetAttribute (int Attrib, bool Value)
75         {
76                 if (Value)
77                         Attributes |= Attrib;
78                 else
79                         Attributes &= (~Attrib);        //NOTE: "~" stands for bitwise negation
80         }
81
82         public bool HasWMAttribute (int Attrib)
83         {
84                 return (Data & Attrib) != 0;
85         }
86
87         public void SetWMAttribute (int Attrib, bool Value)
88         {
89                 if (Value)
90                         Data |= Attrib;
91                 else
92                         Data &= (~Attrib);      //NOTE: "~" stands for bitwise negation
93         }
94
95     public void Write(LispWriter writer) {
96         writer.StartList("tile");
97         writer.Write("id", ID);
98
99         WriteTileImages(writer, "images", Images);
100
101         if(HasAttribute(Attribute.SOLID))
102             writer.Write("solid", true);
103         if(HasAttribute(Attribute.UNISOLID))
104             writer.Write("unisolid", true);
105         if(HasAttribute(Attribute.ICE))
106             writer.Write("ice", true);
107         if(HasAttribute(Attribute.WATER))
108             writer.Write("water", true);
109         if(HasAttribute(Attribute.SLOPE))
110             writer.Write("slope-type", Data);
111         if(HasAttribute(Attribute.HURTS))
112             writer.Write("hurts", true);
113         if(HasAttribute(Attribute.FIRE))
114             writer.Write("fire", true);
115         if(HasAttribute(Attribute.COIN))
116             writer.Write("coin", true);
117         if(HasAttribute(Attribute.FULLBOX))
118             writer.Write("fullbox", true);
119         if(HasAttribute(Attribute.BRICK))
120             writer.Write("brick", true);
121         if(HasAttribute(Attribute.GOAL))
122             writer.Write("goal", true);
123
124         if(Hidden)
125             writer.Write("hidden", true);
126         if(NextTile >= 0)
127             writer.Write("next-tile", NextTile);
128         if(EditorImages != null)
129             WriteTileImages(writer, "editor-images", EditorImages);
130         if(Data != 0)
131             writer.Write("data", Data);
132         if(Images.Count > 1) {
133             if(AnimFps == 1.0)
134               AnimFps = 40;
135             writer.Write("anim-fps", AnimFps);
136         }
137         writer.EndList("tile");
138     }
139
140     public void Parse(Lisp.Parser parser) {
141         int d = parser.Depth;
142         while(parser.Parse() && parser.Depth >= d) {
143             if(parser.Depth == d+1) {
144                 if(parser.Type != Parser.LispType.SYMBOL)
145                     throw new Exception("expected SYMBOL at single tile deserialization level, but found \"" + parser.StringValue + "\"");
146                 string symbol = parser.SymbolValue;
147                 parser.Parse();
148                 switch(symbol) {
149                     case "id":
150                         ID = parser.IntegerValue;
151                     break;
152                     case "images":
153                         ParseTileImages(parser, Images);
154                         break;
155                     case "editor-images":
156                         ParseTileImages(parser, EditorImages);
157                          break;
158                     case "anim-fps":
159                         AnimFps = parser.FloatValue;
160                         break;
161                     case "data":
162                         Data = parser.IntegerValue;
163                         break;
164                     case "next-tile":
165                         NextTile = parser.IntegerValue;
166                         break;
167                     case "hidden":
168                         Hidden = parser.BoolValue;
169                         break;
170                     case "solid":
171                         SetAttribute(Attribute.SOLID, parser.BoolValue);
172                         break;
173                     case "unisolid":
174                         SetAttribute(Attribute.UNISOLID, parser.BoolValue);
175                         break;
176                     case "ice":
177                         SetAttribute(Attribute.ICE, parser.BoolValue);
178                         break;
179                     case "water":
180                         SetAttribute(Attribute.WATER, parser.BoolValue);
181                         break;
182                     case "slope-type":
183                         SetAttribute(Attribute.SLOPE, true);
184                         Data = parser.IntegerValue;
185                         break;
186                     case "hurts":
187                         SetAttribute(Attribute.HURTS, parser.BoolValue);
188                         break;
189                     case "fire":
190                         SetAttribute(Attribute.FIRE, parser.BoolValue);
191                         break;
192                     case "brick":
193                         SetAttribute(Attribute.BRICK, parser.BoolValue);
194                         break;
195                     case "fullbox":
196                         SetAttribute(Attribute.FULLBOX, parser.BoolValue);
197                         break;
198                     case "coin":
199                         SetAttribute(Attribute.COIN, parser.BoolValue);
200                         break;
201                     case "goal":
202                         SetAttribute(Attribute.GOAL, parser.BoolValue);
203                         break;
204
205                 //Worldmap attributes section - these are stored in Data
206                     case "north":
207                         SetWMAttribute(Attribute.WORLDMAP_NORTH, parser.BoolValue);
208                         break;
209                     case "south":
210                         SetWMAttribute(Attribute.WORLDMAP_SOUTH, parser.BoolValue);
211                         break;
212                     case "west":
213                         SetWMAttribute(Attribute.WORLDMAP_WEST, parser.BoolValue);
214                         break;
215                     case "east":
216                         SetWMAttribute(Attribute.WORLDMAP_EAST, parser.BoolValue);
217                         break;
218                     case "stop":
219                         SetWMAttribute(Attribute.WORLDMAP_STOP, parser.BoolValue);
220                         break;
221                     default:
222                         Console.WriteLine("Unknown tile element " + symbol);
223                         break;
224                 }
225             }
226         }
227     }
228
229     private void ParseTileImages(Lisp.Parser parser, ArrayList ImagesList) {
230         if(parser.Type == Parser.LispType.END_LIST)
231             return;
232
233         int d = parser.Depth;
234         do {
235             ImageRegion region = new ImageRegion();
236             if(parser.Type == Parser.LispType.STRING) {
237                 region.ImageFile = parser.StringValue;
238             } else if(parser.Type == Parser.LispType.START_LIST) {
239                 ParseImageRegion(parser, region);
240             } else {
241                 throw new Exception("unexpected lisp data: " + parser.Type);
242             }
243             ImagesList.Add(region);
244         } while(parser.Parse() && parser.Depth >= d);
245     }
246
247     private void WriteTileImages(LispWriter writer, string ListName, ArrayList ImagesList) {
248         if(ImagesList.Count > 0) {
249             writer.StartList(ListName);
250             foreach(ImageRegion region in ImagesList) {
251                 if(region.Region.Width != 0) {
252                     writer.WriteVerbatimLine(
253                             String.Format("(region \"{0}\" {1} {2} {3} {4})",
254                                 region.ImageFile, region.Region.Left,
255                                 region.Region.Top, region.Region.Width,
256                                 region.Region.Height));
257                 } else {
258                     writer.WriteVerbatimLine(
259                             "\"" + region.ImageFile + "\"");
260                 }
261             }
262             writer.EndList(ListName);
263         } else {
264             Console.WriteLine("no images on tile " + ID);
265         }
266     }
267
268     private void ParseImageRegion(Lisp.Parser parser, ImageRegion region) {
269         parser.Parse();
270         if(parser.Type != Parser.LispType.SYMBOL)
271             throw new Exception("expected symbol");
272         if(parser.SymbolValue != "region")
273             throw new Exception("expected region symbol");
274         parser.Parse();
275         if(parser.Type != Parser.LispType.STRING)
276             throw new Exception("expected string");
277         region.ImageFile = parser.StringValue;
278
279         parser.Parse();
280         if(parser.Type != Parser.LispType.INTEGER)
281             throw new Exception("expected integer");
282         region.Region.X = parser.IntegerValue;
283
284         parser.Parse();
285         if(parser.Type != Parser.LispType.INTEGER)
286             throw new Exception("expected integer");
287         region.Region.Y = parser.IntegerValue;
288
289         parser.Parse();
290         if(parser.Type != Parser.LispType.INTEGER)
291             throw new Exception("expected integer");
292         region.Region.Width = parser.IntegerValue;
293
294         parser.Parse();
295         if(parser.Type != Parser.LispType.INTEGER)
296             throw new Exception("expected integer");
297         region.Region.Height = parser.IntegerValue;
298
299         parser.Parse();
300         if(parser.Type != Parser.LispType.END_LIST)
301             throw new Exception("expected END_LIST");
302     }
303 }