Textures can now be tiled, like old Rokon. One difference - columns/rows/tileindex...
[rokon:rokon.git] / src / com / stickycoding / rokon / Scene.java
1 package com.stickycoding.rokon;\r
2 \r
3 import java.lang.reflect.InvocationTargetException;\r
4 import java.lang.reflect.Method;\r
5 import java.util.Arrays;\r
6 \r
7 import javax.microedition.khronos.opengles.GL10;\r
8 \r
9 import android.view.MotionEvent;\r
10 \r
11 import com.badlogic.gdx.physics.box2d.Contact;\r
12 import com.badlogic.gdx.physics.box2d.ContactListener;\r
13 import com.badlogic.gdx.physics.box2d.World;\r
14 import com.stickycoding.rokon.device.Graphics;\r
15 \r
16 /**\r
17  * Scene.java\r
18  * A Scene holds and prepares drawable objects or object groups\r
19  * \r
20  * @author Richard\r
21  */\r
22 public class Scene {\r
23         \r
24         public static final int SCENE_TEXTURE_COUNT = 32;\r
25         public static final int DEFAULT_LAYER_COUNT = 1;\r
26         public static final int DEFAULT_LAYER_OBJECT_COUNT = 32;\r
27 \r
28         protected Layer[] layer;\r
29         protected boolean loadedTextures;\r
30         protected int layerCount;\r
31         protected Window window = null;\r
32         protected Texture[] texturesToLoad;\r
33         protected Texture[] texturesOnHardware;\r
34         protected boolean useInvoke;\r
35         protected World world;\r
36         protected boolean usePhysics = false;\r
37         protected ContactListener contactListener;\r
38         protected boolean useContactListener;\r
39 \r
40         public void onPreDraw(GL10 gl) { }\r
41         public void onPostDraw(GL10 gl) { }\r
42         public void onTouchDown(DrawableObject object, float x, float y, MotionEvent event) { }\r
43         public void onTouchUp(DrawableObject object, float x, float y, MotionEvent event) { }\r
44         public void onTouchMove(DrawableObject object, float x, float y, MotionEvent event) { }\r
45         public void onTouch(DrawableObject object, float x, float y, MotionEvent event) { }\r
46         public void onTouchDown(float x, float y, MotionEvent event) { }\r
47         public void onTouchMove(float x, float y, MotionEvent event) { }\r
48         public void onTouch(float x, float y, MotionEvent event) { }\r
49         public void onTouchUp(float x, float y, MotionEvent event) { }\r
50         \r
51         /**\r
52          * Sets a World for the physics in this Scene\r
53          * Automatically flags usePhysics\r
54          * \r
55          * @param world valid World object\r
56          */\r
57         public void setWorld(World world) {\r
58                 this.world = world;\r
59                 Physics.world = world;\r
60                 usePhysics = true;\r
61         }\r
62         \r
63         /**\r
64          * Returns the World associated with this Scene\r
65          * \r
66          * @return NULL if no World set\r
67          */\r
68         public World getWorld() {\r
69                 return world;\r
70         }\r
71         \r
72         /**\r
73          * Flags to use physics in this Scene\r
74          */\r
75         public void usePhysics() {\r
76                 usePhysics = true;\r
77         }\r
78         \r
79         /**\r
80          * Flags to not use physics\r
81          */\r
82         public void noPhysics() {\r
83                 usePhysics = false;\r
84         }\r
85         \r
86         /**\r
87          * Removes the World from this Scene\r
88          */\r
89         public void removeWorld() {\r
90                 this.world = null;\r
91                 Physics.world = null;\r
92                 usePhysics = false;\r
93         }\r
94         \r
95         /**\r
96          * Triggers the Scene to begin invoking methods on certain events, this is not set by default.\r
97          * If the methods that are to be invoked don't exist, no exceptions will be raised.\r
98          */\r
99         public void useInvoke() {\r
100                 useInvoke = true;\r
101         }\r
102         \r
103         /**\r
104          * Stops the Scene from invoking methods on events, this is the default state\r
105          */\r
106         public void stopInvoke() {\r
107                 useInvoke = false;\r
108         }\r
109         \r
110         /**\r
111          * Invokes a method inside the Scene class, defined by given parameters.\r
112          * If no parameters exist, use the alternative invoke method\r
113          * \r
114          * @param methodName String\r
115          * @param params Class[]\r
116          * @param paramValues Object[]\r
117          * \r
118          * @return TRUE if successful, FALSE otherwise\r
119          */\r
120         public boolean invoke(String methodName, Class<?>[] params, Object[] paramValues) {\r
121                 for(Method m : this.getClass().getDeclaredMethods()) {\r
122                         if(m.getName().equals(methodName)) {\r
123                                 if(Arrays.equals(params, m.getParameterTypes())) {\r
124                                         try {\r
125                                                 m.invoke(this, paramValues);\r
126                                                 return true;\r
127                                         } catch (IllegalArgumentException e) {\r
128                                                 Debug.error("Invoking, IllegalArgument");\r
129                                                 e.printStackTrace();\r
130                                                 return false;\r
131                                         } catch (IllegalAccessException e) {\r
132                                                 Debug.error("Invoking, IllegalAccess");\r
133                                                 e.printStackTrace();\r
134                                                 return false;\r
135                                         } catch (InvocationTargetException e) {\r
136                                                 Debug.error("Invoking, IllegalTarget");\r
137                                                 e.printStackTrace();\r
138                                                 return false;\r
139                                         }\r
140                                 }\r
141                         }\r
142                 }\r
143                 return false;\r
144         }\r
145         \r
146         /**\r
147          * Invokes a method by parameters inside a Callback object\r
148          * \r
149          * @param callback valid Callback object\r
150          * @return TRUE if successful, FALSE otherwise\r
151          */\r
152         public boolean invoke(Callback callback) {\r
153                 if(callback.parameters == null) {\r
154                         return invoke(callback.methodName);\r
155                 } \r
156                 if(callback.parameterTypes == null) {\r
157                         return invoke(callback.methodName, callback.parameters);\r
158                 }\r
159                 return invoke(callback.methodName, callback.parameterTypes, callback.parameters);\r
160         }\r
161         \r
162         /**\r
163          * USE AT YOUR OWN RISK\r
164          * Invokes a method inside the Scene class, it selects the first matching method name and tries to pass on given parameters\r
165          * An error will be raised if this isn't the correct method. This routine is simply for those too lazy (or wanting to save\r
166          * on a little processing time) and are 100% sure there are no name conflicts.\r
167          * \r
168          * IllegalArgumentException may be passed to the Debug class, logcat will be notified - but there is no way to test at your end.\r
169          * \r
170          * @param methodName String\r
171          * @param paramValues Object[]\r
172          * \r
173          * @return TRUE if successful, FALSE otherwise\r
174          */\r
175         public boolean invoke(String methodName, Object[] paramValues) {\r
176                 for(Method m : this.getClass().getDeclaredMethods()) {\r
177                         if(m.getName().equals(methodName)) {\r
178                                 try {\r
179                                         m.invoke(this, paramValues);\r
180                                         return true;\r
181                                 } catch (IllegalArgumentException e) {\r
182                                         Debug.error("Invoking, IllegalArgument");\r
183                                         e.printStackTrace();\r
184                                         return false;\r
185                                 } catch (IllegalAccessException e) {\r
186                                         Debug.error("Invoking, IllegalAccess");\r
187                                         e.printStackTrace();\r
188                                         return false;\r
189                                 } catch (InvocationTargetException e) {\r
190                                         Debug.error("Invoking, IllegalTarget");\r
191                                         e.printStackTrace();\r
192                                         return false;\r
193                                 }\r
194                         }\r
195                 }\r
196                 return false;\r
197         }\r
198         \r
199         /**\r
200          * Invokes a method inside the Scene class, assuming there are no parameters to pass\r
201          * \r
202          * @param methodName String\r
203          * \r
204          * @return TRUE if successful, FALSE otherwise\r
205          */\r
206         public boolean invoke(String methodName) {\r
207                 for(Method m : this.getClass().getDeclaredMethods()) {\r
208                         if(m.getName().equals(methodName)) {\r
209                                 if(m.getParameterTypes().length == 0) {\r
210                                         try {\r
211                                                 m.invoke(this);\r
212                                                 return true;\r
213                                         } catch (IllegalArgumentException e) {\r
214                                                 Debug.error("Invoking, IllegalArgument");\r
215                                                 e.printStackTrace();\r
216                                                 return false;\r
217                                         } catch (IllegalAccessException e) {\r
218                                                 Debug.error("Invoking, IllegalAccess");\r
219                                                 e.printStackTrace();\r
220                                                 return false;\r
221                                         } catch (InvocationTargetException e) {\r
222                                                 Debug.error("Invoking, IllegalTarget");\r
223                                                 e.printStackTrace();\r
224                                                 return false;\r
225                                         }\r
226                                 }\r
227                         }\r
228                 }\r
229                 return false;\r
230         }\r
231         \r
232         protected void handleTouch(MotionEvent event) {\r
233                 event.setLocation(event.getX() * (Graphics.getHalfWidthPixels() / RokonActivity.gameWidth), event.getY() * (Graphics.getHeightPixels()  / RokonActivity.gameHeight));\r
234                 onTouch(event.getX(), event.getY(), event);\r
235                 switch(event.getAction()) {\r
236                         case MotionEvent.ACTION_DOWN:\r
237                                 onTouchDown(event.getX(), event.getY(), event);\r
238                                 break;\r
239                         case MotionEvent.ACTION_UP:\r
240                                 onTouchUp(event.getX(), event.getY(), event);\r
241                                 break;\r
242                         case MotionEvent.ACTION_MOVE:\r
243                                 onTouch(event.getX(), event.getY(), event);\r
244                                 break;\r
245                 }\r
246                 for(int i = 0; i < layerCount; i++) {\r
247                         for(int j = 0; j < layer[i].maximumDrawableObjects; j++) {\r
248                                 DrawableObject object = layer[i].drawableObjects.get(j);\r
249                                 if(object != null && object.isTouchable) {\r
250                                         if(MathHelper.pointInRect(event.getX(), event.getY(), object.x, object.y, object.width, object.height)) {\r
251                                                 onTouch(object, event.getX(), event.getY(), event);\r
252                                                 if(object.getName() != null) {\r
253                                                         invoke(object.getName() + "_onTouch", new Class[] { float.class, float.class, MotionEvent.class }, new Object[] { event.getX(), event.getY(), event });\r
254                                                 }\r
255                                                 switch(event.getAction()) {\r
256                                                         case MotionEvent.ACTION_DOWN:\r
257                                                                 onTouchDown(object, event.getX(), event.getY(), event);\r
258                                                                 if(object.getName() != null) {\r
259                                                                         invoke(object.getName() + "_onTouchDown", new Class[] { float.class, float.class, MotionEvent.class }, new Object[] { event.getX(), event.getY(), event });\r
260                                                                 }\r
261                                                                 break;\r
262                                                         case MotionEvent.ACTION_UP:\r
263                                                                 onTouchUp(object, event.getX(), event.getY(), event);\r
264                                                                 if(object.getName() != null) {\r
265                                                                         invoke(object.getName() + "_onTouchUp", new Class[] { float.class, float.class, MotionEvent.class }, new Object[] { event.getX(), event.getY(), event });\r
266                                                                 }\r
267                                                                 break;\r
268                                                         case MotionEvent.ACTION_MOVE:\r
269                                                                 onTouch(object, event.getX(), event.getY(), event);\r
270                                                                 if(object.getName() != null) {\r
271                                                                         invoke(object.getName() + "_onTouchMove", new Class[] { float.class, float.class, MotionEvent.class }, new Object[] { event.getX(), event.getY(), event });\r
272                                                                 }\r
273                                                                 break;\r
274                                                 }\r
275                                         }\r
276                                 }\r
277                         }\r
278                 }\r
279         }\r
280         \r
281         /**\r
282          * Creates a new Scene with given layer count, and a corresponding maximum DrawableObject count \r
283          * \r
284          * @param layerCount maximum number of layers\r
285          * @param layerObjectCount maximum number of DrawableObjects per layer, the array length must match layerCount\r
286          */\r
287         public Scene(int layerCount, int[] layerObjectCount) {\r
288                 this.layerCount = layerCount;\r
289                 layer = new Layer[layerCount];\r
290                 for(int i = 0; i < layerCount; i++) {\r
291                         layer[i] = new Layer(this, layerObjectCount[i]);\r
292                 }\r
293                 prepareNewScene();\r
294         }\r
295         \r
296         /**\r
297          * Creates a new Scene with given layer count, all layers will have the same maximum number of DrawableObjects\r
298          * \r
299          * @param layerCount maximum number of layers\r
300          * @param layerObjectCount maximum number of DrawableObjects per layer\r
301          */\r
302         public Scene(int layerCount, int layerObjectCount) {\r
303                 this.layerCount = layerCount;\r
304                 layer = new Layer[layerCount];\r
305                 for(int i = 0; i < layerCount; i++) {\r
306                         layer[i] = new Layer(this, layerObjectCount);\r
307                 }\r
308                 prepareNewScene();\r
309         }\r
310         \r
311         /**\r
312          * Creates a new Scene with given layer count, and a default maximum DrawableObject count of DEFAULT_LAYER_OBJECT_COUNT\r
313          * \r
314          * @param layerCount maximum number of layers\r
315          */\r
316         public Scene(int layerCount) {\r
317                 this(layerCount, DEFAULT_LAYER_OBJECT_COUNT);\r
318         }\r
319         \r
320         /**\r
321          * Creates a new Scene with defaults, DEFAULT_LAYER_COUNT and DEFAULT_LAYER_OBJECT_COUNT\r
322          */\r
323         public Scene() {\r
324                 this(DEFAULT_LAYER_COUNT, DEFAULT_LAYER_OBJECT_COUNT);\r
325                 \r
326         }\r
327         \r
328         private void prepareNewScene() {\r
329                 texturesToLoad = new Texture[SCENE_TEXTURE_COUNT];\r
330                 texturesOnHardware = new Texture[SCENE_TEXTURE_COUNT];\r
331         }\r
332         \r
333         /**\r
334          * Flags a Texture to be loaded into this Scene\r
335          * This must be called before RokonActivity.setScene\r
336          * \r
337          * @param texture valid Texture object\r
338          */\r
339         public void useTexture(Texture texture) {\r
340                 for(int i = 0; i < texturesToLoad.length; i++) {\r
341                         if(texturesToLoad[i] == texture) return;\r
342                         if(texturesToLoad[i] == null) {\r
343                                 texturesToLoad[i] = texture;\r
344                                 return;\r
345                         }\r
346                 }\r
347                 Debug.warning("Scene.useTexture", "Tried loading too many Textures onto the Scene, max is " + texturesToLoad.length);\r
348         }\r
349         \r
350         /**\r
351          * Defines the active Window for this Scene\r
352          * If no Window is given, a default static view will be rendered \r
353          * \r
354          * @param window\r
355          */\r
356         public void setWindow(Window window) {\r
357                 if(window == null) {\r
358                         Debug.warning("Scene.setWindow", "Tried setting a NULL Window");\r
359                         return;\r
360                 }\r
361                 this.window = window;\r
362         }\r
363         \r
364         /**\r
365          * Removes the current active Window, returning it to NULL\r
366          */\r
367         public void removeWindow() {\r
368                 window = null;\r
369         }\r
370         \r
371         /**\r
372          * @return NULL if there is no Window associated with this Scene\r
373          */\r
374         public Window getWindow() {\r
375                 if(window == null)\r
376                         return null;\r
377                 return window;\r
378         }\r
379         \r
380         /**\r
381          * Fetches the Layer object associated with the given index\r
382          * \r
383          * @param index the index of the Layer\r
384          * @return NULL if invalid index is given\r
385          */\r
386         public Layer getLayer(int index) {\r
387                 if(index < 0 || index > layerCount) {\r
388                         Debug.warning("Scene.getLayer", "Tried fetching invalid layer (" + index + "), maximum is " + layerCount);\r
389                         return null;\r
390                 }\r
391                 return layer[index];\r
392         }\r
393         \r
394         /**\r
395          * Clears the DrawableObjects from all Layers\r
396          */\r
397         public void clear() {\r
398                 for(int i = 0; i < layerCount; i++) {\r
399                         layer[i].clear();\r
400                 }\r
401         }\r
402         \r
403         /**\r
404          * Clears all the DrawableObjects from a specified Layer\r
405          * \r
406          * @param index the index of the Layer\r
407          */\r
408         public void clearLayer(int index) {\r
409                 if(index <= 0 || index > layerCount) {\r
410                         Debug.warning("Scene.clearLayer", "Tried clearing invalid layer (" + index + "), maximum is " + layerCount);\r
411                         return;\r
412                 }\r
413                 layer[index].clear();\r
414         }\r
415         \r
416         /**\r
417          * Moves a Layer from one index to another, and shuffles the others up or down to accomodate\r
418          * \r
419          * @param startIndex the current index of the Layer\r
420          * @param endIndex the desired final index of the Layer\r
421          */\r
422         public void moveLayer(int startIndex, int endIndex) {\r
423                 if(startIndex == endIndex) {\r
424                         Debug.warning("Scene.moveLayer", "Tried moving a Layer to its own position, stupid");\r
425                         return;\r
426                 }\r
427                 if(startIndex <= 0 || startIndex > layerCount) {\r
428                         Debug.warning("Scene.moveLayer", "Tried moving an invalid Layer, startIndex=" + startIndex + ", maximum is " + layerCount);\r
429                         return;\r
430                 }\r
431                 if(endIndex <= 0 || endIndex > layerCount) {\r
432                         Debug.warning("Scene.moveLayer", "Tried moving an invalid Layer, endIndex=" + endIndex + ", maximum is " + layerCount);\r
433                         return;\r
434                 }\r
435                 Layer temporaryLayer = layer[startIndex];\r
436                 if(endIndex < startIndex) {\r
437                         for(int i = endIndex; i < startIndex; i++) {\r
438                                 layer[i + 1] = layer[i];\r
439                         }\r
440                         layer[endIndex] = temporaryLayer;\r
441                 }\r
442                 if(endIndex > startIndex) { \r
443                         for(int i = startIndex; i < endIndex; i++) {\r
444                                 layer[i] = layer[i + 1];\r
445                         }\r
446                         layer[endIndex] = temporaryLayer;\r
447                 }\r
448         }\r
449         \r
450         /**\r
451          * Switches the position of one Layer with another\r
452          * \r
453          * @param layer1 the index of the first Layer\r
454          * @param layer2 the index of the second Layer\r
455          */\r
456         public void switchLayers(int layer1, int layer2) {\r
457                 if(layer1 == layer2) {\r
458                         Debug.warning("Scene.switchLayers", "Tried switching the same Layer");\r
459                         return;\r
460                 }\r
461                 if(layer1 < 0 || layer1 > layerCount) {\r
462                         Debug.warning("Scene.switchLayers", "Tried switch an invalid Layer, layer1=" + layer1 + ", maximum is " + layerCount);\r
463                         return;\r
464                 }\r
465                 if(layer2 < 0 || layer2 > layerCount) {\r
466                         Debug.warning("Scene.switchLayers", "Tried switch an invalid Layer, layer2=" + layer2 + ", maximum is " + layerCount);\r
467                         return;\r
468                 }\r
469                 Layer temporaryLayer = layer[layer1];\r
470                 layer[layer1] = layer[layer2];\r
471                 layer[layer2] = temporaryLayer;\r
472         }\r
473         \r
474         /**\r
475          * Replaces a Layer object in this Scene\r
476          * \r
477          * @param index a valid index for a Layer, less than getLayerCount\r
478          * @param layer a valid Layer object to replace the existing Layer\r
479          */\r
480         public void setLayer(int index, Layer layer) {\r
481                 if(layer == null) {\r
482                         Debug.warning("Scene.setLayer", "Tried setting to a null Layer");\r
483                         return;\r
484                 }\r
485                 if(index < 0 || index > layerCount) {\r
486                         Debug.warning("Scene.setLayer", "Tried setting an invalid Layer, index=" + index + ", maximum is " + layerCount);\r
487                         return;\r
488                 }\r
489                 this.layer[index] = layer;\r
490         }\r
491         \r
492         /**\r
493          * Adds a DrawableObject to the first (0th) Layer\r
494          * \r
495          * @param drawableObject a valid DrawableObject\r
496          */\r
497         public void add(DrawableObject drawableObject) {\r
498                 layer[0].add(drawableObject);\r
499         }\r
500         \r
501         /**\r
502          * Adds a DrawableObject to a given Layer\r
503          * \r
504          * @param layerIndex a valid index of a Layer\r
505          * @param drawableObject a valid DrawableObject\r
506          */\r
507         public void add(int layerIndex, DrawableObject drawableObject) {\r
508                 if(layerIndex < 0 || layerIndex > layerCount) {\r
509                         Debug.warning("Scene.add", "Tried adding to an invalid Layer, layerIndex=" + layerIndex + ", maximum is " + layerCount);\r
510                         return;\r
511                 }\r
512                 if(drawableObject == null) {\r
513                         Debug.warning("Scene.add", "Tried adding a NULL DrawableObject");\r
514                         return;\r
515                 }\r
516                 layer[layerIndex].add(drawableObject);\r
517         }\r
518         \r
519         /**\r
520          * Removes a DrawableObject from the Scene\r
521          * \r
522          * @param drawableObject a valid DrawableObject\r
523          */\r
524         public void remove(DrawableObject drawableObject) {\r
525                 drawableObject.remove();\r
526         }\r
527         \r
528         protected void onUpdate() {\r
529                 \r
530         }\r
531         \r
532         protected void onGameLoop() {\r
533                 \r
534         }\r
535         \r
536         protected void onSetScene() {\r
537                 loadedTextures = false;\r
538         }\r
539         \r
540         protected void onEndScene() {\r
541                 \r
542         }\r
543         \r
544         protected void onLoadTextures(GL10 gl) {\r
545                 Debug.print("Loading textures onto the Scene");\r
546                 for(int i = 0; i < texturesToLoad.length; i++) {\r
547                         if(texturesToLoad[i] != null) {\r
548                                 texturesToLoad[i].onLoadTexture(gl);\r
549                                 boolean foundSpace = false;\r
550                                 for(int j = 0; j < texturesOnHardware.length; j++) {\r
551                                         if(texturesOnHardware[j] == null) {\r
552                                                 Debug.print("found room...");\r
553                                                 texturesOnHardware[j] = texturesToLoad[i];\r
554                                                 foundSpace = true;\r
555                                                 break;\r
556                                         }\r
557                                 }\r
558                                 if(!foundSpace) {\r
559                                         Debug.warning("Loading more textures than we can remember - will not be there if we onPause, may not be destroyed on Scene death");\r
560                                 }\r
561                                 texturesToLoad[i] = null;\r
562                         }\r
563                 }\r
564                 loadedTextures = true;\r
565         }\r
566         \r
567         protected void onReloadTextures(GL10 gl) {\r
568                 texturesToLoad = texturesOnHardware;\r
569                 for(int i = 0; i < texturesOnHardware.length; i++) {\r
570                         texturesOnHardware[i] = null;\r
571                 }\r
572                 onLoadTextures(gl);\r
573         }\r
574         \r
575         protected void onDraw(GL10 gl) {\r
576                 if(window != null) {\r
577                         window.onUpdate(gl);\r
578                 }\r
579                 onPreDraw(gl);\r
580                 if(usePhysics && !pausePhysics) {\r
581                         world.step(Time.getTicksFraction(), 10, 10);\r
582                 }\r
583                 gl.glClear(GL10.GL_COLOR_BUFFER_BIT);\r
584                 gl.glMatrixMode(GL10.GL_MODELVIEW);\r
585         gl.glLoadIdentity();\r
586                 for(int i = 0; i < layerCount; i++) {\r
587                         layer[i].onDraw(gl);\r
588                 }\r
589                 onPostDraw(gl);\r
590         }\r
591         \r
592         protected boolean pausePhysics = false;\r
593         \r
594         public void pausePhysics() {\r
595                 pausePhysics = true;\r
596         }\r
597         \r
598         public void resumePhysics() {\r
599                 pausePhysics = false;\r
600         }\r
601         \r
602         protected class SceneContactListener implements ContactListener {\r
603 \r
604                 public void beginContact(Contact contact) {\r
605                         // TODO Auto-generated method stub\r
606                         \r
607                 }\r
608 \r
609                 public void endContact(Contact contact) {\r
610                         // TODO Auto-generated method stub\r
611                         \r
612                 }\r
613                 \r
614         }\r
615         \r
616         public void useContactListener() {\r
617                 if(contactListener == null) {\r
618                         contactListener = new SceneContactListener();\r
619                 }\r
620                 useContactListener = true;\r
621         }\r
622         \r
623         public void stopContactListener() {\r
624                 useContactListener = false;\r
625         }\r
626         \r
627         public void setContactListener(ContactListener contactListener) {\r
628                 this.contactListener = contactListener;\r
629         }\r
630 }\r