1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE 32
20 #define ENERGY_YSIZE MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE ENERGY_XSIZE
22 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
24 /* values for Explode_MM() */
25 #define EX_PHASE_START 0
30 /* special positions in the game control window (relative to control window) */
39 #define XX_OVERLOAD 60
40 #define YY_OVERLOAD YY_ENERGY
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL (DX + XX_LEVEL)
44 #define DY_LEVEL (DY + YY_LEVEL)
45 #define DX_KETTLES (DX + XX_KETTLES)
46 #define DY_KETTLES (DY + YY_KETTLES)
47 #define DX_SCORE (DX + XX_SCORE)
48 #define DY_SCORE (DY + YY_SCORE)
49 #define DX_ENERGY (DX + XX_ENERGY)
50 #define DY_ENERGY (DY + YY_ENERGY)
51 #define DX_OVERLOAD (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD (DY + YY_OVERLOAD)
54 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT 0
59 #define GAME_CTRL_ID_MIDDLE 1
60 #define GAME_CTRL_ID_RIGHT 2
62 #define NUM_GAME_BUTTONS 3
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED 0
66 #define DL_LASER_ENABLED 1
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_SHORT 125
70 #define CLICK_DELAY_LONG 250
71 #define AUTO_ROTATE_DELAY CLICK_DELAY_SHORT
73 /* forward declaration for internal use */
74 static int MovingOrBlocked2Element_MM(int, int);
75 static void Bang_MM(int, int);
76 static void RaiseScore_MM(int);
77 static void RemoveMovingField_MM(int, int);
78 static void InitMovingField_MM(int, int, int);
79 static void ContinueMoving_MM(int, int);
80 static void Moving2Blocked_MM(int, int, int *, int *);
83 static int get_element_angle(int element)
85 int element_phase = get_element_phase(element);
87 if (IS_MIRROR_FIXED(element) ||
88 IS_MCDUFFIN(element) ||
91 return 4 * element_phase;
96 static int get_opposite_angle(int angle)
98 int opposite_angle = angle + ANG_RAY_180;
100 /* make sure "opposite_angle" is in valid interval [0, 15] */
101 return (opposite_angle + 16) % 16;
104 static int get_mirrored_angle(int laser_angle, int mirror_angle)
106 int reflected_angle = 16 - laser_angle + mirror_angle;
108 /* make sure "reflected_angle" is in valid interval [0, 15] */
109 return (reflected_angle + 16) % 16;
112 static void InitMovDir_MM(int x, int y)
114 int element = Feld[x][y];
115 static int direction[3][4] =
117 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
118 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
119 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
124 case EL_PACMAN_RIGHT:
128 Feld[x][y] = EL_PACMAN;
129 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
137 static void InitField(int x, int y, boolean init_game)
139 int element = Feld[x][y];
144 Feld[x][y] = EL_EMPTY;
149 if (native_mm_level.auto_count_kettles)
150 game_mm.kettles_still_needed++;
153 case EL_LIGHTBULB_OFF:
154 game_mm.lights_still_needed++;
158 if (IS_MIRROR(element) ||
159 IS_BEAMER_OLD(element) ||
160 IS_BEAMER(element) ||
162 IS_POLAR_CROSS(element) ||
163 IS_DF_MIRROR(element) ||
164 IS_DF_MIRROR_AUTO(element) ||
165 IS_GRID_STEEL_AUTO(element) ||
166 IS_GRID_WOOD_AUTO(element) ||
167 IS_FIBRE_OPTIC(element))
169 if (IS_BEAMER_OLD(element))
171 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
172 element = Feld[x][y];
175 if (!IS_FIBRE_OPTIC(element))
177 static int steps_grid_auto = 0;
179 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
180 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
182 if (IS_GRID_STEEL_AUTO(element) ||
183 IS_GRID_WOOD_AUTO(element))
184 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
186 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
188 game_mm.cycle[game_mm.num_cycle].x = x;
189 game_mm.cycle[game_mm.num_cycle].y = y;
193 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
195 int beamer_nr = BEAMER_NR(element);
196 int nr = laser.beamer[beamer_nr][0].num;
198 laser.beamer[beamer_nr][nr].x = x;
199 laser.beamer[beamer_nr][nr].y = y;
200 laser.beamer[beamer_nr][nr].num = 1;
203 else if (IS_PACMAN(element))
207 else if (IS_MCDUFFIN(element) || IS_LASER(element))
209 laser.start_edge.x = x;
210 laser.start_edge.y = y;
211 laser.start_angle = get_element_angle(element);
218 static void InitCycleElements()
222 if (game_mm.num_cycle == 0) /* no elements to cycle */
227 for(j=0; j<game_mm.num_cycle; j++)
229 int x = game_mm.cycle[j].x;
230 int y = game_mm.cycle[j].y;
231 int step = SIGN(game_mm.cycle[j].steps);
232 int last_element = Feld[x][y];
233 int next_element = get_rotated_element(last_element, step);
235 if (!game_mm.cycle[j].steps)
238 Feld[x][y] = next_element;
241 game_mm.cycle[j].steps -= step;
248 if (setup.quick_doors)
252 Delay(AUTO_ROTATE_DELAY);
256 static void InitLaser()
258 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
259 int step = (IS_LASER(start_element) ? 4 : 0);
261 LX = laser.start_edge.x * TILEX;
262 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
265 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
267 LY = laser.start_edge.y * TILEY;
268 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
269 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
273 XS = 2 * Step[laser.start_angle].x;
274 YS = 2 * Step[laser.start_angle].y;
276 laser.current_angle = laser.start_angle;
278 laser.num_damages = 0;
280 laser.num_beamers = 0;
281 laser.beamer_edge[0] = 0;
283 AddLaserEdge(LX, LY); /* set laser starting edge */
285 pen_ray = GetPixelFromRGB(window,
286 native_mm_level.laser_red * 0xFF,
287 native_mm_level.laser_green * 0xFF,
288 native_mm_level.laser_blue * 0xFF);
291 void InitGameEngine_MM()
295 /* set global editor control values */
296 editor.draw_walls_masked = FALSE;
298 /* set global game control values */
299 game_mm.num_cycle = 0;
300 game_mm.num_pacman = 0;
303 game_mm.energy_left = native_mm_level.time;
304 game_mm.kettles_still_needed =
305 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
306 game_mm.lights_still_needed = 0;
307 game_mm.num_keys = 0;
309 game_mm.level_solved = FALSE;
310 game_mm.game_over = FALSE;
311 game_mm.game_over_cause = 0;
313 /* set global laser control values (must be set before "InitLaser()") */
314 laser.start_edge.x = 0;
315 laser.start_edge.y = 0;
316 laser.start_angle = 0;
318 for (i=0; i<MAX_NUM_BEAMERS; i++)
319 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
321 laser.overloaded = FALSE;
322 laser.overload_value = 0;
323 laser.fuse_off = FALSE;
324 laser.fuse_x = laser.fuse_y = -1;
326 laser.dest_element = EL_EMPTY;
331 for (x=0; x<lev_fieldx; x++)
333 for (y=0; y<lev_fieldy; y++)
335 Feld[x][y] = Ur[x][y];
336 Hit[x][y] = Box[x][y] = 0;
338 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
339 Store[x][y] = Store2[x][y] = 0;
343 InitField(x, y, TRUE);
348 CloseDoor(DOOR_CLOSE_1);
354 void InitGameEngine_MM_AfterFadingIn()
360 /* copy default game door content to main double buffer */
361 BlitBitmap(pix[PIX_DOOR], drawto,
362 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
366 DrawText(DX_LEVEL, DY_LEVEL,
367 int2str(level_nr, 2), FONT_TEXT_2);
368 DrawText(DX_KETTLES, DY_KETTLES,
369 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
370 DrawText(DX_SCORE, DY_SCORE,
371 int2str(game_mm.score, 4), FONT_TEXT_2);
380 /* copy actual game door content to door double buffer for OpenDoor() */
381 BlitBitmap(drawto, pix[PIX_DB_DOOR],
382 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
386 OpenDoor(DOOR_OPEN_ALL);
389 if (setup.sound_loops)
390 PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
392 #if 0 // !!! TEMPORARILY DISABLED !!!
393 for(i=0; i<=game_mm.energy_left; i+=2)
395 if (!setup.sound_loops)
396 PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
399 BlitBitmap(pix[PIX_DOOR], drawto,
400 DOOR_GFX_PAGEX4 + XX_ENERGY,
401 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
403 DX_ENERGY, DY_ENERGY + ENERGY_YSIZE - i);
406 redraw_mask |= REDRAW_DOOR_1;
412 if (setup.quick_doors)
419 if (setup.sound_loops)
424 if (setup.sound_music && num_bg_loops)
425 PlayMusic(level_nr % num_bg_loops);
431 void AddLaserEdge(int lx, int ly)
433 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
435 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
439 laser.edge[laser.num_edges].x = SX + 2 + lx;
440 laser.edge[laser.num_edges].y = SY + 2 + ly;
446 void AddDamagedField(int ex, int ey)
448 laser.damage[laser.num_damages].is_mirror = FALSE;
449 laser.damage[laser.num_damages].angle = laser.current_angle;
450 laser.damage[laser.num_damages].edge = laser.num_edges;
451 laser.damage[laser.num_damages].x = ex;
452 laser.damage[laser.num_damages].y = ey;
462 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
463 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
465 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
471 static int getMaskFromElement(int element)
473 if (IS_GRID(element))
474 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
475 else if (IS_MCDUFFIN(element))
476 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
477 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
478 return IMG_MM_MASK_RECTANGLE;
480 return IMG_MM_MASK_CIRCLE;
488 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
489 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
492 /* follow laser beam until it hits something (at least the screen border) */
493 while (hit_mask == HIT_MASK_NO_HIT)
499 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
500 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
502 printf("ScanPixel: touched screen border!\n");
510 int px = LX + (i % 2) * 2;
511 int py = LY + (i / 2) * 2;
514 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
515 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
518 if (IN_LEV_FIELD(lx, ly))
520 int element = Feld[lx][ly];
522 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
526 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
528 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
530 pixel = ((element & (1 << pos)) ? 1 : 0);
534 int graphic_mask = getMaskFromElement(element);
539 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
544 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
549 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
550 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
553 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
554 hit_mask |= (1 << i);
557 if (hit_mask == HIT_MASK_NO_HIT)
559 /* hit nothing -- go on with another step */
571 int end = 0, rf = laser.num_edges;
573 laser.overloaded = FALSE;
574 laser.stops_inside_element = FALSE;
576 DrawLaser(0, DL_LASER_ENABLED);
579 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
587 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
590 laser.overloaded = TRUE;
594 hit_mask = ScanPixel();
597 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
601 /* hit something -- check out what it was */
602 ELX = (LX + XS) / TILEX;
603 ELY = (LY + YS) / TILEY;
606 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
607 hit_mask, LX, LY, ELX, ELY);
610 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
613 laser.dest_element = element;
618 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
620 /* we have hit the top-right and bottom-left element --
621 choose the bottom-left one */
622 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
623 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
624 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
625 ELX = (LX - 2) / TILEX;
626 ELY = (LY + 2) / TILEY;
629 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
631 /* we have hit the top-left and bottom-right element --
632 choose the top-left one */
633 /* !!! SEE ABOVE !!! */
634 ELX = (LX - 2) / TILEX;
635 ELY = (LY - 2) / TILEY;
639 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
640 hit_mask, LX, LY, ELX, ELY);
643 element = Feld[ELX][ELY];
644 laser.dest_element = element;
647 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
650 LX % TILEX, LY % TILEY,
655 if (!IN_LEV_FIELD(ELX, ELY))
656 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
659 if (element == EL_EMPTY)
661 if (!HitOnlyAnEdge(element, hit_mask))
664 else if (element == EL_FUSE_ON)
666 if (HitPolarizer(element, hit_mask))
669 else if (IS_GRID(element) || IS_DF_GRID(element))
671 if (HitPolarizer(element, hit_mask))
674 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
675 element == EL_GATE_STONE || element == EL_GATE_WOOD)
677 if (HitBlock(element, hit_mask))
683 else if (IS_MCDUFFIN(element))
685 if (HitLaserSource(element, hit_mask))
688 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
689 IS_RECEIVER(element))
691 if (HitLaserDestination(element, hit_mask))
694 else if (IS_WALL(element))
696 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
698 if (HitReflectingWalls(element, hit_mask))
703 if (HitAbsorbingWalls(element, hit_mask))
709 if (HitElement(element, hit_mask))
714 DrawLaser(rf - 1, DL_LASER_ENABLED);
715 rf = laser.num_edges;
719 if (laser.dest_element != Feld[ELX][ELY])
721 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
722 laser.dest_element, Feld[ELX][ELY]);
726 if (!end && !laser.stops_inside_element && !StepBehind())
729 printf("ScanLaser: Go one step back\n");
734 AddLaserEdge(LX, LY);
738 DrawLaser(rf - 1, DL_LASER_ENABLED);
743 if (!IN_LEV_FIELD(ELX, ELY))
744 printf("WARNING! (2) %d, %d\n", ELX, ELY);
748 void DrawLaserExt(int start_edge, int num_edges, int mode)
754 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
755 start_edge, num_edges, mode);
760 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
766 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
771 if (mode == DL_LASER_DISABLED)
773 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
777 /* now draw the laser to the backbuffer and (if enabled) to the screen */
778 DrawLines(drawto, &laser.edge[start_edge], num_edges,
779 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
781 redraw_mask |= REDRAW_FIELD;
783 if (mode == DL_LASER_ENABLED)
786 /* after the laser was deleted, the "damaged" graphics must be restored */
787 if (laser.num_damages)
789 int damage_start = 0;
792 /* determine the starting edge, from which graphics need to be restored */
795 for(i=0; i<laser.num_damages; i++)
797 if (laser.damage[i].edge == start_edge + 1)
805 /* restore graphics from this starting edge to the end of damage list */
806 for(i=damage_start; i<laser.num_damages; i++)
808 int lx = laser.damage[i].x;
809 int ly = laser.damage[i].y;
810 int element = Feld[lx][ly];
812 if (Hit[lx][ly] == laser.damage[i].edge)
813 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
816 if (Box[lx][ly] == laser.damage[i].edge)
819 if (IS_DRAWABLE(element))
820 DrawField_MM(lx, ly);
823 elx = laser.damage[damage_start].x;
824 ely = laser.damage[damage_start].y;
825 element = Feld[elx][ely];
828 if (IS_BEAMER(element))
832 for (i=0; i<laser.num_beamers; i++)
833 printf("-> %d\n", laser.beamer_edge[i]);
834 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
835 mode, elx, ely, Hit[elx][ely], start_edge);
836 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
837 get_element_angle(element), laser.damage[damage_start].angle);
841 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
842 laser.num_beamers > 0 &&
843 start_edge == laser.beamer_edge[laser.num_beamers - 1])
845 /* element is outgoing beamer */
846 laser.num_damages = damage_start + 1;
847 if (IS_BEAMER(element))
848 laser.current_angle = get_element_angle(element);
852 /* element is incoming beamer or other element */
853 laser.num_damages = damage_start;
854 laser.current_angle = laser.damage[laser.num_damages].angle;
859 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
861 elx = laser.start_edge.x;
862 ely = laser.start_edge.y;
863 element = Feld[elx][ely];
866 laser.num_edges = start_edge + 1;
868 laser.current_angle = laser.start_angle;
869 LX = laser.edge[start_edge].x - (SX + 2);
870 LY = laser.edge[start_edge].y - (SY + 2);
871 XS = 2 * Step[laser.current_angle].x;
872 YS = 2 * Step[laser.current_angle].y;
875 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
881 if (IS_BEAMER(element) ||
882 IS_FIBRE_OPTIC(element) ||
883 IS_PACMAN(element) ||
885 IS_POLAR_CROSS(element) ||
886 element == EL_FUSE_ON)
891 printf("element == %d\n", element);
894 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
895 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
899 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
900 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
901 (laser.num_beamers == 0 ||
902 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
904 /* element is incoming beamer or other element */
905 step_size = -step_size;
910 if (IS_BEAMER(element))
912 printf("start_edge == %d, laser.beamer_edge == %d\n",
913 start_edge, laser.beamer_edge);
917 LX += step_size * XS;
918 LY += step_size * YS;
920 else if (element != EL_EMPTY)
929 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
934 void DrawLaser(int start_edge, int mode)
936 if (laser.num_edges - start_edge < 0)
938 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
942 /* check if laser is interrupted by beamer element */
943 if (laser.num_beamers > 0 &&
944 start_edge < laser.beamer_edge[laser.num_beamers - 1])
946 if (mode == DL_LASER_ENABLED)
949 int tmp_start_edge = start_edge;
951 /* draw laser segments forward from the start to the last beamer */
952 for (i=0; i<laser.num_beamers; i++)
954 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
956 if (tmp_num_edges <= 0)
960 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
961 i, laser.beamer_edge[i], tmp_start_edge);
964 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
965 tmp_start_edge = laser.beamer_edge[i];
968 /* draw last segment from last beamer to the end */
969 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
975 int last_num_edges = laser.num_edges;
976 int num_beamers = laser.num_beamers;
978 /* delete laser segments backward from the end to the first beamer */
979 for (i=num_beamers-1; i>=0; i--)
981 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
983 if (laser.beamer_edge[i] - start_edge <= 0)
986 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
987 last_num_edges = laser.beamer_edge[i];
992 if (last_num_edges - start_edge <= 0)
993 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
994 last_num_edges, start_edge);
997 /* delete first segment from start to the first beamer */
998 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1002 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1005 boolean HitElement(int element, int hit_mask)
1007 if (HitOnlyAnEdge(element, hit_mask))
1010 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1011 element = MovingOrBlocked2Element_MM(ELX, ELY);
1014 printf("HitElement (1): element == %d\n", element);
1018 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1019 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1021 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1024 AddDamagedField(ELX, ELY);
1026 /* this is more precise: check if laser would go through the center */
1027 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1029 /* skip the whole element before continuing the scan */
1035 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1037 if (LX/TILEX > ELX || LY/TILEY > ELY)
1039 /* skipping scan positions to the right and down skips one scan
1040 position too much, because this is only the top left scan position
1041 of totally four scan positions (plus one to the right, one to the
1042 bottom and one to the bottom right) */
1052 printf("HitElement (2): element == %d\n", element);
1055 if (LX + 5 * XS < 0 ||
1065 printf("HitElement (3): element == %d\n", element);
1068 if (IS_POLAR(element) &&
1069 ((element - EL_POLAR_START) % 2 ||
1070 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1072 PlaySoundStereo(SND_KINK, ST(ELX));
1073 laser.num_damages--;
1078 if (IS_POLAR_CROSS(element) &&
1079 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1081 PlaySoundStereo(SND_KINK, ST(ELX));
1082 laser.num_damages--;
1087 if (!IS_BEAMER(element) &&
1088 !IS_FIBRE_OPTIC(element) &&
1089 !IS_GRID_WOOD(element) &&
1090 element != EL_FUEL_EMPTY)
1093 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1094 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1096 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1099 LX = ELX * TILEX + 14;
1100 LY = ELY * TILEY + 14;
1101 AddLaserEdge(LX, LY);
1104 if (IS_MIRROR(element) ||
1105 IS_MIRROR_FIXED(element) ||
1106 IS_POLAR(element) ||
1107 IS_POLAR_CROSS(element) ||
1108 IS_DF_MIRROR(element) ||
1109 IS_DF_MIRROR_AUTO(element) ||
1110 element == EL_PRISM ||
1111 element == EL_REFRACTOR)
1113 int current_angle = laser.current_angle;
1116 laser.num_damages--;
1117 AddDamagedField(ELX, ELY);
1118 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1121 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1123 if (IS_MIRROR(element) ||
1124 IS_MIRROR_FIXED(element) ||
1125 IS_DF_MIRROR(element) ||
1126 IS_DF_MIRROR_AUTO(element))
1127 laser.current_angle = get_mirrored_angle(laser.current_angle,
1128 get_element_angle(element));
1130 if (element == EL_PRISM || element == EL_REFRACTOR)
1131 laser.current_angle = RND(16);
1133 XS = 2 * Step[laser.current_angle].x;
1134 YS = 2 * Step[laser.current_angle].y;
1136 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1141 LX += step_size * XS;
1142 LY += step_size * YS;
1145 /* draw sparkles on mirror */
1146 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1147 current_angle != laser.current_angle)
1149 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1153 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1154 current_angle != laser.current_angle)
1155 PlaySoundStereo(SND_LASER, ST(ELX));
1158 (get_opposite_angle(laser.current_angle) ==
1159 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1161 return (laser.overloaded ? TRUE : FALSE);
1164 if (element == EL_FUEL_FULL)
1166 laser.stops_inside_element = TRUE;
1171 if (element == EL_BOMB || element == EL_MINE)
1173 PlaySoundStereo(SND_KINK, ST(ELX));
1175 if (element == EL_MINE)
1176 laser.overloaded = TRUE;
1179 if (element == EL_KETTLE ||
1180 element == EL_CELL ||
1181 element == EL_KEY ||
1182 element == EL_LIGHTBALL ||
1183 element == EL_PACMAN ||
1186 if (!IS_PACMAN(element))
1189 if (element == EL_PACMAN)
1192 if (element == EL_KETTLE || element == EL_CELL)
1194 if (game_mm.kettles_still_needed > 0)
1195 game_mm.kettles_still_needed--;
1199 if (game_mm.kettles_still_needed == 0)
1202 static int xy[4][2] =
1210 PlaySoundStereo(SND_KLING, ST(ELX));
1212 for(y=0; y<lev_fieldy; y++)
1214 for(x=0; x<lev_fieldx; x++)
1216 /* initiate opening animation of exit door */
1217 if (Feld[x][y] == EL_EXIT_CLOSED)
1218 Feld[x][y] = EL_EXIT_OPENING;
1220 /* remove field that blocks receiver */
1221 if (IS_RECEIVER(Feld[x][y]))
1223 int phase = Feld[x][y] - EL_RECEIVER_START;
1224 int blocking_x, blocking_y;
1226 blocking_x = x + xy[phase][0];
1227 blocking_y = y + xy[phase][1];
1229 if (IN_LEV_FIELD(blocking_x, blocking_y))
1231 Feld[blocking_x][blocking_y] = EL_EMPTY;
1232 DrawField_MM(blocking_x, blocking_y);
1238 DrawLaser(0, DL_LASER_ENABLED);
1241 else if (element == EL_KEY)
1243 else if (element == EL_LIGHTBALL)
1245 else if (IS_PACMAN(element))
1247 DeletePacMan(ELX, ELY);
1254 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1256 PlaySoundStereo(SND_KINK, ST(ELX));
1258 DrawLaser(0, DL_LASER_ENABLED);
1260 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1262 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1263 game_mm.lights_still_needed--;
1267 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1268 game_mm.lights_still_needed++;
1271 DrawField_MM(ELX, ELY);
1272 DrawLaser(0, DL_LASER_ENABLED);
1277 laser.stops_inside_element = TRUE;
1283 printf("HitElement (4): element == %d\n", element);
1286 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1287 laser.num_beamers < MAX_NUM_BEAMERS &&
1288 laser.beamer[BEAMER_NR(element)][1].num)
1290 int beamer_angle = get_element_angle(element);
1291 int beamer_nr = BEAMER_NR(element);
1295 printf("HitElement (BEAMER): element == %d\n", element);
1298 laser.num_damages--;
1300 if (IS_FIBRE_OPTIC(element) ||
1301 laser.current_angle == get_opposite_angle(beamer_angle))
1305 LX = ELX * TILEX + 14;
1306 LY = ELY * TILEY + 14;
1307 AddLaserEdge(LX, LY);
1308 AddDamagedField(ELX, ELY);
1309 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1312 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1314 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1315 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1316 ELX = laser.beamer[beamer_nr][pos].x;
1317 ELY = laser.beamer[beamer_nr][pos].y;
1318 LX = ELX * TILEX + 14;
1319 LY = ELY * TILEY + 14;
1321 if (IS_BEAMER(element))
1323 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1324 XS = 2 * Step[laser.current_angle].x;
1325 YS = 2 * Step[laser.current_angle].y;
1328 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1329 AddLaserEdge(LX, LY);
1330 AddDamagedField(ELX, ELY);
1331 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1334 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1336 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1341 LX += step_size * XS;
1342 LY += step_size * YS;
1344 laser.num_beamers++;
1353 boolean HitOnlyAnEdge(int element, int hit_mask)
1355 /* check if the laser hit only the edge of an element and, if so, go on */
1358 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1361 if ((hit_mask == HIT_MASK_TOPLEFT ||
1362 hit_mask == HIT_MASK_TOPRIGHT ||
1363 hit_mask == HIT_MASK_BOTTOMLEFT ||
1364 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1365 laser.current_angle % 4) /* angle is not 90° */
1369 if (hit_mask == HIT_MASK_TOPLEFT)
1374 else if (hit_mask == HIT_MASK_TOPRIGHT)
1379 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1384 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1390 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1395 printf("[HitOnlyAnEdge() == TRUE]\n");
1402 printf("[HitOnlyAnEdge() == FALSE]\n");
1408 boolean HitPolarizer(int element, int hit_mask)
1410 if (HitOnlyAnEdge(element, hit_mask))
1413 if (IS_DF_GRID(element))
1415 int grid_angle = get_element_angle(element);
1418 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1419 grid_angle, laser.current_angle);
1422 AddLaserEdge(LX, LY);
1423 AddDamagedField(ELX, ELY);
1426 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1428 if (laser.current_angle == grid_angle ||
1429 laser.current_angle == get_opposite_angle(grid_angle))
1431 /* skip the whole element before continuing the scan */
1437 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1439 if (LX/TILEX > ELX || LY/TILEY > ELY)
1441 /* skipping scan positions to the right and down skips one scan
1442 position too much, because this is only the top left scan position
1443 of totally four scan positions (plus one to the right, one to the
1444 bottom and one to the bottom right) */
1450 AddLaserEdge(LX, LY);
1456 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1458 LX / TILEX, LY / TILEY,
1459 LX % TILEX, LY % TILEY);
1464 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1465 return HitReflectingWalls(element, hit_mask);
1467 return HitAbsorbingWalls(element, hit_mask);
1469 else if (IS_GRID_STEEL(element))
1470 return HitReflectingWalls(element, hit_mask);
1471 else /* IS_GRID_WOOD */
1472 return HitAbsorbingWalls(element, hit_mask);
1477 boolean HitBlock(int element, int hit_mask)
1479 boolean check = FALSE;
1481 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1482 game_mm.num_keys == 0)
1485 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1488 int ex = ELX * TILEX + 14;
1489 int ey = ELY * TILEY + 14;
1498 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1503 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1504 return HitAbsorbingWalls(element, hit_mask);
1508 AddLaserEdge(LX - XS, LY - YS);
1509 AddDamagedField(ELX, ELY);
1512 Box[ELX][ELY] = laser.num_edges;
1514 return HitReflectingWalls(element, hit_mask);
1517 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1519 int xs = XS / 2, ys = YS / 2;
1520 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1521 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1523 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1524 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1526 laser.overloaded = (element == EL_GATE_STONE);
1530 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1531 (hit_mask == HIT_MASK_TOP ||
1532 hit_mask == HIT_MASK_LEFT ||
1533 hit_mask == HIT_MASK_RIGHT ||
1534 hit_mask == HIT_MASK_BOTTOM))
1535 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1536 hit_mask == HIT_MASK_BOTTOM),
1537 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1538 hit_mask == HIT_MASK_RIGHT));
1539 AddLaserEdge(LX, LY);
1544 if (element == EL_GATE_STONE && Box[ELX][ELY])
1546 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1558 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1560 int xs = XS / 2, ys = YS / 2;
1561 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1562 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1564 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1565 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1567 laser.overloaded = (element == EL_BLOCK_STONE);
1572 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1573 (hit_mask == HIT_MASK_TOP ||
1574 hit_mask == HIT_MASK_LEFT ||
1575 hit_mask == HIT_MASK_RIGHT ||
1576 hit_mask == HIT_MASK_BOTTOM))
1577 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1578 hit_mask == HIT_MASK_BOTTOM),
1579 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1580 hit_mask == HIT_MASK_RIGHT));
1581 AddDamagedField(ELX, ELY);
1583 LX = ELX * TILEX + 14;
1584 LY = ELY * TILEY + 14;
1585 AddLaserEdge(LX, LY);
1587 laser.stops_inside_element = TRUE;
1595 boolean HitLaserSource(int element, int hit_mask)
1597 if (HitOnlyAnEdge(element, hit_mask))
1600 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1601 laser.overloaded = TRUE;
1606 boolean HitLaserDestination(int element, int hit_mask)
1608 if (HitOnlyAnEdge(element, hit_mask))
1611 if (element != EL_EXIT_OPEN &&
1612 !(IS_RECEIVER(element) &&
1613 game_mm.kettles_still_needed == 0 &&
1614 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1616 PlaySoundStereo(SND_HOLZ, ST(ELX));
1620 if (IS_RECEIVER(element) ||
1621 (IS_22_5_ANGLE(laser.current_angle) &&
1622 (ELX != (LX + 6 * XS) / TILEX ||
1623 ELY != (LY + 6 * YS) / TILEY ||
1632 LX = ELX * TILEX + 14;
1633 LY = ELY * TILEY + 14;
1635 laser.stops_inside_element = TRUE;
1638 AddLaserEdge(LX, LY);
1639 AddDamagedField(ELX, ELY);
1641 if (game_mm.lights_still_needed == 0)
1642 game_mm.level_solved = TRUE;
1647 boolean HitReflectingWalls(int element, int hit_mask)
1649 /* check if laser hits side of a wall with an angle that is not 90° */
1650 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1651 hit_mask == HIT_MASK_LEFT ||
1652 hit_mask == HIT_MASK_RIGHT ||
1653 hit_mask == HIT_MASK_BOTTOM))
1655 PlaySoundStereo(SND_HUI, ST(ELX));
1658 if (!IS_DF_GRID(element))
1659 AddLaserEdge(LX, LY);
1661 /* check if laser hits wall with an angle of 45° */
1662 if (!IS_22_5_ANGLE(laser.current_angle))
1664 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1667 laser.current_angle = get_mirrored_angle(laser.current_angle,
1670 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1673 laser.current_angle = get_mirrored_angle(laser.current_angle,
1677 AddLaserEdge(LX, LY);
1678 XS = 2 * Step[laser.current_angle].x;
1679 YS = 2 * Step[laser.current_angle].y;
1683 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1685 laser.current_angle = get_mirrored_angle(laser.current_angle,
1690 if (!IS_DF_GRID(element))
1691 AddLaserEdge(LX, LY);
1696 if (!IS_DF_GRID(element))
1697 AddLaserEdge(LX, LY + YS / 2);
1700 if (!IS_DF_GRID(element))
1701 AddLaserEdge(LX, LY);
1704 YS = 2 * Step[laser.current_angle].y;
1708 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1710 laser.current_angle = get_mirrored_angle(laser.current_angle,
1715 if (!IS_DF_GRID(element))
1716 AddLaserEdge(LX, LY);
1721 if (!IS_DF_GRID(element))
1722 AddLaserEdge(LX + XS / 2, LY);
1725 if (!IS_DF_GRID(element))
1726 AddLaserEdge(LX, LY);
1729 XS = 2 * Step[laser.current_angle].x;
1735 /* reflection at the edge of reflecting DF style wall */
1736 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1738 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1739 hit_mask == HIT_MASK_TOPRIGHT) ||
1740 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1741 hit_mask == HIT_MASK_TOPLEFT) ||
1742 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1743 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1744 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1745 hit_mask == HIT_MASK_BOTTOMRIGHT))
1748 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1749 ANG_MIRROR_135 : ANG_MIRROR_45);
1751 PlaySoundStereo(SND_HUI, ST(ELX));
1752 AddDamagedField(ELX, ELY);
1753 AddLaserEdge(LX, LY);
1755 laser.current_angle = get_mirrored_angle(laser.current_angle,
1762 AddLaserEdge(LX, LY);
1768 /* reflection inside an edge of reflecting DF style wall */
1769 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1771 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1772 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1773 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1774 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1775 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1776 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1777 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1778 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1781 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1782 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1783 ANG_MIRROR_135 : ANG_MIRROR_45);
1785 PlaySoundStereo(SND_HUI, ST(ELX));
1787 AddDamagedField(ELX, ELY);
1789 AddLaserEdge(LX - XS, LY - YS);
1790 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1791 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1793 laser.current_angle = get_mirrored_angle(laser.current_angle,
1800 AddLaserEdge(LX, LY);
1806 /* check if laser hits DF style wall with an angle of 90° */
1807 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1809 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1810 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1811 (IS_VERT_ANGLE(laser.current_angle) &&
1812 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1814 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1816 /* laser at last step touched nothing or the same side of the wall */
1817 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1819 AddDamagedField(ELX, ELY);
1825 last_hit_mask = hit_mask;
1832 if (!HitOnlyAnEdge(element, hit_mask))
1834 laser.overloaded = TRUE;
1841 boolean HitAbsorbingWalls(int element, int hit_mask)
1843 if (HitOnlyAnEdge(element, hit_mask))
1847 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1849 AddLaserEdge(LX - XS, LY - YS);
1855 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1857 AddLaserEdge(LX - XS, LY - YS);
1862 if (IS_WALL_WOOD(element) ||
1863 IS_DF_WALL_WOOD(element) ||
1864 IS_GRID_WOOD(element) ||
1865 IS_GRID_WOOD_FIXED(element) ||
1866 IS_GRID_WOOD_AUTO(element) ||
1867 element == EL_FUSE_ON ||
1868 element == EL_BLOCK_WOOD ||
1869 element == EL_GATE_WOOD)
1871 PlaySoundStereo(SND_HOLZ, ST(ELX));
1875 if (IS_WALL_ICE(element))
1879 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1880 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1882 /* check if laser hits wall with an angle of 90° */
1883 if (IS_90_ANGLE(laser.current_angle))
1884 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1886 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1892 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1893 mask = 15 - (8 >> i);
1894 else if (ABS(XS) == 4 &&
1896 (XS > 0) == (i % 2) &&
1897 (YS < 0) == (i / 2))
1898 mask = 3 + (i / 2) * 9;
1899 else if (ABS(YS) == 4 &&
1901 (XS < 0) == (i % 2) &&
1902 (YS > 0) == (i / 2))
1903 mask = 5 + (i % 2) * 5;
1907 laser.wall_mask = mask;
1909 else if (IS_WALL_AMOEBA(element))
1911 int elx = (LX - 2 * XS) / TILEX;
1912 int ely = (LY - 2 * YS) / TILEY;
1913 int element2 = Feld[elx][ely];
1916 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1918 laser.dest_element = EL_EMPTY;
1925 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1926 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1928 if (IS_90_ANGLE(laser.current_angle))
1929 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1931 laser.dest_element = element2 | EL_WALL_AMOEBA;
1933 laser.wall_mask = mask;
1939 void OpenExit(int x, int y)
1943 if (!MovDelay[x][y]) /* next animation frame */
1944 MovDelay[x][y] = 4 * delay;
1946 if (MovDelay[x][y]) /* wait some time before next frame */
1951 phase = MovDelay[x][y] / delay;
1952 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1953 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
1955 if (!MovDelay[x][y])
1957 Feld[x][y] = EL_EXIT_OPEN;
1963 void OpenSurpriseBall(int x, int y)
1967 if (!MovDelay[x][y]) /* next animation frame */
1968 MovDelay[x][y] = 50 * delay;
1970 if (MovDelay[x][y]) /* wait some time before next frame */
1973 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1976 int graphic = el2gfx(Store[x][y]);
1978 int dx = RND(26), dy = RND(26);
1980 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
1981 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
1982 SX + x * TILEX + dx, SY + y * TILEY + dy);
1983 MarkTileDirty(x, y);
1986 if (!MovDelay[x][y])
1988 Feld[x][y] = Store[x][y];
1997 void MeltIce(int x, int y)
2002 if (!MovDelay[x][y]) /* next animation frame */
2003 MovDelay[x][y] = frames * delay;
2005 if (MovDelay[x][y]) /* wait some time before next frame */
2008 int wall_mask = Store2[x][y];
2009 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2012 phase = frames - MovDelay[x][y] / delay - 1;
2014 if (!MovDelay[x][y])
2018 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2019 Store[x][y] = Store2[x][y] = 0;
2021 DrawWalls_MM(x, y, Feld[x][y]);
2023 if (Feld[x][y] == EL_WALL_ICE)
2024 Feld[x][y] = EL_EMPTY;
2026 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
2027 if (laser.damage[i].is_mirror)
2031 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2033 DrawLaser(0, DL_LASER_DISABLED);
2037 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2039 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2041 laser.redraw = TRUE;
2046 void GrowAmoeba(int x, int y)
2051 if (!MovDelay[x][y]) /* next animation frame */
2052 MovDelay[x][y] = frames * delay;
2054 if (MovDelay[x][y]) /* wait some time before next frame */
2057 int wall_mask = Store2[x][y];
2058 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2061 phase = MovDelay[x][y] / delay;
2063 if (!MovDelay[x][y])
2065 Feld[x][y] = real_element;
2066 Store[x][y] = Store2[x][y] = 0;
2068 DrawWalls_MM(x, y, Feld[x][y]);
2069 DrawLaser(0, DL_LASER_ENABLED);
2071 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2072 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2076 static void Explode_MM(int x, int y, int phase, int mode)
2078 int num_phase = 9, delay = 2;
2079 int last_phase = num_phase * delay;
2080 int half_phase = (num_phase / 2) * delay;
2082 laser.redraw = TRUE;
2084 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2086 int center_element = Feld[x][y];
2088 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2090 /* put moving element to center field (and let it explode there) */
2091 center_element = MovingOrBlocked2Element_MM(x, y);
2092 RemoveMovingField_MM(x, y);
2093 Feld[x][y] = center_element;
2096 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2097 Store[x][y] = center_element;
2099 Store[x][y] = EL_EMPTY;
2100 Store2[x][y] = mode;
2101 Feld[x][y] = EL_EXPLODING_OPAQUE;
2102 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2108 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2110 if (phase == half_phase)
2112 Feld[x][y] = EL_EXPLODING_TRANSP;
2114 if (x == ELX && y == ELY)
2118 if (phase == last_phase)
2120 if (Store[x][y] == EL_BOMB)
2122 laser.num_damages--;
2123 DrawLaser(0, DL_LASER_DISABLED);
2124 laser.num_edges = 0;
2126 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2127 Store[x][y] = EL_EMPTY;
2129 else if (IS_MCDUFFIN(Store[x][y]))
2131 game_mm.game_over = TRUE;
2132 game_mm.game_over_cause = GAME_OVER_BOMB;
2133 Store[x][y] = EL_EMPTY;
2136 Feld[x][y] = Store[x][y];
2137 Store[x][y] = Store2[x][y] = 0;
2138 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2139 InitField(x, y, FALSE);
2142 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2144 int graphic = IMG_MM_DEFAULT_EXPLODING;
2145 int graphic_phase = (phase / delay - 1);
2149 if (Store2[x][y] == EX_KETTLE)
2151 if (graphic_phase < 3)
2152 graphic = IMG_MM_KETTLE_EXPLODING;
2153 else if (graphic_phase < 5)
2159 graphic = IMG_EMPTY;
2163 else if (Store2[x][y] == EX_SHORT)
2165 if (graphic_phase < 4)
2169 graphic = IMG_EMPTY;
2174 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2176 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2177 FX + x * TILEX, FY + y * TILEY);
2178 MarkTileDirty(x, y);
2182 static void Bang_MM(int x, int y)
2184 int element = Feld[x][y];
2185 int mode = EX_NORMAL;
2188 DrawLaser(0, DL_LASER_ENABLED);
2207 if (IS_PACMAN(element))
2208 PlaySoundStereo(SND_QUIEK, ST(x));
2209 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2210 PlaySoundStereo(SND_ROAAAR, ST(x));
2211 else if (element == EL_KEY)
2212 PlaySoundStereo(SND_KLING, ST(x));
2214 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2216 Explode_MM(x, y, EX_PHASE_START, mode);
2219 void TurnRound(int x, int y)
2231 { 0, 0 }, { 0, 0 }, { 0, 0 },
2236 int left, right, back;
2240 { MV_DOWN, MV_UP, MV_RIGHT },
2241 { MV_UP, MV_DOWN, MV_LEFT },
2243 { MV_LEFT, MV_RIGHT, MV_DOWN },
2244 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2245 { MV_RIGHT, MV_LEFT, MV_UP }
2248 int element = Feld[x][y];
2249 int old_move_dir = MovDir[x][y];
2250 int right_dir = turn[old_move_dir].right;
2251 int back_dir = turn[old_move_dir].back;
2252 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2253 int right_x = x+right_dx, right_y = y+right_dy;
2255 if (element == EL_PACMAN)
2257 boolean can_turn_right = FALSE;
2259 if (IN_LEV_FIELD(right_x, right_y) &&
2260 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2261 can_turn_right = TRUE;
2264 MovDir[x][y] = right_dir;
2266 MovDir[x][y] = back_dir;
2272 static void StartMoving_MM(int x, int y)
2274 int element = Feld[x][y];
2279 if (CAN_MOVE(element))
2283 if (MovDelay[x][y]) /* wait some time before next movement */
2291 /* now make next step */
2293 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2295 if (element == EL_PACMAN &&
2296 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2297 !ObjHit(newx, newy, HIT_POS_CENTER))
2299 Store[newx][newy] = Feld[newx][newy];
2300 Feld[newx][newy] = EL_EMPTY;
2301 DrawField_MM(newx, newy);
2303 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2304 ObjHit(newx, newy, HIT_POS_CENTER))
2306 /* object was running against a wall */
2313 InitMovingField_MM(x, y, MovDir[x][y]);
2317 ContinueMoving_MM(x, y);
2320 static void ContinueMoving_MM(int x, int y)
2322 int element = Feld[x][y];
2323 int direction = MovDir[x][y];
2324 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2325 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2326 int horiz_move = (dx!=0);
2327 int newx = x + dx, newy = y + dy;
2328 int step = (horiz_move ? dx : dy) * TILEX / 8;
2330 MovPos[x][y] += step;
2332 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2334 Feld[x][y] = EL_EMPTY;
2335 Feld[newx][newy] = element;
2337 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2338 MovDelay[newx][newy] = 0;
2340 if (!CAN_MOVE(element))
2341 MovDir[newx][newy] = 0;
2344 DrawField_MM(newx, newy);
2346 Stop[newx][newy] = TRUE;
2348 if (element == EL_PACMAN)
2350 if (Store[newx][newy] == EL_BOMB)
2351 Bang_MM(newx, newy);
2353 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2354 (LX + 2 * XS) / TILEX == newx &&
2355 (LY + 2 * YS) / TILEY == newy)
2362 else /* still moving on */
2365 laser.redraw = TRUE;
2368 void ClickElement(int mx, int my, int button)
2370 static unsigned int click_delay = 0;
2371 static int click_delay_value = CLICK_DELAY_SHORT;
2372 static boolean new_button = TRUE;
2374 int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2376 if (button == MB_RELEASED)
2379 click_delay_value = CLICK_DELAY_SHORT;
2381 /* release eventually hold auto-rotating mirror */
2382 RotateMirror(x, y, MB_RELEASED);
2387 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2390 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2393 if (!IN_PIX_FIELD(mx - SX, my - SY))
2396 if (Feld[x][y] == EL_EMPTY)
2399 element = Feld[x][y];
2401 if (IS_MIRROR(element) ||
2402 IS_BEAMER(element) ||
2403 IS_POLAR(element) ||
2404 IS_POLAR_CROSS(element) ||
2405 IS_DF_MIRROR(element) ||
2406 IS_DF_MIRROR_AUTO(element))
2408 RotateMirror(x, y, button);
2410 else if (IS_MCDUFFIN(element))
2412 if (!laser.fuse_off)
2414 DrawLaser(0, DL_LASER_DISABLED);
2420 element = get_rotated_element(element, BUTTON_ROTATION(button));
2421 laser.start_angle = get_element_angle(element);
2425 Feld[x][y] = element;
2430 if (!laser.fuse_off)
2433 else if (element == EL_FUSE_ON && laser.fuse_off)
2435 if (x != laser.fuse_x || y != laser.fuse_y)
2438 laser.fuse_off = FALSE;
2439 laser.fuse_x = laser.fuse_y = -1;
2441 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2444 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2446 laser.fuse_off = TRUE;
2449 laser.overloaded = FALSE;
2451 DrawLaser(0, DL_LASER_DISABLED);
2452 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2454 else if (element == EL_LIGHTBALL)
2458 DrawLaser(0, DL_LASER_ENABLED);
2461 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2465 void RotateMirror(int x, int y, int button)
2467 static int hold_x = -1, hold_y = -1;
2469 if (button == MB_RELEASED)
2471 /* release eventually hold auto-rotating mirror */
2478 if (IS_MIRROR(Feld[x][y]) ||
2479 IS_POLAR_CROSS(Feld[x][y]) ||
2480 IS_POLAR(Feld[x][y]) ||
2481 IS_BEAMER(Feld[x][y]) ||
2482 IS_DF_MIRROR(Feld[x][y]) ||
2483 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2484 IS_GRID_WOOD_AUTO(Feld[x][y]))
2486 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2488 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2490 if (button == MB_LEFTBUTTON)
2492 /* left mouse button only for manual adjustment, no auto-rotating;
2493 freeze mirror for until mouse button released */
2497 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2498 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2501 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2503 int edge = Hit[x][y];
2509 DrawLaser(edge - 1, DL_LASER_DISABLED);
2513 else if (ObjHit(x, y, HIT_POS_CENTER))
2515 int edge = Hit[x][y];
2519 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2523 DrawLaser(edge - 1, DL_LASER_DISABLED);
2530 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2535 if ((IS_BEAMER(Feld[x][y]) ||
2536 IS_POLAR(Feld[x][y]) ||
2537 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2541 if (IS_BEAMER(Feld[x][y]))
2544 printf("TEST (%d, %d) [%d] [%d]\n",
2546 laser.beamer_edge, laser.beamer[1].num);
2556 DrawLaser(0, DL_LASER_ENABLED);
2560 void AutoRotateMirrors()
2562 static unsigned int rotate_delay = 0;
2565 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2568 for (x=0; x<lev_fieldx; x++)
2570 for (y=0; y<lev_fieldy; y++)
2572 int element = Feld[x][y];
2574 if (IS_DF_MIRROR_AUTO(element) ||
2575 IS_GRID_WOOD_AUTO(element) ||
2576 IS_GRID_STEEL_AUTO(element) ||
2577 element == EL_REFRACTOR)
2578 RotateMirror(x, y, MB_RIGHTBUTTON);
2583 boolean ObjHit(int obx, int oby, int bits)
2590 if (bits & HIT_POS_CENTER)
2592 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2596 if (bits & HIT_POS_EDGE)
2599 if (ReadPixel(drawto,
2600 SX + obx + 31 * (i % 2),
2601 SY + oby + 31 * (i / 2)) == pen_ray)
2605 if (bits & HIT_POS_BETWEEN)
2608 if (ReadPixel(drawto,
2609 SX + 4 + obx + 22 * (i % 2),
2610 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2617 void DeletePacMan(int px, int py)
2623 if (game_mm.num_pacman <= 1)
2625 game_mm.num_pacman = 0;
2629 for(i=0; i<game_mm.num_pacman; i++)
2630 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2633 game_mm.num_pacman--;
2635 for(j=i; j<game_mm.num_pacman; j++)
2637 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2638 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2639 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2643 void ColorCycling(void)
2645 static int CC, Cc = 0;
2647 static int color, old = 0xF00, new = 0x010, mult = 1;
2648 static unsigned short red, green, blue;
2650 if (color_status == STATIC_COLORS)
2655 if (CC < Cc || CC > Cc + 50)
2659 color = old + new * mult;
2665 if (ABS(mult) == 16)
2674 red = 0x0e00 * ((color & 0xF00) >> 8);
2675 green = 0x0e00 * ((color & 0x0F0) >> 4);
2676 blue = 0x0e00 * ((color & 0x00F));
2677 SetRGB(pen_magicolor[0], red, green, blue);
2679 red = 0x1111 * ((color & 0xF00) >> 8);
2680 green = 0x1111 * ((color & 0x0F0) >> 4);
2681 blue = 0x1111 * ((color & 0x00F));
2682 SetRGB(pen_magicolor[1], red, green, blue);
2686 static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
2688 static unsigned int action_delay = 0;
2689 static unsigned int pacman_delay = 0;
2690 static unsigned int energy_delay = 0;
2691 static unsigned int overload_delay = 0;
2697 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2699 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2702 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2704 element = Feld[x][y];
2706 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2707 StartMoving_MM(x, y);
2708 else if (IS_MOVING(x, y))
2709 ContinueMoving_MM(x, y);
2710 else if (IS_EXPLODING(element))
2711 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2712 else if (element == EL_EXIT_OPENING)
2714 else if (element == EL_GRAY_BALL_OPENING)
2715 OpenSurpriseBall(x, y);
2716 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2718 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2722 AutoRotateMirrors();
2725 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2727 /* redraw after Explode_MM() ... */
2729 DrawLaser(0, DL_LASER_ENABLED);
2730 laser.redraw = FALSE;
2735 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2739 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2741 DrawLaser(0, DL_LASER_DISABLED);
2746 if (DelayReached(&energy_delay, 4000))
2748 game_mm.energy_left--;
2749 if (game_mm.energy_left >= 0)
2752 BlitBitmap(pix[PIX_DOOR], drawto,
2753 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2754 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2755 DX_ENERGY, DY_ENERGY);
2757 redraw_mask |= REDRAW_DOOR_1;
2759 else if (setup.time_limit)
2763 for(i=15; i>=0; i--)
2766 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2768 pen_ray = GetPixelFromRGB(window,
2769 native_mm_level.laser_red * 0x11 * i,
2770 native_mm_level.laser_green * 0x11 * i,
2771 native_mm_level.laser_blue * 0x11 * i);
2772 DrawLaser(0, DL_LASER_ENABLED);
2777 StopSound(SND_WARNTON);
2780 DrawLaser(0, DL_LASER_DISABLED);
2781 game_mm.game_over = TRUE;
2782 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2785 if (Request("Out of magic energy ! Play it again ?",
2786 REQ_ASK | REQ_STAY_CLOSED))
2792 game_status = MAINMENU;
2801 element = laser.dest_element;
2804 if (element != Feld[ELX][ELY])
2806 printf("element == %d, Feld[ELX][ELY] == %d\n",
2807 element, Feld[ELX][ELY]);
2811 if (!laser.overloaded && laser.overload_value == 0 &&
2812 element != EL_BOMB &&
2813 element != EL_MINE &&
2814 element != EL_BALL_GRAY &&
2815 element != EL_BLOCK_STONE &&
2816 element != EL_BLOCK_WOOD &&
2817 element != EL_FUSE_ON &&
2818 element != EL_FUEL_FULL &&
2819 !IS_WALL_ICE(element) &&
2820 !IS_WALL_AMOEBA(element))
2823 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2824 (!laser.overloaded && laser.overload_value > 0)) &&
2825 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2827 if (laser.overloaded)
2828 laser.overload_value++;
2830 laser.overload_value--;
2832 if (game_mm.cheat_no_overload)
2834 laser.overloaded = FALSE;
2835 laser.overload_value = 0;
2838 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2840 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2841 int color_down = 0xFF - color_up;
2844 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2845 (15 - (laser.overload_value / 6)) * color_scale);
2847 pen_ray = GetPixelFromRGB(window,
2848 (native_mm_level.laser_red ? 0xFF : color_up),
2849 (native_mm_level.laser_green ? color_down : 0x00),
2850 (native_mm_level.laser_blue ? color_down : 0x00));
2851 DrawLaser(0, DL_LASER_ENABLED);
2855 if (laser.overloaded)
2857 if (setup.sound_loops)
2858 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
2860 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2863 if (!laser.overloaded)
2864 StopSound(SND_WARNTON);
2866 if (laser.overloaded)
2869 BlitBitmap(pix[PIX_DOOR], drawto,
2870 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2871 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2872 - laser.overload_value,
2873 OVERLOAD_XSIZE, laser.overload_value,
2874 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2875 - laser.overload_value);
2877 redraw_mask |= REDRAW_DOOR_1;
2882 BlitBitmap(pix[PIX_DOOR], drawto,
2883 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2884 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2885 DX_OVERLOAD, DY_OVERLOAD);
2887 redraw_mask |= REDRAW_DOOR_1;
2890 if (laser.overload_value == MAX_LASER_OVERLOAD)
2894 for(i=15; i>=0; i--)
2897 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2900 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2901 DrawLaser(0, DL_LASER_ENABLED);
2906 DrawLaser(0, DL_LASER_DISABLED);
2907 game_mm.game_over = TRUE;
2908 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2911 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2912 REQ_ASK | REQ_STAY_CLOSED))
2918 game_status = MAINMENU;
2932 if (element == EL_BOMB && CT > 1500)
2934 if (game_mm.cheat_no_explosion)
2938 laser.num_damages--;
2939 DrawLaser(0, DL_LASER_DISABLED);
2940 laser.num_edges = 0;
2945 laser.dest_element = EL_EXPLODING_OPAQUE;
2949 laser.num_damages--;
2950 DrawLaser(0, DL_LASER_DISABLED);
2952 laser.num_edges = 0;
2953 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2955 if (Request("Bomb killed Mc Duffin ! Play it again ?",
2956 REQ_ASK | REQ_STAY_CLOSED))
2962 game_status = MAINMENU;
2970 if (element == EL_FUSE_ON && CT > 500)
2972 laser.fuse_off = TRUE;
2975 DrawLaser(0, DL_LASER_DISABLED);
2976 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
2979 if (element == EL_BALL_GRAY && CT > 1500)
2981 static int new_elements[] =
2984 EL_MIRROR_FIXED_START,
2986 EL_POLAR_CROSS_START,
2992 int num_new_elements = sizeof(new_elements) / sizeof(int);
2993 int new_element = new_elements[RND(num_new_elements)];
2995 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
2996 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
2998 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3009 element = EL_MIRROR_START + RND(16);
3015 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3022 element = (rnd == 0 ? EL_FUSE_ON :
3023 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3024 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3025 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3026 EL_MIRROR_FIXED_START + rnd - 25);
3031 graphic = el2gfx(element);
3039 BlitBitmap(pix[PIX_BACK], drawto,
3040 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3041 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3042 SX + ELX * TILEX + x,
3043 SY + ELY * TILEY + y);
3045 MarkTileDirty(ELX, ELY);
3048 DrawLaser(0, DL_LASER_ENABLED);
3053 Feld[ELX][ELY] = element;
3054 DrawField_MM(ELX, ELY);
3057 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3060 /* above stuff: GRAY BALL -> PRISM !!! */
3062 LX = ELX * TILEX + 14;
3063 LY = ELY * TILEY + 14;
3064 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3071 laser.num_edges -= 2;
3072 laser.num_damages--;
3076 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3077 if (laser.damage[i].is_mirror)
3081 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3083 DrawLaser(0, DL_LASER_DISABLED);
3085 DrawLaser(0, DL_LASER_DISABLED);
3091 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3098 if (IS_WALL_ICE(element) && CT > 1000)
3100 PlaySoundStereo(SND_SLURP, ST(ELX));
3105 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3106 Store[ELX][ELY] = EL_WALL_ICE;
3107 Store2[ELX][ELY] = laser.wall_mask;
3109 laser.dest_element = Feld[ELX][ELY];
3123 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3127 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3132 if (Feld[ELX][ELY] == EL_WALL_ICE)
3133 Feld[ELX][ELY] = EL_EMPTY;
3137 LX = laser.edge[laser.num_edges].x - (SX + 2);
3138 LY = laser.edge[laser.num_edges].y - (SY + 2);
3141 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3142 if (laser.damage[i].is_mirror)
3146 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3148 DrawLaser(0, DL_LASER_DISABLED);
3155 if (IS_WALL_AMOEBA(element) && CT > 1200)
3157 int k1, k2, k3, dx, dy, de, dm;
3158 int element2 = Feld[ELX][ELY];
3160 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3163 for (i = laser.num_damages - 1; i>=0; i--)
3164 if (laser.damage[i].is_mirror)
3167 r = laser.num_edges;
3168 d = laser.num_damages;
3175 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3178 DrawLaser(0, DL_LASER_ENABLED);
3181 x = laser.damage[k1].x;
3182 y = laser.damage[k1].y;
3188 if (laser.wall_mask & (1 << i))
3190 if (ReadPixel(drawto,
3191 SX + ELX * TILEX + 14 + (i % 2) * 2,
3192 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3194 if (ReadPixel(drawto,
3195 SX + ELX * TILEX + 31 * (i % 2),
3196 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3205 if (laser.wall_mask & (1 << i))
3207 if (ReadPixel(drawto,
3208 SX + ELX * TILEX + 31 * (i % 2),
3209 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3216 if (laser.num_beamers > 0 ||
3217 k1 < 1 || k2 < 4 || k3 < 4 ||
3218 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3221 laser.num_edges = r;
3222 laser.num_damages = d;
3223 DrawLaser(0, DL_LASER_DISABLED);
3226 Feld[ELX][ELY] = element | laser.wall_mask;
3229 de = Feld[ELX][ELY];
3230 dm = laser.wall_mask;
3236 int x = ELX, y = ELY;
3237 int wall_mask = laser.wall_mask;
3241 DrawLaser(0, DL_LASER_ENABLED);
3243 PlaySoundStereo(SND_AMOEBE, ST(dx));
3247 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3248 Store[x][y] = EL_WALL_AMOEBA;
3249 Store2[x][y] = wall_mask;
3257 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3259 DrawLaser(0, DL_LASER_ENABLED);
3261 PlaySoundStereo(SND_AMOEBE, ST(dx));
3265 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3270 DrawLaser(0, DL_LASER_ENABLED);
3275 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3276 laser.stops_inside_element && CT > 1500)
3281 if (ABS(XS) > ABS(YS))
3295 x = ELX + Step[k * 4].x;
3296 y = ELY + Step[k * 4].y;
3298 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3301 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3309 laser.overloaded = (element == EL_BLOCK_STONE);
3313 PlaySoundStereo(SND_BONG, ST(ELX));
3316 Feld[x][y] = element;
3318 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3321 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3323 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3324 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3332 if (element == EL_FUEL_FULL && CT > 200)
3334 for(i=game_mm.energy_left; i<=MAX_LASER_ENERGY; i+=2)
3337 BlitBitmap(pix[PIX_DOOR], drawto,
3338 DOOR_GFX_PAGEX4 + XX_ENERGY,
3339 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3340 ENERGY_XSIZE, i, DX_ENERGY,
3341 DY_ENERGY + ENERGY_YSIZE - i);
3344 redraw_mask |= REDRAW_DOOR_1;
3350 game_mm.energy_left = MAX_LASER_ENERGY;
3351 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3352 DrawField_MM(ELX, ELY);
3354 DrawLaser(0, DL_LASER_ENABLED);
3362 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
3365 ClickElement(0, 0, MB_NOT_PRESSED);
3367 GameActions_MM_Ext(action, warp_mode);
3373 int mx, my, ox, oy, nx, ny;
3377 if (++p >= game_mm.num_pacman)
3379 game_mm.pacman[p].dir--;
3383 game_mm.pacman[p].dir++;
3385 if (game_mm.pacman[p].dir > 4)
3386 game_mm.pacman[p].dir = 1;
3388 if (game_mm.pacman[p].dir % 2)
3391 my = game_mm.pacman[p].dir - 2;
3396 mx = 3 - game_mm.pacman[p].dir;
3399 ox = game_mm.pacman[p].x;
3400 oy = game_mm.pacman[p].y;
3403 element = Feld[nx][ny];
3404 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3407 if (!IS_EATABLE4PACMAN(element))
3410 if (ObjHit(nx, ny, HIT_POS_CENTER))
3413 Feld[ox][oy] = EL_EMPTY;
3415 EL_PACMAN_RIGHT - 1 +
3416 (game_mm.pacman[p].dir - 1 +
3417 (game_mm.pacman[p].dir % 2) * 2);
3419 game_mm.pacman[p].x = nx;
3420 game_mm.pacman[p].y = ny;
3422 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3424 if (element != EL_EMPTY)
3426 int graphic = el2gfx(Feld[nx][ny]);
3431 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3434 ox = SX + ox * TILEX;
3435 oy = SY + oy * TILEY;
3437 for(i=1; i<33; i+=2)
3438 BlitBitmap(bitmap, window,
3439 src_x, src_y, TILEX, TILEY,
3440 ox + i * mx, oy + i * my);
3441 Ct = Ct + Counter() - CT;
3443 DrawField_MM(nx, ny);
3446 if (!laser.fuse_off)
3448 DrawLaser(0, DL_LASER_ENABLED);
3450 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3452 AddDamagedField(nx, ny);
3453 laser.damage[laser.num_damages - 1].edge = 0;
3457 if (element == EL_BOMB)
3459 DeletePacMan(nx, ny);
3462 if (IS_WALL_AMOEBA(element) &&
3463 (LX + 2 * XS) / TILEX == nx &&
3464 (LY + 2 * YS) / TILEY == ny)
3476 boolean raise_level = FALSE;
3479 if (local_player->MovPos)
3482 local_player->LevelSolved = FALSE;
3485 if (game_mm.energy_left)
3487 if (setup.sound_loops)
3488 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3490 while(game_mm.energy_left > 0)
3492 if (!setup.sound_loops)
3493 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3496 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3497 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3502 game_mm.energy_left--;
3503 if (game_mm.energy_left >= 0)
3506 BlitBitmap(pix[PIX_DOOR], drawto,
3507 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3508 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3509 DX_ENERGY, DY_ENERGY);
3511 redraw_mask |= REDRAW_DOOR_1;
3518 if (setup.sound_loops)
3519 StopSound(SND_SIRR);
3521 else if (native_mm_level.time == 0) /* level without time limit */
3523 if (setup.sound_loops)
3524 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3526 while(TimePlayed < 999)
3528 if (!setup.sound_loops)
3529 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3530 if (TimePlayed < 999 && !(TimePlayed % 10))
3531 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3532 if (TimePlayed < 900 && !(TimePlayed % 10))
3538 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3545 if (setup.sound_loops)
3546 StopSound(SND_SIRR);
3553 CloseDoor(DOOR_CLOSE_1);
3555 Request("Level solved !", REQ_CONFIRM);
3557 if (level_nr == leveldir_current->handicap_level)
3559 leveldir_current->handicap_level++;
3560 SaveLevelSetup_SeriesInfo();
3563 if (level_editor_test_game)
3564 game_mm.score = -1; /* no highscore when playing from editor */
3565 else if (level_nr < leveldir_current->last_level)
3566 raise_level = TRUE; /* advance to next level */
3568 if ((hi_pos = NewHiScore_MM()) >= 0)
3570 game_status = HALLOFFAME;
3571 // DrawHallOfFame(hi_pos);
3577 game_status = MAINMENU;
3591 // LoadScore(level_nr);
3593 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3594 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3597 for (k=0; k<MAX_SCORE_ENTRIES; k++)
3599 if (game_mm.score > highscore[k].Score)
3601 /* player has made it to the hall of fame */
3603 if (k < MAX_SCORE_ENTRIES - 1)
3605 int m = MAX_SCORE_ENTRIES - 1;
3608 for (l=k; l<MAX_SCORE_ENTRIES; l++)
3609 if (!strcmp(setup.player_name, highscore[l].Name))
3611 if (m == k) /* player's new highscore overwrites his old one */
3617 strcpy(highscore[l].Name, highscore[l - 1].Name);
3618 highscore[l].Score = highscore[l - 1].Score;
3625 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3626 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3627 highscore[k].Score = game_mm.score;
3633 else if (!strncmp(setup.player_name, highscore[k].Name,
3634 MAX_PLAYER_NAME_LEN))
3635 break; /* player already there with a higher score */
3640 // if (position >= 0)
3641 // SaveScore(level_nr);
3646 static void InitMovingField_MM(int x, int y, int direction)
3648 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3649 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3651 MovDir[x][y] = direction;
3652 MovDir[newx][newy] = direction;
3653 if (Feld[newx][newy] == EL_EMPTY)
3654 Feld[newx][newy] = EL_BLOCKED;
3657 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3659 int direction = MovDir[x][y];
3660 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3661 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3667 static void Blocked2Moving_MM(int x, int y,
3668 int *comes_from_x, int *comes_from_y)
3670 int oldx = x, oldy = y;
3671 int direction = MovDir[x][y];
3673 if (direction == MV_LEFT)
3675 else if (direction == MV_RIGHT)
3677 else if (direction == MV_UP)
3679 else if (direction == MV_DOWN)
3682 *comes_from_x = oldx;
3683 *comes_from_y = oldy;
3686 static int MovingOrBlocked2Element_MM(int x, int y)
3688 int element = Feld[x][y];
3690 if (element == EL_BLOCKED)
3694 Blocked2Moving_MM(x, y, &oldx, &oldy);
3695 return Feld[oldx][oldy];
3702 static void RemoveField(int x, int y)
3704 Feld[x][y] = EL_EMPTY;
3711 static void RemoveMovingField_MM(int x, int y)
3713 int oldx = x, oldy = y, newx = x, newy = y;
3715 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3718 if (IS_MOVING(x, y))
3720 Moving2Blocked_MM(x, y, &newx, &newy);
3721 if (Feld[newx][newy] != EL_BLOCKED)
3724 else if (Feld[x][y] == EL_BLOCKED)
3726 Blocked2Moving_MM(x, y, &oldx, &oldy);
3727 if (!IS_MOVING(oldx, oldy))
3731 Feld[oldx][oldy] = EL_EMPTY;
3732 Feld[newx][newy] = EL_EMPTY;
3733 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3734 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3736 DrawLevelField_MM(oldx, oldy);
3737 DrawLevelField_MM(newx, newy);
3740 void PlaySoundLevel(int x, int y, int sound_nr)
3742 int sx = SCREENX(x), sy = SCREENY(y);
3744 int silence_distance = 8;
3746 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3747 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3750 if (!IN_LEV_FIELD(x, y) ||
3751 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3752 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3755 volume = SOUND_MAX_VOLUME;
3758 stereo = (sx - SCR_FIELDX/2) * 12;
3760 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3761 if (stereo > SOUND_MAX_RIGHT)
3762 stereo = SOUND_MAX_RIGHT;
3763 if (stereo < SOUND_MAX_LEFT)
3764 stereo = SOUND_MAX_LEFT;
3767 if (!IN_SCR_FIELD(sx, sy))
3769 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3770 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3772 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3775 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3778 static void RaiseScore_MM(int value)
3780 game_mm.score += value;
3782 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3787 void RaiseScoreElement_MM(int element)
3792 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3795 RaiseScore_MM(native_mm_level.score[SC_KEY]);