1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <math.h>
4 #include <string.h>
5
6 #include "xtux.h"
7 #include "server.h"
8 #include "entity.h"
9 #include "clients.h"
10 #include "sv_map.h"
11 #include "weapon.h"
12 #include "world.h"
13 #include "sv_net.h"
14 #include "ai.h"
15 #include "item.h"
16 #include "game.h"
17
18 extern server_t server;
19 extern game_t game;
20 extern byte num_weapon_types;
21
22 /* Start and end of entity list, initially NULL */
23 entity *root;
24 entity *tail;
25
26 int num_entities;
27 byte num_entity_types;
28
29 /* Keeps track of how entities of each class there are */
30 int class_population[NUM_ENT_CLASSES];
31
32 static int id_num = 1;
33
34 static void entity_update_movement(float secs);
35 static void entity_check_collisions(void);
36 static void entity_animate(void);
37 static void entity_die(entity *ent);
38
39
40 /* Called once at program initialisation */
entity_init(void)41 void entity_init(void)
42 {
43 num_entity_types = entity_type_init();
44 }
45
46
entity_drip(entity * ent,byte color1,byte color2)47 void entity_drip(entity *ent, byte color1, byte color2)
48 {
49 netmsg msg;
50
51 msg.type = NETMSG_PARTICLES;
52 msg.particles.effect = P_DRIP;
53 msg.particles.dir = ent->dir;
54 msg.particles.length = 1;
55 msg.particles.color1 = color1;
56 msg.particles.color2 = color2;
57 /* Draw with offsets to make it look good on the client */
58 msg.particles.x = ent->x + ent->type_info->width/2;
59 msg.particles.y = ent->y + ent->type_info->height/2;
60 sv_net_send_to_all(msg);
61
62 }
63
64
entity_spawn_effect(entity * ent)65 void entity_spawn_effect(entity *ent)
66 {
67 netmsg msg;
68
69 /* Send respawning effect to clients */
70 msg.type = NETMSG_PARTICLES;
71 msg.particles.effect = P_SPAWN;
72 msg.particles.color1 = COL_WHITE;
73 msg.particles.color2 = COL_GRAY;
74 msg.particles.length = ent->type_info->width / 2;
75 msg.particles.x = ent->x + ent->type_info->width/2;
76 msg.particles.y = ent->y + ent->type_info->height/2;
77 sv_net_send_to_all(msg);
78
79 }
80
81 /* How often the entity drips while invisible */
82 #define INVISIBLE_DRIP_TIME 500
83
entity_update(float secs)84 void entity_update(float secs)
85 {
86 ent_type_t *et;
87 entity *ent, *next;
88 msec_t now, time;
89 int i;
90
91 now = server.now;
92
93 for( ent = root ; ent != NULL ; ent = ent->next ) {
94 et = ent->type_info;
95
96
97
98 if( ent->dripping ) {
99 time = (ent->dripping==1)? et->drip_time : INVISIBLE_DRIP_TIME;
100 if( now - ent->last_drip >= time ) {
101 entity_drip(ent, ent->drip1, ent->drip2);
102 ent->last_drip = now;
103 }
104 }
105
106 if( ent->lookatdir > 0 ) {
107 ent->speed = 0;
108 /* make the entity slowly turn towards the lookatpoint */
109 if( ent->dir > ent->lookatdir )
110 ent->dir -= MAX( 1, (ent->dir - ent->lookatdir)/10);
111 else if( ent->dir < ent->lookatdir )
112 ent->dir += MAX( 1, (ent->lookatdir - ent->dir)/10);
113 else
114 ent->lookatdir = -1;
115 } else if( ent->mode == ALIVE )
116 ent->speed = et->speed;
117 else if( ent->mode == LIMBO ) { /* Respawn items */
118 if( ent->respawn_time && (now >= ent->respawn_time) ) {
119 ent->mode = ALIVE;
120 ent->respawn_time = 0;
121 entity_spawn_effect(ent);
122 }
123 }
124
125 if( ent->powerup ) {
126 /* Entity is wounded */
127 if( ent->powerup & (1<<PU_WOUND) )
128 if( now - ent->last_wound >= M_SEC ) {
129 entity_drip(ent, COL_RED, COL_RED); /* Drip blood */
130 ent->health -= 2;
131 ent->last_wound = now;
132 }
133
134 if( ent->powerup & (1<<PU_INVISIBILITY) ) {
135 ent->visible = 0;
136 if( ent->x_v || ent->y_v ) { /* Drip if moving */
137 ent->dripping = 2;
138 ent->drip1 = ent->color1;
139 ent->drip2 = ent->color2;
140 } else {
141 ent->dripping = et->dripping;
142 ent->drip1 = et->drip1;
143 ent->drip2 = et->drip2;
144 }
145 }
146
147 for( i=0 ; i<NUM_POWERUPS ; i++ ) {
148 if( ent->powerup & (1<<i) && now > ent->powerup_expire[i] ) {
149 ent->powerup &= ~(1<<i);
150 ent->powerup_expire[i] = 0;
151
152 switch( i ) {
153 case PU_INVISIBILITY:
154 ent->dripping = et->dripping;
155 ent->drip1 = et->drip1;
156 ent->drip2 = et->drip2;
157 ent->visible = 1;
158 break;
159 case PU_FROZEN:
160 if( ent->mode == FROZEN )
161 ent->mode = ALIVE;
162 break;
163 case PU_E:
164 /* Worn off, entity is now on a come down... */
165 if( ent->mode == ENLIGHTENED ) {
166 ent->mode = COMEDOWN;
167 ent->speed = et->speed * 0.50;
168 ent->powerup |= (1<<PU_E); /* Restore bit */
169 ent->powerup_expire[PU_E] = now + M_SEC * 10;
170 } else if( ent->mode == COMEDOWN ) {
171 ent->mode = ALIVE;
172 }
173 break;
174 }
175 }
176 }
177 }
178 }
179
180 entity_update_movement(secs);
181 entity_check_collisions();
182 entity_animate();
183
184 for( ent = root ; ent != NULL ; ent = next ) {
185 next = ent->next;
186 if( ent->mode >= FROZEN && ent->health <= 0 )
187 entity_die(ent); /* Set to either dead or dying */
188
189 if( ent->mode == DEAD ) {
190 if( ent->controller == CTRL_CLIENT )
191 spawn(ent, 1); /* Respawn */
192 else
193 entity_delete(ent);
194 }
195 }
196
197 }
198
199
entity_update_movement(float secs)200 static void entity_update_movement(float secs)
201 {
202 entity *ent;
203
204 for( ent = root ; ent != NULL ; ent = ent->next ) {
205 if( ent->mode >= ALIVE && ent->ai && ent->controller == CTRL_AI ) {
206 switch( ent->ai ) {
207 case AI_NONE:
208 break;
209 case AI_FIGHT:
210 if( ent->weapon ) {
211 ai_shooter_think(ent);
212 } else
213 ai_fight_think(ent);
214 break;
215 case AI_FLEE:
216 ai_flee_think(ent);
217 break;
218 case AI_SEEK:
219 ai_seek_think(ent);
220 break;
221 case AI_ROTATE:
222 ai_rotate_think(ent);
223 break;
224 default:
225 break;
226 }
227 ai_move(ent);
228 }
229
230 if( ent->class != PROJECTILE )
231 world_friction(ent, secs);
232
233 if( ent->mode >= ALIVE ) {
234 if( ent->x_v > 0 ) {
235 if( ent->x_v > ent->speed )
236 ent->x_v = ent->speed;
237 } else if( ent->x_v < 0 ) {
238 if( ent->x_v < -ent->speed )
239 ent->x_v = -ent->speed;
240 }
241
242 if( ent->y_v > 0 ) {
243 if( ent->y_v > ent->speed )
244 ent->y_v = ent->speed;
245 } else if( ent->y_v < 0 ) {
246 if( ent->y_v < -ent->speed )
247 ent->y_v = -ent->speed;
248 }
249 }
250
251 world_check_entity_move(ent, secs);
252
253 }
254
255 }
256
257
258 /*
259
260 Here's the order (from ../common/entity_type.h). You only need
261 to handle collision between first and the second->class's that
262 come after first's->class.
263
264 GOODIE,
265 BADDIE,
266 NEUTRAL,
267 ITEM,
268 PROJECTILE,
269 NUM_ENT_CLASSES
270 */
271
272
col_goodie(entity * goodie,entity * second)273 static void col_goodie(entity *goodie, entity *second)
274 {
275 entity *shooter;
276 point pt;
277 int damage;
278
279 switch( second->class ) {
280 case GOODIE:
281 /* Nothing.... */
282 break;
283 case BADDIE:
284 /* Touch damage is delt over a second */
285 if( (damage = second->type_info->touchdamage / server.fps ) < 1 )
286 damage = 1; /* Correct for rounding down */
287 goodie->health -= damage;
288 if( goodie->health <= 0 )
289 entity_killed(second, goodie, pt, 0);
290 /* Slow the baddie down while he does damage */
291 second->x_v *= 0.25;
292 second->y_v *= 0.25;
293 break;
294 case NEUTRAL:
295 /* Nothing... */
296 break;
297 case ITEM:
298 item_collision(second, goodie);
299 break;
300 case PROJECTILE:
301 shooter = findent(second->pid);
302 pt.x = second->x;
303 pt.y = second->y;
304 weapon_hit( shooter, goodie, pt, second->weapon );
305 second->health = 0; /* Remove projectile */
306 break;
307 default:
308 printf("NOT HANDLED!\n");
309 }
310
311 }
312
313
col_baddie(entity * baddie,entity * second)314 static void col_baddie(entity *baddie, entity *second)
315 {
316 entity *shooter;
317 point pt;
318 int damage;
319
320 switch( second->class ) {
321 case BADDIE:
322 /* Do damage if baddies are fighting */
323 if( baddie->target == second ) {
324 if( (damage = baddie->type_info->touchdamage / server.fps) < 1 )
325 damage = 1; /* Correct for rounding down */
326 second->health -= damage;
327 second->target = baddie;
328 } else if( second->target == baddie ) {
329 if( (damage = second->type_info->touchdamage / server.fps) < 1 )
330 damage = 1; /* Correct for rounding down */
331 baddie->health -= damage;
332 baddie->target = second;
333 }
334 break;
335 case NEUTRAL:
336 /* Nothing.... */
337 break;
338 case ITEM:
339 /* item_collision(second, baddie); */
340 break;
341 case PROJECTILE:
342 shooter = findent(second->pid);
343 pt.x = second->x;
344 pt.y = second->y;
345 weapon_hit( shooter, baddie, pt, second->weapon);
346 second->health = 0; /* Remove projectile */
347 break;
348 default:
349 printf("NOT HANDLED!\n");
350 }
351
352 }
353
354
col_neutral(entity * neutral,entity * second)355 static void col_neutral(entity *neutral, entity *second)
356 {
357 entity *shooter;
358 point pt;
359
360 switch( second->class ) {
361 case NEUTRAL:
362 break;
363 case ITEM:
364 /* Nothing.... */
365 break;
366 case PROJECTILE:
367 shooter = findent(second->pid);
368 pt.x = second->x;
369 pt.y = second->y;
370 weapon_hit( shooter, neutral, pt, second->weapon);
371 second->health = 0; /* Remove projectile */
372 break;
373 default:
374 printf("NOT HANDLED!\n");
375 }
376
377 }
378
379
col_item(entity * item,entity * second)380 static void col_item(entity *item, entity *second)
381 {
382
383 switch( second->class ) {
384 case ITEM:
385 /* Nothing.... */
386 break;
387 case PROJECTILE:
388 /* Nothing.... */
389 break;
390 default:
391 printf("NOT HANDLED!\n");
392 }
393
394 }
395
396
397 /* In theory this should only be called with both first and second
398 being projectiles */
col_projectile(entity * proj,entity * second)399 static void col_projectile(entity *proj, entity *second)
400 {
401 entity *shooter;
402 point pt;
403
404 switch( second->class ) {
405 case PROJECTILE:
406 /* Projectile hits second */
407 if( (shooter = findent(proj->pid)) != NULL ) {
408 pt.x = proj->x;
409 pt.y = proj->y;
410 weapon_hit( shooter, second, pt, proj->weapon);
411 }
412
413 /* Second hits projectile */
414 if( (shooter = findent(second->pid)) != NULL ) {
415 pt.x = second->x;
416 pt.y = second->y;
417 weapon_hit( shooter, proj, pt, second->weapon);
418 }
419
420 break;
421 default:
422 printf("NOT HANDLED!\n");
423 }
424
425 }
426
427
428 static void (*handle_collision[NUM_ENT_CLASSES])(entity *, entity *) = {
429 col_goodie,
430 col_baddie,
431 col_neutral,
432 col_item,
433 col_projectile
434 };
435
436
437 /* FIXME: Optimise and make correct (use intersection tests). */
entity_check_collisions(void)438 void entity_check_collisions(void)
439 {
440 entity *ent1, *ent2;
441 entity *first, *second;
442 int d1, d2; /* Diameters for entities 1, 2 */
443 int max_dist; /* Maximum distance 1 & 2 can be apart and still clip */
444
445 for( ent1 = root ; ent1 != NULL ; ent1 = ent1->next ) {
446 if( ent1->mode < FROZEN || ent1->health <= 0 )
447 continue; /* Skip LIMBO, DEAD & DYING entities */
448 d1 = MAX( ent1->type_info->width, ent1->type_info->height );
449 for( ent2 = ent1->next ; ent2 != NULL ; ent2 = ent2->next ) {
450 if( ent1->class == NEUTRAL && ent2->class == NEUTRAL )
451 continue;
452 if( ent2->mode < FROZEN || ent1->health <= 0 )
453 continue; /* Skip LIMBO, DEAD & DYING entities */
454 if( ent1->pid == ent2->id || ent2->pid == ent1->id )
455 continue; /* Don't clip on parent */
456 d2 = MAX( ent2->type_info->width, ent2->type_info->height );
457 max_dist = (d1 + d2) / 2;
458 if( entity_dist(ent1, ent2) < max_dist ) {
459 /* Call the function for the "lowest" class (in the
460 entity-class enumeration) with args (lowest, highest) */
461 if( ent1->class < ent2->class ) {
462 first = ent1;
463 second = ent2;
464 } else {
465 first = ent2;
466 second = ent1;
467 }
468 handle_collision[first->class]( first, second );
469 }
470 }
471 }
472
473 }
474
475
476 /* Creates a new entity on the end of the list of type "type" and returns
477 a pointer to the newly created entity */
entity_new(byte type,float x,float y,float x_v,float y_v)478 entity *entity_new(byte type, float x, float y, float x_v, float y_v)
479 {
480 ent_type_t *et;
481 entity *ent;
482
483 if( (et = entity_type(type)) == NULL ) {
484 printf("Error, no entity of type %d\n", (int)type);
485 return NULL;
486 }
487
488 /* Create entity */
489 if( (ent = entity_alloc()) == NULL)
490 ERR_QUIT("Error creating new entity", EXIT_FAILURE);
491
492 memset( ent, 0, sizeof(entity) );
493
494 /* Add it to the list */
495 if( root == NULL )
496 root = ent; /* The new root entity */
497
498 if( tail != NULL )
499 tail->next = ent;
500
501 ent->prev = tail;
502 tail = ent;
503 tail->next = NULL;
504
505 num_entities++;
506 id_num++;
507
508 ent->id = id_num;
509 ent->x = x;
510 ent->y = y;
511 ent->x_v = x_v;
512 ent->y_v = y_v;
513
514 /* Set values from default type for this particular entity */
515 ent->type_info = et;
516 ent->type = type;
517 ent->class = et->class;
518 ent->mode = ALIVE;
519 ent->speed = et->speed;
520 ent->health = et->health;
521 ent->dripping = et->dripping;
522 ent->drip1 = et->drip1;
523 ent->drip2 = et->drip2;
524 ent->cliplevel = et->cliplevel;
525 ent->color1 = COL_WHITE;
526 ent->color2 = COL_BLUE;
527 ent->last_fired = server.now;
528 ent->controller = CTRL_AI;
529 ent->visible = 1;
530 ent->lookatdir = -1;
531
532 if( num_weapon_types > 0 ) {
533 if( (ent->has_weapon = (byte *)malloc(num_weapon_types)) == NULL ) {
534 perror("Malloc");
535 ERR_QUIT("Error malloc'ing has_weapon array", -1);
536 }
537 weapon_reset(ent);
538 } else {
539 ERR_QUIT("num_weapon_types out of range!\n", num_weapon_types);
540 }
541
542 memset(ent->powerup_expire, 0, sizeof(ent->powerup_expire));
543
544 if( et->ai ) {
545 ent->cliplevel = AICLIP;
546 ent->ai = et->ai;
547 } else { /* Default AI's for each class */
548 switch( et->class ) {
549 case BADDIE:
550 ent->cliplevel = AICLIP;
551 case GOODIE:
552 ent->ai = AI_FIGHT;
553 break;
554 case NEUTRAL:
555 ent->ai = AI_FLEE;
556 break;
557 case PROJECTILE:
558 ent->ai = AI_NONE;
559 break;
560 case ITEM:
561 if( et->item_action == GIVEWEAPON )
562 ent->ai = AI_ROTATE;
563 break;
564 }
565 }
566
567 class_population[ ent->class ]++;
568
569 /*
570 printf("created new %s (type %d, class %d) %d entities\n",
571 et->name, type, ent->class, num_entities);
572 */
573
574 return ent;
575
576 }
577
578
entity_delete(entity * ent)579 void entity_delete(entity *ent)
580 {
581 entity *seeker;
582 weap_type_t *wt;
583
584 /* If something has current entity as target, remove it */
585 for( seeker = root ; seeker != NULL ; seeker = seeker->next ) {
586 if( seeker->target == ent )
587 seeker->target = NULL;
588 }
589
590 /* Does entity explode when it dies? */
591 if( ent->class == PROJECTILE ) {
592 wt = weapon_type(ent->weapon);
593 if( wt->explosion )
594 weapon_explode(ent, wt->explosion, wt->splashdamage);
595 }
596
597 if( num_weapon_types > 0 ) {
598 free( ent->has_weapon );
599 }
600
601 num_entities--;
602 class_population[ ent->class ]--;
603
604 /* Make sure that root & Tail will still point at the
605 right spot after the entity is deleted */
606 if( root == ent )
607 root = ent->next;
608 if( tail == ent )
609 tail = ent->prev;
610
611 if( ent->prev )
612 ent->prev->next = ent->next;
613
614 if( ent->next )
615 ent->next->prev = ent->prev;
616
617 free(ent);
618
619 }
620
621
622 /* Deletes all entities, returns the number of entities deleted */
entity_remove_all_non_clients(void)623 int entity_remove_all_non_clients(void)
624 {
625 entity *ent, *next;
626 int num = 0;
627
628 /* printf("Removing all non client entities\n"); */
629
630 ent = root;
631 while( ent != NULL ) {
632 num++;
633 next = ent->next;
634 if( ent->cl ) /* Don't delete clients... */
635 ent->mode = LIMBO; /* Limbo until they send the "ready" message */
636 else
637 entity_delete(ent);
638 ent = next;
639 }
640
641 return num;
642
643 }
644
645
entity_to_netmsg(entity * ent)646 netmsg entity_to_netmsg(entity *ent)
647 {
648 netmsg msg;
649
650 msg.entity.entity_type = ent->type;
651 msg.entity.dir = ent->dir;
652 msg.entity.mode = ent->mode;
653 msg.entity.x = ent->x + 0.5; /* Round floats to integers */
654 msg.entity.y = ent->y + 0.5;
655 msg.entity.img = ent->img;
656 msg.entity.weapon = ent->weapon;
657
658 return msg;
659
660 }
661
662
entity_killed(entity * shooter,entity * victim,point P,byte weaptype)663 void entity_killed(entity *shooter, entity *victim, point P, byte weaptype)
664 {
665 char buf[TEXTMESSAGE_STRLEN], *killerboy;
666 int change = 0;
667 weap_type_t *wt;
668
669 victim->health = 0; /* Just to make sure */
670 victim->frame = 0;
671
672 /* No frags for killing items or projectiles */
673 if( victim->class == PROJECTILE || victim->class == ITEM ) {
674 return;
675 }
676
677 if( victim == shooter ) { /* Suicide */
678 victim->frags--;
679 if( victim->name ) {
680 snprintf(buf, TEXTMESSAGE_STRLEN, "%s couldn't cope",victim->name);
681 sv_net_send_text_to_all(buf);
682 }
683 return;
684 } else {
685 /* Who was the victim? */
686 switch( game.mode ) {
687 case SAVETHEWORLD:
688 if( victim->class != GOODIE )
689 change++;
690 break;
691 case HOLYWAR:
692 if( victim->class == NEUTRAL )
693 change--;
694 else {
695 change++;
696 /* OBITUARY IF CLIENT?? */
697 }
698 break;
699 default:
700 ERR_QUIT("Game mode out of range!", game.mode );
701 }
702 }
703
704 if( shooter ) /* Update frag count */
705 shooter->frags += change;
706
707 if( victim->name ) {
708 if( shooter ) {
709 if( shooter->name )
710 killerboy = shooter->name;
711 else
712 killerboy = shooter->type_info->name;
713
714 if( (wt = weapon_type(weaptype)) && wt->obituary )
715 snprintf(buf, TEXTMESSAGE_STRLEN, wt->obituary,
716 victim->name, killerboy);
717 else
718 snprintf(buf, TEXTMESSAGE_STRLEN,
719 "%s got de-mapped by %s%s", victim->name,
720 shooter->name? "" : "a ", killerboy);
721 sv_net_send_text_to_all(buf);
722 }
723 }
724
725 }
726
727
entity_animate(void)728 void entity_animate(void)
729 {
730 animation_t *ani;
731 ent_type_t *et;
732 entity *ent;
733 msec_t framelen;
734 int animate, frames;
735
736 for( ent = root ; ent != NULL ; ent = ent->next ) {
737 ani = entity_type_animation(ent->type_info, ent->mode);
738 framelen = ani->framelen;
739
740 animate = 0; /* Don't animate by default */
741 if( ani->images > 1 ) {
742 if( ani->stationary_ani ) /* Animates when stationary */
743 animate = 1;
744 else if( (ent->x_v || ent->y_v) || ent->mode == ENLIGHTENED ) {
745 animate = 1;
746 framelen /= 2;
747 }
748 }
749
750 if( animate ) {
751 if( framelen )
752 frames = (server.now - ent->last_frame) / framelen;
753 else
754 frames = 0;
755 if( frames > 0 ) {
756 ent->last_frame = server.now;
757 ent->frame += frames;
758 if( ent->frame >= ani->frames ) {
759 /* End of this loop of animation. If the entity is
760 in it's dying animation, make it dead. */
761 if( ent->mode == DYING )
762 ent->mode = DEAD;
763 else
764 ent->frame = 0;
765 }
766 }
767
768 ent->img = ani->order[ent->frame];
769
770 } else {
771
772 if( ent->mode == DYING ) {
773 if( (et = entity_type(ent->type)) != NULL )
774 printf("Killed off %s\n", et->name);
775 ent->mode = DEAD;
776 } else
777 ent->img = 0;
778 }
779 }
780
781 }
782
783
784
785 /* returns pixels between the CENTER of ent0 & ent1 */
entity_dist(entity * ent0,entity * ent1)786 float entity_dist(entity *ent0, entity *ent1)
787 {
788 float x_dist, y_dist;
789
790 x_dist = (ent0->x + ent0->type_info->width/2)
791 - (ent1->x + ent1->type_info->width/2);
792 y_dist = (ent0->y + ent0->type_info->height/2)
793 - (ent1->y + ent1->type_info->height/2);
794 return sqrt( x_dist * x_dist + y_dist * y_dist );
795
796 }
797
798
799 /* Find entity that has the entity-id of id */
findent(int id)800 entity *findent(int id)
801 {
802 entity *ent;
803
804 for( ent=root ; ent != NULL ; ent = ent->next ) {
805 if( ent->id == id )
806 return ent;
807 }
808
809 return NULL;
810
811 }
812
813
814 /* Called when victim's health <= 0 */
entity_die(entity * ent)815 static void entity_die(entity *ent)
816 {
817
818 if( ent->type_info->animation[ DYING ] ) {
819 ent->mode = DYING;
820 ent->last_frame = server.now;
821 ent->frame = 0;
822 } else
823 ent->mode = DEAD;
824
825 if( ent->type_info->explosive ) {
826 weapon_explode(ent, 50, ent->type_info->explosive);
827 }
828
829
830 }
831
832
833 /* Returns a pointer to newly created entity, returns NULL on error */
entity_alloc(void)834 entity *entity_alloc(void)
835 {
836 entity *ent;
837
838 if( (ent = (entity *)malloc( sizeof(entity) )) == NULL)
839 perror("Malloc");
840
841 return ent;
842
843 }
844
845
846
847
848
849
850