1
0
mirror of https://github.com/borgesdan/xn65 synced 2024-12-29 21:54:47 +01:00

282 lines
8.7 KiB
C++

#include "enemy.hpp"
#include "extensions.hpp"
#include "level.hpp"
#include "player.hpp"
#include "gem.hpp"
#include <cstdlib>
#include <fstream>
#include <string>
namespace PlatformerStarterKit {
Level::Level(xna::sptr<xna::IServiceProvider> const& serviceProvider, xna::String const& path) : path(path)
{
content = xna::snew<xna::ContentManager>(serviceProvider, "Content");
timeRemaining = xna::TimeSpan::FromMinutes(2.0);
layers = std::vector<xna::PTexture2D>(3);
for (size_t i = 0; i < layers.size(); ++i) {
const auto segmentIndex = rand() % 3;
xna::String file = "Backgrounds/Layer" + std::to_string(i) + "_" + std::to_string(segmentIndex);
layers[i] = content->Load<xna::PTexture2D>(file);
exitReachedSound = content->Load<xna::PSoundEffect>("Sounds/ExitReached");
}
}
xna::sptr<PlatformerStarterKit::Player> Level::Player() const
{
return player;
}
void Level::LoadTiles(xna::String const& path)
{
int width = 0;
std::vector<xna::String> lines;
std::fstream reader(path);
xna::String line;
std::getline(reader, line);
width = line.size();
while (reader.good()) {
if (line.size() != width)
std::exception("The length of line {0} is different from all preceeding lines.");
lines.push_back(line);
line.clear();
std::getline(reader, line);
}
tiles = std::vector<std::vector<Tile>>(width, std::vector<Tile>(lines.size()));
for (size_t y = 0; y < lines.size(); ++y) {
for (size_t x = 0; x < width; ++x) {
auto tileType = lines[y][x];
tiles[x][y] = LoadTile(tileType, x, y);
}
}
if (!player)
throw std::runtime_error("A level must have a starting point.");
if (exit == InvalidPosition)
throw std::runtime_error("A level must have an exit.");
}
Tile Level::LoadTile(char tileType, int x, int y) {
switch (tileType) {
case '.':
return Tile(nullptr, TileCollision::Passable);
case 'X':
return LoadExitTile(x, y);
case 'G':
return LoadGemTile(x, y);
case '-':
return LoadTile("Platform", TileCollision::Platform);
case 'A':
return LoadEnemyTile(x, y, "MonsterA");
case 'B':
return LoadEnemyTile(x, y, "MonsterB");
case 'C':
return LoadEnemyTile(x, y, "MonsterC");
case 'D':
return LoadEnemyTile(x, y, "MonsterD");
case '~':
return LoadVarietyTile("BlockB", 2, TileCollision::Platform);
case ':':
return LoadVarietyTile("BlockB", 2, TileCollision::Passable);
case '1':
return LoadStartTile(x, y);
case '#':
return LoadVarietyTile("BlockA", 7, TileCollision::Impassable);
default:
std::exception("Unsupported tile type character");
}
return Tile(nullptr, TileCollision::Passable);;
}
Tile Level::LoadTile(xna::String const& name, TileCollision collision) {
return Tile(content->Load<xna::PTexture2D>("Tiles/" + name), collision);
}
Tile Level::LoadVarietyTile(xna::String const& baseName, int variationCount, TileCollision collision) {
auto index = rand() % variationCount;
return LoadTile(baseName + std::to_string(index), collision);
}
Tile Level::LoadStartTile(int x, int y) {
if (player != nullptr)
std::exception("A level may only have one starting point.");
start = RectangleExtensions::GetBottomCenter(GetBounds(x, --y));
const auto _this = shared_from_this();
player = xna::snew<PlatformerStarterKit::Player>(_this, start);
return Tile(nullptr, TileCollision::Passable);
}
Tile Level::LoadExitTile(int x, int y) {
if (exit != InvalidPosition)
std::exception("A level may only have one exit.");
exit = GetBounds(x, y).Center();
return LoadTile("Exit", TileCollision::Passable);
}
Tile Level::LoadEnemyTile(int x, int y, xna::String const& spriteSet) {
const auto position = RectangleExtensions::GetBottomCenter(GetBounds(x, y));
const auto _this = shared_from_this();
enemies.push_back(xna::snew<Enemy>(_this, position, spriteSet));
return Tile(nullptr, TileCollision::Passable);
}
Tile Level::LoadGemTile(int x, int y) {
const auto position = GetBounds(x, y).Center();
const auto _this = shared_from_this();
gems.push_back(xna::snew<Gem>(_this, xna::Vector2(position.X, position.Y)));
return Tile(nullptr, TileCollision::Passable);
}
void Level::UpdateGems(xna::GameTime const& gameTime)
{
for (size_t i = 0; i < gems.size(); ++i) {
auto& gem = gems[i];
if (gem->IsCollected())
continue;
gem->Update(gameTime);
if (gem->BoundingCircle().Intersects(player->BoundingRectangle())) {
OnGemCollected(gem, player);
}
}
}
void Level::OnPlayerKilled(xna::sptr<Enemy> killedBy)
{
player->OnKilled(killedBy);
}
void Level::UpdateEnemies(xna::GameTime const& gameTime)
{
for (size_t i = 0; i < enemies.size(); ++i) {
auto& enemy = enemies[i];
enemy->Update(gameTime);
if (enemy->BoundingRectangle().Intersects(player->BoundingRectangle())) {
OnPlayerKilled(enemy);
}
}
}
void Level::OnExitReached()
{
player->OnReachedExit();
exitReachedSound->Play();
reachedExit = true;
}
void Level::DrawTiles(xna::SpriteBatch& spriteBatch)
{
for (size_t y = 0; y < Height(); ++y) {
for (size_t x = 0; x < Width(); ++x) {
auto& texture = tiles[x][y].Texture;
if (texture) {
const auto s = xna::Vector2(x, y);
const auto position = s * Tile::Size();
spriteBatch.Draw(texture, position, xna::Colors::White);
}
}
}
}
void Level::OnGemCollected(xna::sptr<Gem>& gem, xna::sptr<PlatformerStarterKit::Player>& collectedBy)
{
score += Gem::PointValue;
gem->OnCollected(collectedBy);
}
void Level::Initialize()
{
LoadTiles(path);
}
void Level::Update(xna::GameTime const& gameTime) {
if (!player->IsAlive() || timeRemaining == xna::TimeSpan::Zero()) {
player->ApplyPhysics(gameTime);
}
else if (reachedExit) {
auto seconds = static_cast<int64_t>(std::round(gameTime.ElapsedGameTime.TotalSeconds() * 100.0f));
seconds = std::min(seconds, static_cast<int64_t>(std::ceil(timeRemaining.TotalSeconds())));
timeRemaining = timeRemaining - xna::TimeSpan::FromSeconds(seconds);
score += seconds * PointsPerSecond;
}
else {
timeRemaining = timeRemaining - gameTime.ElapsedGameTime;
player->Update(gameTime);
UpdateGems(gameTime);
auto playerBounds = player->BoundingRectangle();
auto heigth = Height();
if (playerBounds.Top() >= heigth * Tile::Height)
OnPlayerKilled(nullptr);
UpdateEnemies(gameTime);
if (player->IsAlive() &&
player->IsOnGround() &&
player->BoundingRectangle().Contains(exit)) {
OnExitReached();
}
}
if (timeRemaining < xna::TimeSpan::Zero())
timeRemaining = xna::TimeSpan::Zero();
}
void Level::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch)
{
for (size_t i = 0; i <= EntityLayer; ++i) {
auto& layer = layers[i];
spriteBatch.Draw(layer, xna::Vector2::Zero(), xna::Colors::White);
}
DrawTiles(spriteBatch);
for (size_t i = 0; i < gems.size(); ++i) {
auto& gem = gems[i];
if (gem->IsCollected())
continue;
gem->Draw(gameTime, spriteBatch);
}
player->Draw(gameTime, spriteBatch);
for (size_t i = 0; i < enemies.size(); ++i) {
auto& enemy = enemies[i];
enemy->Draw(gameTime, spriteBatch);
}
for (size_t i = EntityLayer + 1; i < layers.size(); ++i) {
auto& layer = layers[i];
spriteBatch.Draw(layer, xna::Vector2::Zero(), xna::Colors::White);
}
}
void Level::StartNewLife() {
player->Reset(start);
}
}