- merged a few duplicated if's in player
[supertux.git] / src / player.cpp
1 //  $Id$
2 //
3 //  SuperTux -  A Jump'n Run
4 //  Copyright (C) 2003 Tobias Glaesser <tobi.web@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <math.h>
21
22 #include "gameloop.h"
23 #include "globals.h"
24 #include "player.h"
25 #include "defines.h"
26 #include "scene.h"
27 #include "tile.h"
28 #include "sprite.h"
29 #include "screen.h"
30
31 Surface* tux_life;
32 std::vector<Surface*> tux_right;
33 std::vector<Surface*> tux_left;
34 Surface* smalltux_jump_left;
35 Surface* smalltux_jump_right;
36 Surface* smalltux_stand_left;
37 Surface* smalltux_stand_right;
38 Sprite*  smalltux_gameover;
39 Sprite*  smalltux_skid_left;
40 Sprite*  smalltux_skid_right;
41
42 Sprite* bigtux_right;
43 Sprite* bigtux_left;
44 Sprite* bigtux_right_jump;
45 Sprite* bigtux_left_jump;
46 Sprite* ducktux_right;
47 Sprite* ducktux_left;
48 Surface* skidtux_right;
49 Surface* skidtux_left;
50 Surface* firetux_right[3];
51 Surface* firetux_left[3];
52 Surface* bigfiretux_right[3];
53 Surface* bigfiretux_left[3];
54 Surface* bigfiretux_right_jump;
55 Surface* bigfiretux_left_jump;
56 Surface* duckfiretux_right;
57 Surface* duckfiretux_left;
58 Surface* skidfiretux_right;
59 Surface* skidfiretux_left;
60 Surface* cape_right[2];
61 Surface* cape_left[2];
62 Surface* bigcape_right[2];
63 Surface* bigcape_left[2];
64
65 PlayerKeymap keymap;
66
67 PlayerKeymap::PlayerKeymap()
68 {
69   keymap.jump  = SDLK_UP;
70   keymap.duck  = SDLK_DOWN;
71   keymap.left  = SDLK_LEFT;
72   keymap.right = SDLK_RIGHT;
73   keymap.fire  = SDLK_LCTRL;
74 }
75
76 void player_input_init(player_input_type* pplayer_input)
77 {
78   pplayer_input->down = UP;
79   pplayer_input->fire = UP;
80   pplayer_input->left = UP;
81   pplayer_input->old_fire = UP;
82   pplayer_input->right = UP;
83   pplayer_input->up = UP;
84 }
85
86 void
87 Player::init()
88 {
89   Level* plevel = World::current()->get_level();
90
91   base.width = 32;
92   base.height = 32;
93
94   size = SMALL;
95   got_coffee = false;
96
97   base.x = plevel->start_pos_x;
98   base.y = plevel->start_pos_y;
99   base.xm = 0;
100   base.ym = 0;
101   previous_base = old_base = base;
102   dir = RIGHT;
103   duck = false;
104
105   dying   = DYING_NOT;
106   jumping = false;
107
108   frame_main = 0;
109   frame_ = 0;
110   
111   player_input_init(&input);
112
113   invincible_timer.init(true);
114   skidding_timer.init(true);
115   safe_timer.init(true);
116   frame_timer.init(true);
117
118   physic.reset();
119 }
120
121 int
122 Player::key_event(SDLKey key, int state)
123 {
124   if(key == keymap.right)
125     {
126       input.right = state;
127       return true;
128     }
129   else if(key == keymap.left)
130     {
131       input.left = state;
132       return true;
133     }
134   else if(key == keymap.jump)
135     {
136       input.up = state;
137       return true;
138     }
139   else if(key == keymap.duck)
140     {
141       input.down = state;
142       return true;
143     }
144   else if(key == keymap.fire)
145     {
146       input.fire = state;
147       return true;
148     }
149   else
150     return false;
151 }
152
153 void
154 Player::level_begin()
155 {
156   base.x  = 100;
157   base.y  = 170;
158   base.xm = 0;
159   base.ym = 0;
160   previous_base = old_base = base;
161   duck = false;
162
163   dying = DYING_NOT;
164
165   player_input_init(&input);
166
167   invincible_timer.init(true);
168   skidding_timer.init(true);
169   safe_timer.init(true);
170   frame_timer.init(true);
171
172   physic.reset();
173 }
174
175 void
176 Player::action(double frame_ratio)
177 {
178   bool jumped_in_solid = false;
179
180   /* Move tux: */
181   previous_base = base;
182
183   /* --- HANDLE TUX! --- */
184   if(dying == DYING_NOT)
185     handle_input();
186
187   physic.apply(frame_ratio, base.x, base.y);
188
189   if(dying == DYING_NOT) 
190     {
191       base_type target = base;
192
193       collision_swept_object_map(&old_base, &base);
194
195       // Don't accelerate Tux if he is running against a wall
196       if (target.x != base.x)
197         {
198           physic.set_velocity_x(0);
199         }
200
201       // special exception for cases where we're stuck under tiles after
202       // being ducked. In this case we drift out
203       if(!duck && on_ground() && old_base.x == base.x && old_base.y == base.y
204          && collision_object_map(&base)) {
205         base.x += frame_ratio * WALK_SPEED * (dir ? 1 : -1);
206         previous_base = old_base = base;
207       }
208       keep_in_bounds();
209
210       // Land:
211       if (!on_ground())
212         {
213           physic.enable_gravity(true);
214           if(under_solid())
215             {
216               // fall down
217               physic.set_velocity_y(0);
218               jumped_in_solid = true;
219             }
220         }
221       else
222         {
223           /* Land: */
224           if (physic.get_velocity_y() < 0)
225             {
226               base.y = (int)(((int)base.y / 32) * 32);
227               physic.set_velocity_y(0);
228             }
229
230           physic.enable_gravity(false);
231           /* Reset score multiplier (for multi-hits): */
232           player_status.score_multiplier = 1;
233         }
234
235       if(jumped_in_solid)
236         {
237           if (isbrick(base.x, base.y) ||
238               isfullbox(base.x, base.y))
239             {
240               World::current()->trygrabdistro(base.x, base.y - 32,BOUNCE);
241               World::current()->trybumpbadguy(base.x, base.y - 64);
242
243               World::current()->trybreakbrick(base.x, base.y, size == SMALL);
244
245               bumpbrick(base.x, base.y);
246               World::current()->tryemptybox(base.x, base.y, RIGHT);
247             }
248
249           if (isbrick(base.x+ 31, base.y) ||
250               isfullbox(base.x+ 31, base.y))
251             {
252               World::current()->trygrabdistro(base.x+ 31, base.y - 32,BOUNCE);
253               World::current()->trybumpbadguy(base.x+ 31, base.y - 64);
254
255               if(size == BIG)
256                 World::current()->trybreakbrick(base.x+ 31, base.y, size == SMALL);
257
258               bumpbrick(base.x+ 31, base.y);
259               World::current()->tryemptybox(base.x+ 31, base.y, LEFT);
260             }
261         }
262
263       grabdistros();
264
265       if (jumped_in_solid)
266         {
267           ++base.y;
268           ++old_base.y;
269           if(on_ground())
270             {
271               /* Make sure jumping is off. */
272               jumping = false;
273             }
274         }
275     }
276
277   /* ---- DONE HANDLING TUX! --- */
278
279   // check some timers
280   skidding_timer.check();
281   invincible_timer.check();
282   safe_timer.check();
283 }
284
285 bool
286 Player::on_ground()
287 {
288   return ( issolid(base.x + base.width / 2, base.y + base.height) ||
289            issolid(base.x + 1, base.y + base.height) ||
290            issolid(base.x + base.width - 1, base.y + base.height)  );
291 }
292
293 bool
294 Player::under_solid()
295 {
296   return ( issolid(base.x + base.width / 2, base.y) ||
297            issolid(base.x + 1, base.y) ||
298            issolid(base.x + base.width - 1, base.y)  );
299 }
300
301 void
302 Player::handle_horizontal_input()
303 {
304   float vx = physic.get_velocity_x();
305   float vy = physic.get_velocity_y();
306   float ax = physic.get_acceleration_x();
307   float ay = physic.get_acceleration_y();
308
309   float dirsign = 0;
310   if(input.left == DOWN && input.right == UP && (!duck || physic.get_velocity_y() != 0)) {
311       dir = LEFT;
312       dirsign = -1;
313   } else if(input.left == UP && input.right == DOWN && (!duck || physic.get_velocity_y() != 0)) {
314       dir = RIGHT;
315       dirsign = 1;
316   }
317
318   if (input.fire == UP) {
319       ax = dirsign * WALK_ACCELERATION_X;
320       // limit speed
321       if(vx >= MAX_WALK_XM && dirsign > 0) {
322         vx = MAX_WALK_XM;
323         ax = 0;
324       } else if(vx <= -MAX_WALK_XM && dirsign < 0) {
325         vx = -MAX_WALK_XM;
326         ax = 0;
327       }
328   } else {
329       ax = dirsign * RUN_ACCELERATION_X;
330       // limit speed
331       if(vx >= MAX_RUN_XM && dirsign > 0) {
332         vx = MAX_RUN_XM;
333         ax = 0;
334       } else if(vx <= -MAX_RUN_XM && dirsign < 0) {
335         vx = -MAX_RUN_XM;
336         ax = 0;
337       }
338   }
339
340   // we can reach WALK_SPEED without any acceleration
341   if(dirsign != 0 && fabs(vx) < WALK_SPEED) {
342     vx = dirsign * WALK_SPEED;
343   }
344
345   // changing directions?
346   if(on_ground() && ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0))) {
347       if(fabs(vx)>SKID_XM && !skidding_timer.check()) {
348           skidding_timer.start(SKID_TIME);
349           play_sound(sounds[SND_SKID], SOUND_CENTER_SPEAKER);
350           ax *= 2.5;
351       } else {
352           ax *= 2;
353       }
354   }
355
356   // we get slower when not pressing any keys
357   if(dirsign == 0) {
358       if(fabs(vx) < WALK_SPEED) {
359           vx = 0;
360           ax = 0;
361       } else if(vx < 0) {
362           ax = WALK_ACCELERATION_X * 1.5;
363       } else {
364           ax = WALK_ACCELERATION_X * -1.5;
365       }
366   }
367
368   // if we're on ice slow down acceleration or deceleration
369   if (isice(base.x, base.y + base.height))
370   {
371     /* the acceleration/deceleration rate on ice is inversely proportional to
372      * the current velocity.
373      */
374
375     // increasing 1 will increase acceleration/deceleration rate
376     // decreasing 1 will decrease acceleration/deceleration rate
377     //  must stay above zero, though
378     if (ax != 0) ax *= 1 / fabs(vx);
379   }
380
381   physic.set_velocity(vx, vy);
382   physic.set_acceleration(ax, ay);
383 }
384
385 void
386 Player::handle_vertical_input()
387 {
388   if(input.up == DOWN)
389     {
390       if (on_ground())
391         {
392           // jump higher if we are running
393           if (physic.get_velocity_x() > MAX_WALK_XM)
394             physic.set_velocity_y(5.8);
395           else
396             physic.set_velocity_y(5.2);
397
398           --base.y;
399           jumping = true;
400           if (size == SMALL)
401             play_sound(sounds[SND_JUMP], SOUND_CENTER_SPEAKER);
402           else
403             play_sound(sounds[SND_BIGJUMP], SOUND_CENTER_SPEAKER);
404         }
405     }
406   else if(input.up == UP && jumping)
407     {
408       jumping = false;
409       if(physic.get_velocity_y() > 0) {
410         physic.set_velocity_y(0);
411       }
412     }
413 }
414
415 void
416 Player::handle_input()
417 {
418   /* Handle horizontal movement: */
419     handle_horizontal_input();
420
421   /* Jump/jumping? */
422
423   if ( input.up == DOWN || (input.up == UP && jumping))
424     {
425       handle_vertical_input();
426     }
427
428   /* Shoot! */
429
430   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
431     {
432       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
433     }
434
435   /* tux animations: */
436   if(!frame_timer.check())
437     {
438       frame_timer.start(25);
439       if (input.right == UP && input.left == UP)
440         {
441           frame_main = 1;
442           frame_ = 1;
443         }
444       else
445         {
446           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
447               (global_frame_counter % 4) == 0)
448             frame_main = (frame_main + 1) % 4;
449
450           frame_ = frame_main;
451
452           if (frame_ == 3)
453             frame_ = 1;
454         }
455     }
456
457   /* Duck! */
458   if (input.down == DOWN && size == BIG && !duck && physic.get_velocity_y() == 0)
459     {
460       duck = true;
461       base.height = 32;                             
462       base.y += 32;
463       // changing base size confuses collision otherwise
464       old_base = previous_base = base;
465     }
466   else if(input.down == UP && size == BIG && duck && physic.get_velocity_y() == 0)
467     {
468       duck = false;
469       base.y -= 32;
470       base.height = 64;
471       old_base = previous_base = base;
472     }
473 }
474
475 void
476 Player::grabdistros()
477 {
478   /* Grab distros: */
479   if (!dying)
480     {
481       World::current()->trygrabdistro(base.x, base.y, NO_BOUNCE);
482       World::current()->trygrabdistro(base.x+ 31, base.y, NO_BOUNCE);
483
484       World::current()->trygrabdistro(base.x, base.y + base.height, NO_BOUNCE);
485       World::current()->trygrabdistro(base.x+ 31, base.y + base.height, NO_BOUNCE);
486
487       if(size == BIG)
488         {
489           World::current()->trygrabdistro(base.x, base.y + base.height / 2, NO_BOUNCE);
490           World::current()->trygrabdistro(base.x+ 31, base.y + base.height / 2, NO_BOUNCE);
491         }
492
493     }
494
495   /* Enough distros for a One-up? */
496   if (player_status.distros >= DISTROS_LIFEUP)
497     {
498       player_status.distros = player_status.distros - DISTROS_LIFEUP;
499       if(player_status.lives < MAX_LIVES)
500         ++player_status.lives;
501       /*We want to hear the sound even, if MAX_LIVES is reached*/
502       play_sound(sounds[SND_LIFEUP], SOUND_CENTER_SPEAKER);
503     }
504 }
505
506 void
507 Player::draw()
508 {
509   if (!safe_timer.started() || (global_frame_counter % 2) == 0)
510     {
511       if (dying == DYING_SQUISHED)
512         {
513           smalltux_gameover->draw(base.x - scroll_x, base.y);
514         }
515       else
516         {
517           if (size == SMALL)
518             {
519               if (invincible_timer.started())
520                 {
521                   if (dir == RIGHT)
522                     cape_right[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
523                   else
524                     cape_left[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
525                 }
526
527               if (!got_coffee)
528                 {
529                   if (!skidding_timer.started())
530                     {
531                       if (physic.get_velocity_y() != 0)
532                         {
533                           if (dir == RIGHT)
534                             smalltux_jump_right->draw( base.x - scroll_x, base.y - 10);
535                           else
536                             smalltux_jump_left->draw( base.x - scroll_x, base.y - 10);                   
537                         }
538                       else
539                         {
540                           if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
541                             {
542                               if (dir == RIGHT)
543                                 smalltux_stand_right->draw( base.x - scroll_x, base.y - 9);
544                               else
545                                 smalltux_stand_left->draw( base.x - scroll_x, base.y - 9);
546                             }
547                           else // moving
548                             {
549                               if (dir == RIGHT)
550                                 tux_right[(global_frame_counter/2) % tux_right.size()]->draw(base.x - scroll_x, base.y - 9);
551                               else
552                                 tux_left[(global_frame_counter/2) % tux_left.size()]->draw(base.x - scroll_x, base.y - 9);
553                             }
554                         }
555                     }
556                   else
557                     {
558                       if (dir == RIGHT)
559                         smalltux_skid_right->draw(base.x - scroll_x, base.y);
560                       else
561                         smalltux_skid_left->draw(base.x - scroll_x, base.y); 
562                     }
563                 }
564               else
565                 {
566                   /* Tux got coffee! */
567
568                   if (dir == RIGHT)
569                     {
570                       firetux_right[frame_]->draw( base.x- scroll_x, base.y);
571                     }
572                   else
573                     {
574                       firetux_left[frame_]->draw( base.x- scroll_x, base.y);
575                     }
576                 }
577             }
578           else // Large Tux
579             {
580               if (invincible_timer.started())
581                 {
582                   float capex = base.x + (base.width - bigcape_right[0]->w) / 2;
583                   capex -= scroll_x;
584                   float capey = base.y + (base.height - bigcape_right[0]->h) / 2;
585                 
586                   /* Draw cape (just not in ducked mode since that looks silly): */
587                   if (dir == RIGHT)
588                     bigcape_right[global_frame_counter % 2]->draw(capex, capey);
589                   else
590                     bigcape_left[global_frame_counter % 2]->draw(capex, capey);
591                 }
592
593               if (!got_coffee)
594                 {
595                   if (!duck)
596                     {
597                       if (!skidding_timer.started())
598                         {
599                           if (physic.get_velocity_y() == 0)
600                             {
601                               if (dir == RIGHT)
602                                 bigtux_right->draw(base.x - scroll_x, base.y);
603                               else
604                                 bigtux_left->draw(base.x - scroll_x, base.y);
605                             }
606                           else
607                             {
608                               if (dir == RIGHT)
609                                 bigtux_right_jump->draw(base.x - scroll_x, base.y);
610                               else
611                                 bigtux_left_jump->draw(base.x - scroll_x, base.y);
612                             }
613                         }
614                       else
615                         {
616                           if (dir == RIGHT)
617                             skidtux_right->draw(base.x - scroll_x - 8, base.y);
618                           else
619                             skidtux_left->draw(base.x - scroll_x - 8, base.y);
620                         }
621                     }
622                   else
623                     {
624                       if (dir == RIGHT)
625                         ducktux_right->draw(base.x - scroll_x, base.y);
626                       else
627                         ducktux_left->draw(base.x - scroll_x, base.y);
628                     }
629                 }
630               else
631                 {
632                   /* Tux has coffee! */
633                   if (!duck)
634                     {
635                       if (!skidding_timer.started())
636                         {
637                           if (!jumping || physic.get_velocity_y() > 0)
638                             {
639                               if (dir == RIGHT)
640                                 bigfiretux_right[frame_]->draw(base.x- scroll_x - 8, base.y);
641                               else
642                                 bigfiretux_left[frame_]->draw(base.x- scroll_x - 8, base.y);
643                             }
644                           else
645                             {
646                               if (dir == RIGHT)
647                                 bigfiretux_right_jump->draw(base.x- scroll_x - 8, base.y);
648                               else
649                                 bigfiretux_left_jump->draw(base.x- scroll_x - 8, base.y);
650                             }
651                         }
652                       else
653                         {
654                           if (dir == RIGHT)
655                             skidfiretux_right->draw(base.x- scroll_x - 8, base.y);
656                           else
657                             skidfiretux_left->draw(base.x- scroll_x - 8, base.y);
658                         }
659                     }
660                   else
661                     {
662                       if (dir == RIGHT)
663                         duckfiretux_right->draw( base.x- scroll_x - 8, base.y - 16);
664                       else
665                         duckfiretux_left->draw( base.x- scroll_x - 8, base.y - 16);
666                     }
667                 }
668             }
669         }
670     }
671
672   if (debug_mode)
673     fillrect(base.x - scroll_x, base.y, 32, 32, 75,75,75, 150);
674 }
675
676 void
677 Player::collision(void* p_c_object, int c_object)
678 {
679   BadGuy* pbad_c = NULL;
680
681   switch (c_object)
682     {
683     case CO_BADGUY:
684       pbad_c = (BadGuy*) p_c_object;
685
686      /* Hurt player if he touches a badguy */
687       if (!pbad_c->dying && !dying &&
688           !safe_timer.started() &&
689           pbad_c->mode != BadGuy::HELD)
690         {
691           if (pbad_c->mode == BadGuy::FLAT && input.fire == DOWN)
692             {
693               pbad_c->mode = BadGuy::HELD;
694               pbad_c->base.y-=8;
695             }
696           else if (pbad_c->mode == BadGuy::FLAT)
697             {
698               // Don't get hurt if we're kicking a flat badguy!
699             }
700           else if (pbad_c->mode == BadGuy::KICK)
701             {
702               /* Hurt if you get hit by kicked laptop: */
703               if (!invincible_timer.started())
704                 {
705                   kill(SHRINK);
706                 }
707               else
708                 {
709                    pbad_c->dying = DYING_FALLING;
710                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
711                    World::current()->add_score(pbad_c->base.x - scroll_x,
712                                                pbad_c->base.y,
713                                                25 * player_status.score_multiplier);
714                 }
715             }
716           else
717             {
718               if (!invincible_timer.started())
719                 {
720                   kill(SHRINK);
721                 }
722               else
723                 {
724                   pbad_c->kill_me();
725                 }
726             }
727           player_status.score_multiplier++;
728         }
729       break;
730     default:
731       break;
732     }
733
734 }
735
736 /* Kill Player! */
737
738 void
739 Player::kill(int mode)
740 {
741   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
742
743   physic.set_velocity_x(0);
744
745   if (mode == SHRINK && size == BIG)
746     {
747       if (got_coffee)
748         got_coffee = false;
749
750       size = SMALL;
751       base.height = 32;
752       duck = false;
753
754       safe_timer.start(TUX_SAFE_TIME);
755     }
756   else
757     {
758       physic.enable_gravity(true);
759       physic.set_acceleration(0, 0);
760       physic.set_velocity(0, 7);
761       dying = DYING_SQUISHED;
762     }
763 }
764
765 void
766 Player::is_dying()
767 {
768   remove_powerups();
769   dying = DYING_NOT;
770 }
771
772 bool Player::is_dead()
773 {
774   if(base.y > screen->h)
775     return true;
776   else
777     return false;
778 }
779
780 /* Remove Tux's power ups */
781 void
782 Player::remove_powerups()
783 {
784   got_coffee = false;
785   size = SMALL;
786   base.height = 32;
787 }
788
789 void
790 Player::keep_in_bounds()
791 {
792   Level* plevel = World::current()->get_level();
793
794   /* Keep tux in bounds: */
795   if (base.x < 0)
796     { // Lock Tux to the size of the level, so that he doesn't fall of
797       // on the left side
798       base.x = 0;
799     }
800   else if (base.x < scroll_x)
801     { 
802       base.x = scroll_x;
803     }
804
805   /* Keep in-bounds, vertically: */
806   if (base.y > screen->h)
807     {
808       kill(KILL);
809     }
810
811   int scroll_threshold = screen->w/2 - 80;
812   if (debug_mode)
813     {
814       scroll_x += screen->w/2;
815       // Backscrolling for debug mode
816       if (scroll_x < base.x - 80)
817         scroll_x = base.x - 80;
818       else if (scroll_x > base.x + 80)
819         scroll_x = base.x + 80;
820       scroll_x -= screen->w/2;
821
822       if(scroll_x < 0)
823         scroll_x = 0;
824     }
825   else
826     {
827       if (base.x > scroll_threshold + scroll_x
828           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
829         {
830           // FIXME: Scrolling needs to be handled by a seperate View
831           // class, doing it as a player huck is ugly
832           
833           // Scroll the screen in past center:
834           scroll_x = base.x - scroll_threshold;
835           
836           // Lock the scrolling to the levelsize, so that we don't
837           // scroll over the right border
838           if (scroll_x > 32 * plevel->width - screen->w)
839             scroll_x = 32 * plevel->width - screen->w;
840         }
841     }
842 }
843
844 // EOF //
845