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);
356 /* copy default game door content to main double buffer */
357 BlitBitmap(pix[PIX_DOOR], drawto,
358 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
362 DrawText(DX_LEVEL, DY_LEVEL,
363 int2str(level_nr, 2), FONT_TEXT_2);
364 DrawText(DX_KETTLES, DY_KETTLES,
365 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
366 DrawText(DX_SCORE, DY_SCORE,
367 int2str(game_mm.score, 4), FONT_TEXT_2);
376 /* copy actual game door content to door double buffer for OpenDoor() */
377 BlitBitmap(drawto, pix[PIX_DB_DOOR],
378 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
382 OpenDoor(DOOR_OPEN_ALL);
385 if (setup.sound_loops)
386 PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
388 #if 0 // !!! TEMPORARILY DISABLED !!!
389 for(i=0; i<=game_mm.energy_left; i+=2)
391 if (!setup.sound_loops)
392 PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
395 BlitBitmap(pix[PIX_DOOR], drawto,
396 DOOR_GFX_PAGEX4 + XX_ENERGY,
397 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
399 DX_ENERGY, DY_ENERGY + ENERGY_YSIZE - i);
402 redraw_mask |= REDRAW_DOOR_1;
408 if (setup.quick_doors)
415 if (setup.sound_loops)
420 if (setup.sound_music && num_bg_loops)
421 PlayMusic(level_nr % num_bg_loops);
427 void AddLaserEdge(int lx, int ly)
429 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
431 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
435 laser.edge[laser.num_edges].x = SX + 2 + lx;
436 laser.edge[laser.num_edges].y = SY + 2 + ly;
442 void AddDamagedField(int ex, int ey)
444 laser.damage[laser.num_damages].is_mirror = FALSE;
445 laser.damage[laser.num_damages].angle = laser.current_angle;
446 laser.damage[laser.num_damages].edge = laser.num_edges;
447 laser.damage[laser.num_damages].x = ex;
448 laser.damage[laser.num_damages].y = ey;
458 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
459 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
461 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
467 static int getMaskFromElement(int element)
469 if (IS_GRID(element))
470 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
471 else if (IS_MCDUFFIN(element))
472 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
473 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
474 return IMG_MM_MASK_RECTANGLE;
476 return IMG_MM_MASK_CIRCLE;
484 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
485 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
488 /* follow laser beam until it hits something (at least the screen border) */
489 while (hit_mask == HIT_MASK_NO_HIT)
495 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
496 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
498 printf("ScanPixel: touched screen border!\n");
509 px = SX + LX + (i % 2) * 2;
510 py = SY + LY + (i / 2) * 2;
511 lx = (px - SX + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
512 ly = (py - SY + TILEY) / TILEY - 1; /* negative values! */
514 if (IN_LEV_FIELD(lx, ly))
516 int element = Feld[lx][ly];
518 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
520 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
523 ((py - SY - ly * TILEY) / MINI_TILEX) * 2 +
524 (px - SX - lx * TILEX) / MINI_TILEY;
526 pixel = ((element & (1 << pos)) ? 1 : 0);
530 int graphic_mask = getMaskFromElement(element);
532 int dx = px - lx * TILEX;
533 int dy = py - ly * TILEY;
537 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
542 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
547 if (px < REAL_SX || px >= REAL_SX + FULL_SXSIZE ||
548 py < REAL_SY || py >= REAL_SY + FULL_SYSIZE)
554 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
555 hit_mask |= (1 << i);
558 if (hit_mask == HIT_MASK_NO_HIT)
560 /* hit nothing -- go on with another step */
572 int end = 0, rf = laser.num_edges;
574 laser.overloaded = FALSE;
575 laser.stops_inside_element = FALSE;
577 DrawLaser(0, DL_LASER_ENABLED);
580 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
588 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
591 laser.overloaded = TRUE;
595 hit_mask = ScanPixel();
598 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
602 /* hit something -- check out what it was */
603 ELX = (LX + XS) / TILEX;
604 ELY = (LY + YS) / TILEY;
607 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
608 hit_mask, LX, LY, ELX, ELY);
611 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
614 laser.dest_element = element;
619 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
621 /* we have hit the top-right and bottom-left element --
622 choose the bottom-left one */
623 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
624 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
625 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
626 ELX = (LX - 2) / TILEX;
627 ELY = (LY + 2) / TILEY;
630 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
632 /* we have hit the top-left and bottom-right element --
633 choose the top-left one */
634 /* !!! SEE ABOVE !!! */
635 ELX = (LX - 2) / TILEX;
636 ELY = (LY - 2) / TILEY;
640 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
641 hit_mask, LX, LY, ELX, ELY);
644 element = Feld[ELX][ELY];
645 laser.dest_element = element;
648 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
651 LX % TILEX, LY % TILEY,
656 if (!IN_LEV_FIELD(ELX, ELY))
657 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
660 if (element == EL_EMPTY)
662 if (!HitOnlyAnEdge(element, hit_mask))
665 else if (element == EL_FUSE_ON)
667 if (HitPolarizer(element, hit_mask))
670 else if (IS_GRID(element) || IS_DF_GRID(element))
672 if (HitPolarizer(element, hit_mask))
675 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
676 element == EL_GATE_STONE || element == EL_GATE_WOOD)
678 if (HitBlock(element, hit_mask))
684 else if (IS_MCDUFFIN(element))
686 if (HitLaserSource(element, hit_mask))
689 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
690 IS_RECEIVER(element))
692 if (HitLaserDestination(element, hit_mask))
695 else if (IS_WALL(element))
697 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
699 if (HitReflectingWalls(element, hit_mask))
704 if (HitAbsorbingWalls(element, hit_mask))
710 if (HitElement(element, hit_mask))
715 DrawLaser(rf - 1, DL_LASER_ENABLED);
716 rf = laser.num_edges;
720 if (laser.dest_element != Feld[ELX][ELY])
722 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
723 laser.dest_element, Feld[ELX][ELY]);
727 if (!end && !laser.stops_inside_element && !StepBehind())
730 printf("ScanLaser: Go one step back\n");
735 AddLaserEdge(LX, LY);
739 DrawLaser(rf - 1, DL_LASER_ENABLED);
744 if (!IN_LEV_FIELD(ELX, ELY))
745 printf("WARNING! (2) %d, %d\n", ELX, ELY);
749 void DrawLaserExt(int start_edge, int num_edges, int mode)
755 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
756 start_edge, num_edges, mode);
761 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
767 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
772 if (mode == DL_LASER_DISABLED)
774 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
778 /* now draw the laser to the backbuffer and (if enabled) to the screen */
779 DrawLines(drawto, &laser.edge[start_edge], num_edges,
780 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
782 redraw_mask |= REDRAW_FIELD;
784 if (mode == DL_LASER_ENABLED)
787 /* after the laser was deleted, the "damaged" graphics must be restored */
788 if (laser.num_damages)
790 int damage_start = 0;
793 /* determine the starting edge, from which graphics need to be restored */
796 for(i=0; i<laser.num_damages; i++)
798 if (laser.damage[i].edge == start_edge + 1)
806 /* restore graphics from this starting edge to the end of damage list */
807 for(i=damage_start; i<laser.num_damages; i++)
809 int lx = laser.damage[i].x;
810 int ly = laser.damage[i].y;
811 int element = Feld[lx][ly];
813 if (Hit[lx][ly] == laser.damage[i].edge)
814 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
817 if (Box[lx][ly] == laser.damage[i].edge)
820 if (IS_DRAWABLE(element))
821 DrawField_MM(lx, ly);
824 elx = laser.damage[damage_start].x;
825 ely = laser.damage[damage_start].y;
826 element = Feld[elx][ely];
829 if (IS_BEAMER(element))
833 for (i=0; i<laser.num_beamers; i++)
834 printf("-> %d\n", laser.beamer_edge[i]);
835 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
836 mode, elx, ely, Hit[elx][ely], start_edge);
837 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
838 get_element_angle(element), laser.damage[damage_start].angle);
842 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
843 laser.num_beamers > 0 &&
844 start_edge == laser.beamer_edge[laser.num_beamers - 1])
846 /* element is outgoing beamer */
847 laser.num_damages = damage_start + 1;
848 if (IS_BEAMER(element))
849 laser.current_angle = get_element_angle(element);
853 /* element is incoming beamer or other element */
854 laser.num_damages = damage_start;
855 laser.current_angle = laser.damage[laser.num_damages].angle;
860 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
862 elx = laser.start_edge.x;
863 ely = laser.start_edge.y;
864 element = Feld[elx][ely];
867 laser.num_edges = start_edge + 1;
869 laser.current_angle = laser.start_angle;
870 LX = laser.edge[start_edge].x - (SX + 2);
871 LY = laser.edge[start_edge].y - (SY + 2);
872 XS = 2 * Step[laser.current_angle].x;
873 YS = 2 * Step[laser.current_angle].y;
876 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
882 if (IS_BEAMER(element) ||
883 IS_FIBRE_OPTIC(element) ||
884 IS_PACMAN(element) ||
886 IS_POLAR_CROSS(element) ||
887 element == EL_FUSE_ON)
892 printf("element == %d\n", element);
895 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
896 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
900 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
901 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
902 (laser.num_beamers == 0 ||
903 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
905 /* element is incoming beamer or other element */
906 step_size = -step_size;
911 if (IS_BEAMER(element))
913 printf("start_edge == %d, laser.beamer_edge == %d\n",
914 start_edge, laser.beamer_edge);
918 LX += step_size * XS;
919 LY += step_size * YS;
921 else if (element != EL_EMPTY)
930 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
935 void DrawLaser(int start_edge, int mode)
937 if (laser.num_edges - start_edge < 0)
939 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
943 /* check if laser is interrupted by beamer element */
944 if (laser.num_beamers > 0 &&
945 start_edge < laser.beamer_edge[laser.num_beamers - 1])
947 if (mode == DL_LASER_ENABLED)
950 int tmp_start_edge = start_edge;
952 /* draw laser segments forward from the start to the last beamer */
953 for (i=0; i<laser.num_beamers; i++)
955 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
957 if (tmp_num_edges <= 0)
961 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
962 i, laser.beamer_edge[i], tmp_start_edge);
965 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
966 tmp_start_edge = laser.beamer_edge[i];
969 /* draw last segment from last beamer to the end */
970 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
976 int last_num_edges = laser.num_edges;
977 int num_beamers = laser.num_beamers;
979 /* delete laser segments backward from the end to the first beamer */
980 for (i=num_beamers-1; i>=0; i--)
982 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
984 if (laser.beamer_edge[i] - start_edge <= 0)
987 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
988 last_num_edges = laser.beamer_edge[i];
993 if (last_num_edges - start_edge <= 0)
994 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
995 last_num_edges, start_edge);
998 /* delete first segment from start to the first beamer */
999 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1003 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1006 boolean HitElement(int element, int hit_mask)
1008 if (HitOnlyAnEdge(element, hit_mask))
1011 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1012 element = MovingOrBlocked2Element_MM(ELX, ELY);
1015 printf("HitElement (1): element == %d\n", element);
1019 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1020 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1022 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1025 AddDamagedField(ELX, ELY);
1027 /* this is more precise: check if laser would go through the center */
1028 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1030 /* skip the whole element before continuing the scan */
1036 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1038 if (LX/TILEX > ELX || LY/TILEY > ELY)
1040 /* skipping scan positions to the right and down skips one scan
1041 position too much, because this is only the top left scan position
1042 of totally four scan positions (plus one to the right, one to the
1043 bottom and one to the bottom right) */
1053 printf("HitElement (2): element == %d\n", element);
1056 if (LX + 5 * XS < 0 ||
1066 printf("HitElement (3): element == %d\n", element);
1069 if (IS_POLAR(element) &&
1070 ((element - EL_POLAR_START) % 2 ||
1071 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1073 PlaySoundStereo(SND_KINK, ST(ELX));
1074 laser.num_damages--;
1079 if (IS_POLAR_CROSS(element) &&
1080 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1082 PlaySoundStereo(SND_KINK, ST(ELX));
1083 laser.num_damages--;
1088 if (!IS_BEAMER(element) &&
1089 !IS_FIBRE_OPTIC(element) &&
1090 !IS_GRID_WOOD(element) &&
1091 element != EL_FUEL_EMPTY)
1094 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1095 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1097 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1100 LX = ELX * TILEX + 14;
1101 LY = ELY * TILEY + 14;
1102 AddLaserEdge(LX, LY);
1105 if (IS_MIRROR(element) ||
1106 IS_MIRROR_FIXED(element) ||
1107 IS_POLAR(element) ||
1108 IS_POLAR_CROSS(element) ||
1109 IS_DF_MIRROR(element) ||
1110 IS_DF_MIRROR_AUTO(element) ||
1111 element == EL_PRISM ||
1112 element == EL_REFRACTOR)
1114 int current_angle = laser.current_angle;
1117 laser.num_damages--;
1118 AddDamagedField(ELX, ELY);
1119 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1122 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1124 if (IS_MIRROR(element) ||
1125 IS_MIRROR_FIXED(element) ||
1126 IS_DF_MIRROR(element) ||
1127 IS_DF_MIRROR_AUTO(element))
1128 laser.current_angle = get_mirrored_angle(laser.current_angle,
1129 get_element_angle(element));
1131 if (element == EL_PRISM || element == EL_REFRACTOR)
1132 laser.current_angle = RND(16);
1134 XS = 2 * Step[laser.current_angle].x;
1135 YS = 2 * Step[laser.current_angle].y;
1137 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1142 LX += step_size * XS;
1143 LY += step_size * YS;
1146 /* draw sparkles on mirror */
1147 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1148 current_angle != laser.current_angle)
1150 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1154 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1155 current_angle != laser.current_angle)
1156 PlaySoundStereo(SND_LASER, ST(ELX));
1159 (get_opposite_angle(laser.current_angle) ==
1160 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1162 return (laser.overloaded ? TRUE : FALSE);
1165 if (element == EL_FUEL_FULL)
1167 laser.stops_inside_element = TRUE;
1172 if (element == EL_BOMB || element == EL_MINE)
1174 PlaySoundStereo(SND_KINK, ST(ELX));
1176 if (element == EL_MINE)
1177 laser.overloaded = TRUE;
1180 if (element == EL_KETTLE ||
1181 element == EL_CELL ||
1182 element == EL_KEY ||
1183 element == EL_LIGHTBALL ||
1184 element == EL_PACMAN ||
1187 if (!IS_PACMAN(element))
1190 if (element == EL_PACMAN)
1193 if (element == EL_KETTLE || element == EL_CELL)
1197 if (game_mm.kettles_still_needed == 0)
1200 static int xy[4][2] =
1208 PlaySoundStereo(SND_KLING, ST(ELX));
1210 for(y=0; y<lev_fieldy; y++)
1212 for(x=0; x<lev_fieldx; x++)
1214 /* initiate opening animation of exit door */
1215 if (Feld[x][y] == EL_EXIT_CLOSED)
1216 Feld[x][y] = EL_EXIT_OPENING;
1218 /* remove field that blocks receiver */
1219 if (IS_RECEIVER(Feld[x][y]))
1221 int phase = Feld[x][y] - EL_RECEIVER_START;
1222 int blocking_x, blocking_y;
1224 blocking_x = x + xy[phase][0];
1225 blocking_y = y + xy[phase][1];
1227 if (IN_LEV_FIELD(blocking_x, blocking_y))
1229 Feld[blocking_x][blocking_y] = EL_EMPTY;
1230 DrawField_MM(blocking_x, blocking_y);
1236 DrawLaser(0, DL_LASER_ENABLED);
1239 else if (element == EL_KEY)
1241 else if (element == EL_LIGHTBALL)
1243 else if (IS_PACMAN(element))
1245 DeletePacMan(ELX, ELY);
1252 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1254 PlaySoundStereo(SND_KINK, ST(ELX));
1256 DrawLaser(0, DL_LASER_ENABLED);
1258 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1260 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1261 game_mm.lights_still_needed--;
1265 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1266 game_mm.lights_still_needed++;
1269 DrawField_MM(ELX, ELY);
1270 DrawLaser(0, DL_LASER_ENABLED);
1275 laser.stops_inside_element = TRUE;
1281 printf("HitElement (4): element == %d\n", element);
1284 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1285 laser.num_beamers < MAX_NUM_BEAMERS &&
1286 laser.beamer[BEAMER_NR(element)][1].num)
1288 int beamer_angle = get_element_angle(element);
1289 int beamer_nr = BEAMER_NR(element);
1293 printf("HitElement (BEAMER): element == %d\n", element);
1296 laser.num_damages--;
1298 if (IS_FIBRE_OPTIC(element) ||
1299 laser.current_angle == get_opposite_angle(beamer_angle))
1303 LX = ELX * TILEX + 14;
1304 LY = ELY * TILEY + 14;
1305 AddLaserEdge(LX, LY);
1306 AddDamagedField(ELX, ELY);
1307 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1310 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1312 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1313 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1314 ELX = laser.beamer[beamer_nr][pos].x;
1315 ELY = laser.beamer[beamer_nr][pos].y;
1316 LX = ELX * TILEX + 14;
1317 LY = ELY * TILEY + 14;
1319 if (IS_BEAMER(element))
1321 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1322 XS = 2 * Step[laser.current_angle].x;
1323 YS = 2 * Step[laser.current_angle].y;
1326 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1327 AddLaserEdge(LX, LY);
1328 AddDamagedField(ELX, ELY);
1329 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1332 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1334 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1339 LX += step_size * XS;
1340 LY += step_size * YS;
1342 laser.num_beamers++;
1351 boolean HitOnlyAnEdge(int element, int hit_mask)
1353 /* check if the laser hit only the edge of an element and, if so, go on */
1356 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1359 if ((hit_mask == HIT_MASK_TOPLEFT ||
1360 hit_mask == HIT_MASK_TOPRIGHT ||
1361 hit_mask == HIT_MASK_BOTTOMLEFT ||
1362 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1363 laser.current_angle % 4) /* angle is not 90° */
1367 if (hit_mask == HIT_MASK_TOPLEFT)
1372 else if (hit_mask == HIT_MASK_TOPRIGHT)
1377 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1382 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1388 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1393 printf("[HitOnlyAnEdge() == TRUE]\n");
1400 printf("[HitOnlyAnEdge() == FALSE]\n");
1406 boolean HitPolarizer(int element, int hit_mask)
1408 if (HitOnlyAnEdge(element, hit_mask))
1411 if (IS_DF_GRID(element))
1413 int grid_angle = get_element_angle(element);
1416 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1417 grid_angle, laser.current_angle);
1420 AddLaserEdge(LX, LY);
1421 AddDamagedField(ELX, ELY);
1424 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1426 if (laser.current_angle == grid_angle ||
1427 laser.current_angle == get_opposite_angle(grid_angle))
1429 /* skip the whole element before continuing the scan */
1435 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1437 if (LX/TILEX > ELX || LY/TILEY > ELY)
1439 /* skipping scan positions to the right and down skips one scan
1440 position too much, because this is only the top left scan position
1441 of totally four scan positions (plus one to the right, one to the
1442 bottom and one to the bottom right) */
1448 AddLaserEdge(LX, LY);
1454 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1456 LX / TILEX, LY / TILEY,
1457 LX % TILEX, LY % TILEY);
1462 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1463 return HitReflectingWalls(element, hit_mask);
1465 return HitAbsorbingWalls(element, hit_mask);
1467 else if (IS_GRID_STEEL(element))
1468 return HitReflectingWalls(element, hit_mask);
1469 else /* IS_GRID_WOOD */
1470 return HitAbsorbingWalls(element, hit_mask);
1475 boolean HitBlock(int element, int hit_mask)
1477 boolean check = FALSE;
1479 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1480 game_mm.num_keys == 0)
1483 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1486 int ex = ELX * TILEX + 14;
1487 int ey = ELY * TILEY + 14;
1496 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1501 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1502 return HitAbsorbingWalls(element, hit_mask);
1506 AddLaserEdge(LX - XS, LY - YS);
1507 AddDamagedField(ELX, ELY);
1510 Box[ELX][ELY] = laser.num_edges;
1512 return HitReflectingWalls(element, hit_mask);
1515 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1517 int xs = XS / 2, ys = YS / 2;
1518 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1519 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1521 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1522 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1524 laser.overloaded = (element == EL_GATE_STONE);
1528 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1529 (hit_mask == HIT_MASK_TOP ||
1530 hit_mask == HIT_MASK_LEFT ||
1531 hit_mask == HIT_MASK_RIGHT ||
1532 hit_mask == HIT_MASK_BOTTOM))
1533 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1534 hit_mask == HIT_MASK_BOTTOM),
1535 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1536 hit_mask == HIT_MASK_RIGHT));
1537 AddLaserEdge(LX, LY);
1542 if (element == EL_GATE_STONE && Box[ELX][ELY])
1544 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1556 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1558 int xs = XS / 2, ys = YS / 2;
1559 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1560 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1562 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1563 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1565 laser.overloaded = (element == EL_BLOCK_STONE);
1570 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1571 (hit_mask == HIT_MASK_TOP ||
1572 hit_mask == HIT_MASK_LEFT ||
1573 hit_mask == HIT_MASK_RIGHT ||
1574 hit_mask == HIT_MASK_BOTTOM))
1575 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1576 hit_mask == HIT_MASK_BOTTOM),
1577 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1578 hit_mask == HIT_MASK_RIGHT));
1579 AddDamagedField(ELX, ELY);
1581 LX = ELX * TILEX + 14;
1582 LY = ELY * TILEY + 14;
1583 AddLaserEdge(LX, LY);
1585 laser.stops_inside_element = TRUE;
1593 boolean HitLaserSource(int element, int hit_mask)
1595 if (HitOnlyAnEdge(element, hit_mask))
1598 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1599 laser.overloaded = TRUE;
1604 boolean HitLaserDestination(int element, int hit_mask)
1606 if (HitOnlyAnEdge(element, hit_mask))
1609 if (element != EL_EXIT_OPEN &&
1610 !(IS_RECEIVER(element) &&
1611 game_mm.kettles_still_needed == 0 &&
1612 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1614 PlaySoundStereo(SND_HOLZ, ST(ELX));
1618 if (IS_RECEIVER(element) ||
1619 (IS_22_5_ANGLE(laser.current_angle) &&
1620 (ELX != (LX + 6 * XS) / TILEX ||
1621 ELY != (LY + 6 * YS) / TILEY ||
1630 LX = ELX * TILEX + 14;
1631 LY = ELY * TILEY + 14;
1633 laser.stops_inside_element = TRUE;
1636 AddLaserEdge(LX, LY);
1637 AddDamagedField(ELX, ELY);
1639 if (game_mm.lights_still_needed == 0)
1640 game_mm.level_solved = TRUE;
1645 boolean HitReflectingWalls(int element, int hit_mask)
1647 /* check if laser hits side of a wall with an angle that is not 90° */
1648 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1649 hit_mask == HIT_MASK_LEFT ||
1650 hit_mask == HIT_MASK_RIGHT ||
1651 hit_mask == HIT_MASK_BOTTOM))
1653 PlaySoundStereo(SND_HUI, ST(ELX));
1656 if (!IS_DF_GRID(element))
1657 AddLaserEdge(LX, LY);
1659 /* check if laser hits wall with an angle of 45° */
1660 if (!IS_22_5_ANGLE(laser.current_angle))
1662 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1665 laser.current_angle = get_mirrored_angle(laser.current_angle,
1668 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1671 laser.current_angle = get_mirrored_angle(laser.current_angle,
1675 AddLaserEdge(LX, LY);
1676 XS = 2 * Step[laser.current_angle].x;
1677 YS = 2 * Step[laser.current_angle].y;
1681 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1683 laser.current_angle = get_mirrored_angle(laser.current_angle,
1688 if (!IS_DF_GRID(element))
1689 AddLaserEdge(LX, LY);
1694 if (!IS_DF_GRID(element))
1695 AddLaserEdge(LX, LY + YS / 2);
1698 if (!IS_DF_GRID(element))
1699 AddLaserEdge(LX, LY);
1702 YS = 2 * Step[laser.current_angle].y;
1706 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1708 laser.current_angle = get_mirrored_angle(laser.current_angle,
1713 if (!IS_DF_GRID(element))
1714 AddLaserEdge(LX, LY);
1719 if (!IS_DF_GRID(element))
1720 AddLaserEdge(LX + XS / 2, LY);
1723 if (!IS_DF_GRID(element))
1724 AddLaserEdge(LX, LY);
1727 XS = 2 * Step[laser.current_angle].x;
1733 /* reflection at the edge of reflecting DF style wall */
1734 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1736 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1737 hit_mask == HIT_MASK_TOPRIGHT) ||
1738 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1739 hit_mask == HIT_MASK_TOPLEFT) ||
1740 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1741 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1742 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1743 hit_mask == HIT_MASK_BOTTOMRIGHT))
1746 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1747 ANG_MIRROR_135 : ANG_MIRROR_45);
1749 PlaySoundStereo(SND_HUI, ST(ELX));
1750 AddDamagedField(ELX, ELY);
1751 AddLaserEdge(LX, LY);
1753 laser.current_angle = get_mirrored_angle(laser.current_angle,
1760 AddLaserEdge(LX, LY);
1766 /* reflection inside an edge of reflecting DF style wall */
1767 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1769 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1770 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1771 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1772 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1773 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1774 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1775 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1776 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1779 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1780 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1781 ANG_MIRROR_135 : ANG_MIRROR_45);
1783 PlaySoundStereo(SND_HUI, ST(ELX));
1785 AddDamagedField(ELX, ELY);
1787 AddLaserEdge(LX - XS, LY - YS);
1788 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1789 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1791 laser.current_angle = get_mirrored_angle(laser.current_angle,
1798 AddLaserEdge(LX, LY);
1804 /* check if laser hits DF style wall with an angle of 90° */
1805 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1807 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1808 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1809 (IS_VERT_ANGLE(laser.current_angle) &&
1810 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1812 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1814 /* laser at last step touched nothing or the same side of the wall */
1815 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1817 AddDamagedField(ELX, ELY);
1823 last_hit_mask = hit_mask;
1830 if (!HitOnlyAnEdge(element, hit_mask))
1832 laser.overloaded = TRUE;
1839 boolean HitAbsorbingWalls(int element, int hit_mask)
1841 if (HitOnlyAnEdge(element, hit_mask))
1845 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1847 AddLaserEdge(LX - XS, LY - YS);
1853 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1855 AddLaserEdge(LX - XS, LY - YS);
1860 if (IS_WALL_WOOD(element) ||
1861 IS_DF_WALL_WOOD(element) ||
1862 IS_GRID_WOOD(element) ||
1863 IS_GRID_WOOD_FIXED(element) ||
1864 IS_GRID_WOOD_AUTO(element) ||
1865 element == EL_FUSE_ON ||
1866 element == EL_BLOCK_WOOD ||
1867 element == EL_GATE_WOOD)
1869 PlaySoundStereo(SND_HOLZ, ST(ELX));
1873 if (IS_WALL_ICE(element))
1877 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1878 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1880 /* check if laser hits wall with an angle of 90° */
1881 if (IS_90_ANGLE(laser.current_angle))
1882 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1884 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1890 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1891 mask = 15 - (8 >> i);
1892 else if (ABS(XS) == 4 &&
1894 (XS > 0) == (i % 2) &&
1895 (YS < 0) == (i / 2))
1896 mask = 3 + (i / 2) * 9;
1897 else if (ABS(YS) == 4 &&
1899 (XS < 0) == (i % 2) &&
1900 (YS > 0) == (i / 2))
1901 mask = 5 + (i % 2) * 5;
1905 laser.wall_mask = mask;
1907 else if (IS_WALL_AMOEBA(element))
1909 int elx = (LX - 2 * XS) / TILEX;
1910 int ely = (LY - 2 * YS) / TILEY;
1911 int element2 = Feld[elx][ely];
1914 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1916 laser.dest_element = EL_EMPTY;
1923 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1924 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1926 if (IS_90_ANGLE(laser.current_angle))
1927 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1929 laser.dest_element = element2 | EL_WALL_AMOEBA;
1931 laser.wall_mask = mask;
1937 void OpenExit(int x, int y)
1941 if (!MovDelay[x][y]) /* next animation frame */
1942 MovDelay[x][y] = 4 * delay;
1944 if (MovDelay[x][y]) /* wait some time before next frame */
1949 phase = MovDelay[x][y] / delay;
1950 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1951 DrawGraphic_MM(x, y, EL_EXIT_OPEN - phase);
1953 if (!MovDelay[x][y])
1955 Feld[x][y] = EL_EXIT_OPEN;
1961 void OpenSurpriseBall(int x, int y)
1965 if (!MovDelay[x][y]) /* next animation frame */
1966 MovDelay[x][y] = 50 * delay;
1968 if (MovDelay[x][y]) /* wait some time before next frame */
1971 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1974 int graphic = el2gfx(Store[x][y]);
1976 int dx = RND(26), dy = RND(26);
1978 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
1979 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
1980 SX + x * TILEX + dx, SY + y * TILEY + dy);
1981 MarkTileDirty(x, y);
1984 if (!MovDelay[x][y])
1986 Feld[x][y] = Store[x][y];
1995 void MeltIce(int x, int y)
2000 if (!MovDelay[x][y]) /* next animation frame */
2001 MovDelay[x][y] = frames * delay;
2003 if (MovDelay[x][y]) /* wait some time before next frame */
2006 int wall_mask = Store2[x][y];
2007 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2010 phase = frames - MovDelay[x][y] / delay - 1;
2012 if (!MovDelay[x][y])
2016 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2017 Store[x][y] = Store2[x][y] = 0;
2019 DrawWalls_MM(x, y, Feld[x][y]);
2021 if (Feld[x][y] == EL_WALL_ICE)
2022 Feld[x][y] = EL_EMPTY;
2024 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
2025 if (laser.damage[i].is_mirror)
2029 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2031 DrawLaser(0, DL_LASER_DISABLED);
2035 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2037 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2039 laser.redraw = TRUE;
2044 void GrowAmoeba(int x, int y)
2049 if (!MovDelay[x][y]) /* next animation frame */
2050 MovDelay[x][y] = frames * delay;
2052 if (MovDelay[x][y]) /* wait some time before next frame */
2055 int wall_mask = Store2[x][y];
2056 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2059 phase = MovDelay[x][y] / delay;
2061 if (!MovDelay[x][y])
2063 Feld[x][y] = real_element;
2064 Store[x][y] = Store2[x][y] = 0;
2066 DrawWalls_MM(x, y, Feld[x][y]);
2067 DrawLaser(0, DL_LASER_ENABLED);
2069 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2070 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2074 static void Explode_MM(int x, int y, int phase, int mode)
2076 int num_phase = 9, delay = 2;
2077 int last_phase = num_phase * delay;
2078 int half_phase = (num_phase / 2) * delay;
2080 laser.redraw = TRUE;
2082 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2084 int center_element = Feld[x][y];
2086 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2088 /* put moving element to center field (and let it explode there) */
2089 center_element = MovingOrBlocked2Element_MM(x, y);
2090 RemoveMovingField_MM(x, y);
2091 Feld[x][y] = center_element;
2094 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2095 Store[x][y] = center_element;
2097 Store[x][y] = EL_EMPTY;
2098 Store2[x][y] = mode;
2099 Feld[x][y] = EL_EXPLODING_OPAQUE;
2100 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2106 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2108 if (phase == half_phase)
2110 Feld[x][y] = EL_EXPLODING_TRANSP;
2112 if (x == ELX && y == ELY)
2116 if (phase == last_phase)
2118 if (Store[x][y] == EL_BOMB)
2120 laser.num_damages--;
2121 DrawLaser(0, DL_LASER_DISABLED);
2122 laser.num_edges = 0;
2124 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2125 Store[x][y] = EL_EMPTY;
2127 else if (IS_MCDUFFIN(Store[x][y]))
2129 game_mm.game_over = TRUE;
2130 game_mm.game_over_cause = GAME_OVER_BOMB;
2131 Store[x][y] = EL_EMPTY;
2134 Store[x][y] = Store2[x][y] = 0;
2135 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2136 InitField(x, y, FALSE);
2139 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2141 int graphic = IMG_MM_DEFAULT_EXPLODING;
2142 int graphic_phase = (phase / delay - 1);
2146 if (Store2[x][y] == EX_KETTLE)
2148 if (graphic_phase < 3)
2149 graphic = IMG_MM_KETTLE_EXPLODING;
2150 else if (graphic_phase < 5)
2156 graphic = IMG_EMPTY;
2160 else if (Store2[x][y] == EX_SHORT)
2162 if (graphic_phase < 4)
2166 graphic = GFX_EMPTY;
2171 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2173 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2174 FX + x * TILEX, FY + y * TILEY);
2175 MarkTileDirty(x, y);
2179 static void Bang_MM(int x, int y)
2181 int element = Feld[x][y];
2182 int mode = EX_NORMAL;
2185 DrawLaser(0, DL_LASER_ENABLED);
2204 if (IS_PACMAN(element))
2205 PlaySoundStereo(SND_QUIEK, ST(x));
2206 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2207 PlaySoundStereo(SND_ROAAAR, ST(x));
2208 else if (element == EL_KEY)
2209 PlaySoundStereo(SND_KLING, ST(x));
2211 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2213 Explode_MM(x, y, EX_PHASE_START, mode);
2216 void TurnRound(int x, int y)
2228 { 0, 0 }, { 0, 0 }, { 0, 0 },
2233 int left, right, back;
2237 { MV_DOWN, MV_UP, MV_RIGHT },
2238 { MV_UP, MV_DOWN, MV_LEFT },
2240 { MV_LEFT, MV_RIGHT, MV_DOWN },
2241 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2242 { MV_RIGHT, MV_LEFT, MV_UP }
2245 int element = Feld[x][y];
2246 int old_move_dir = MovDir[x][y];
2247 int right_dir = turn[old_move_dir].right;
2248 int back_dir = turn[old_move_dir].back;
2249 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2250 int right_x = x+right_dx, right_y = y+right_dy;
2252 if (element == EL_PACMAN)
2254 boolean can_turn_right = FALSE;
2256 if (IN_LEV_FIELD(right_x, right_y) &&
2257 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2258 can_turn_right = TRUE;
2261 MovDir[x][y] = right_dir;
2263 MovDir[x][y] = back_dir;
2269 static void StartMoving_MM(int x, int y)
2271 int element = Feld[x][y];
2276 if (CAN_MOVE(element))
2280 if (MovDelay[x][y]) /* wait some time before next movement */
2288 /* now make next step */
2290 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2292 if (element == EL_PACMAN &&
2293 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2294 !ObjHit(newx, newy, HIT_POS_CENTER))
2296 Store[newx][newy] = Feld[newx][newy];
2297 Feld[newx][newy] = EL_EMPTY;
2298 DrawField_MM(newx, newy);
2300 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2301 ObjHit(newx, newy, HIT_POS_CENTER))
2303 /* object was running against a wall */
2310 InitMovingField_MM(x, y, MovDir[x][y]);
2314 ContinueMoving_MM(x, y);
2317 static void ContinueMoving_MM(int x, int y)
2319 int element = Feld[x][y];
2320 int direction = MovDir[x][y];
2321 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2322 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2323 int horiz_move = (dx!=0);
2324 int newx = x + dx, newy = y + dy;
2325 int step = (horiz_move ? dx : dy) * TILEX / 8;
2327 MovPos[x][y] += step;
2329 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2331 Feld[x][y] = EL_EMPTY;
2332 Feld[newx][newy] = element;
2334 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2335 MovDelay[newx][newy] = 0;
2337 if (!CAN_MOVE(element))
2338 MovDir[newx][newy] = 0;
2341 DrawField_MM(newx, newy);
2343 Stop[newx][newy] = TRUE;
2345 if (element == EL_PACMAN)
2347 if (Store[newx][newy] == EL_BOMB)
2348 Bang_MM(newx, newy);
2350 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2351 (LX + 2 * XS) / TILEX == newx &&
2352 (LY + 2 * YS) / TILEY == newy)
2359 else /* still moving on */
2362 laser.redraw = TRUE;
2365 void ClickElement(int mx, int my, int button)
2367 static unsigned int click_delay = 0;
2368 static int click_delay_value = CLICK_DELAY_SHORT;
2369 static boolean new_button = TRUE;
2371 int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2373 if (button == MB_RELEASED)
2376 click_delay_value = CLICK_DELAY_SHORT;
2378 /* release eventually hold auto-rotating mirror */
2379 RotateMirror(x, y, MB_RELEASED);
2384 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2387 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2390 if (!IN_PIX_FIELD(mx - SX, my - SY))
2393 if (Feld[x][y] == EL_EMPTY)
2396 element = Feld[x][y];
2398 if (IS_MIRROR(element) ||
2399 IS_BEAMER(element) ||
2400 IS_POLAR(element) ||
2401 IS_POLAR_CROSS(element) ||
2402 IS_DF_MIRROR(element) ||
2403 IS_DF_MIRROR_AUTO(element))
2405 RotateMirror(x, y, button);
2407 else if (IS_MCDUFFIN(element))
2409 if (!laser.fuse_off)
2411 DrawLaser(0, DL_LASER_DISABLED);
2417 element = get_rotated_element(element, BUTTON_ROTATION(button));
2418 laser.start_angle = get_element_angle(element);
2422 Feld[x][y] = element;
2427 if (!laser.fuse_off)
2430 else if (element == EL_FUSE_ON && laser.fuse_off)
2432 if (x != laser.fuse_x || y != laser.fuse_y)
2435 laser.fuse_off = FALSE;
2436 laser.fuse_x = laser.fuse_y = -1;
2438 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2441 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2443 laser.fuse_off = TRUE;
2446 laser.overloaded = FALSE;
2448 DrawLaser(0, DL_LASER_DISABLED);
2449 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2451 else if (element == EL_LIGHTBALL)
2455 DrawLaser(0, DL_LASER_ENABLED);
2458 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2462 void RotateMirror(int x, int y, int button)
2464 static int hold_x = -1, hold_y = -1;
2466 if (button == MB_RELEASED)
2468 /* release eventually hold auto-rotating mirror */
2475 if (IS_MIRROR(Feld[x][y]) ||
2476 IS_POLAR_CROSS(Feld[x][y]) ||
2477 IS_POLAR(Feld[x][y]) ||
2478 IS_BEAMER(Feld[x][y]) ||
2479 IS_DF_MIRROR(Feld[x][y]) ||
2480 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2481 IS_GRID_WOOD_AUTO(Feld[x][y]))
2483 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2485 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2487 if (button == MB_LEFTBUTTON)
2489 /* left mouse button only for manual adjustment, no auto-rotating;
2490 freeze mirror for until mouse button released */
2494 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2495 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2498 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2500 int edge = Hit[x][y];
2506 DrawLaser(edge - 1, DL_LASER_DISABLED);
2510 else if (ObjHit(x, y, HIT_POS_CENTER))
2512 int edge = Hit[x][y];
2516 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2520 DrawLaser(edge - 1, DL_LASER_DISABLED);
2527 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2532 if ((IS_BEAMER(Feld[x][y]) ||
2533 IS_POLAR(Feld[x][y]) ||
2534 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2538 if (IS_BEAMER(Feld[x][y]))
2541 printf("TEST (%d, %d) [%d] [%d]\n",
2543 laser.beamer_edge, laser.beamer[1].num);
2553 DrawLaser(0, DL_LASER_ENABLED);
2557 void AutoRotateMirrors()
2559 static unsigned int rotate_delay = 0;
2562 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2565 for (x=0; x<lev_fieldx; x++)
2567 for (y=0; y<lev_fieldy; y++)
2569 int element = Feld[x][y];
2571 if (IS_DF_MIRROR_AUTO(element) ||
2572 IS_GRID_WOOD_AUTO(element) ||
2573 IS_GRID_STEEL_AUTO(element) ||
2574 element == EL_REFRACTOR)
2575 RotateMirror(x, y, MB_RIGHTBUTTON);
2580 boolean ObjHit(int obx, int oby, int bits)
2587 if (bits & HIT_POS_CENTER)
2589 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2593 if (bits & HIT_POS_EDGE)
2596 if (ReadPixel(drawto,
2597 SX + obx + 31 * (i % 2),
2598 SY + oby + 31 * (i / 2)) == pen_ray)
2602 if (bits & HIT_POS_BETWEEN)
2605 if (ReadPixel(drawto,
2606 SX + 4 + obx + 22 * (i % 2),
2607 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2614 void DeletePacMan(int px, int py)
2620 if (game_mm.num_pacman <= 1)
2622 game_mm.num_pacman = 0;
2626 for(i=0; i<game_mm.num_pacman; i++)
2627 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2630 game_mm.num_pacman--;
2632 for(j=i; j<game_mm.num_pacman; j++)
2634 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2635 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2636 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2640 void ColorCycling(void)
2642 static int CC, Cc = 0;
2644 static int color, old = 0xF00, new = 0x010, mult = 1;
2645 static unsigned short red, green, blue;
2647 if (color_status == STATIC_COLORS)
2652 if (CC < Cc || CC > Cc + 50)
2656 color = old + new * mult;
2662 if (ABS(mult) == 16)
2671 red = 0x0e00 * ((color & 0xF00) >> 8);
2672 green = 0x0e00 * ((color & 0x0F0) >> 4);
2673 blue = 0x0e00 * ((color & 0x00F));
2674 SetRGB(pen_magicolor[0], red, green, blue);
2676 red = 0x1111 * ((color & 0xF00) >> 8);
2677 green = 0x1111 * ((color & 0x0F0) >> 4);
2678 blue = 0x1111 * ((color & 0x00F));
2679 SetRGB(pen_magicolor[1], red, green, blue);
2683 static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
2685 static unsigned int action_delay = 0;
2686 static unsigned int pacman_delay = 0;
2687 static unsigned int energy_delay = 0;
2688 static unsigned int overload_delay = 0;
2694 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2696 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2699 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2701 element = Feld[x][y];
2703 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2704 StartMoving_MM(x, y);
2705 else if (IS_MOVING(x, y))
2706 ContinueMoving_MM(x, y);
2707 else if (IS_EXPLODING(element))
2708 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2709 else if (element == EL_EXIT_OPENING)
2711 else if (element == EL_GRAY_BALL_OPENING)
2712 OpenSurpriseBall(x, y);
2713 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2715 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2719 AutoRotateMirrors();
2722 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2724 /* redraw after Explode_MM() ... */
2726 DrawLaser(0, DL_LASER_ENABLED);
2727 laser.redraw = FALSE;
2732 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2736 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2738 DrawLaser(0, DL_LASER_DISABLED);
2743 if (DelayReached(&energy_delay, 4000))
2745 game_mm.energy_left--;
2746 if (game_mm.energy_left >= 0)
2749 BlitBitmap(pix[PIX_DOOR], drawto,
2750 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2751 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2752 DX_ENERGY, DY_ENERGY);
2754 redraw_mask |= REDRAW_DOOR_1;
2756 else if (setup.time_limit)
2760 for(i=15; i>=0; i--)
2763 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2765 pen_ray = GetPixelFromRGB(window,
2766 native_mm_level.laser_red * 0x11 * i,
2767 native_mm_level.laser_green * 0x11 * i,
2768 native_mm_level.laser_blue * 0x11 * i);
2769 DrawLaser(0, DL_LASER_ENABLED);
2774 StopSound(SND_WARNTON);
2777 DrawLaser(0, DL_LASER_DISABLED);
2778 game_mm.game_over = TRUE;
2779 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2782 if (Request("Out of magic energy ! Play it again ?",
2783 REQ_ASK | REQ_STAY_CLOSED))
2789 game_status = MAINMENU;
2798 element = laser.dest_element;
2801 if (element != Feld[ELX][ELY])
2803 printf("element == %d, Feld[ELX][ELY] == %d\n",
2804 element, Feld[ELX][ELY]);
2808 if (!laser.overloaded && laser.overload_value == 0 &&
2809 element != EL_BOMB &&
2810 element != EL_MINE &&
2811 element != EL_BALL_GRAY &&
2812 element != EL_BLOCK_STONE &&
2813 element != EL_BLOCK_WOOD &&
2814 element != EL_FUSE_ON &&
2815 element != EL_FUEL_FULL &&
2816 !IS_WALL_ICE(element) &&
2817 !IS_WALL_AMOEBA(element))
2820 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2821 (!laser.overloaded && laser.overload_value > 0)) &&
2822 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2824 if (laser.overloaded)
2825 laser.overload_value++;
2827 laser.overload_value--;
2829 if (game_mm.cheat_no_overload)
2831 laser.overloaded = FALSE;
2832 laser.overload_value = 0;
2835 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2837 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2838 int color_down = 0xFF - color_up;
2841 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2842 (15 - (laser.overload_value / 6)) * color_scale);
2844 pen_ray = GetPixelFromRGB(window,
2845 (native_mm_level.laser_red ? 0xFF : color_up),
2846 (native_mm_level.laser_green ? color_down : 0x00),
2847 (native_mm_level.laser_blue ? color_down : 0x00));
2848 DrawLaser(0, DL_LASER_ENABLED);
2852 if (laser.overloaded)
2854 if (setup.sound_loops)
2855 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
2857 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2860 if (!laser.overloaded)
2861 StopSound(SND_WARNTON);
2863 if (laser.overloaded)
2866 BlitBitmap(pix[PIX_DOOR], drawto,
2867 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2868 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2869 - laser.overload_value,
2870 OVERLOAD_XSIZE, laser.overload_value,
2871 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2872 - laser.overload_value);
2874 redraw_mask |= REDRAW_DOOR_1;
2879 BlitBitmap(pix[PIX_DOOR], drawto,
2880 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2881 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2882 DX_OVERLOAD, DY_OVERLOAD);
2884 redraw_mask |= REDRAW_DOOR_1;
2887 if (laser.overload_value == MAX_LASER_OVERLOAD)
2891 for(i=15; i>=0; i--)
2894 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2897 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2898 DrawLaser(0, DL_LASER_ENABLED);
2903 DrawLaser(0, DL_LASER_DISABLED);
2904 game_mm.game_over = TRUE;
2905 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2908 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2909 REQ_ASK | REQ_STAY_CLOSED))
2915 game_status = MAINMENU;
2929 if (element == EL_BOMB && CT > 1500)
2931 if (game_mm.cheat_no_explosion)
2935 laser.num_damages--;
2936 DrawLaser(0, DL_LASER_DISABLED);
2937 laser.num_edges = 0;
2942 laser.dest_element = EL_EXPLODING_OPAQUE;
2946 laser.num_damages--;
2947 DrawLaser(0, DL_LASER_DISABLED);
2949 laser.num_edges = 0;
2950 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2952 if (Request("Bomb killed Mc Duffin ! Play it again ?",
2953 REQ_ASK | REQ_STAY_CLOSED))
2959 game_status = MAINMENU;
2967 if (element == EL_FUSE_ON && CT > 500)
2969 laser.fuse_off = TRUE;
2972 DrawLaser(0, DL_LASER_DISABLED);
2973 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
2976 if (element == EL_BALL_GRAY && CT > 1500)
2978 static int new_elements[] =
2981 EL_MIRROR_FIXED_START,
2983 EL_POLAR_CROSS_START,
2989 int num_new_elements = sizeof(new_elements) / sizeof(int);
2990 int new_element = new_elements[RND(num_new_elements)];
2992 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
2993 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
2995 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3006 element = EL_MIRROR_START + RND(16);
3012 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3019 element = (rnd == 0 ? EL_FUSE_ON :
3020 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3021 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3022 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3023 EL_MIRROR_FIXED_START + rnd - 25);
3028 graphic = el2gfx(element);
3036 BlitBitmap(pix[PIX_BACK], drawto,
3037 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3038 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3039 SX + ELX * TILEX + x,
3040 SY + ELY * TILEY + y);
3042 MarkTileDirty(ELX, ELY);
3045 DrawLaser(0, DL_LASER_ENABLED);
3050 Feld[ELX][ELY] = element;
3051 DrawField_MM(ELX, ELY);
3054 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3057 /* above stuff: GRAY BALL -> PRISM !!! */
3059 LX = ELX * TILEX + 14;
3060 LY = ELY * TILEY + 14;
3061 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3068 laser.num_edges -= 2;
3069 laser.num_damages--;
3073 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3074 if (laser.damage[i].is_mirror)
3078 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3080 DrawLaser(0, DL_LASER_DISABLED);
3082 DrawLaser(0, DL_LASER_DISABLED);
3088 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3095 if (IS_WALL_ICE(element) && CT > 1000)
3097 PlaySoundStereo(SND_SLURP, ST(ELX));
3102 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3103 Store[ELX][ELY] = EL_WALL_ICE;
3104 Store2[ELX][ELY] = laser.wall_mask;
3106 laser.dest_element = Feld[ELX][ELY];
3120 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3124 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3129 if (Feld[ELX][ELY] == EL_WALL_ICE)
3130 Feld[ELX][ELY] = EL_EMPTY;
3134 LX = laser.edge[laser.num_edges].x - (SX + 2);
3135 LY = laser.edge[laser.num_edges].y - (SY + 2);
3138 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3139 if (laser.damage[i].is_mirror)
3143 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3145 DrawLaser(0, DL_LASER_DISABLED);
3152 if (IS_WALL_AMOEBA(element) && CT > 1200)
3154 int k1, k2, k3, dx, dy, de, dm;
3155 int element2 = Feld[ELX][ELY];
3157 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3160 for (i = laser.num_damages - 1; i>=0; i--)
3161 if (laser.damage[i].is_mirror)
3164 r = laser.num_edges;
3165 d = laser.num_damages;
3172 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3175 DrawLaser(0, DL_LASER_ENABLED);
3178 x = laser.damage[k1].x;
3179 y = laser.damage[k1].y;
3185 if (laser.wall_mask & (1 << i))
3187 if (ReadPixel(drawto,
3188 SX + ELX * TILEX + 14 + (i % 2) * 2,
3189 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3191 if (ReadPixel(drawto,
3192 SX + ELX * TILEX + 31 * (i % 2),
3193 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3202 if (laser.wall_mask & (1 << i))
3204 if (ReadPixel(drawto,
3205 SX + ELX * TILEX + 31 * (i % 2),
3206 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3213 if (laser.num_beamers > 0 ||
3214 k1 < 1 || k2 < 4 || k3 < 4 ||
3215 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3218 laser.num_edges = r;
3219 laser.num_damages = d;
3220 DrawLaser(0, DL_LASER_DISABLED);
3223 Feld[ELX][ELY] = element | laser.wall_mask;
3226 de = Feld[ELX][ELY];
3227 dm = laser.wall_mask;
3233 int x = ELX, y = ELY;
3234 int wall_mask = laser.wall_mask;
3238 DrawLaser(0, DL_LASER_ENABLED);
3240 PlaySoundStereo(SND_AMOEBE, ST(dx));
3244 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3245 Store[x][y] = EL_WALL_AMOEBA;
3246 Store2[x][y] = wall_mask;
3254 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3256 DrawLaser(0, DL_LASER_ENABLED);
3258 PlaySoundStereo(SND_AMOEBE, ST(dx));
3262 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3267 DrawLaser(0, DL_LASER_ENABLED);
3272 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3273 laser.stops_inside_element && CT > 1500)
3278 if (ABS(XS) > ABS(YS))
3292 x = ELX + Step[k * 4].x;
3293 y = ELY + Step[k * 4].y;
3295 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3298 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3306 laser.overloaded = (element == EL_BLOCK_STONE);
3310 PlaySoundStereo(SND_BONG, ST(ELX));
3313 Feld[x][y] = element;
3315 DrawGraphic_MM(ELX, ELY, -1);
3318 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3320 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3321 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3329 if (element == EL_FUEL_FULL && CT > 200)
3331 for(i=game_mm.energy_left; i<=MAX_LASER_ENERGY; i+=2)
3334 BlitBitmap(pix[PIX_DOOR], drawto,
3335 DOOR_GFX_PAGEX4 + XX_ENERGY,
3336 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3337 ENERGY_XSIZE, i, DX_ENERGY,
3338 DY_ENERGY + ENERGY_YSIZE - i);
3341 redraw_mask |= REDRAW_DOOR_1;
3347 game_mm.energy_left = MAX_LASER_ENERGY;
3348 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3349 DrawField_MM(ELX, ELY);
3351 DrawLaser(0, DL_LASER_ENABLED);
3359 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
3362 ClickElement(0, 0, MB_NOT_PRESSED);
3364 GameActions_MM_Ext(action, warp_mode);
3370 int mx, my, ox, oy, nx, ny;
3374 if (++p >= game_mm.num_pacman)
3376 game_mm.pacman[p].dir--;
3380 game_mm.pacman[p].dir++;
3382 if (game_mm.pacman[p].dir > 4)
3383 game_mm.pacman[p].dir = 1;
3385 if (game_mm.pacman[p].dir % 2)
3388 my = game_mm.pacman[p].dir - 2;
3393 mx = 3 - game_mm.pacman[p].dir;
3396 ox = game_mm.pacman[p].x;
3397 oy = game_mm.pacman[p].y;
3400 element = Feld[nx][ny];
3401 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3404 if (!IS_EATABLE4PACMAN(element))
3407 if (ObjHit(nx, ny, HIT_POS_CENTER))
3410 Feld[ox][oy] = EL_EMPTY;
3412 EL_PACMAN_RIGHT - 1 +
3413 (game_mm.pacman[p].dir - 1 +
3414 (game_mm.pacman[p].dir % 2) * 2);
3416 game_mm.pacman[p].x = nx;
3417 game_mm.pacman[p].y = ny;
3418 g = Feld[nx][ny] - EL_PACMAN_RIGHT;
3419 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3421 if (element != EL_EMPTY)
3426 ox = SX + ox * TILEX;
3427 oy = SY + oy * TILEY;
3429 for(i=1; i<33; i+=2)
3432 // !!! temporary fix to compile -- change to game graphics !!!
3433 BlitBitmap(drawto, window,
3434 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3435 ox + i * mx, oy + i * my);
3437 BlitBitmap(pix[PIX_BACK], window,
3438 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3439 ox + i * mx, oy + i * my);
3442 Ct = Ct + Counter() - CT;
3444 DrawField_MM(nx, ny);
3447 if (!laser.fuse_off)
3449 DrawLaser(0, DL_LASER_ENABLED);
3451 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3453 AddDamagedField(nx, ny);
3454 laser.damage[laser.num_damages - 1].edge = 0;
3458 if (element == EL_BOMB)
3460 DeletePacMan(nx, ny);
3463 if (IS_WALL_AMOEBA(element) &&
3464 (LX + 2 * XS) / TILEX == nx &&
3465 (LY + 2 * YS) / TILEY == ny)
3477 boolean raise_level = FALSE;
3480 if (local_player->MovPos)
3483 local_player->LevelSolved = FALSE;
3486 if (game_mm.energy_left)
3488 if (setup.sound_loops)
3489 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3491 while(game_mm.energy_left > 0)
3493 if (!setup.sound_loops)
3494 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3497 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3498 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3503 game_mm.energy_left--;
3504 if (game_mm.energy_left >= 0)
3507 BlitBitmap(pix[PIX_DOOR], drawto,
3508 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3509 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3510 DX_ENERGY, DY_ENERGY);
3512 redraw_mask |= REDRAW_DOOR_1;
3519 if (setup.sound_loops)
3520 StopSound(SND_SIRR);
3522 else if (native_mm_level.time == 0) /* level without time limit */
3524 if (setup.sound_loops)
3525 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3527 while(TimePlayed < 999)
3529 if (!setup.sound_loops)
3530 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3531 if (TimePlayed < 999 && !(TimePlayed % 10))
3532 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3533 if (TimePlayed < 900 && !(TimePlayed % 10))
3539 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3546 if (setup.sound_loops)
3547 StopSound(SND_SIRR);
3554 CloseDoor(DOOR_CLOSE_1);
3556 Request("Level solved !", REQ_CONFIRM);
3558 if (level_nr == leveldir_current->handicap_level)
3560 leveldir_current->handicap_level++;
3561 SaveLevelSetup_SeriesInfo();
3564 if (level_editor_test_game)
3565 game_mm.score = -1; /* no highscore when playing from editor */
3566 else if (level_nr < leveldir_current->last_level)
3567 raise_level = TRUE; /* advance to next level */
3569 if ((hi_pos = NewHiScore_MM()) >= 0)
3571 game_status = HALLOFFAME;
3572 // DrawHallOfFame(hi_pos);
3578 game_status = MAINMENU;
3592 // LoadScore(level_nr);
3594 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3595 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3598 for (k=0; k<MAX_SCORE_ENTRIES; k++)
3600 if (game_mm.score > highscore[k].Score)
3602 /* player has made it to the hall of fame */
3604 if (k < MAX_SCORE_ENTRIES - 1)
3606 int m = MAX_SCORE_ENTRIES - 1;
3609 for (l=k; l<MAX_SCORE_ENTRIES; l++)
3610 if (!strcmp(setup.player_name, highscore[l].Name))
3612 if (m == k) /* player's new highscore overwrites his old one */
3618 strcpy(highscore[l].Name, highscore[l - 1].Name);
3619 highscore[l].Score = highscore[l - 1].Score;
3626 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3627 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3628 highscore[k].Score = game_mm.score;
3634 else if (!strncmp(setup.player_name, highscore[k].Name,
3635 MAX_PLAYER_NAME_LEN))
3636 break; /* player already there with a higher score */
3641 // if (position >= 0)
3642 // SaveScore(level_nr);
3647 static void InitMovingField_MM(int x, int y, int direction)
3649 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3650 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3652 MovDir[x][y] = direction;
3653 MovDir[newx][newy] = direction;
3654 if (Feld[newx][newy] == EL_EMPTY)
3655 Feld[newx][newy] = EL_BLOCKED;
3658 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3660 int direction = MovDir[x][y];
3661 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3662 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3668 static void Blocked2Moving_MM(int x, int y,
3669 int *comes_from_x, int *comes_from_y)
3671 int oldx = x, oldy = y;
3672 int direction = MovDir[x][y];
3674 if (direction == MV_LEFT)
3676 else if (direction == MV_RIGHT)
3678 else if (direction == MV_UP)
3680 else if (direction == MV_DOWN)
3683 *comes_from_x = oldx;
3684 *comes_from_y = oldy;
3687 static int MovingOrBlocked2Element_MM(int x, int y)
3689 int element = Feld[x][y];
3691 if (element == EL_BLOCKED)
3695 Blocked2Moving_MM(x, y, &oldx, &oldy);
3696 return Feld[oldx][oldy];
3703 static void RemoveField(int x, int y)
3705 Feld[x][y] = EL_EMPTY;
3712 static void RemoveMovingField_MM(int x, int y)
3714 int oldx = x, oldy = y, newx = x, newy = y;
3716 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3719 if (IS_MOVING(x, y))
3721 Moving2Blocked_MM(x, y, &newx, &newy);
3722 if (Feld[newx][newy] != EL_BLOCKED)
3725 else if (Feld[x][y] == EL_BLOCKED)
3727 Blocked2Moving_MM(x, y, &oldx, &oldy);
3728 if (!IS_MOVING(oldx, oldy))
3732 Feld[oldx][oldy] = EL_EMPTY;
3733 Feld[newx][newy] = EL_EMPTY;
3734 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3735 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3737 DrawLevelField_MM(oldx, oldy);
3738 DrawLevelField_MM(newx, newy);
3741 void PlaySoundLevel(int x, int y, int sound_nr)
3743 int sx = SCREENX(x), sy = SCREENY(y);
3745 int silence_distance = 8;
3747 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3748 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3751 if (!IN_LEV_FIELD(x, y) ||
3752 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3753 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3756 volume = SOUND_MAX_VOLUME;
3759 stereo = (sx - SCR_FIELDX/2) * 12;
3761 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3762 if (stereo > SOUND_MAX_RIGHT)
3763 stereo = SOUND_MAX_RIGHT;
3764 if (stereo < SOUND_MAX_LEFT)
3765 stereo = SOUND_MAX_LEFT;
3768 if (!IN_SCR_FIELD(sx, sy))
3770 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3771 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3773 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3776 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3779 static void RaiseScore_MM(int value)
3781 game_mm.score += value;
3783 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3788 void RaiseScoreElement_MM(int element)
3793 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3796 RaiseScore_MM(native_mm_level.score[SC_KEY]);