Really low graphics patch thanks to Stephan Beyer
[crack-attack.git] / src / Garbage.cxx
blob9e81f17c0d3ee7cc8406dfe8569f4ea959c7ecdf
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"
38 using namespace std;
40 void Garbage::initializeStatic ( int _x, int _y, int _height, int _width,
41 int _flavor )
43 x = _x;
44 y = _y;
45 height = _height;
46 width = _width;
47 flavor = _flavor;
48 f_y = 0;
50 state = GS_STATIC;
51 alarm = 0;
52 pop_alarm = 0;
53 sections_popped = 0;
54 initial_fall = false;
55 awaking_combo = null;
57 // add ourselves to the grid
58 for (int h = height; h--; )
59 for (int w = width; w--; )
60 Grid::addGarbage(x + w, y + h, this, GR_GARBAGE);
63 void Garbage::initializeFalling ( int _x, int _y, int _height, int _width,
64 int _flavor )
66 x = _x;
67 y = _y;
68 height = _height;
69 width = _width;
70 flavor = _flavor;
71 f_y = 0;
73 state = GS_FALLING;
74 alarm = Game::time_step + GC_HANG_DELAY;
75 pop_alarm = 0;
76 sections_popped = 0;
77 initial_fall = true;
78 awaking_combo = null;
80 // add ourselves to the grid
81 for (int h = height; h--; )
82 for (int w = width; w--; )
83 Grid::addGarbage(x + w, y + h, this, GR_FALLING);
86 void Garbage::initializeAwaking ( int _x, int _y, int _height, int pop_delay,
87 int awake_delay, ComboTabulator *combo, int _pop_color )
89 x = _x;
90 y = _y;
91 height = _height;
92 width = GC_PLAY_WIDTH;
93 flavor = GF_NORMAL;
94 f_y = 0;
96 state = GS_AWAKING;
97 alarm = Game::time_step + awake_delay;
98 pop_alarm = Game::time_step + pop_delay;
99 sections_popped = 0;
100 pop_direction = BlockManager::generatePopDirection(height * width);
101 pop_color = _pop_color;
102 initial_fall = false;
104 // Although garbage does not participate in combos as such, this is needed
105 // so that the garbage can pass the combo on to blocks above it if it falls
106 // when it awakes. Note that this never happens because garbage currently
107 // only awakes as the second row of a three row or taller shattering
108 // garbage. Thus, all blocks above it will be BS_AWAKING when the garbage
109 // calls their startFalling().
110 awaking_combo = combo;
112 // change the game state
113 Game::awaking_count++;
115 // add ourselves to the grid
116 for (int h = height; h--; )
117 for (int w = width; w--; )
118 Grid::addGarbage(x + w, y + h, this, GR_IMMUTABLE);
121 void Garbage::timeStep ( int &l_x, int &l_y )
123 // We must advance l_x and l_y based on our size.
125 // normal garbage dimensions
126 if (height == 1 || width == GC_PLAY_WIDTH) {
127 l_y += height - 1;
128 l_x += width - 1;
130 // special garbage dimensions
131 } else {
132 l_x += width - 1;
133 // if it's not our top row, don't time step
134 if (l_y != y + height - 1) return;
137 // First, the states that may change do to falling.
139 if (state & GS_STATIC) {
140 // We may have to fall.
142 bool flag = true;
143 for (int w = width; w--; )
144 if (!(Grid::stateAt(x + w, y - 1) & GR_EMPTY)) {
145 flag = false;
146 break;
148 if (flag)
149 startFalling();
151 } else if (state & GS_AWAKING) {
152 // The alarm has been set to go off when we're done awaking. When the pop
153 // alarm goes off, we have to pop one more of our sections. If that's the
154 // last section, we don't reset the pop timer. In about a million places,
155 // we assume that awaking garbage is as wide as the grid.
157 if (sections_popped < width * height)
158 if (pop_alarm == Game::time_step) {
159 sections_popped++;
160 if (sections_popped < width * height) {
161 if (pop_direction & (1 << 3))
162 pop_direction = (1 << 0);
163 else
164 pop_direction <<= 1;
165 pop_alarm = Game::time_step + GC_INTERNAL_POP_DELAY;
169 if (alarm == Game::time_step) {
171 // change the game state
172 Game::awaking_count--;
174 // if we're going to fall
175 bool flag = true;
176 for (int w = width; w--; )
177 if (!(Grid::stateAt(x + w, y - 1) & GR_EMPTY)) {
178 flag = false;
179 break;
181 if (flag)
183 startFalling(awaking_combo, true, true);
185 else {
187 // change our state
188 state = GS_STATIC;
190 // update the grid
191 for (int h = height; h--; )
192 for (int w = width; w--; )
193 Grid::changeState(x + w, y + h, this, GR_GARBAGE);
198 // Deal with all other states.
200 if (state & GS_FALLING) {
201 // We are assured that the timeStep() of any blocks below us has already
202 // been called. Note that to start a fall, all we have to do is set our
203 // state to GS_FALLING. This code will deal with the rest.
205 if (alarm == Game::time_step)
206 // hang alarm goes off
207 alarm = 0;
209 // if the hang alarm has gone off
210 if (alarm == 0) {
212 // if we're at the bottom of a grid element
213 if (f_y == 0) {
215 // if we're still going to fall
216 bool flag = true;
217 for (int w = width; w--; )
218 if (!(Grid::stateAt(x + w, y - 1) & GR_EMPTY)) {
219 flag = false;
220 break;
222 if (flag) {
224 // shift our grid position down to the next row
225 y--;
226 f_y = GC_STEPS_PER_GRID;
228 // update the grid
229 for (int h = height; h--; )
230 for (int w = width; w--; )
231 Grid::remove(x + w, y + h + 1, this);
232 for (int h = height; h--; )
233 for (int w = width; w--; )
234 Grid::addGarbage(x + w, y + h, this, GR_FALLING);
236 // if we've landed
237 } else {
239 // change our state
240 state = BS_STATIC;
242 // if this is the end of our initial fall
243 if (initial_fall) {
244 initial_fall = false;
245 if (!(MetaState::mode & CM_REALLY_LOW_GRAPHICS))
246 Spring::notifyImpact(height, width);
247 Grid::notifyImpact(y, height);
249 X::notifyImpact(*this);
252 // update the grid
253 for (int h = height; h--; )
254 for (int w = width; w--; )
255 Grid::changeState(x + w, y + h, this, GR_GARBAGE);
259 // if we still are, fall
260 if (state & GS_FALLING)
261 f_y -= GC_FALL_VELOCITY;
266 void Garbage::startFalling ( ComboTabulator *combo, bool no_hang,
267 bool self_call )
269 * While garbage doesn't have a current combo and doesn't have to deal with
270 * such things, it does need to pass combo falls along to it's upward neighbors.
273 // if we're calling our own startFalling() this has already been checked
274 if (!self_call) {
276 if (!(state & BS_STATIC)) return;
278 // if we're not going to fall
279 for (int w = width; w--; )
280 if (!(Grid::stateAt(x + w, y - 1) & (GR_EMPTY | GR_FALLING)))
281 return;
284 // change our state
285 state = GS_FALLING;
287 // set the hang alarm and update the grid
288 if (no_hang) {
289 alarm = 0;
290 for (int h = height; h--; )
291 for (int w = width; w--; )
292 Grid::changeState(x + w, y + h, this, GR_FALLING);
294 } else {
295 alarm = Game::time_step + GC_HANG_DELAY;
296 for (int h = height; h--; )
297 for (int w = width; w--; )
298 Grid::changeState(x + w, y + h, this, GR_HANGING | GR_FALLING);
301 // tell our upward neighbors to start a combo fall
302 if (y + height < GC_PLAY_HEIGHT) {
303 for (int w = width; w--; ) {
304 if (Grid::stateAt(x + w, y + height) & GR_BLOCK)
305 Grid::blockAt(x + w, y + height).startFalling(combo, no_hang);
306 else if (Grid::stateAt(x + w, y + height) & GR_GARBAGE)
307 Grid::garbageAt(x + w, y + height).startFalling(combo, no_hang);
312 void Garbage::startShattering ( int &s_x, int s_y, int &pop_delay,
313 int awake_delay, ComboTabulator *combo )
315 * This is called for each row we occupy, with s_x equal to our left most
316 * position and s_y indicating which row the call is for. We must convert
317 * ourselves to blocks or new garbage along that row. Additionally, we must
318 * advance s_x to our right side as well as pop_delay and delete ourselves if
319 * this is our top row.
321 * Note that we may want to change the block/garbage conversion behavior.
324 #ifndef NDEBUG
325 // otherwise assert will bite us
326 for (int w = 0; w < width; w++)
327 Grid::remove(s_x + w, s_y, this);
328 #endif
330 // if it's an even row, perhaps shatter into new garbage
331 if ((width == GC_PLAY_WIDTH && ((s_y - y) & (1 << 0))
332 && Random::chanceIn(GC_GARBAGE_TO_GARBAGE_SHATTER))
333 || flavor == GF_SHATTER_TO_NORMAL_GARBAGE) {
334 GarbageManager::newAwakingGarbage(s_x, s_y, 1, pop_delay, awake_delay,
335 combo, flavor);
336 s_x += GC_PLAY_WIDTH;
337 pop_delay += GC_PLAY_WIDTH * GC_INTERNAL_POP_DELAY;
339 // otherwise, shatter into blocks
340 } else
341 for (int w = 0; w < width; w++) {
342 BlockManager::newAwakingBlock(s_x, s_y, pop_delay, awake_delay, combo,
343 flavor);
344 s_x++;
345 pop_delay += GC_INTERNAL_POP_DELAY;
348 // If it's our top row, enter shatter state; we are no longer on the grid
349 // but we stay around a bit to animate our shattering; since we're not in the
350 // grid, our time step will never be called; that's OK! We'll be deleted by
351 // the display code, sloppy but fastest.
352 if (s_y + 1 == y + height) {
354 // change our state
355 state = GS_SHATTERING;
357 // set the deletion alarm
358 alarm = Game::time_step + DC_SHATTER_TIME;
360 // notify extreme effects of our demise
361 X::notifyShatter(*this);