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");
513 px = SX + LX + (i % 2) * 2;
514 py = SY + LY + (i / 2) * 2;
515 lx = (px - SX + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
516 ly = (py - SY + 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)
524 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
527 ((py - SY - ly * TILEY) / MINI_TILEX) * 2 +
528 (px - SX - lx * TILEX) / MINI_TILEY;
530 pixel = ((element & (1 << pos)) ? 1 : 0);
534 int graphic_mask = getMaskFromElement(element);
536 int dx = px - SX - lx * TILEX;
537 int dy = py - SY - ly * TILEY;
541 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
546 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
551 if (px < REAL_SX || px >= REAL_SX + FULL_SXSIZE ||
552 py < REAL_SY || py >= REAL_SY + FULL_SYSIZE)
558 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
559 hit_mask |= (1 << i);
562 if (hit_mask == HIT_MASK_NO_HIT)
564 /* hit nothing -- go on with another step */
576 int end = 0, rf = laser.num_edges;
578 laser.overloaded = FALSE;
579 laser.stops_inside_element = FALSE;
581 DrawLaser(0, DL_LASER_ENABLED);
584 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
592 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
595 laser.overloaded = TRUE;
599 hit_mask = ScanPixel();
602 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
606 /* hit something -- check out what it was */
607 ELX = (LX + XS) / TILEX;
608 ELY = (LY + YS) / TILEY;
611 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
612 hit_mask, LX, LY, ELX, ELY);
615 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
618 laser.dest_element = element;
623 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
625 /* we have hit the top-right and bottom-left element --
626 choose the bottom-left one */
627 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
628 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
629 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
630 ELX = (LX - 2) / TILEX;
631 ELY = (LY + 2) / TILEY;
634 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
636 /* we have hit the top-left and bottom-right element --
637 choose the top-left one */
638 /* !!! SEE ABOVE !!! */
639 ELX = (LX - 2) / TILEX;
640 ELY = (LY - 2) / TILEY;
644 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
645 hit_mask, LX, LY, ELX, ELY);
648 element = Feld[ELX][ELY];
649 laser.dest_element = element;
652 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
655 LX % TILEX, LY % TILEY,
660 if (!IN_LEV_FIELD(ELX, ELY))
661 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
664 if (element == EL_EMPTY)
666 if (!HitOnlyAnEdge(element, hit_mask))
669 else if (element == EL_FUSE_ON)
671 if (HitPolarizer(element, hit_mask))
674 else if (IS_GRID(element) || IS_DF_GRID(element))
676 if (HitPolarizer(element, hit_mask))
679 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
680 element == EL_GATE_STONE || element == EL_GATE_WOOD)
682 if (HitBlock(element, hit_mask))
688 else if (IS_MCDUFFIN(element))
690 if (HitLaserSource(element, hit_mask))
693 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
694 IS_RECEIVER(element))
696 if (HitLaserDestination(element, hit_mask))
699 else if (IS_WALL(element))
701 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
703 if (HitReflectingWalls(element, hit_mask))
708 if (HitAbsorbingWalls(element, hit_mask))
714 if (HitElement(element, hit_mask))
719 DrawLaser(rf - 1, DL_LASER_ENABLED);
720 rf = laser.num_edges;
724 if (laser.dest_element != Feld[ELX][ELY])
726 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
727 laser.dest_element, Feld[ELX][ELY]);
731 if (!end && !laser.stops_inside_element && !StepBehind())
734 printf("ScanLaser: Go one step back\n");
739 AddLaserEdge(LX, LY);
743 DrawLaser(rf - 1, DL_LASER_ENABLED);
748 if (!IN_LEV_FIELD(ELX, ELY))
749 printf("WARNING! (2) %d, %d\n", ELX, ELY);
753 void DrawLaserExt(int start_edge, int num_edges, int mode)
759 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
760 start_edge, num_edges, mode);
765 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
771 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
776 if (mode == DL_LASER_DISABLED)
778 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
782 /* now draw the laser to the backbuffer and (if enabled) to the screen */
783 DrawLines(drawto, &laser.edge[start_edge], num_edges,
784 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
786 redraw_mask |= REDRAW_FIELD;
788 if (mode == DL_LASER_ENABLED)
791 /* after the laser was deleted, the "damaged" graphics must be restored */
792 if (laser.num_damages)
794 int damage_start = 0;
797 /* determine the starting edge, from which graphics need to be restored */
800 for(i=0; i<laser.num_damages; i++)
802 if (laser.damage[i].edge == start_edge + 1)
810 /* restore graphics from this starting edge to the end of damage list */
811 for(i=damage_start; i<laser.num_damages; i++)
813 int lx = laser.damage[i].x;
814 int ly = laser.damage[i].y;
815 int element = Feld[lx][ly];
817 if (Hit[lx][ly] == laser.damage[i].edge)
818 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
821 if (Box[lx][ly] == laser.damage[i].edge)
824 if (IS_DRAWABLE(element))
825 DrawField_MM(lx, ly);
828 elx = laser.damage[damage_start].x;
829 ely = laser.damage[damage_start].y;
830 element = Feld[elx][ely];
833 if (IS_BEAMER(element))
837 for (i=0; i<laser.num_beamers; i++)
838 printf("-> %d\n", laser.beamer_edge[i]);
839 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
840 mode, elx, ely, Hit[elx][ely], start_edge);
841 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
842 get_element_angle(element), laser.damage[damage_start].angle);
846 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
847 laser.num_beamers > 0 &&
848 start_edge == laser.beamer_edge[laser.num_beamers - 1])
850 /* element is outgoing beamer */
851 laser.num_damages = damage_start + 1;
852 if (IS_BEAMER(element))
853 laser.current_angle = get_element_angle(element);
857 /* element is incoming beamer or other element */
858 laser.num_damages = damage_start;
859 laser.current_angle = laser.damage[laser.num_damages].angle;
864 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
866 elx = laser.start_edge.x;
867 ely = laser.start_edge.y;
868 element = Feld[elx][ely];
871 laser.num_edges = start_edge + 1;
873 laser.current_angle = laser.start_angle;
874 LX = laser.edge[start_edge].x - (SX + 2);
875 LY = laser.edge[start_edge].y - (SY + 2);
876 XS = 2 * Step[laser.current_angle].x;
877 YS = 2 * Step[laser.current_angle].y;
880 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
886 if (IS_BEAMER(element) ||
887 IS_FIBRE_OPTIC(element) ||
888 IS_PACMAN(element) ||
890 IS_POLAR_CROSS(element) ||
891 element == EL_FUSE_ON)
896 printf("element == %d\n", element);
899 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
900 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
904 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
905 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
906 (laser.num_beamers == 0 ||
907 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
909 /* element is incoming beamer or other element */
910 step_size = -step_size;
915 if (IS_BEAMER(element))
917 printf("start_edge == %d, laser.beamer_edge == %d\n",
918 start_edge, laser.beamer_edge);
922 LX += step_size * XS;
923 LY += step_size * YS;
925 else if (element != EL_EMPTY)
934 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
939 void DrawLaser(int start_edge, int mode)
941 if (laser.num_edges - start_edge < 0)
943 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
947 /* check if laser is interrupted by beamer element */
948 if (laser.num_beamers > 0 &&
949 start_edge < laser.beamer_edge[laser.num_beamers - 1])
951 if (mode == DL_LASER_ENABLED)
954 int tmp_start_edge = start_edge;
956 /* draw laser segments forward from the start to the last beamer */
957 for (i=0; i<laser.num_beamers; i++)
959 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
961 if (tmp_num_edges <= 0)
965 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
966 i, laser.beamer_edge[i], tmp_start_edge);
969 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
970 tmp_start_edge = laser.beamer_edge[i];
973 /* draw last segment from last beamer to the end */
974 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
980 int last_num_edges = laser.num_edges;
981 int num_beamers = laser.num_beamers;
983 /* delete laser segments backward from the end to the first beamer */
984 for (i=num_beamers-1; i>=0; i--)
986 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
988 if (laser.beamer_edge[i] - start_edge <= 0)
991 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
992 last_num_edges = laser.beamer_edge[i];
997 if (last_num_edges - start_edge <= 0)
998 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
999 last_num_edges, start_edge);
1002 /* delete first segment from start to the first beamer */
1003 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1007 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1010 boolean HitElement(int element, int hit_mask)
1012 if (HitOnlyAnEdge(element, hit_mask))
1015 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1016 element = MovingOrBlocked2Element_MM(ELX, ELY);
1019 printf("HitElement (1): element == %d\n", element);
1023 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1024 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1026 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1029 AddDamagedField(ELX, ELY);
1031 /* this is more precise: check if laser would go through the center */
1032 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1034 /* skip the whole element before continuing the scan */
1040 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1042 if (LX/TILEX > ELX || LY/TILEY > ELY)
1044 /* skipping scan positions to the right and down skips one scan
1045 position too much, because this is only the top left scan position
1046 of totally four scan positions (plus one to the right, one to the
1047 bottom and one to the bottom right) */
1057 printf("HitElement (2): element == %d\n", element);
1060 if (LX + 5 * XS < 0 ||
1070 printf("HitElement (3): element == %d\n", element);
1073 if (IS_POLAR(element) &&
1074 ((element - EL_POLAR_START) % 2 ||
1075 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1077 PlaySoundStereo(SND_KINK, ST(ELX));
1078 laser.num_damages--;
1083 if (IS_POLAR_CROSS(element) &&
1084 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1086 PlaySoundStereo(SND_KINK, ST(ELX));
1087 laser.num_damages--;
1092 if (!IS_BEAMER(element) &&
1093 !IS_FIBRE_OPTIC(element) &&
1094 !IS_GRID_WOOD(element) &&
1095 element != EL_FUEL_EMPTY)
1098 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1099 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1101 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1104 LX = ELX * TILEX + 14;
1105 LY = ELY * TILEY + 14;
1106 AddLaserEdge(LX, LY);
1109 if (IS_MIRROR(element) ||
1110 IS_MIRROR_FIXED(element) ||
1111 IS_POLAR(element) ||
1112 IS_POLAR_CROSS(element) ||
1113 IS_DF_MIRROR(element) ||
1114 IS_DF_MIRROR_AUTO(element) ||
1115 element == EL_PRISM ||
1116 element == EL_REFRACTOR)
1118 int current_angle = laser.current_angle;
1121 laser.num_damages--;
1122 AddDamagedField(ELX, ELY);
1123 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1126 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1128 if (IS_MIRROR(element) ||
1129 IS_MIRROR_FIXED(element) ||
1130 IS_DF_MIRROR(element) ||
1131 IS_DF_MIRROR_AUTO(element))
1132 laser.current_angle = get_mirrored_angle(laser.current_angle,
1133 get_element_angle(element));
1135 if (element == EL_PRISM || element == EL_REFRACTOR)
1136 laser.current_angle = RND(16);
1138 XS = 2 * Step[laser.current_angle].x;
1139 YS = 2 * Step[laser.current_angle].y;
1141 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1146 LX += step_size * XS;
1147 LY += step_size * YS;
1150 /* draw sparkles on mirror */
1151 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1152 current_angle != laser.current_angle)
1154 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1158 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1159 current_angle != laser.current_angle)
1160 PlaySoundStereo(SND_LASER, ST(ELX));
1163 (get_opposite_angle(laser.current_angle) ==
1164 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1166 return (laser.overloaded ? TRUE : FALSE);
1169 if (element == EL_FUEL_FULL)
1171 laser.stops_inside_element = TRUE;
1176 if (element == EL_BOMB || element == EL_MINE)
1178 PlaySoundStereo(SND_KINK, ST(ELX));
1180 if (element == EL_MINE)
1181 laser.overloaded = TRUE;
1184 if (element == EL_KETTLE ||
1185 element == EL_CELL ||
1186 element == EL_KEY ||
1187 element == EL_LIGHTBALL ||
1188 element == EL_PACMAN ||
1191 if (!IS_PACMAN(element))
1194 if (element == EL_PACMAN)
1197 if (element == EL_KETTLE || element == EL_CELL)
1201 if (game_mm.kettles_still_needed == 0)
1204 static int xy[4][2] =
1212 PlaySoundStereo(SND_KLING, ST(ELX));
1214 for(y=0; y<lev_fieldy; y++)
1216 for(x=0; x<lev_fieldx; x++)
1218 /* initiate opening animation of exit door */
1219 if (Feld[x][y] == EL_EXIT_CLOSED)
1220 Feld[x][y] = EL_EXIT_OPENING;
1222 /* remove field that blocks receiver */
1223 if (IS_RECEIVER(Feld[x][y]))
1225 int phase = Feld[x][y] - EL_RECEIVER_START;
1226 int blocking_x, blocking_y;
1228 blocking_x = x + xy[phase][0];
1229 blocking_y = y + xy[phase][1];
1231 if (IN_LEV_FIELD(blocking_x, blocking_y))
1233 Feld[blocking_x][blocking_y] = EL_EMPTY;
1234 DrawField_MM(blocking_x, blocking_y);
1240 DrawLaser(0, DL_LASER_ENABLED);
1243 else if (element == EL_KEY)
1245 else if (element == EL_LIGHTBALL)
1247 else if (IS_PACMAN(element))
1249 DeletePacMan(ELX, ELY);
1256 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1258 PlaySoundStereo(SND_KINK, ST(ELX));
1260 DrawLaser(0, DL_LASER_ENABLED);
1262 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1264 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1265 game_mm.lights_still_needed--;
1269 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1270 game_mm.lights_still_needed++;
1273 DrawField_MM(ELX, ELY);
1274 DrawLaser(0, DL_LASER_ENABLED);
1279 laser.stops_inside_element = TRUE;
1285 printf("HitElement (4): element == %d\n", element);
1288 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1289 laser.num_beamers < MAX_NUM_BEAMERS &&
1290 laser.beamer[BEAMER_NR(element)][1].num)
1292 int beamer_angle = get_element_angle(element);
1293 int beamer_nr = BEAMER_NR(element);
1297 printf("HitElement (BEAMER): element == %d\n", element);
1300 laser.num_damages--;
1302 if (IS_FIBRE_OPTIC(element) ||
1303 laser.current_angle == get_opposite_angle(beamer_angle))
1307 LX = ELX * TILEX + 14;
1308 LY = ELY * TILEY + 14;
1309 AddLaserEdge(LX, LY);
1310 AddDamagedField(ELX, ELY);
1311 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1314 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1316 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1317 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1318 ELX = laser.beamer[beamer_nr][pos].x;
1319 ELY = laser.beamer[beamer_nr][pos].y;
1320 LX = ELX * TILEX + 14;
1321 LY = ELY * TILEY + 14;
1323 if (IS_BEAMER(element))
1325 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1326 XS = 2 * Step[laser.current_angle].x;
1327 YS = 2 * Step[laser.current_angle].y;
1330 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1331 AddLaserEdge(LX, LY);
1332 AddDamagedField(ELX, ELY);
1333 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1336 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1338 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1343 LX += step_size * XS;
1344 LY += step_size * YS;
1346 laser.num_beamers++;
1355 boolean HitOnlyAnEdge(int element, int hit_mask)
1357 /* check if the laser hit only the edge of an element and, if so, go on */
1360 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1363 if ((hit_mask == HIT_MASK_TOPLEFT ||
1364 hit_mask == HIT_MASK_TOPRIGHT ||
1365 hit_mask == HIT_MASK_BOTTOMLEFT ||
1366 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1367 laser.current_angle % 4) /* angle is not 90° */
1371 if (hit_mask == HIT_MASK_TOPLEFT)
1376 else if (hit_mask == HIT_MASK_TOPRIGHT)
1381 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1386 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1392 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1397 printf("[HitOnlyAnEdge() == TRUE]\n");
1404 printf("[HitOnlyAnEdge() == FALSE]\n");
1410 boolean HitPolarizer(int element, int hit_mask)
1412 if (HitOnlyAnEdge(element, hit_mask))
1415 if (IS_DF_GRID(element))
1417 int grid_angle = get_element_angle(element);
1420 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1421 grid_angle, laser.current_angle);
1424 AddLaserEdge(LX, LY);
1425 AddDamagedField(ELX, ELY);
1428 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1430 if (laser.current_angle == grid_angle ||
1431 laser.current_angle == get_opposite_angle(grid_angle))
1433 /* skip the whole element before continuing the scan */
1439 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1441 if (LX/TILEX > ELX || LY/TILEY > ELY)
1443 /* skipping scan positions to the right and down skips one scan
1444 position too much, because this is only the top left scan position
1445 of totally four scan positions (plus one to the right, one to the
1446 bottom and one to the bottom right) */
1452 AddLaserEdge(LX, LY);
1458 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1460 LX / TILEX, LY / TILEY,
1461 LX % TILEX, LY % TILEY);
1466 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1467 return HitReflectingWalls(element, hit_mask);
1469 return HitAbsorbingWalls(element, hit_mask);
1471 else if (IS_GRID_STEEL(element))
1472 return HitReflectingWalls(element, hit_mask);
1473 else /* IS_GRID_WOOD */
1474 return HitAbsorbingWalls(element, hit_mask);
1479 boolean HitBlock(int element, int hit_mask)
1481 boolean check = FALSE;
1483 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1484 game_mm.num_keys == 0)
1487 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1490 int ex = ELX * TILEX + 14;
1491 int ey = ELY * TILEY + 14;
1500 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1505 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1506 return HitAbsorbingWalls(element, hit_mask);
1510 AddLaserEdge(LX - XS, LY - YS);
1511 AddDamagedField(ELX, ELY);
1514 Box[ELX][ELY] = laser.num_edges;
1516 return HitReflectingWalls(element, hit_mask);
1519 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1521 int xs = XS / 2, ys = YS / 2;
1522 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1523 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1525 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1526 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1528 laser.overloaded = (element == EL_GATE_STONE);
1532 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1533 (hit_mask == HIT_MASK_TOP ||
1534 hit_mask == HIT_MASK_LEFT ||
1535 hit_mask == HIT_MASK_RIGHT ||
1536 hit_mask == HIT_MASK_BOTTOM))
1537 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1538 hit_mask == HIT_MASK_BOTTOM),
1539 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1540 hit_mask == HIT_MASK_RIGHT));
1541 AddLaserEdge(LX, LY);
1546 if (element == EL_GATE_STONE && Box[ELX][ELY])
1548 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1560 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1562 int xs = XS / 2, ys = YS / 2;
1563 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1564 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1566 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1567 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1569 laser.overloaded = (element == EL_BLOCK_STONE);
1574 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1575 (hit_mask == HIT_MASK_TOP ||
1576 hit_mask == HIT_MASK_LEFT ||
1577 hit_mask == HIT_MASK_RIGHT ||
1578 hit_mask == HIT_MASK_BOTTOM))
1579 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1580 hit_mask == HIT_MASK_BOTTOM),
1581 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1582 hit_mask == HIT_MASK_RIGHT));
1583 AddDamagedField(ELX, ELY);
1585 LX = ELX * TILEX + 14;
1586 LY = ELY * TILEY + 14;
1587 AddLaserEdge(LX, LY);
1589 laser.stops_inside_element = TRUE;
1597 boolean HitLaserSource(int element, int hit_mask)
1599 if (HitOnlyAnEdge(element, hit_mask))
1602 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1603 laser.overloaded = TRUE;
1608 boolean HitLaserDestination(int element, int hit_mask)
1610 if (HitOnlyAnEdge(element, hit_mask))
1613 if (element != EL_EXIT_OPEN &&
1614 !(IS_RECEIVER(element) &&
1615 game_mm.kettles_still_needed == 0 &&
1616 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1618 PlaySoundStereo(SND_HOLZ, ST(ELX));
1622 if (IS_RECEIVER(element) ||
1623 (IS_22_5_ANGLE(laser.current_angle) &&
1624 (ELX != (LX + 6 * XS) / TILEX ||
1625 ELY != (LY + 6 * YS) / TILEY ||
1634 LX = ELX * TILEX + 14;
1635 LY = ELY * TILEY + 14;
1637 laser.stops_inside_element = TRUE;
1640 AddLaserEdge(LX, LY);
1641 AddDamagedField(ELX, ELY);
1643 if (game_mm.lights_still_needed == 0)
1644 game_mm.level_solved = TRUE;
1649 boolean HitReflectingWalls(int element, int hit_mask)
1651 /* check if laser hits side of a wall with an angle that is not 90° */
1652 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1653 hit_mask == HIT_MASK_LEFT ||
1654 hit_mask == HIT_MASK_RIGHT ||
1655 hit_mask == HIT_MASK_BOTTOM))
1657 PlaySoundStereo(SND_HUI, ST(ELX));
1660 if (!IS_DF_GRID(element))
1661 AddLaserEdge(LX, LY);
1663 /* check if laser hits wall with an angle of 45° */
1664 if (!IS_22_5_ANGLE(laser.current_angle))
1666 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1669 laser.current_angle = get_mirrored_angle(laser.current_angle,
1672 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1675 laser.current_angle = get_mirrored_angle(laser.current_angle,
1679 AddLaserEdge(LX, LY);
1680 XS = 2 * Step[laser.current_angle].x;
1681 YS = 2 * Step[laser.current_angle].y;
1685 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1687 laser.current_angle = get_mirrored_angle(laser.current_angle,
1692 if (!IS_DF_GRID(element))
1693 AddLaserEdge(LX, LY);
1698 if (!IS_DF_GRID(element))
1699 AddLaserEdge(LX, LY + YS / 2);
1702 if (!IS_DF_GRID(element))
1703 AddLaserEdge(LX, LY);
1706 YS = 2 * Step[laser.current_angle].y;
1710 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1712 laser.current_angle = get_mirrored_angle(laser.current_angle,
1717 if (!IS_DF_GRID(element))
1718 AddLaserEdge(LX, LY);
1723 if (!IS_DF_GRID(element))
1724 AddLaserEdge(LX + XS / 2, LY);
1727 if (!IS_DF_GRID(element))
1728 AddLaserEdge(LX, LY);
1731 XS = 2 * Step[laser.current_angle].x;
1737 /* reflection at the edge of reflecting DF style wall */
1738 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1740 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1741 hit_mask == HIT_MASK_TOPRIGHT) ||
1742 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1743 hit_mask == HIT_MASK_TOPLEFT) ||
1744 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1745 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1746 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1747 hit_mask == HIT_MASK_BOTTOMRIGHT))
1750 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1751 ANG_MIRROR_135 : ANG_MIRROR_45);
1753 PlaySoundStereo(SND_HUI, ST(ELX));
1754 AddDamagedField(ELX, ELY);
1755 AddLaserEdge(LX, LY);
1757 laser.current_angle = get_mirrored_angle(laser.current_angle,
1764 AddLaserEdge(LX, LY);
1770 /* reflection inside an edge of reflecting DF style wall */
1771 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1773 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1774 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1775 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1776 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1777 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1778 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1779 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1780 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1783 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1784 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1785 ANG_MIRROR_135 : ANG_MIRROR_45);
1787 PlaySoundStereo(SND_HUI, ST(ELX));
1789 AddDamagedField(ELX, ELY);
1791 AddLaserEdge(LX - XS, LY - YS);
1792 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1793 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1795 laser.current_angle = get_mirrored_angle(laser.current_angle,
1802 AddLaserEdge(LX, LY);
1808 /* check if laser hits DF style wall with an angle of 90° */
1809 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1811 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1812 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1813 (IS_VERT_ANGLE(laser.current_angle) &&
1814 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1816 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1818 /* laser at last step touched nothing or the same side of the wall */
1819 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1821 AddDamagedField(ELX, ELY);
1827 last_hit_mask = hit_mask;
1834 if (!HitOnlyAnEdge(element, hit_mask))
1836 laser.overloaded = TRUE;
1843 boolean HitAbsorbingWalls(int element, int hit_mask)
1845 if (HitOnlyAnEdge(element, hit_mask))
1849 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1851 AddLaserEdge(LX - XS, LY - YS);
1857 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1859 AddLaserEdge(LX - XS, LY - YS);
1864 if (IS_WALL_WOOD(element) ||
1865 IS_DF_WALL_WOOD(element) ||
1866 IS_GRID_WOOD(element) ||
1867 IS_GRID_WOOD_FIXED(element) ||
1868 IS_GRID_WOOD_AUTO(element) ||
1869 element == EL_FUSE_ON ||
1870 element == EL_BLOCK_WOOD ||
1871 element == EL_GATE_WOOD)
1873 PlaySoundStereo(SND_HOLZ, ST(ELX));
1877 if (IS_WALL_ICE(element))
1881 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1882 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1884 /* check if laser hits wall with an angle of 90° */
1885 if (IS_90_ANGLE(laser.current_angle))
1886 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1888 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1894 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1895 mask = 15 - (8 >> i);
1896 else if (ABS(XS) == 4 &&
1898 (XS > 0) == (i % 2) &&
1899 (YS < 0) == (i / 2))
1900 mask = 3 + (i / 2) * 9;
1901 else if (ABS(YS) == 4 &&
1903 (XS < 0) == (i % 2) &&
1904 (YS > 0) == (i / 2))
1905 mask = 5 + (i % 2) * 5;
1909 laser.wall_mask = mask;
1911 else if (IS_WALL_AMOEBA(element))
1913 int elx = (LX - 2 * XS) / TILEX;
1914 int ely = (LY - 2 * YS) / TILEY;
1915 int element2 = Feld[elx][ely];
1918 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1920 laser.dest_element = EL_EMPTY;
1927 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1928 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1930 if (IS_90_ANGLE(laser.current_angle))
1931 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1933 laser.dest_element = element2 | EL_WALL_AMOEBA;
1935 laser.wall_mask = mask;
1941 void OpenExit(int x, int y)
1945 if (!MovDelay[x][y]) /* next animation frame */
1946 MovDelay[x][y] = 4 * delay;
1948 if (MovDelay[x][y]) /* wait some time before next frame */
1953 phase = MovDelay[x][y] / delay;
1954 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1955 DrawGraphic_MM(x, y, EL_EXIT_OPEN - phase);
1957 if (!MovDelay[x][y])
1959 Feld[x][y] = EL_EXIT_OPEN;
1965 void OpenSurpriseBall(int x, int y)
1969 if (!MovDelay[x][y]) /* next animation frame */
1970 MovDelay[x][y] = 50 * delay;
1972 if (MovDelay[x][y]) /* wait some time before next frame */
1975 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1978 int graphic = el2gfx(Store[x][y]);
1980 int dx = RND(26), dy = RND(26);
1982 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
1983 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
1984 SX + x * TILEX + dx, SY + y * TILEY + dy);
1985 MarkTileDirty(x, y);
1988 if (!MovDelay[x][y])
1990 Feld[x][y] = Store[x][y];
1999 void MeltIce(int x, int y)
2004 if (!MovDelay[x][y]) /* next animation frame */
2005 MovDelay[x][y] = frames * delay;
2007 if (MovDelay[x][y]) /* wait some time before next frame */
2010 int wall_mask = Store2[x][y];
2011 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2014 phase = frames - MovDelay[x][y] / delay - 1;
2016 if (!MovDelay[x][y])
2020 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2021 Store[x][y] = Store2[x][y] = 0;
2023 DrawWalls_MM(x, y, Feld[x][y]);
2025 if (Feld[x][y] == EL_WALL_ICE)
2026 Feld[x][y] = EL_EMPTY;
2028 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
2029 if (laser.damage[i].is_mirror)
2033 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2035 DrawLaser(0, DL_LASER_DISABLED);
2039 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2041 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2043 laser.redraw = TRUE;
2048 void GrowAmoeba(int x, int y)
2053 if (!MovDelay[x][y]) /* next animation frame */
2054 MovDelay[x][y] = frames * delay;
2056 if (MovDelay[x][y]) /* wait some time before next frame */
2059 int wall_mask = Store2[x][y];
2060 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2063 phase = MovDelay[x][y] / delay;
2065 if (!MovDelay[x][y])
2067 Feld[x][y] = real_element;
2068 Store[x][y] = Store2[x][y] = 0;
2070 DrawWalls_MM(x, y, Feld[x][y]);
2071 DrawLaser(0, DL_LASER_ENABLED);
2073 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2074 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2078 static void Explode_MM(int x, int y, int phase, int mode)
2080 int num_phase = 9, delay = 2;
2081 int last_phase = num_phase * delay;
2082 int half_phase = (num_phase / 2) * delay;
2084 laser.redraw = TRUE;
2086 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2088 int center_element = Feld[x][y];
2090 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2092 /* put moving element to center field (and let it explode there) */
2093 center_element = MovingOrBlocked2Element_MM(x, y);
2094 RemoveMovingField_MM(x, y);
2095 Feld[x][y] = center_element;
2098 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2099 Store[x][y] = center_element;
2101 Store[x][y] = EL_EMPTY;
2102 Store2[x][y] = mode;
2103 Feld[x][y] = EL_EXPLODING_OPAQUE;
2104 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2110 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2112 if (phase == half_phase)
2114 Feld[x][y] = EL_EXPLODING_TRANSP;
2116 if (x == ELX && y == ELY)
2120 if (phase == last_phase)
2122 if (Store[x][y] == EL_BOMB)
2124 laser.num_damages--;
2125 DrawLaser(0, DL_LASER_DISABLED);
2126 laser.num_edges = 0;
2128 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2129 Store[x][y] = EL_EMPTY;
2131 else if (IS_MCDUFFIN(Store[x][y]))
2133 game_mm.game_over = TRUE;
2134 game_mm.game_over_cause = GAME_OVER_BOMB;
2135 Store[x][y] = EL_EMPTY;
2138 Feld[x][y] = Store[x][y];
2139 Store[x][y] = Store2[x][y] = 0;
2140 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2141 InitField(x, y, FALSE);
2144 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2146 int graphic = IMG_MM_DEFAULT_EXPLODING;
2147 int graphic_phase = (phase / delay - 1);
2151 if (Store2[x][y] == EX_KETTLE)
2153 if (graphic_phase < 3)
2154 graphic = IMG_MM_KETTLE_EXPLODING;
2155 else if (graphic_phase < 5)
2161 graphic = IMG_EMPTY;
2165 else if (Store2[x][y] == EX_SHORT)
2167 if (graphic_phase < 4)
2171 graphic = GFX_EMPTY;
2176 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2178 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2179 FX + x * TILEX, FY + y * TILEY);
2180 MarkTileDirty(x, y);
2184 static void Bang_MM(int x, int y)
2186 int element = Feld[x][y];
2187 int mode = EX_NORMAL;
2190 DrawLaser(0, DL_LASER_ENABLED);
2209 if (IS_PACMAN(element))
2210 PlaySoundStereo(SND_QUIEK, ST(x));
2211 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2212 PlaySoundStereo(SND_ROAAAR, ST(x));
2213 else if (element == EL_KEY)
2214 PlaySoundStereo(SND_KLING, ST(x));
2216 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2218 Explode_MM(x, y, EX_PHASE_START, mode);
2221 void TurnRound(int x, int y)
2233 { 0, 0 }, { 0, 0 }, { 0, 0 },
2238 int left, right, back;
2242 { MV_DOWN, MV_UP, MV_RIGHT },
2243 { MV_UP, MV_DOWN, MV_LEFT },
2245 { MV_LEFT, MV_RIGHT, MV_DOWN },
2246 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2247 { MV_RIGHT, MV_LEFT, MV_UP }
2250 int element = Feld[x][y];
2251 int old_move_dir = MovDir[x][y];
2252 int right_dir = turn[old_move_dir].right;
2253 int back_dir = turn[old_move_dir].back;
2254 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2255 int right_x = x+right_dx, right_y = y+right_dy;
2257 if (element == EL_PACMAN)
2259 boolean can_turn_right = FALSE;
2261 if (IN_LEV_FIELD(right_x, right_y) &&
2262 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2263 can_turn_right = TRUE;
2266 MovDir[x][y] = right_dir;
2268 MovDir[x][y] = back_dir;
2274 static void StartMoving_MM(int x, int y)
2276 int element = Feld[x][y];
2281 if (CAN_MOVE(element))
2285 if (MovDelay[x][y]) /* wait some time before next movement */
2293 /* now make next step */
2295 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2297 if (element == EL_PACMAN &&
2298 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2299 !ObjHit(newx, newy, HIT_POS_CENTER))
2301 Store[newx][newy] = Feld[newx][newy];
2302 Feld[newx][newy] = EL_EMPTY;
2303 DrawField_MM(newx, newy);
2305 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2306 ObjHit(newx, newy, HIT_POS_CENTER))
2308 /* object was running against a wall */
2315 InitMovingField_MM(x, y, MovDir[x][y]);
2319 ContinueMoving_MM(x, y);
2322 static void ContinueMoving_MM(int x, int y)
2324 int element = Feld[x][y];
2325 int direction = MovDir[x][y];
2326 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2327 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2328 int horiz_move = (dx!=0);
2329 int newx = x + dx, newy = y + dy;
2330 int step = (horiz_move ? dx : dy) * TILEX / 8;
2332 MovPos[x][y] += step;
2334 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2336 Feld[x][y] = EL_EMPTY;
2337 Feld[newx][newy] = element;
2339 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2340 MovDelay[newx][newy] = 0;
2342 if (!CAN_MOVE(element))
2343 MovDir[newx][newy] = 0;
2346 DrawField_MM(newx, newy);
2348 Stop[newx][newy] = TRUE;
2350 if (element == EL_PACMAN)
2352 if (Store[newx][newy] == EL_BOMB)
2353 Bang_MM(newx, newy);
2355 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2356 (LX + 2 * XS) / TILEX == newx &&
2357 (LY + 2 * YS) / TILEY == newy)
2364 else /* still moving on */
2367 laser.redraw = TRUE;
2370 void ClickElement(int mx, int my, int button)
2372 static unsigned int click_delay = 0;
2373 static int click_delay_value = CLICK_DELAY_SHORT;
2374 static boolean new_button = TRUE;
2376 int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2378 if (button == MB_RELEASED)
2381 click_delay_value = CLICK_DELAY_SHORT;
2383 /* release eventually hold auto-rotating mirror */
2384 RotateMirror(x, y, MB_RELEASED);
2389 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2392 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2395 if (!IN_PIX_FIELD(mx - SX, my - SY))
2398 if (Feld[x][y] == EL_EMPTY)
2401 element = Feld[x][y];
2403 if (IS_MIRROR(element) ||
2404 IS_BEAMER(element) ||
2405 IS_POLAR(element) ||
2406 IS_POLAR_CROSS(element) ||
2407 IS_DF_MIRROR(element) ||
2408 IS_DF_MIRROR_AUTO(element))
2410 RotateMirror(x, y, button);
2412 else if (IS_MCDUFFIN(element))
2414 if (!laser.fuse_off)
2416 DrawLaser(0, DL_LASER_DISABLED);
2422 element = get_rotated_element(element, BUTTON_ROTATION(button));
2423 laser.start_angle = get_element_angle(element);
2427 Feld[x][y] = element;
2432 if (!laser.fuse_off)
2435 else if (element == EL_FUSE_ON && laser.fuse_off)
2437 if (x != laser.fuse_x || y != laser.fuse_y)
2440 laser.fuse_off = FALSE;
2441 laser.fuse_x = laser.fuse_y = -1;
2443 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2446 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2448 laser.fuse_off = TRUE;
2451 laser.overloaded = FALSE;
2453 DrawLaser(0, DL_LASER_DISABLED);
2454 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2456 else if (element == EL_LIGHTBALL)
2460 DrawLaser(0, DL_LASER_ENABLED);
2463 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2467 void RotateMirror(int x, int y, int button)
2469 static int hold_x = -1, hold_y = -1;
2471 if (button == MB_RELEASED)
2473 /* release eventually hold auto-rotating mirror */
2480 if (IS_MIRROR(Feld[x][y]) ||
2481 IS_POLAR_CROSS(Feld[x][y]) ||
2482 IS_POLAR(Feld[x][y]) ||
2483 IS_BEAMER(Feld[x][y]) ||
2484 IS_DF_MIRROR(Feld[x][y]) ||
2485 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2486 IS_GRID_WOOD_AUTO(Feld[x][y]))
2488 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2490 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2492 if (button == MB_LEFTBUTTON)
2494 /* left mouse button only for manual adjustment, no auto-rotating;
2495 freeze mirror for until mouse button released */
2499 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2500 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2503 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2505 int edge = Hit[x][y];
2511 DrawLaser(edge - 1, DL_LASER_DISABLED);
2515 else if (ObjHit(x, y, HIT_POS_CENTER))
2517 int edge = Hit[x][y];
2521 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2525 DrawLaser(edge - 1, DL_LASER_DISABLED);
2532 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2537 if ((IS_BEAMER(Feld[x][y]) ||
2538 IS_POLAR(Feld[x][y]) ||
2539 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2543 if (IS_BEAMER(Feld[x][y]))
2546 printf("TEST (%d, %d) [%d] [%d]\n",
2548 laser.beamer_edge, laser.beamer[1].num);
2558 DrawLaser(0, DL_LASER_ENABLED);
2562 void AutoRotateMirrors()
2564 static unsigned int rotate_delay = 0;
2567 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2570 for (x=0; x<lev_fieldx; x++)
2572 for (y=0; y<lev_fieldy; y++)
2574 int element = Feld[x][y];
2576 if (IS_DF_MIRROR_AUTO(element) ||
2577 IS_GRID_WOOD_AUTO(element) ||
2578 IS_GRID_STEEL_AUTO(element) ||
2579 element == EL_REFRACTOR)
2580 RotateMirror(x, y, MB_RIGHTBUTTON);
2585 boolean ObjHit(int obx, int oby, int bits)
2592 if (bits & HIT_POS_CENTER)
2594 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2598 if (bits & HIT_POS_EDGE)
2601 if (ReadPixel(drawto,
2602 SX + obx + 31 * (i % 2),
2603 SY + oby + 31 * (i / 2)) == pen_ray)
2607 if (bits & HIT_POS_BETWEEN)
2610 if (ReadPixel(drawto,
2611 SX + 4 + obx + 22 * (i % 2),
2612 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2619 void DeletePacMan(int px, int py)
2625 if (game_mm.num_pacman <= 1)
2627 game_mm.num_pacman = 0;
2631 for(i=0; i<game_mm.num_pacman; i++)
2632 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2635 game_mm.num_pacman--;
2637 for(j=i; j<game_mm.num_pacman; j++)
2639 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2640 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2641 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2645 void ColorCycling(void)
2647 static int CC, Cc = 0;
2649 static int color, old = 0xF00, new = 0x010, mult = 1;
2650 static unsigned short red, green, blue;
2652 if (color_status == STATIC_COLORS)
2657 if (CC < Cc || CC > Cc + 50)
2661 color = old + new * mult;
2667 if (ABS(mult) == 16)
2676 red = 0x0e00 * ((color & 0xF00) >> 8);
2677 green = 0x0e00 * ((color & 0x0F0) >> 4);
2678 blue = 0x0e00 * ((color & 0x00F));
2679 SetRGB(pen_magicolor[0], red, green, blue);
2681 red = 0x1111 * ((color & 0xF00) >> 8);
2682 green = 0x1111 * ((color & 0x0F0) >> 4);
2683 blue = 0x1111 * ((color & 0x00F));
2684 SetRGB(pen_magicolor[1], red, green, blue);
2688 static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
2690 static unsigned int action_delay = 0;
2691 static unsigned int pacman_delay = 0;
2692 static unsigned int energy_delay = 0;
2693 static unsigned int overload_delay = 0;
2699 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2701 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2704 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2706 element = Feld[x][y];
2708 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2709 StartMoving_MM(x, y);
2710 else if (IS_MOVING(x, y))
2711 ContinueMoving_MM(x, y);
2712 else if (IS_EXPLODING(element))
2713 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2714 else if (element == EL_EXIT_OPENING)
2716 else if (element == EL_GRAY_BALL_OPENING)
2717 OpenSurpriseBall(x, y);
2718 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2720 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2724 AutoRotateMirrors();
2727 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2729 /* redraw after Explode_MM() ... */
2731 DrawLaser(0, DL_LASER_ENABLED);
2732 laser.redraw = FALSE;
2737 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2741 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2743 DrawLaser(0, DL_LASER_DISABLED);
2748 if (DelayReached(&energy_delay, 4000))
2750 game_mm.energy_left--;
2751 if (game_mm.energy_left >= 0)
2754 BlitBitmap(pix[PIX_DOOR], drawto,
2755 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2756 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2757 DX_ENERGY, DY_ENERGY);
2759 redraw_mask |= REDRAW_DOOR_1;
2761 else if (setup.time_limit)
2765 for(i=15; i>=0; i--)
2768 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2770 pen_ray = GetPixelFromRGB(window,
2771 native_mm_level.laser_red * 0x11 * i,
2772 native_mm_level.laser_green * 0x11 * i,
2773 native_mm_level.laser_blue * 0x11 * i);
2774 DrawLaser(0, DL_LASER_ENABLED);
2779 StopSound(SND_WARNTON);
2782 DrawLaser(0, DL_LASER_DISABLED);
2783 game_mm.game_over = TRUE;
2784 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2787 if (Request("Out of magic energy ! Play it again ?",
2788 REQ_ASK | REQ_STAY_CLOSED))
2794 game_status = MAINMENU;
2803 element = laser.dest_element;
2806 if (element != Feld[ELX][ELY])
2808 printf("element == %d, Feld[ELX][ELY] == %d\n",
2809 element, Feld[ELX][ELY]);
2813 if (!laser.overloaded && laser.overload_value == 0 &&
2814 element != EL_BOMB &&
2815 element != EL_MINE &&
2816 element != EL_BALL_GRAY &&
2817 element != EL_BLOCK_STONE &&
2818 element != EL_BLOCK_WOOD &&
2819 element != EL_FUSE_ON &&
2820 element != EL_FUEL_FULL &&
2821 !IS_WALL_ICE(element) &&
2822 !IS_WALL_AMOEBA(element))
2825 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2826 (!laser.overloaded && laser.overload_value > 0)) &&
2827 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2829 if (laser.overloaded)
2830 laser.overload_value++;
2832 laser.overload_value--;
2834 if (game_mm.cheat_no_overload)
2836 laser.overloaded = FALSE;
2837 laser.overload_value = 0;
2840 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2842 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2843 int color_down = 0xFF - color_up;
2846 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2847 (15 - (laser.overload_value / 6)) * color_scale);
2849 pen_ray = GetPixelFromRGB(window,
2850 (native_mm_level.laser_red ? 0xFF : color_up),
2851 (native_mm_level.laser_green ? color_down : 0x00),
2852 (native_mm_level.laser_blue ? color_down : 0x00));
2853 DrawLaser(0, DL_LASER_ENABLED);
2857 if (laser.overloaded)
2859 if (setup.sound_loops)
2860 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
2862 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2865 if (!laser.overloaded)
2866 StopSound(SND_WARNTON);
2868 if (laser.overloaded)
2871 BlitBitmap(pix[PIX_DOOR], drawto,
2872 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2873 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2874 - laser.overload_value,
2875 OVERLOAD_XSIZE, laser.overload_value,
2876 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2877 - laser.overload_value);
2879 redraw_mask |= REDRAW_DOOR_1;
2884 BlitBitmap(pix[PIX_DOOR], drawto,
2885 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2886 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2887 DX_OVERLOAD, DY_OVERLOAD);
2889 redraw_mask |= REDRAW_DOOR_1;
2892 if (laser.overload_value == MAX_LASER_OVERLOAD)
2896 for(i=15; i>=0; i--)
2899 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2902 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2903 DrawLaser(0, DL_LASER_ENABLED);
2908 DrawLaser(0, DL_LASER_DISABLED);
2909 game_mm.game_over = TRUE;
2910 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2913 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2914 REQ_ASK | REQ_STAY_CLOSED))
2920 game_status = MAINMENU;
2934 if (element == EL_BOMB && CT > 1500)
2936 if (game_mm.cheat_no_explosion)
2940 laser.num_damages--;
2941 DrawLaser(0, DL_LASER_DISABLED);
2942 laser.num_edges = 0;
2947 laser.dest_element = EL_EXPLODING_OPAQUE;
2951 laser.num_damages--;
2952 DrawLaser(0, DL_LASER_DISABLED);
2954 laser.num_edges = 0;
2955 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2957 if (Request("Bomb killed Mc Duffin ! Play it again ?",
2958 REQ_ASK | REQ_STAY_CLOSED))
2964 game_status = MAINMENU;
2972 if (element == EL_FUSE_ON && CT > 500)
2974 laser.fuse_off = TRUE;
2977 DrawLaser(0, DL_LASER_DISABLED);
2978 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
2981 if (element == EL_BALL_GRAY && CT > 1500)
2983 static int new_elements[] =
2986 EL_MIRROR_FIXED_START,
2988 EL_POLAR_CROSS_START,
2994 int num_new_elements = sizeof(new_elements) / sizeof(int);
2995 int new_element = new_elements[RND(num_new_elements)];
2997 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
2998 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3000 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3011 element = EL_MIRROR_START + RND(16);
3017 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3024 element = (rnd == 0 ? EL_FUSE_ON :
3025 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3026 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3027 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3028 EL_MIRROR_FIXED_START + rnd - 25);
3033 graphic = el2gfx(element);
3041 BlitBitmap(pix[PIX_BACK], drawto,
3042 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3043 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3044 SX + ELX * TILEX + x,
3045 SY + ELY * TILEY + y);
3047 MarkTileDirty(ELX, ELY);
3050 DrawLaser(0, DL_LASER_ENABLED);
3055 Feld[ELX][ELY] = element;
3056 DrawField_MM(ELX, ELY);
3059 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3062 /* above stuff: GRAY BALL -> PRISM !!! */
3064 LX = ELX * TILEX + 14;
3065 LY = ELY * TILEY + 14;
3066 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3073 laser.num_edges -= 2;
3074 laser.num_damages--;
3078 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3079 if (laser.damage[i].is_mirror)
3083 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3085 DrawLaser(0, DL_LASER_DISABLED);
3087 DrawLaser(0, DL_LASER_DISABLED);
3093 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3100 if (IS_WALL_ICE(element) && CT > 1000)
3102 PlaySoundStereo(SND_SLURP, ST(ELX));
3107 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3108 Store[ELX][ELY] = EL_WALL_ICE;
3109 Store2[ELX][ELY] = laser.wall_mask;
3111 laser.dest_element = Feld[ELX][ELY];
3125 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3129 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3134 if (Feld[ELX][ELY] == EL_WALL_ICE)
3135 Feld[ELX][ELY] = EL_EMPTY;
3139 LX = laser.edge[laser.num_edges].x - (SX + 2);
3140 LY = laser.edge[laser.num_edges].y - (SY + 2);
3143 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3144 if (laser.damage[i].is_mirror)
3148 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3150 DrawLaser(0, DL_LASER_DISABLED);
3157 if (IS_WALL_AMOEBA(element) && CT > 1200)
3159 int k1, k2, k3, dx, dy, de, dm;
3160 int element2 = Feld[ELX][ELY];
3162 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3165 for (i = laser.num_damages - 1; i>=0; i--)
3166 if (laser.damage[i].is_mirror)
3169 r = laser.num_edges;
3170 d = laser.num_damages;
3177 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3180 DrawLaser(0, DL_LASER_ENABLED);
3183 x = laser.damage[k1].x;
3184 y = laser.damage[k1].y;
3190 if (laser.wall_mask & (1 << i))
3192 if (ReadPixel(drawto,
3193 SX + ELX * TILEX + 14 + (i % 2) * 2,
3194 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3196 if (ReadPixel(drawto,
3197 SX + ELX * TILEX + 31 * (i % 2),
3198 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3207 if (laser.wall_mask & (1 << i))
3209 if (ReadPixel(drawto,
3210 SX + ELX * TILEX + 31 * (i % 2),
3211 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3218 if (laser.num_beamers > 0 ||
3219 k1 < 1 || k2 < 4 || k3 < 4 ||
3220 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3223 laser.num_edges = r;
3224 laser.num_damages = d;
3225 DrawLaser(0, DL_LASER_DISABLED);
3228 Feld[ELX][ELY] = element | laser.wall_mask;
3231 de = Feld[ELX][ELY];
3232 dm = laser.wall_mask;
3238 int x = ELX, y = ELY;
3239 int wall_mask = laser.wall_mask;
3243 DrawLaser(0, DL_LASER_ENABLED);
3245 PlaySoundStereo(SND_AMOEBE, ST(dx));
3249 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3250 Store[x][y] = EL_WALL_AMOEBA;
3251 Store2[x][y] = wall_mask;
3259 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3261 DrawLaser(0, DL_LASER_ENABLED);
3263 PlaySoundStereo(SND_AMOEBE, ST(dx));
3267 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3272 DrawLaser(0, DL_LASER_ENABLED);
3277 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3278 laser.stops_inside_element && CT > 1500)
3283 if (ABS(XS) > ABS(YS))
3297 x = ELX + Step[k * 4].x;
3298 y = ELY + Step[k * 4].y;
3300 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3303 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3311 laser.overloaded = (element == EL_BLOCK_STONE);
3315 PlaySoundStereo(SND_BONG, ST(ELX));
3318 Feld[x][y] = element;
3320 DrawGraphic_MM(ELX, ELY, -1);
3323 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3325 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3326 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3334 if (element == EL_FUEL_FULL && CT > 200)
3336 for(i=game_mm.energy_left; i<=MAX_LASER_ENERGY; i+=2)
3339 BlitBitmap(pix[PIX_DOOR], drawto,
3340 DOOR_GFX_PAGEX4 + XX_ENERGY,
3341 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3342 ENERGY_XSIZE, i, DX_ENERGY,
3343 DY_ENERGY + ENERGY_YSIZE - i);
3346 redraw_mask |= REDRAW_DOOR_1;
3352 game_mm.energy_left = MAX_LASER_ENERGY;
3353 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3354 DrawField_MM(ELX, ELY);
3356 DrawLaser(0, DL_LASER_ENABLED);
3364 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
3367 ClickElement(0, 0, MB_NOT_PRESSED);
3369 GameActions_MM_Ext(action, warp_mode);
3375 int mx, my, ox, oy, nx, ny;
3379 if (++p >= game_mm.num_pacman)
3381 game_mm.pacman[p].dir--;
3385 game_mm.pacman[p].dir++;
3387 if (game_mm.pacman[p].dir > 4)
3388 game_mm.pacman[p].dir = 1;
3390 if (game_mm.pacman[p].dir % 2)
3393 my = game_mm.pacman[p].dir - 2;
3398 mx = 3 - game_mm.pacman[p].dir;
3401 ox = game_mm.pacman[p].x;
3402 oy = game_mm.pacman[p].y;
3405 element = Feld[nx][ny];
3406 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3409 if (!IS_EATABLE4PACMAN(element))
3412 if (ObjHit(nx, ny, HIT_POS_CENTER))
3415 Feld[ox][oy] = EL_EMPTY;
3417 EL_PACMAN_RIGHT - 1 +
3418 (game_mm.pacman[p].dir - 1 +
3419 (game_mm.pacman[p].dir % 2) * 2);
3421 game_mm.pacman[p].x = nx;
3422 game_mm.pacman[p].y = ny;
3423 g = Feld[nx][ny] - EL_PACMAN_RIGHT;
3424 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3426 if (element != EL_EMPTY)
3431 ox = SX + ox * TILEX;
3432 oy = SY + oy * TILEY;
3434 for(i=1; i<33; i+=2)
3437 // !!! temporary fix to compile -- change to game graphics !!!
3438 BlitBitmap(drawto, window,
3439 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3440 ox + i * mx, oy + i * my);
3442 BlitBitmap(pix[PIX_BACK], window,
3443 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3444 ox + i * mx, oy + i * my);
3447 Ct = Ct + Counter() - CT;
3449 DrawField_MM(nx, ny);
3452 if (!laser.fuse_off)
3454 DrawLaser(0, DL_LASER_ENABLED);
3456 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3458 AddDamagedField(nx, ny);
3459 laser.damage[laser.num_damages - 1].edge = 0;
3463 if (element == EL_BOMB)
3465 DeletePacMan(nx, ny);
3468 if (IS_WALL_AMOEBA(element) &&
3469 (LX + 2 * XS) / TILEX == nx &&
3470 (LY + 2 * YS) / TILEY == ny)
3482 boolean raise_level = FALSE;
3485 if (local_player->MovPos)
3488 local_player->LevelSolved = FALSE;
3491 if (game_mm.energy_left)
3493 if (setup.sound_loops)
3494 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3496 while(game_mm.energy_left > 0)
3498 if (!setup.sound_loops)
3499 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3502 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3503 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3508 game_mm.energy_left--;
3509 if (game_mm.energy_left >= 0)
3512 BlitBitmap(pix[PIX_DOOR], drawto,
3513 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3514 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3515 DX_ENERGY, DY_ENERGY);
3517 redraw_mask |= REDRAW_DOOR_1;
3524 if (setup.sound_loops)
3525 StopSound(SND_SIRR);
3527 else if (native_mm_level.time == 0) /* level without time limit */
3529 if (setup.sound_loops)
3530 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3532 while(TimePlayed < 999)
3534 if (!setup.sound_loops)
3535 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3536 if (TimePlayed < 999 && !(TimePlayed % 10))
3537 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3538 if (TimePlayed < 900 && !(TimePlayed % 10))
3544 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3551 if (setup.sound_loops)
3552 StopSound(SND_SIRR);
3559 CloseDoor(DOOR_CLOSE_1);
3561 Request("Level solved !", REQ_CONFIRM);
3563 if (level_nr == leveldir_current->handicap_level)
3565 leveldir_current->handicap_level++;
3566 SaveLevelSetup_SeriesInfo();
3569 if (level_editor_test_game)
3570 game_mm.score = -1; /* no highscore when playing from editor */
3571 else if (level_nr < leveldir_current->last_level)
3572 raise_level = TRUE; /* advance to next level */
3574 if ((hi_pos = NewHiScore_MM()) >= 0)
3576 game_status = HALLOFFAME;
3577 // DrawHallOfFame(hi_pos);
3583 game_status = MAINMENU;
3597 // LoadScore(level_nr);
3599 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3600 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3603 for (k=0; k<MAX_SCORE_ENTRIES; k++)
3605 if (game_mm.score > highscore[k].Score)
3607 /* player has made it to the hall of fame */
3609 if (k < MAX_SCORE_ENTRIES - 1)
3611 int m = MAX_SCORE_ENTRIES - 1;
3614 for (l=k; l<MAX_SCORE_ENTRIES; l++)
3615 if (!strcmp(setup.player_name, highscore[l].Name))
3617 if (m == k) /* player's new highscore overwrites his old one */
3623 strcpy(highscore[l].Name, highscore[l - 1].Name);
3624 highscore[l].Score = highscore[l - 1].Score;
3631 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3632 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3633 highscore[k].Score = game_mm.score;
3639 else if (!strncmp(setup.player_name, highscore[k].Name,
3640 MAX_PLAYER_NAME_LEN))
3641 break; /* player already there with a higher score */
3646 // if (position >= 0)
3647 // SaveScore(level_nr);
3652 static void InitMovingField_MM(int x, int y, int direction)
3654 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3655 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3657 MovDir[x][y] = direction;
3658 MovDir[newx][newy] = direction;
3659 if (Feld[newx][newy] == EL_EMPTY)
3660 Feld[newx][newy] = EL_BLOCKED;
3663 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3665 int direction = MovDir[x][y];
3666 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3667 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3673 static void Blocked2Moving_MM(int x, int y,
3674 int *comes_from_x, int *comes_from_y)
3676 int oldx = x, oldy = y;
3677 int direction = MovDir[x][y];
3679 if (direction == MV_LEFT)
3681 else if (direction == MV_RIGHT)
3683 else if (direction == MV_UP)
3685 else if (direction == MV_DOWN)
3688 *comes_from_x = oldx;
3689 *comes_from_y = oldy;
3692 static int MovingOrBlocked2Element_MM(int x, int y)
3694 int element = Feld[x][y];
3696 if (element == EL_BLOCKED)
3700 Blocked2Moving_MM(x, y, &oldx, &oldy);
3701 return Feld[oldx][oldy];
3708 static void RemoveField(int x, int y)
3710 Feld[x][y] = EL_EMPTY;
3717 static void RemoveMovingField_MM(int x, int y)
3719 int oldx = x, oldy = y, newx = x, newy = y;
3721 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3724 if (IS_MOVING(x, y))
3726 Moving2Blocked_MM(x, y, &newx, &newy);
3727 if (Feld[newx][newy] != EL_BLOCKED)
3730 else if (Feld[x][y] == EL_BLOCKED)
3732 Blocked2Moving_MM(x, y, &oldx, &oldy);
3733 if (!IS_MOVING(oldx, oldy))
3737 Feld[oldx][oldy] = EL_EMPTY;
3738 Feld[newx][newy] = EL_EMPTY;
3739 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3740 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3742 DrawLevelField_MM(oldx, oldy);
3743 DrawLevelField_MM(newx, newy);
3746 void PlaySoundLevel(int x, int y, int sound_nr)
3748 int sx = SCREENX(x), sy = SCREENY(y);
3750 int silence_distance = 8;
3752 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3753 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3756 if (!IN_LEV_FIELD(x, y) ||
3757 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3758 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3761 volume = SOUND_MAX_VOLUME;
3764 stereo = (sx - SCR_FIELDX/2) * 12;
3766 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3767 if (stereo > SOUND_MAX_RIGHT)
3768 stereo = SOUND_MAX_RIGHT;
3769 if (stereo < SOUND_MAX_LEFT)
3770 stereo = SOUND_MAX_LEFT;
3773 if (!IN_SCR_FIELD(sx, sy))
3775 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3776 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3778 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3781 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3784 static void RaiseScore_MM(int value)
3786 game_mm.score += value;
3788 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3793 void RaiseScoreElement_MM(int element)
3798 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3801 RaiseScore_MM(native_mm_level.score[SC_KEY]);