#include "precomp.h" #include // global data (source scope) static Game* game; // FPS-counter static float fps = 0; static timer fpstimer; static int delay = 1; // mountain peaks (push player away) static float peakx[16] = { 248, 537, 695, 867, 887, 213, 376, 480, 683, 984, 364, 77, 85, 522, 414, 856 }; static float peaky[16] = { 199, 223, 83, 374, 694, 639, 469, 368, 545, 145, 63, 41, 392, 285, 447, 352 }; static float peakh[16] = { 200, 150, 160, 255, 200, 255, 200, 300, 120, 100, 80, 80, 80, 160, 160, 160 }; // player, bullet and smoke data static int aliveP1 = MAXP1, aliveP2 = MAXP2; static Bullet bullet[MAXBULLET]; // sine table float sin_table[720]; float cos_table[720]; // grid //const int gridWidth = 36; //const int gridHeight = 1000; Tank* tankGridP1[gridHeight][gridWidth][256]; Tank* tankGridP2[gridHeight][gridWidth][256]; Tank* tankGridDead[gridHeight][gridWidth][256]; int intGrid[gridHeight][gridWidth][3]; //P1,P2,Dead // smoke particle effect tick function void Smoke::Tick() { unsigned int p = frame >> 3; if (frame < 64) if (!(frame++ & 7)) puff[p].x = xpos, puff[p].y = ypos << 8, puff[p].vy = -450, puff[p].life = 63; for ( unsigned int i = 0; i < p; i++ ) if ((frame < 64) || (i & 1)) { puff[i].x++, puff[i].y += puff[i].vy, puff[i].vy += 3; int frame = (puff[i].life > 13)?(9 - (puff[i].life - 14) / 5):(puff[i].life / 2); game->m_Smoke->SetFrame( frame ); game->m_Smoke->Draw( puff[i].x - 12, (puff[i].y >> 8) - 12, game->m_Surface ); if (!--puff[i].life) puff[i].x = xpos, puff[i].y = ypos << 8, puff[i].vy = -450, puff[i].life = 63; } } // bullet Tick function void Bullet::Tick() { if (!(flags & Bullet::ACTIVE)) return; vec2 prevpos = pos; pos += 1.5f * speed, prevpos -= pos - prevpos; game->m_Surface->AddLine( prevpos.x, prevpos.y, pos.x, pos.y, 0x555555 ); if ((pos.x < 0) || (pos.x > (SCRWIDTH - 1)) || (pos.y < 0) || (pos.y > (SCRHEIGHT - 1))) flags = 0; // off-screen unsigned int start = 0, end = MAXP1; if (flags & P1) start = MAXP1, end = MAXP1 + MAXP2; for ( unsigned int i = start; i < end; i++ ) // check all opponents { Tank* t = game->m_Tank[i]; if (!((t->flags & Tank::ACTIVE) && (pos.x > (t->pos.x - 2)) && (pos.y > (t->pos.y - 2)) && (pos.x < (t->pos.x + 2)) && (pos.y < (t->pos.y + 2)))) continue; if (t->flags & Tank::P1) aliveP1--; else aliveP2--; // update counters t->flags &= Tank::P1|Tank::P2; // kill tank flags = 0; // destroy bullet break; } } // Tank::Fire - spawns a bullet void Tank::Fire( unsigned int party, vec2& pos, vec2& dir ) { for ( unsigned int i = 0; i < MAXBULLET; i++ ) if (!(bullet[i].flags & Bullet::ACTIVE)) { bullet[i].flags |= Bullet::ACTIVE + party; // set owner, set active bullet[i].pos = pos, bullet[i].speed = speed; break; } } // Tank::Tick - update single tank void Tank::Tick() { if (!(flags & ACTIVE)) // dead tank { smoke.xpos = (int)pos.x, smoke.ypos = (int)pos.y; return smoke.Tick(); } vec2 force = normalize( target - pos ); // evade mountain peaks for ( unsigned int i = 0; i < 16; i++ ) { vec2 d( pos.x - peakx[i], pos.y - peaky[i] ); float sd = (d.x * d.x + d.y * d.y) * 0.2f; if (sd < 1500) { force += d * 0.03f * (peakh[i] / sd); float r = sqrtf( sd ); int prevX = -1; int prevY = -1; int newcolor = 0x000500; for (int j = 0; j < 720; j++) { int x = (int)(peakx[i] + r * sin_table[j]); int y = (int)(peaky[i] + r * cos_table[j]); //game->m_Surface->AddPlot( x, y, 0x000500 ); if (x == prevX && y == prevY) { newcolor += 0x000500; } else { game->m_Surface->AddPlot(prevX, prevY, newcolor/*0x000500*/); newcolor = 0x000500; } prevX = x; prevY = y; } game->m_Surface->AddPlot(prevX, prevY, newcolor/*0x000500*/); } } // evade other tanks int thisGridX = pos.x / tileWidth; int thisGridY = pos.y / tileHeight; TankEvasion(this, &force, thisGridX, thisGridY, 0, tankGridP1); TankEvasion(this, &force, thisGridX, thisGridY, 1, tankGridP2); TankEvasion(this, &force, thisGridX, thisGridY, 2, tankGridDead); if (thisGridX > 0) { TankEvasion(this, &force, thisGridX - 1, thisGridY, 0, tankGridP1); TankEvasion(this, &force, thisGridX - 1, thisGridY, 1, tankGridP2); TankEvasion(this, &force, thisGridX - 1, thisGridY, 2, tankGridDead); if (thisGridY > 0) { TankEvasion(this, &force, thisGridX - 1, thisGridY - 1, 0, tankGridP1); TankEvasion(this, &force, thisGridX - 1, thisGridY - 1, 1, tankGridP2); TankEvasion(this, &force, thisGridX - 1, thisGridY - 1, 2, tankGridDead); } if (thisGridY < gridHeight - 1) { TankEvasion(this, &force, thisGridX - 1, thisGridY + 1, 0, tankGridP1); TankEvasion(this, &force, thisGridX - 1, thisGridY + 1, 1, tankGridP2); TankEvasion(this, &force, thisGridX - 1, thisGridY + 1, 2, tankGridDead); } } if (thisGridY > 0) { TankEvasion(this, &force, thisGridX, thisGridY - 1, 0, tankGridP1); TankEvasion(this, &force, thisGridX, thisGridY - 1, 1, tankGridP2); TankEvasion(this, &force, thisGridX, thisGridY - 1, 2, tankGridDead); } if (thisGridX < gridWidth - 1) { TankEvasion(this, &force, thisGridX + 1, thisGridY, 0, tankGridP1); TankEvasion(this, &force, thisGridX + 1, thisGridY, 1, tankGridP2); TankEvasion(this, &force, thisGridX + 1, thisGridY, 2, tankGridDead); if (thisGridY < gridHeight - 1) { TankEvasion(this, &force, thisGridX + 1, thisGridY + 1, 0, tankGridP1); TankEvasion(this, &force, thisGridX + 1, thisGridY + 1, 1, tankGridP2); TankEvasion(this, &force, thisGridX + 1, thisGridY + 1, 2, tankGridDead); } if (thisGridY > 0) { TankEvasion(this, &force, thisGridX + 1, thisGridY - 1, 0, tankGridP1); TankEvasion(this, &force, thisGridX + 1, thisGridY - 1, 1, tankGridP2); TankEvasion(this, &force, thisGridX + 1, thisGridY - 1, 2, tankGridDead); } } if (thisGridY < gridHeight - 1) { TankEvasion(this, &force, thisGridX, thisGridY + 1, 0, tankGridP1); TankEvasion(this, &force, thisGridX, thisGridY + 1, 1, tankGridP2); TankEvasion(this, &force, thisGridX, thisGridY + 1, 2, tankGridDead); } // evade user dragged line if ((flags & P1) && (game->m_LButton)) { float x1 = (float)game->m_DStartX, y1 = (float)game->m_DStartY; float x2 = (float)game->m_MouseX, y2 = (float)game->m_MouseY; vec2 N = normalize( vec2( y2 - y1, x1 - x2 ) ); float dist = dot( N, pos ) - dot( N, vec2( x1, y1 ) ); if (fabs( dist ) < 10) if (dist > 0) force += 20.0f * N; else force -= 20.0f * N; } // update speed using accumulated force speed += force, speed = normalize( speed ), pos += speed * maxspeed * 0.5f; // shoot, if reloading completed if (--reloading >= 0) return; int enemyFlag = 0; int tankFlag = 2; auto enemyGrid = tankGridP1; //Tank*(*enemyGrid)[gridHeight][gridWidth][256] = &tankGridP1; if (flags & P1) { enemyFlag = 1; enemyGrid = tankGridP2; tankFlag = 4; } // for all tanks in a given area around this tank for (int y = 0; y < 7; y++) { for (int x = 0; x < 7; x++) { int gridposx = thisGridX + x - 3; // calculate which field we are working on int gridposy = thisGridY + y - 3; if (gridposx < 0 || gridposy < 0) // if that field falls outside of the grid skip it continue; if (gridposx > gridWidth || gridposy > gridHeight) continue; // for every tank in that gridsquare for (int i = 0; i < intGrid[gridposy][gridposx][enemyFlag]; i++) { Tank* foundtank = enemyGrid[gridposy][gridposx][i]; bool fire = TankShoot(this, foundtank); if (fire) { return; } } } } //unsigned int start = 0, end = MAXP1; //if (flags & P1) // start = MAXP1, end = MAXP1 + MAXP2; //for (unsigned int i = start; i < end; i++) // for all enemy tanks //{ // Tank* enemy = game->m_Tank[i]; // if (enemy->flags & ACTIVE) // check if enemy tank is alive // { // bool fire = TankShoot(this, enemy); // if (fire) // { // return; // } // } //} } bool Tank::TankShoot(Tank* current, Tank* enemy) { vec2 d = enemy->pos - current->pos; // calculate distance between this tank and the enemy one float dsquared = d.x * d.x + d.y * d.y; // squared distance to evade sqrts if ((dsquared < 10000) && (dot(normalize(d), speed) > 0.99999f)) { Fire(current->flags & (P1 | P2), current->pos, current->speed); // shoot current->reloading = 200; // and wait before next shot is ready return true; } return false; } void Tank::TankEvasion(Tank* current, vec2* force, int gridX, int gridY, int gridindex, Tank* tankGrid[gridHeight][gridWidth][256]) { int tileSize = intGrid[gridY][gridX][gridindex]; for (int i = 0; i < tileSize; i++) { if (tankGrid[gridY][gridX][i] == current) continue; vec2 d = current->pos - tankGrid[gridY][gridX][i]->pos; float dsqr = d.x * d.x + d.y * d.y; if (dsqr < 64) *force += normalize(d) * 2.0f; else if (dsqr < 256) *force += normalize(d) * 0.4f; } } // Game::Init - Load data, setup playfield void Game::Init() { m_Heights = new Surface( "testdata/heightmap.png" ), m_Backdrop = new Surface( 1024, 768 ), m_Grid = new Surface( 1024, 768 ); Pixel* a1 = m_Grid->GetBuffer(), *a2 = m_Backdrop->GetBuffer(), *a3 = m_Heights->GetBuffer(); for ( int y = 0; y < 768; y++ ) for ( int idx = y * 1024, x = 0; x < 1024; x++, idx++ ) a1[idx] = (((x & 31) == 0) | ((y & 31) == 0)) ? 0x6600 : 0; for ( int y = 0; y < 767; y++ ) for ( int idx = y * 1024, x = 0; x < 1023; x++, idx++ ) { vec3 N = normalize( vec3( (float)(a3[idx + 1] & 255) - (a3[idx] & 255), 1.5f, (float)(a3[idx + 1024] & 255) - (a3[idx] & 255) ) ), L( 1, 4, 2.5f ); float h = (float)(a3[x + y * 1024] & 255) * 0.0005f, dx = x - 512.f, dy = y - 384.f, d = sqrtf( dx * dx + dy * dy ), dt = dot( N, normalize( L ) ); int u = max( 0, min( 1023, (int)(x - dx * h) ) ), v = max( 0, min( 767, (int)(y - dy * h) ) ), r = (int)Rand( 255 ); a2[idx] = AddBlend( a1[u + v * 1024], ScaleColor( ScaleColor( 0x33aa11, r ) + ScaleColor( 0xffff00, (255 - r) ), (int)(max( 0.0f, dt ) * 80.0f) + 10 ) ); } m_Tank = new Tank*[MAXP1 + MAXP2]; m_P1Sprite = new Sprite( new Surface( "testdata/p1tank.tga" ), 1, Sprite::FLARE ); m_P2Sprite = new Sprite( new Surface( "testdata/p2tank.tga" ), 1, Sprite::FLARE ); m_PXSprite = new Sprite( new Surface( "testdata/deadtank.tga" ), 1, Sprite::BLACKFLARE ); m_Smoke = new Sprite( new Surface( "testdata/smoke.tga" ), 10, Sprite::FLARE ); for (int y = 0; y < gridHeight; y++) for (int x = 0; x < gridWidth; x++) { intGrid[y][x][0] = 0; intGrid[y][x][1] = 0; intGrid[y][x][2] = 0; } // create blue tanks for ( unsigned int i = 0; i < MAXP1; i++ ) { Tank* t = m_Tank[i] = new Tank(); t->pos = vec2( (float)((i % 5) * 20), (float)((i / 5) * 20 + 50) ); t->target = vec2( SCRWIDTH, SCRHEIGHT ); // initially move to bottom right corner t->speed = vec2(0, 0), t->flags = Tank::ACTIVE | Tank::P1; t->maxspeed = (i < (MAXP1 / 2)) ? 0.65f : 0.45f; int gridX = t->pos.x / tileWidth; int gridY = t->pos.y / tileHeight; tankGridP1[gridY][gridX][intGrid[gridY][gridX][0]] = t; intGrid[gridY][gridX][0]++; } // create red tanks for ( unsigned int i = 0; i < MAXP2; i++ ) { Tank* t = m_Tank[i + MAXP1] = new Tank(); t->pos = vec2( (float)((i % 12) * 20 + 900), (float)((i / 12) * 20 + 600) ); t->target = vec2( 424, 336 ); // move to player base t->speed = vec2( 0, 0 ), t->flags = Tank::ACTIVE|Tank::P2, t->maxspeed = 0.3f; int gridX = t->pos.x / tileWidth; int gridY = t->pos.y / tileHeight; tankGridP2[gridY][gridX][intGrid[gridY][gridX][1]] = t; intGrid[gridY][gridX][1]++; } game = this; // for global reference m_LButton = m_PrevButton = false; //fill precalculated sin and cos lookup table for (int i = 0; i < 720; i++) { sin_table[i] = sinf((float)i * PI / 360.0f); } for (int i = 0; i < 720; i++) { cos_table[i] = cosf((float)i * PI / 360.0f); } fpstimer.reset(); } // Game::DrawTanks - draw the tanks void Game::DrawTanks() { for ( unsigned int i = 0; i < (MAXP1 + MAXP2); i++ ) { Tank* t = m_Tank[i]; float x = t->pos.x, y = t->pos.y; vec2 p1( x + 70 * t->speed.x + 22 * t->speed.y, y + 70 * t->speed.y - 22 * t->speed.x ); vec2 p2( x + 70 * t->speed.x - 22 * t->speed.y, y + 70 * t->speed.y + 22 * t->speed.x ); if (!(m_Tank[i]->flags & Tank::ACTIVE)) m_PXSprite->Draw( (int)x - 4, (int)y - 4, m_Surface ); // draw dead tank else if (t->flags & Tank::P1) // draw blue tank { m_P1Sprite->Draw( (int)x - 4, (int)y - 4, m_Surface ); m_Surface->Line( x, y, x + 8 * t->speed.x, y + 8 * t->speed.y, 0x4444ff ); } else // draw red tank { m_P2Sprite->Draw( (int)x - 4, (int)y - 4, m_Surface ); m_Surface->Line( x, y, x + 8 * t->speed.x, y + 8 * t->speed.y, 0xff4444 ); } if ((x >= 0) && (x < SCRWIDTH) && (y >= 0) && (y < SCRHEIGHT)) m_Backdrop->GetBuffer()[(int)x + (int)y * SCRWIDTH] = SubBlend( m_Backdrop->GetBuffer()[(int)x + (int)y * SCRWIDTH], 0x030303 ); // tracks } } // Game::PlayerInput - handle player input void Game::PlayerInput() { if (m_LButton) { if (!m_PrevButton) m_DStartX = m_MouseX, m_DStartY = m_MouseY, m_DFrames = 0; // start line m_Surface->ThickLine( m_DStartX, m_DStartY, m_MouseX, m_MouseY, 0xffffff ); m_DFrames++; } else { if ((m_PrevButton) && (m_DFrames < 15)) // new target location for ( unsigned int i = 0; i < MAXP1; i++ ) m_Tank[i]->target = vec2( (float)m_MouseX, (float)m_MouseY ); m_Surface->Line( 0, (float)m_MouseY, SCRWIDTH - 1, (float)m_MouseY, 0xffffff ); m_Surface->Line( (float)m_MouseX, 0, (float)m_MouseX, SCRHEIGHT - 1, 0xffffff ); } m_PrevButton = m_LButton; } // Game::Tick - main game loop void Game::Tick( float a_DT ) { if (!--delay) { delay = 8; float elapsed = fpstimer.elapsed(); fpstimer.reset(); fps = 8000.0f / elapsed; } POINT p; GetCursorPos( &p ); ScreenToClient( FindWindow( NULL, "Template" ), &p ); m_LButton = (GetAsyncKeyState( VK_LBUTTON ) != 0), m_MouseX = p.x, m_MouseY = p.y; m_Backdrop->CopyTo(m_Surface, 0, 0); for (int y = 0; y < gridHeight; y++) { for (int x = 0; x < gridWidth; x++) { intGrid[y][x][0] = 0; intGrid[y][x][1] = 0; intGrid[y][x][2] = 0; } } for (unsigned int i = 0; i < (MAXP1 + MAXP2); i++) { Tank* t = m_Tank[i]; int gridX = t->pos.x / tileWidth; int gridY = t->pos.y / tileHeight; if (!(t->flags & Tank::ACTIVE)) { tankGridDead[gridY][gridX][intGrid[gridY][gridX][2]] = t; intGrid[gridY][gridX][2]++; } else if ((t->flags & Tank::P1)) { tankGridP1[gridY][gridX][intGrid[gridY][gridX][0]] = t; intGrid[gridY][gridX][0]++; } else if ((t->flags & Tank::P2)) { tankGridP2[gridY][gridX][intGrid[gridY][gridX][1]] = t; intGrid[gridY][gridX][1]++; } else { throw std::invalid_argument("This tank is not dead nor in Team 1 or 2"); } } for ( unsigned int i = 0; i < (MAXP1 + MAXP2); i++ ) m_Tank[i]->Tick(); for ( unsigned int i = 0; i < MAXBULLET; i++ ) bullet[i].Tick(); DrawTanks(); PlayerInput(); char buffer[128]; sprintf(buffer, "fps %6.2f", fps); m_Surface->Print(buffer, 10, 20, 0xff9999); if ((aliveP1 > 0) && (aliveP2 > 0)) { sprintf( buffer, "blue army: %03i red army: %03i", aliveP1, aliveP2 ); return m_Surface->Print( buffer, 10, 10, 0xffff00 ); } if (aliveP1 == 0) { sprintf( buffer, "sad, you lose... red left: %i", aliveP2 ); return m_Surface->Print( buffer, 200, 370, 0xffff00 ); } sprintf( buffer, "nice, you win! blue left: %i", aliveP1 ); m_Surface->Print( buffer, 200, 370, 0xffff00 ); }