/* Tower Toppler - Nebulus * Copyright (C) 2000-2012 Andreas Röver * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "robots.h" #include "decl.h" #include "level.h" #include "screen.h" #include "toppler.h" #include "sound.h" #include #define MAX_OBJECTS 4 /* this field contains the robots */ static struct { /* the position of the robot */ int anglepos; long verticalpos; /* what kind of robot it is, an under classification and what kind it will be after the appearing animation */ rob_kinds kind; long subKind; rob_kinds futureKind; /* a timer for the animations of the robots */ long time; } object[MAX_OBJECTS]; /* the position up to where the robots are worked out */ static int robots_ready; static int robots_angle; /* the data for the next cross that will appear */ static int next_cross_timer; static int cross_direction; static int nextcrosscolor; /******** PRIVATE FUNCTIONS ********/ /* returns the index of the figure the given figure (nr) collides with or -1 if there is no such object */ static int figurecollision(int nr) { /* help field for collisions between two objects */ static unsigned char collision[16] = { 0x1c, 0x3e, 0x3e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x3e, 0x3e, 0x1c, 0x00 }; int t; long i, j; for (t = 0; t < MAX_OBJECTS; t++) { if (t != nr && object[t].kind != OBJ_KIND_CROSS && object[t].kind != OBJ_KIND_NOTHING && object[t].kind != OBJ_KIND_DISAPPEAR && object[t].kind != OBJ_KIND_APPEAR) { i = object[nr].anglepos - object[t].anglepos; j = object[t].verticalpos - object[nr].verticalpos; if ((-4 < i) && (i < 4) && (-8 < j) && (j < 8)) { if ((collision[j + 7] >> (i + 3)) & 1) return t; else return -1; } } } return -1; } /* returns true, if the robot cannot be at the given position without colliding with an element from the tower */ static bool testroboter(int nr) { return (!lev_testfigure((long)object[nr].anglepos, object[nr].verticalpos, -2L, 1L, 1L, 1L, 7L)); } /* makes the robot disappear when it falls into water */ static void drown_robot(int nr) { object[nr].verticalpos = 0; object[nr].time = 0; object[nr].kind = OBJ_KIND_DISAPPEAR; } /* tests the underground of the given object (only used for freeze ball) returns 0 if nothing needs to be done 1 for falling 2 for reverse direction */ static int test_robot_undergr(int nr) { int row, col; row = object[nr].verticalpos / 4 - 1; col = (object[nr].anglepos / 8) & 0xf; if (lev_is_box(row, col)) return 0; if (lev_is_platform(row, col) || lev_is_stick(row, col)) { if (lev_is_elevator(row, col)) return 2; else return 0; } if ((object[nr].anglepos & 7) < 2) { if (lev_is_empty(row, (col - 1) & 0xf)) return 1; if (lev_is_door(row, (col - 1) & 0xf)) return 1; if ((object[nr].subKind & 0x80) == 0) return 2; else return 0; } if ((object[nr].anglepos & 7) > 6) { if (lev_is_empty(row, (col + 1) & 0xf)) return 1; if (lev_is_door(row, (col + 1) & 0xf)) return 1; if ((object[nr].subKind & 0x80) == 0) return 0; else return 2; } return 1; } /* update the position for the cross */ static void updatecross(int t) { object[t].anglepos += object[t].subKind; object[t].time -= object[t].subKind; /* after the cross reached the middle speed it up */ if (object[t].anglepos == 60) object[t].subKind *= 2; /* if cross reached screenedge, remove it an reinitialize counter for next one */ if (((object[t].subKind > 0) && (object[t].anglepos >= 120)) || ((object[t].subKind < 0) && (object[t].anglepos <= 0))) { object[t].kind = OBJ_KIND_NOTHING; next_cross_timer = 125; } } /* remove objects that drop below the screen */ static bool checkverticalposition(int verticalpos, int t) { if (object[t].verticalpos + 48 < verticalpos) { object[t].kind = OBJ_KIND_DISAPPEAR; object[t].time = 0; return true; } else return false; } /* checks if the robot is at a position that it can not be if not remove it */ static bool checkvalidposition(int t) { if (testroboter(t)) { object[t].kind = OBJ_KIND_DISAPPEAR; object[t].time = 0; return true; } else return false; } /* move the robot a the horizontal amount, if this places it at an impossible position, reverse its direction and don't move it */ static void moverobothorizontal(int t) { object[t].anglepos += object[t].subKind; object[t].anglepos &= 0x7f; if (testroboter(t) || (figurecollision(t) != -1)) { object[t].subKind = -object[t].subKind; object[t].anglepos += object[t].subKind; object[t].anglepos &= 0x7f; } } /******* PUBLIC FUNCTIONS *********/ rob_kinds rob_kind(int nr) { return object[nr].kind; } int rob_time(int nr) { return object[nr].time; } int rob_angle(int nr) { return object[nr].anglepos; } int rob_vertical(int nr) { return object[nr].verticalpos; } void rob_initialize(void) { for (int b = 0; b < MAX_OBJECTS; b++) { object[b].kind = OBJ_KIND_NOTHING; object[b].time = -1; } next_cross_timer = 125; nextcrosscolor = rand() / (RAND_MAX / 8); cross_direction = 1; robots_ready = 0; robots_angle = 0; } int rob_topplercollision(int angle, int vertical) { long i, j; for (int t = 0; t < MAX_OBJECTS; t++) { if (object[t].kind != OBJ_KIND_CROSS && object[t].kind != OBJ_KIND_NOTHING && object[t].kind != OBJ_KIND_DISAPPEAR && object[t].kind != OBJ_KIND_APPEAR) { i = angle - object[t].anglepos; j = object[t].verticalpos - vertical; if ((-4 < i) && (i < 4) && (-8 < j) && (j < 8)) return t; } else if (object[t].kind == OBJ_KIND_CROSS) { if (object[t].anglepos >= 53 && object[t].anglepos <= 67 && object[t].verticalpos + 7 >= vertical && vertical + 9 >= object[t].verticalpos) return t; } } return -1; } int rob_snowballcollision(int angle, int vertical) { /* help field for collisions between two objects */ static unsigned char collision[16] = { 0x00, 0x1c, 0x1c, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x10 }; long i, j; for (int t = 0; t < MAX_OBJECTS; t++) { if (object[t].kind != OBJ_KIND_CROSS && object[t].kind != OBJ_KIND_NOTHING && object[t].kind != OBJ_KIND_DISAPPEAR && object[t].kind != OBJ_KIND_APPEAR) { i = angle - object[t].anglepos; j = object[t].verticalpos - vertical; if ((-4 < i) && (i < 4) && (-8 < j) && (j < 8)) { if ((collision[j + 7] >> (i + 3)) & 1) return t; else return -1; } } } return -1; } void rob_new(int verticalpos) { static struct { unsigned char r, g, b; } crosscols[8] = { { 0xff, 0x00, 0x00 }, { 0x00, 0xff, 0x00 }, { 0x00, 0x00, 0xff }, { 0xff, 0xff, 0x00 }, { 0x00, 0xff, 0xff }, { 0xff, 0x00, 0xff }, { 0x80, 0x80, 0x80 }, { 0xff, 0xff, 0xff } }; int a, b; int h = verticalpos / 4 + 9; for (int t = 0; t < MAX_OBJECTS; t++) { if (object[t].kind == OBJ_KIND_NOTHING) { /* if there is currently no chance for a new robot check for a cross */ if ((robots_ready == lev_towerrows() * 8) || (h <= robots_ready)) { /* check if time is ripe */ if (next_cross_timer == -1) return; next_cross_timer--; if (next_cross_timer != -1) return; /* set colors for the cross */ scr_setcrosscolor(crosscols[nextcrosscolor].r, crosscols[nextcrosscolor].g, crosscols[nextcrosscolor].b); nextcrosscolor = (nextcrosscolor + 1) & 7; /* fill in the data for the robot */ object[t].kind = OBJ_KIND_CROSS; object[t].time = 0; cross_direction *= -1; object[t].subKind = cross_direction; if (cross_direction > 0) object[t].anglepos = 0; else object[t].anglepos = 120; object[t].verticalpos = verticalpos; ttsounds::instance()->startsound(SND_CROSS); } else { bool is_robo = false; /* find next robot */ do { b = lev_tower(robots_ready, robots_angle); is_robo = lev_is_robot(robots_ready, robots_angle); a = robots_angle; h = robots_ready; robots_angle = (robots_angle + 1) & 0xf; } while ((!is_robo) && robots_angle != 0); if (robots_angle == 0) robots_ready++; /* no robot found */ if (!is_robo) return; /* fill in data for robot */ switch (b) { case TB_ROBOT1: object[t].subKind = 1; object[t].futureKind = OBJ_KIND_FREEZEBALL; break; case TB_ROBOT2: object[t].subKind = 1; object[t].futureKind = OBJ_KIND_JUMPBALL; break; case TB_ROBOT3: object[t].subKind = 0; object[t].futureKind = OBJ_KIND_JUMPBALL; break; case TB_ROBOT4: object[t].subKind = 1; object[t].futureKind = OBJ_KIND_ROBOT_VERT; break; case TB_ROBOT5: object[t].subKind = 2; object[t].futureKind = OBJ_KIND_ROBOT_VERT; break; case TB_ROBOT6: object[t].subKind = 1; object[t].futureKind = OBJ_KIND_ROBOT_HORIZ; break; case TB_ROBOT7: object[t].subKind = 2; object[t].futureKind = OBJ_KIND_ROBOT_HORIZ; break; } object[t].anglepos = (a * 8) + 4; object[t].verticalpos = h * 4; object[t].kind = OBJ_KIND_APPEAR; object[t].time = 0; /* empty the field in the level datastructure */ lev_clear(h, a); } return; } } } void rob_update(void) { /* the vertical movement of the jumping ball */ static long jumping_ball[11] = { 2, 2, 1, 1, 0, 0, -1, -1, -2, -2, -4 }; int h; for (int t = 0; t < MAX_OBJECTS; t++) { switch (object[t].kind) { case OBJ_KIND_NOTHING: break; case OBJ_KIND_CROSS: updatecross(t); break; case OBJ_KIND_DISAPPEAR: if (object[t].time == 6) object[t].kind = OBJ_KIND_NOTHING; else object[t].time++; break; case OBJ_KIND_APPEAR: if (object[t].time == 6) { object[t].kind = object[t].futureKind; object[t].time = 0; } else object[t].time++; break; case OBJ_KIND_FREEZEBALL_FROZEN: object[t].time--; if (object[t].time > 0) break; object[t].kind = OBJ_KIND_FREEZEBALL; case OBJ_KIND_FREEZEBALL: if (checkverticalposition(top_verticalpos(), t) || checkvalidposition(t)) break; switch (test_robot_undergr(t)) { case 1: object[t].kind = OBJ_KIND_FREEZEBALL_FALLING; object[t].time = 10; break; case 2: object[t].subKind = -object[t].subKind; break; } moverobothorizontal(t); break; case OBJ_KIND_FREEZEBALL_FALLING: if (checkverticalposition(top_verticalpos(), t) || checkvalidposition(t)) break; moverobothorizontal(t); if (object[t].verticalpos + jumping_ball[object[t].time] < 0) { drown_robot(t); break; } h = jumping_ball[object[t].time]; while (h != 0) { object[t].verticalpos += h; if (!testroboter(t) || (figurecollision(t) != -1)) break; object[t].verticalpos -= h; if (h > 0) h--; else h++; } if ((h == 0) && (jumping_ball[object[t].time] < 0)) { object[t].kind = OBJ_KIND_FREEZEBALL; object[t].time = 0; } break; case OBJ_KIND_JUMPBALL: if (checkverticalposition(top_verticalpos(), t) || checkvalidposition(t)) break; h = object[t].subKind; moverobothorizontal(t); if (h * object[t].subKind < 0) { unsigned char w = (object[t].anglepos - top_anglepos()) & 0x7f; if (w >= 0x40) w |= 0x80; if (w >= 0x80) w = 0xff & (~w + 1); ttsounds::instance()->setsoundvol(SND_BOINK, 120-w); ttsounds::instance()->startsound(SND_BOINK); } if (object[t].verticalpos + jumping_ball[object[t].time] < 0) { drown_robot(t); break; } h = jumping_ball[object[t].time]; while (h != 0) { object[t].verticalpos += h; if (!testroboter(t) || (figurecollision(t) != -1)) break; object[t].verticalpos -= h; if (h > 0) h--; else h++; } /* ball is bouncing */ if ((h == 0) && (jumping_ball[object[t].time] < 0)) { unsigned char w = (object[t].anglepos - top_anglepos()) & 0x7f; if (w >= 0x40) w |= 0x80; if (w >= 0x80) w = 0xff & (~w + 1); ttsounds::instance()->setsoundvol(SND_BOINK, 128-2*w); ttsounds::instance()->startsound(SND_BOINK); /* restart bounce cyclus */ object[t].time = 0; /* start the bounding ball moving sideways in direction of the animal */ if (object[t].subKind == 0 && top_visible() && top_walking() && object[t].verticalpos == top_verticalpos()) { /* check if the animal is near enough to the ball */ unsigned char w = (object[t].anglepos - top_anglepos()) & 0x7f; h = -1; if (w >= 0x40) w |= 0x80; if (w >= 0x80) { w = 0xff & (~w + 1); h = 1; } if (w < 0x20) object[t].subKind = h; } break; } object[t].time++; if (object[t].time > 10) object[t].time = 10; break; case OBJ_KIND_ROBOT_HORIZ: if (checkverticalposition(top_verticalpos(), t) || checkvalidposition(t)) break; moverobothorizontal(t); object[t].time = (object[t].time + 1) & 0x1f; break; case OBJ_KIND_ROBOT_VERT: if (checkverticalposition(top_verticalpos(), t) || checkvalidposition(t)) break; if ((object[t].verticalpos + object[t].subKind) < 0) drown_robot(t); else { object[t].verticalpos += object[t].subKind; if (testroboter(t) || (figurecollision(t) != -1)) { object[t].subKind *= -1; object[t].verticalpos += object[t].subKind; } } object[t].time = (object[t].time + 1) & 0x1f; break; } } } int rob_gothit(int nr) { if (object[nr].kind == OBJ_KIND_FREEZEBALL) { object[nr].time = 0x4b; object[nr].kind = OBJ_KIND_FREEZEBALL_FROZEN; return 0; } else if (object[nr].kind == OBJ_KIND_JUMPBALL) { object[nr].kind = OBJ_KIND_DISAPPEAR; object[nr].time = 0; return 100; } else return 0; } void rob_disappearall(void) { for (int t = 0; t < MAX_OBJECTS; t++) { if (object[t].kind != OBJ_KIND_NOTHING) { object[t].kind = OBJ_KIND_DISAPPEAR; object[t].time = 0; } } }