fixed score display and converted tilemap to a gameobject
[supertux.git] / src / world.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; either version 2
11 //  of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 // 
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21 //  02111-1307, USA.
22
23 #include <iostream>
24 #include <math.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include "globals.h"
28 #include "scene.h"
29 #include "screen.h"
30 #include "defines.h"
31 #include "world.h"
32 #include "level.h"
33 #include "tile.h"
34 #include "resources.h"
35 #include "gameobjs.h"
36 #include "viewport.h"
37 #include "display_manager.h"
38 #include "background.h"
39 #include "tilemap.h"
40
41 Surface* img_distro[4];
42
43 World* World::current_ = 0;
44
45 World::World(const std::string& filename)
46 {
47   // FIXME: Move this to action and draw and everywhere else where the
48   // world calls child functions
49   current_ = this;
50
51   level = new Level(filename);
52   tux.init();
53
54   set_defaults();
55
56   get_level()->load_gfx();
57   activate_bad_guys();
58   // add background
59   activate_particle_systems();
60   Background* bg = new Background(displaymanager);
61   if(level->img_bkgd) {
62     bg->set_image(level->img_bkgd, level->bkgd_speed);
63   } else {
64     bg->set_gradient(level->bkgd_top, level->bkgd_bottom);
65   }
66   gameobjects.push_back(bg);
67
68   // add tilemap
69   gameobjects.push_back(new TileMap(displaymanager, get_level()));
70   activate_objects();
71   get_level()->load_song();
72
73   apply_bonuses();
74
75   scrolling_timer.init(true);
76 }
77
78 World::World(const std::string& subset, int level_nr)
79 {
80   // FIXME: Move this to action and draw and everywhere else where the
81   // world calls child functions
82   current_ = this;
83
84   level = new Level(subset, level_nr);
85   tux.init();
86
87   set_defaults();
88
89   get_level()->load_gfx();
90   activate_bad_guys();
91   activate_objects();
92   activate_particle_systems();
93   Background* bg = new Background(displaymanager);
94   if(level->img_bkgd) {
95     bg->set_image(level->img_bkgd, level->bkgd_speed);
96   } else {
97     bg->set_gradient(level->bkgd_top, level->bkgd_bottom);
98   }
99   gameobjects.push_back(bg);
100   // add tilemap
101   gameobjects.push_back(new TileMap(displaymanager, get_level()));  
102   get_level()->load_song();
103
104   apply_bonuses();
105
106   scrolling_timer.init(true);
107 }
108
109 void
110 World::apply_bonuses()
111 {
112   // Apply bonuses from former levels
113   switch (player_status.bonus)
114     {
115     case PlayerStatus::NO_BONUS:
116       break;
117
118     case PlayerStatus::FLOWER_BONUS:
119       tux.got_power = tux.FIRE_POWER;  // FIXME: add ice power to here
120       // fall through
121
122     case PlayerStatus::GROWUP_BONUS:
123       // FIXME: Move this to Player class
124       tux.size = BIG;
125       tux.base.height = 64;
126       tux.base.y -= 32;
127       break;
128     }
129 }
130
131 World::~World()
132 {
133   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
134     delete *i;
135
136   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
137     delete *i;
138
139   for (std::vector<_GameObject*>::iterator i = gameobjects.begin();
140           i != gameobjects.end(); ++i) {
141     Drawable* drawable = dynamic_cast<Drawable*> (*i);
142     if(drawable)
143       displaymanager.remove_drawable(drawable);
144     delete *i;
145   }
146
147   delete level;
148 }
149
150 void
151 World::set_defaults()
152 {
153   // Set defaults: 
154   scroll_x = 0;
155
156   player_status.score_multiplier = 1;
157
158   counting_distros = false;
159   distro_counter = 0;
160
161   /* set current song/music */
162   currentmusic = LEVEL_MUSIC;
163 }
164
165 void
166 World::activate_bad_guys()
167 {
168   for (std::vector<BadGuyData>::iterator i = level->badguy_data.begin();
169        i != level->badguy_data.end();
170        ++i)
171     {
172       add_bad_guy(i->x, i->y, i->kind, i->stay_on_platform);
173     }
174 }
175
176 void
177 World::activate_objects()
178 {
179   for (std::vector< ObjectData<TrampolineData> >::iterator i = level->trampoline_data.begin();
180        i != level->trampoline_data.end();
181        ++i)
182   {
183     add_object<Trampoline, ObjectData<TrampolineData> >(*i);
184   }
185 }
186
187 void
188 World::activate_particle_systems()
189 {
190   if (level->particle_system == "clouds")
191     {
192       gameobjects.push_back(new CloudParticleSystem(displaymanager));
193     }
194   else if (level->particle_system == "snow")
195     {
196       gameobjects.push_back(new SnowParticleSystem(displaymanager));
197     }
198   else if (level->particle_system != "")
199     {
200       st_abort("unknown particle system specified in level", "");
201     }
202 }
203
204 void
205 World::draw()
206 {
207   /* Draw objects */
208   displaymanager.get_viewport().set_translation(Vector(scroll_x, scroll_y));
209   displaymanager.draw();
210   
211   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
212     (*i)->draw();
213
214   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
215     (*i)->draw();
216
217   tux.draw();
218
219   for (unsigned int i = 0; i < bullets.size(); ++i)
220     bullets[i].draw();
221
222   for (unsigned int i = 0; i < upgrades.size(); ++i)
223     upgrades[i].draw();
224 }
225
226 void
227 World::action(double frame_ratio)
228 {
229   tux.action(frame_ratio);
230   tux.check_bounds(level->back_scrolling, (bool)level->hor_autoscroll_speed);
231   scrolling(frame_ratio);
232
233   for (unsigned int i = 0; i < bullets.size(); ++i)
234     bullets[i].action(frame_ratio);
235   
236   for (unsigned int i = 0; i < upgrades.size(); i++)
237     upgrades[i].action(frame_ratio);
238
239   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
240     (*i)->action(frame_ratio);
241
242   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
243      (*i)->action(frame_ratio);
244
245   /* update objects */
246   for(std::vector<_GameObject*>::iterator i = gameobjects.begin();
247       i != gameobjects.end(); ++i)
248     (*i)->action(frame_ratio);
249
250   /* Handle all possible collisions. */
251   collision_handler();
252   
253   // Cleanup marked badguys
254   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end();
255       /* ++i handled at end of the loop */) {
256     if ((*i)->is_removable()) {
257       delete *i;
258       i =  bad_guys.erase(i);
259     } else {
260       ++i;
261     }
262   }
263
264   for(std::vector<_GameObject*>::iterator i = gameobjects.begin();
265       i != gameobjects.end(); /* nothing */) {
266     if((*i)->is_valid() == false) {
267       Drawable* drawable = dynamic_cast<Drawable*> (*i);
268       if(drawable)
269         displaymanager.remove_drawable(drawable);
270       
271       delete *i;
272       i = gameobjects.erase(i);
273     } else {
274       ++i;
275     }
276   }
277 }
278
279 /* the space that it takes for the screen to start scrolling, regarding */
280 /* screen bounds (in pixels) */
281 // should be higher than screen->w/2 (400)
282 #define X_SPACE (500-16)
283 // should be less than screen->h/2 (300)
284 #define Y_SPACE 250
285
286 // the time it takes to move the camera (in ms)
287 #define CHANGE_DIR_SCROLL_SPEED 2000
288
289 /* This functions takes cares of the scrolling */
290 void World::scrolling(double frame_ratio)
291 {
292   /* Y-axis scrolling */
293
294   float tux_pos_y = tux.base.y + (tux.base.height/2);
295
296   if(level->height > VISIBLE_TILES_Y-1 && !tux.dying)
297     {
298     if (scroll_y < tux_pos_y - (screen->h - Y_SPACE))
299       scroll_y = tux_pos_y - (screen->h - Y_SPACE);
300     else if (scroll_y > tux_pos_y - Y_SPACE)
301       scroll_y = tux_pos_y - Y_SPACE;
302     }
303
304   // this code prevent the screen to scroll before the start or after the level's end
305   if(scroll_y > level->height * 32 - screen->h)
306     scroll_y = level->height * 32 - screen->h;
307   if(scroll_y < 0)
308     scroll_y = 0;
309
310   /* X-axis scrolling */
311
312   /* Auto scrolling */
313   if(level->hor_autoscroll_speed)
314   {
315     scroll_x += level->hor_autoscroll_speed * frame_ratio;
316     return;
317   }
318
319
320   /* Horizontal backscrolling */
321   float tux_pos_x = tux.base.x + (tux.base.width/2);
322
323   if(tux.old_dir != tux.dir && level->back_scrolling)
324     scrolling_timer.start(CHANGE_DIR_SCROLL_SPEED);
325
326   bool right = false;
327   bool left = false;
328   if (tux.physic.get_velocity_x() > 0)
329     right = true;
330   else if (tux.physic.get_velocity_x() < 0)
331     left = true;
332   else
333     {
334     if (tux.dir == RIGHT)
335       right = true;
336     else
337       left = true;
338     }
339
340   if(scrolling_timer.check())
341   {
342     float final_scroll_x;
343     float constant1;
344     float constant2;
345     if (right)
346       final_scroll_x = tux_pos_x - (screen->w - X_SPACE);
347     else
348       final_scroll_x = tux_pos_x - X_SPACE;
349
350     if((tux.physic.get_velocity_x() > 0 && tux.dir == RIGHT) || (tux.physic.get_velocity_x() < 0 && tux.dir == LEFT))
351     {
352       constant1 = 1.0;
353       constant2 = .4;
354     }
355     else
356     {
357       constant1 = 0.;
358       constant2 = 0.;
359     }
360     
361     float number = 2.5/(frame_ratio * CHANGE_DIR_SCROLL_SPEED/1000)*exp((CHANGE_DIR_SCROLL_SPEED-scrolling_timer.get_left())/1400.);
362     if(left) number *= -1.;
363
364     scroll_x += number
365             + constant1 * tux.physic.get_velocity_x() * frame_ratio
366             + constant2 * tux.physic.get_acceleration_x() * frame_ratio * frame_ratio;
367
368     if ((right && final_scroll_x - scroll_x < 0) || (left && final_scroll_x - scroll_x > 0))
369       scroll_x = final_scroll_x;
370     
371   }
372   else
373   {
374     if (right && scroll_x < tux_pos_x - (screen->w - X_SPACE))
375       scroll_x = tux_pos_x - (screen->w - X_SPACE);
376     else if (left && scroll_x > tux_pos_x - X_SPACE && level->back_scrolling)
377       scroll_x = tux_pos_x - X_SPACE;
378   }
379
380   // this code prevent the screen to scroll before the start or after the level's end
381   if(scroll_x > level->width * 32 - screen->w)
382     scroll_x = level->width * 32 - screen->w;
383   if(scroll_x < 0)
384     scroll_x = 0;
385 }
386
387 void
388 World::collision_handler()
389 {
390   // CO_BULLET & CO_BADGUY check
391   for(unsigned int i = 0; i < bullets.size(); ++i)
392     {
393       for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j)
394         {
395           if((*j)->dying != DYING_NOT)
396             continue;
397           
398           if(rectcollision(bullets[i].base, (*j)->base))
399             {
400               // We have detected a collision and now call the
401               // collision functions of the collided objects.
402               // collide with bad_guy first, since bullet_collision will
403               // delete the bullet
404               (*j)->collision(&bullets[i], CO_BULLET);
405               bullets[i].collision(CO_BADGUY);
406               break; // bullet is invalid now, so break
407             }
408         }
409     }
410
411   /* CO_BADGUY & CO_BADGUY check */
412   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
413     {
414       if((*i)->dying != DYING_NOT)
415         continue;
416       
417       BadGuys::iterator j = i;
418       ++j;
419       for (; j != bad_guys.end(); ++j)
420         {
421           if(j == i || (*j)->dying != DYING_NOT)
422             continue;
423
424           if(rectcollision((*i)->base, (*j)->base))
425             {
426               // We have detected a collision and now call the
427               // collision functions of the collided objects.
428               (*j)->collision(*i, CO_BADGUY);
429               (*i)->collision(*j, CO_BADGUY);
430             }
431         }
432     }
433
434   if(tux.dying != DYING_NOT) return;
435     
436   // CO_BADGUY & CO_PLAYER check 
437   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
438     {
439       if((*i)->dying != DYING_NOT)
440         continue;
441       
442       if(rectcollision_offset((*i)->base, tux.base, 0, 0))
443         {
444           // We have detected a collision and now call the collision
445           // functions of the collided objects.
446           if (tux.previous_base.y < tux.base.y &&
447               tux.previous_base.y + tux.previous_base.height 
448               < (*i)->base.y + (*i)->base.height/2
449               && !tux.invincible_timer.started())
450             {
451               (*i)->collision(&tux, CO_PLAYER, COLLISION_SQUISH);
452             }
453           else
454             {
455               tux.collision(*i, CO_BADGUY);
456               (*i)->collision(&tux, CO_PLAYER, COLLISION_NORMAL);
457             }
458         }
459     }
460
461   // CO_UPGRADE & CO_PLAYER check
462   for(unsigned int i = 0; i < upgrades.size(); ++i)
463     {
464       if(rectcollision(upgrades[i].base, tux.base))
465         {
466           // We have detected a collision and now call the collision
467           // functions of the collided objects.
468           upgrades[i].collision(&tux, CO_PLAYER, COLLISION_NORMAL);
469         }
470     }
471
472   // CO_TRAMPOLINE & (CO_PLAYER or CO_BADGUY)
473   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
474   {
475     if (rectcollision((*i)->base, tux.base))
476     {
477       if (tux.previous_base.y < tux.base.y &&
478           tux.previous_base.y + tux.previous_base.height 
479           < (*i)->base.y + (*i)->base.height/2)
480       {
481         (*i)->collision(&tux, CO_PLAYER, COLLISION_SQUISH);
482       }
483       else if (tux.previous_base.y <= tux.base.y)
484       {
485         tux.collision(*i, CO_TRAMPOLINE);
486         (*i)->collision(&tux, CO_PLAYER, COLLISION_NORMAL);
487       }
488     }
489   }
490 }
491
492 void
493 World::add_score(const Vector& pos, int s)
494 {
495   player_status.score += s;
496
497   gameobjects.push_back(new FloatingScore(displaymanager, pos, s));
498 }
499
500 void
501 World::add_bouncy_distro(const Vector& pos)
502 {
503   gameobjects.push_back(new BouncyDistro(displaymanager, pos));
504 }
505
506 void
507 World::add_broken_brick(const Vector& pos, Tile* tile)
508 {
509   add_broken_brick_piece(pos, Vector(-1, -4), tile);
510   add_broken_brick_piece(pos + Vector(0, 16), Vector(-1.5, -3), tile);
511
512   add_broken_brick_piece(pos + Vector(16, 0), Vector(1, -4), tile);
513   add_broken_brick_piece(pos + Vector(16, 16), Vector(1.5, -3), tile);
514 }
515
516 void
517 World::add_broken_brick_piece(const Vector& pos, const Vector& movement,
518     Tile* tile)
519 {
520   gameobjects.push_back(new BrokenBrick(displaymanager, tile, pos, movement));
521 }
522
523 void
524 World::add_bouncy_brick(const Vector& pos)
525 {
526   gameobjects.push_back(new BouncyBrick(displaymanager, pos));
527 }
528
529 BadGuy*
530 World::add_bad_guy(float x, float y, BadGuyKind kind, bool stay_on_platform)
531 {
532   BadGuy* badguy = new BadGuy(x,y,kind, stay_on_platform);
533   bad_guys.push_back(badguy);
534   return badguy;
535 }
536
537 template<class T, class U>
538 T*
539 World::add_object(U data)
540 {
541   T* tobject = new T(data);
542
543   if (data.type == OBJ_TRAMPOLINE)
544     trampolines.push_back(tobject);
545
546   return tobject;
547 }
548
549 void
550 World::add_upgrade(float x, float y, Direction dir, UpgradeKind kind)
551 {
552   Upgrade new_upgrade;
553   new_upgrade.init(x,y,dir,kind);
554   upgrades.push_back(new_upgrade);
555 }
556
557 void 
558 World::add_bullet(float x, float y, float xm, Direction dir)
559 {
560   if(tux.got_power == tux.FIRE_POWER)
561     {
562     if(bullets.size() > MAX_FIRE_BULLETS-1)
563       return;
564     }
565   else if(tux.got_power == tux.ICE_POWER)
566     {
567     if(bullets.size() > MAX_ICE_BULLETS-1)
568       return;
569     }
570
571   Bullet new_bullet;
572   if(tux.got_power == tux.FIRE_POWER)
573     new_bullet.init(x,y,xm,dir, FIRE_BULLET);
574   else if(tux.got_power == tux.ICE_POWER)
575     new_bullet.init(x,y,xm,dir, ICE_BULLET);
576   bullets.push_back(new_bullet);
577   
578   play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
579 }
580
581 void
582 World::play_music(int musictype)
583 {
584   currentmusic = musictype;
585   switch(currentmusic) {
586     case HURRYUP_MUSIC:
587       music_manager->play_music(get_level()->get_level_music_fast());
588       break;
589     case LEVEL_MUSIC:
590       music_manager->play_music(get_level()->get_level_music());
591       break;
592     case HERRING_MUSIC:
593       music_manager->play_music(herring_song);
594       break;
595     default:
596       music_manager->halt_music();
597       break;
598   }
599 }
600
601 int
602 World::get_music_type()
603 {
604   return currentmusic;
605 }
606
607 /* Break a brick: */
608 bool
609 World::trybreakbrick(float x, float y, bool small)
610 {
611   Level* plevel = get_level();
612   
613   Tile* tile = gettile(x, y);
614   if (tile->brick)
615     {
616       if (tile->data > 0)
617         {
618           /* Get a distro from it: */
619           add_bouncy_distro(
620               Vector(((int)(x + 1) / 32) * 32, (int)(y / 32) * 32));
621
622           // TODO: don't handle this in a global way but per-tile...
623           if (!counting_distros)
624             {
625               counting_distros = true;
626               distro_counter = 5;
627             }
628           else
629             {
630               distro_counter--;
631             }
632
633           if (distro_counter <= 0)
634             {
635               counting_distros = false;
636               plevel->change(x, y, TM_IA, tile->next_tile);
637             }
638
639           play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
640           player_status.score = player_status.score + SCORE_DISTRO;
641           player_status.distros++;
642           return true;
643         }
644       else if (!small)
645         {
646           /* Get rid of it: */
647           plevel->change(x, y, TM_IA, tile->next_tile);
648           
649           /* Replace it with broken bits: */
650           add_broken_brick(Vector(
651                                  ((int)(x + 1) / 32) * 32,
652                                  (int)(y / 32) * 32), tile);
653           
654           /* Get some score: */
655           play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
656           player_status.score = player_status.score + SCORE_BRICK;
657           
658           return true;
659         }
660     }
661
662   return false;
663 }
664
665 /* Empty a box: */
666 void
667 World::tryemptybox(float x, float y, Direction col_side)
668 {
669   Tile* tile = gettile(x,y);
670   if (!tile->fullbox)
671     return;
672
673   // according to the collision side, set the upgrade direction
674   if(col_side == LEFT)
675     col_side = RIGHT;
676   else
677     col_side = LEFT;
678
679   int posx = ((int)(x+1) / 32) * 32;
680   int posy = (int)(y/32) * 32 - 32;
681   switch(tile->data)
682     {
683     case 1: // Box with a distro!
684       add_bouncy_distro(Vector(posx, posy));
685       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
686       player_status.score = player_status.score + SCORE_DISTRO;
687       player_status.distros++;
688       break;
689
690     case 2: // Add a fire flower upgrade!
691       if (tux.size == SMALL)     /* Tux is small, add mints! */
692         add_upgrade(posx, posy, col_side, UPGRADE_GROWUP);
693       else     /* Tux is big, add a fireflower: */
694         add_upgrade(posx, posy, col_side, UPGRADE_FIREFLOWER);
695       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
696       break;
697     
698     case 5: // Add an ice flower upgrade!
699       if (tux.size == SMALL)     /* Tux is small, add mints! */
700         add_upgrade(posx, posy, col_side, UPGRADE_GROWUP);
701       else     /* Tux is big, add an iceflower: */
702         add_upgrade(posx, posy, col_side, UPGRADE_ICEFLOWER);
703       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
704       break;
705
706     case 3: // Add a golden herring
707       add_upgrade(posx, posy, col_side, UPGRADE_HERRING);
708       break;
709
710     case 4: // Add a 1up extra
711       add_upgrade(posx, posy, col_side, UPGRADE_1UP);
712       break;
713     default:
714       break;
715     }
716
717   /* Empty the box: */
718   level->change(x, y, TM_IA, tile->next_tile);
719 }
720
721 /* Try to grab a distro: */
722 void
723 World::trygrabdistro(float x, float y, int bounciness)
724 {
725   Tile* tile = gettile(x, y);
726   if (tile && tile->distro)
727     {
728       level->change(x, y, TM_IA, tile->next_tile);
729       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
730
731       if (bounciness == BOUNCE)
732         {
733           add_bouncy_distro(Vector(((int)(x + 1) / 32) * 32,
734                                   (int)(y / 32) * 32));
735         }
736
737       player_status.score = player_status.score + SCORE_DISTRO;
738       player_status.distros++;
739     }
740 }
741
742 /* Try to bump a bad guy from below: */
743 void
744 World::trybumpbadguy(float x, float y)
745 {
746   // Bad guys: 
747   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
748     {
749       if ((*i)->base.x >= x - 32 && (*i)->base.x <= x + 32 &&
750           (*i)->base.y >= y - 16 && (*i)->base.y <= y + 16)
751         {
752           (*i)->collision(&tux, CO_PLAYER, COLLISION_BUMP);
753         }
754     }
755
756   // Upgrades:
757   for (unsigned int i = 0; i < upgrades.size(); i++)
758     {
759       if (upgrades[i].base.height == 32 &&
760           upgrades[i].base.x >= x - 32 && upgrades[i].base.x <= x + 32 &&
761           upgrades[i].base.y >= y - 16 && upgrades[i].base.y <= y + 16)
762         {
763           upgrades[i].collision(&tux, CO_PLAYER, COLLISION_BUMP);
764         }
765     }
766 }
767
768 /* EOF */
769