Development back on the trunk!
[crack-attack.git] / src / Garbage.cxx
blob3bc90a625e86d2ef1e447878c7fd4d7ae63c2bd0
1 /*
2 * Garbage.cxx
3 * Daniel Nelson - 8/21/0
5 * Copyright (C) 2000 Daniel Nelson
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * Daniel Nelson - aluminumangel.org
22 * 174 W. 18th Ave.
23 * Columbus, OH 43210
25 * The garbage blocks is what this details.
28 #include "Game.h"
29 #include "Grid.h"
30 #include "Garbage.h"
31 #include "BlockManager.h"
32 #include "GarbageManager.h"
33 #include "Displayer.h"
34 #include "Spring.h"
35 #include "Random.h"
36 #include "X.h"
37 #ifdef AUDIO_ENABLED
38 #include "Sound.h"
39 #endif
41 #include <cassert>
43 using namespace std;
45 void Garbage::initializeStatic ( int _x, int _y, int _height, int _width,
46 int _flavor )
48 x = _x;
49 y = _y;
50 height = _height;
51 width = _width;
52 flavor = _flavor;
53 f_y = 0;
55 state = GS_STATIC;
56 alarm = 0;
57 pop_alarm = 0;
58 sections_popped = 0;
59 initial_fall = false;
60 awaking_combo = null;
62 // add ourselves to the grid
63 for (int h = height; h--; )
64 for (int w = width; w--; )
65 Grid::addGarbage(x + w, y + h, this, GR_GARBAGE);
68 void Garbage::initializeFalling ( int _x, int _y, int _height, int _width,
69 int _flavor )
71 //MESSAGE("Entering initialize Falling _x " << _x << " _y " << _y << " _height " << _height << " _width " << _width);
72 x = _x;
73 y = _y;
74 height = _height;
75 width = _width;
76 flavor = _flavor;
77 f_y = 0;
79 //MESSAGE("Second block start");
80 state = GS_FALLING;
81 alarm = Game::time_step + GC_HANG_DELAY;
82 pop_alarm = 0;
83 sections_popped = 0;
84 initial_fall = true;
85 awaking_combo = NULL;
87 //MESSAGE("Add us to the grid height: " << height << " width " << width);
88 // add ourselves to the grid
89 for (int h = height; h--; )
90 for (int w = width; w--; )
91 Grid::addGarbage(x + w, y + h, this, GR_FALLING);
92 //MESSAGE("Out and alive!");
95 void Garbage::initializeAwaking ( int _x, int _y, int _height, int pop_delay,
96 int awake_delay, ComboTabulator *combo, int _pop_color )
98 x = _x;
99 y = _y;
100 height = _height;
101 width = GC_PLAY_WIDTH;
102 flavor = GF_NORMAL;
103 f_y = 0;
105 #ifdef AUDIO_ENABLED
106 Sound::play( GC_SOUND_GARBAGE_SHATTERING, this-> width * this->height );
107 #endif
110 state = GS_AWAKING;
111 alarm = Game::time_step + awake_delay;
112 pop_alarm = Game::time_step + pop_delay;
113 sections_popped = 0;
114 pop_direction = BlockManager::generatePopDirection(height * width);
115 pop_color = _pop_color;
116 initial_fall = false;
118 // Although garbage does not participate in combos as such, this is needed
119 // so that the garbage can pass the combo on to blocks above it if it falls
120 // when it awakes. Note that this never happens because garbage currently
121 // only awakes as the second row of a three row or taller shattering
122 // garbage. Thus, all blocks above it will be BS_AWAKING when the garbage
123 // calls their startFalling().
124 awaking_combo = combo;
126 // change the game state
127 Game::awaking_count++;
129 // add ourselves to the grid
130 for (int h = height; h--; )
131 for (int w = width; w--; )
132 Grid::addGarbage(x + w, y + h, this, GR_IMMUTABLE);
135 void Garbage::timeStep ( int &l_x, int &l_y )
137 // We must advance l_x and l_y based on our size.
139 // normal garbage dimensions
140 if (height == 1 || width == GC_PLAY_WIDTH) {
141 l_y += height - 1;
142 l_x += width - 1;
144 // special garbage dimensions
145 } else {
146 l_x += width - 1;
147 // if it's not our top row, don't time step
148 if (l_y != y + height - 1) return;
151 // First, the states that may change do to falling.
153 if (state & GS_STATIC) {
154 // We may have to fall.
156 bool flag = true;
157 for (int w = width; w--; )
158 if (!(Grid::stateAt(x + w, y - 1) & GR_EMPTY)) {
159 flag = false;
160 break;
162 if (flag)
163 startFalling();
165 } else if (state & GS_AWAKING) {
166 // The alarm has been set to go off when we're done awaking. When the pop
167 // alarm goes off, we have to pop one more of our sections. If that's the
168 // last section, we don't reset the pop timer. In about a million places,
169 // we assume that awaking garbage is as wide as the grid.
171 if (sections_popped < width * height)
172 if (pop_alarm == Game::time_step) {
173 sections_popped++;
174 if (sections_popped < width * height) {
175 if (pop_direction & (1 << 3))
176 pop_direction = (1 << 0);
177 else
178 pop_direction <<= 1;
179 pop_alarm = Game::time_step + GC_INTERNAL_POP_DELAY;
183 if (alarm == Game::time_step) {
185 // change the game state
186 Game::awaking_count--;
188 // if we're going to fall
189 bool flag = true;
190 for (int w = width; w--; )
191 if (!(Grid::stateAt(x + w, y - 1) & GR_EMPTY)) {
192 flag = false;
193 break;
195 if (flag)
197 startFalling(awaking_combo, true, true);
199 else {
201 // change our state
202 state = GS_STATIC;
204 // update the grid
205 for (int h = height; h--; )
206 for (int w = width; w--; )
207 Grid::changeState(x + w, y + h, this, GR_GARBAGE);
212 // Deal with all other states.
214 if (state & GS_FALLING) {
215 // We are assured that the timeStep() of any blocks below us has already
216 // been called. Note that to start a fall, all we have to do is set our
217 // state to GS_FALLING. This code will deal with the rest.
219 if (alarm == Game::time_step)
220 // hang alarm goes off
221 alarm = 0;
223 // if the hang alarm has gone off
224 if (alarm == 0) {
226 // if we're at the bottom of a grid element
227 if (f_y == 0) {
229 // if we're still going to fall
230 bool flag = true;
231 for (int w = width; w--; )
232 if (!(Grid::stateAt(x + w, y - 1) & GR_EMPTY)) {
233 flag = false;
234 break;
236 if (flag) {
238 // shift our grid position down to the next row
239 y--;
240 f_y = GC_STEPS_PER_GRID;
242 // update the grid
243 for (int h = height; h--; )
244 for (int w = width; w--; )
245 Grid::remove(x + w, y + h + 1, this);
246 for (int h = height; h--; )
247 for (int w = width; w--; )
248 Grid::addGarbage(x + w, y + h, this, GR_FALLING);
250 // if we've landed
251 } else {
253 // change our state
254 state = BS_STATIC;
255 #ifdef AUDIO_ENABLED
256 Sound::play( GC_SOUND_GARBAGE_FALLEN, this->width * this->height );
257 #endif
259 // if this is the end of our initial fall
260 if (initial_fall) {
261 initial_fall = false;
262 if (!(MetaState::mode & CM_REALLY_LOW_GRAPHICS))
263 Spring::notifyImpact(height, width);
264 Grid::notifyImpact(y, height);
266 X::notifyImpact(*this);
269 // update the grid
270 for (int h = height; h--; )
271 for (int w = width; w--; )
272 Grid::changeState(x + w, y + h, this, GR_GARBAGE);
276 // if we still are, fall
277 if (state & GS_FALLING)
278 f_y -= GC_FALL_VELOCITY;
283 void Garbage::startFalling ( ComboTabulator *combo, bool no_hang,
284 bool self_call )
286 * While garbage doesn't have a current combo and doesn't have to deal with
287 * such things, it does need to pass combo falls along to it's upward neighbors.
290 // if we're calling our own startFalling() this has already been checked
291 if (!self_call) {
293 if (!(state & BS_STATIC)) return;
295 // if we're not going to fall
296 for (int w = width; w--; )
297 if (!(Grid::stateAt(x + w, y - 1) & (GR_EMPTY | GR_FALLING)))
298 return;
301 // change our state
302 state = GS_FALLING;
304 // set the hang alarm and update the grid
305 if (no_hang) {
306 alarm = 0;
307 for (int h = height; h--; )
308 for (int w = width; w--; )
309 Grid::changeState(x + w, y + h, this, GR_FALLING);
311 } else {
312 alarm = Game::time_step + GC_HANG_DELAY;
313 for (int h = height; h--; )
314 for (int w = width; w--; )
315 Grid::changeState(x + w, y + h, this, GR_HANGING | GR_FALLING);
318 // tell our upward neighbors to start a combo fall
319 if (y + height < GC_PLAY_HEIGHT) {
320 for (int w = width; w--; ) {
321 if (Grid::stateAt(x + w, y + height) & GR_BLOCK)
322 Grid::blockAt(x + w, y + height).startFalling(combo, no_hang);
323 else if (Grid::stateAt(x + w, y + height) & GR_GARBAGE)
324 Grid::garbageAt(x + w, y + height).startFalling(combo, no_hang);
329 void Garbage::startShattering ( int &s_x, int s_y, int &pop_delay,
330 int awake_delay, ComboTabulator *combo )
332 * This is called for each row we occupy, with s_x equal to our left most
333 * position and s_y indicating which row the call is for. We must convert
334 * ourselves to blocks or new garbage along that row. Additionally, we must
335 * advance s_x to our right side as well as pop_delay and delete ourselves if
336 * this is our top row.
338 * Note that we may want to change the block/garbage conversion behavior.
341 #ifndef NDEBUG
342 // otherwise assert will bite us
343 for (int w = 0; w < width; w++)
344 Grid::remove(s_x + w, s_y, this);
345 #endif
346 #ifdef AUDIO_ENABLED
347 Sound::play( GC_SOUND_GARBAGE_SHATTERING, this-> width * this->height );
348 #endif
350 // if it's an even row, perhaps shatter into new garbage
351 if ((width == GC_PLAY_WIDTH && ((s_y - y) & (1 << 0))
352 && Random::chanceIn(GC_GARBAGE_TO_GARBAGE_SHATTER))
353 || flavor == GF_SHATTER_TO_NORMAL_GARBAGE) {
354 GarbageManager::newAwakingGarbage(s_x, s_y, 1, pop_delay, awake_delay,
355 combo, flavor);
356 s_x += GC_PLAY_WIDTH;
357 pop_delay += GC_PLAY_WIDTH * GC_INTERNAL_POP_DELAY;
359 // otherwise, shatter into blocks
360 } else
361 for (int w = 0; w < width; w++) {
362 BlockManager::newAwakingBlock(s_x, s_y, pop_delay, awake_delay, combo,
363 flavor);
364 s_x++;
365 pop_delay += GC_INTERNAL_POP_DELAY;
368 // If it's our top row, enter shatter state; we are no longer on the grid
369 // but we stay around a bit to animate our shattering; since we're not in the
370 // grid, our time step will never be called; that's OK! We'll be deleted by
371 // the display code, sloppy but fastest.
372 if (s_y + 1 == y + height) {
374 // change our state
375 state = GS_SHATTERING;
377 // set the deletion alarm
378 alarm = Game::time_step + DC_SHATTER_TIME;
380 // notify extreme effects of our demise
381 X::notifyShatter(*this);