Пример #1
0
bool Rect::contains(const Int2 &point) const
{
	return (point.getX() >= this->getLeft()) &&
		(point.getY() >= this->getTop()) &&
		(point.getX() < this->getRight()) &&
		(point.getY() < this->getBottom());
}
Пример #2
0
Renderer::Renderer(int width, int height, bool fullscreen, double letterboxAspect)
{
    Debug::mention("Renderer", "Initializing.");

    assert(width > 0);
    assert(height > 0);

    this->letterboxAspect = letterboxAspect;

    // Initialize window. The SDL_Surface is obtained from this window.
    this->window = [width, height, fullscreen]()
    {
        std::string title = "OpenTESArena";
        return fullscreen ?
               SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED,
                                SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP) :
               SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED,
                                SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_RESIZABLE);
    }();
    Debug::check(this->window != nullptr, "Renderer", "SDL_CreateWindow");

    // Initialize renderer context.
    this->renderer = this->createRenderer();

    // Use window dimensions, just in case it's fullscreen and the given width and
    // height are ignored.
    Int2 windowDimensions = this->getWindowDimensions();

    // Initialize native frame buffer.
    this->nativeTexture = this->createTexture(Renderer::DEFAULT_PIXELFORMAT,
                          SDL_TEXTUREACCESS_TARGET, windowDimensions.getX(), windowDimensions.getY());
    Debug::check(this->nativeTexture != nullptr, "Renderer",
                 "Couldn't create native frame buffer, " + std::string(SDL_GetError()));

    // Initialize 320x200 frame buffer.
    this->originalTexture = this->createTexture(Renderer::DEFAULT_PIXELFORMAT,
                            SDL_TEXTUREACCESS_TARGET, Renderer::ORIGINAL_WIDTH, Renderer::ORIGINAL_HEIGHT);

    // Don't initialize the game world buffer until the 3D renderer is initialized.
    this->clProgram = nullptr;
    this->gameWorldTexture = nullptr;
    this->fullGameWindow = false;

    // Set the original frame buffer to not use transparency by default.
    this->useTransparencyBlending(false);
}
Пример #3
0
Int2 Renderer::originalPointToNative(const Int2 &originalPoint) const
{
    // From original point to letterbox point.
    double originalXPercent = static_cast<double>(originalPoint.getX()) /
                              static_cast<double>(Renderer::ORIGINAL_WIDTH);
    double originalYPercent = static_cast<double>(originalPoint.getY()) /
                              static_cast<double>(Renderer::ORIGINAL_HEIGHT);

    const auto letterbox = this->getLetterboxDimensions();
    Int2 letterboxPoint(
        static_cast<double>(letterbox.w) * originalXPercent,
        static_cast<double>(letterbox.h) * originalYPercent);

    // Then from letterbox point to native point.
    Int2 nativePoint(
        letterboxPoint.getX() + letterbox.x,
        letterboxPoint.getY() + letterbox.y);

    return nativePoint;
}
Пример #4
0
Int2 Renderer::nativePointToOriginal(const Int2 &nativePoint) const
{
    // From native point to letterbox point.
    Int2 windowDimensions = this->getWindowDimensions();
    const auto letterbox = this->getLetterboxDimensions();

    Int2 letterboxPoint(
        nativePoint.getX() - letterbox.x,
        nativePoint.getY() - letterbox.y);

    // Then from letterbox point to original point.
    double letterboxXPercent = static_cast<double>(letterboxPoint.getX()) /
                               static_cast<double>(letterbox.w);
    double letterboxYPercent = static_cast<double>(letterboxPoint.getY()) /
                               static_cast<double>(letterbox.h);

    Int2 originalPoint(
        static_cast<double>(Renderer::ORIGINAL_WIDTH) * letterboxXPercent,
        static_cast<double>(Renderer::ORIGINAL_HEIGHT) * letterboxYPercent);

    return originalPoint;
}
Пример #5
0
SDL_Surface *Renderer::getScreenshot() const
{
    const Int2 dimensions = this->getWindowDimensions();
    SDL_Surface *screenshot = Surface::createSurfaceWithFormat(
                                  dimensions.getX(), dimensions.getY(),
                                  Renderer::DEFAULT_BPP, Renderer::DEFAULT_PIXELFORMAT);

    int status = SDL_RenderReadPixels(this->renderer, nullptr,
                                      screenshot->format->format, screenshot->pixels, screenshot->pitch);

    if (status == 0)
    {
        Debug::mention("Renderer", "Screenshot taken.");
    }
    else
    {
        Debug::crash("Renderer", "Couldn't take screenshot, " +
                     std::string(SDL_GetError()));
    }

    return screenshot;
}
Пример #6
0
GameWorldPanel::GameWorldPanel(GameState *gameState)
	: Panel(gameState)
{
	assert(gameState->gameDataIsActive());

	this->playerNameTextBox = [gameState]()
	{
		int x = 17;
		int y = 154;
		Color color(215, 121, 8);
		std::string text = gameState->getGameData()->getPlayer().getFirstName();
		auto &font = gameState->getFontManager().getFont(FontName::Char);
		auto alignment = TextAlignment::Left;
		return std::unique_ptr<TextBox>(new TextBox(
			x,
			y,
			color,
			text,
			font,
			alignment,
			gameState->getRenderer()));
	}();

	this->automapButton = []()
	{
		auto function = [](GameState *gameState)
		{
			std::unique_ptr<Panel> automapPanel(new AutomapPanel(gameState));
			gameState->setPanel(std::move(automapPanel));
		};
		return std::unique_ptr<Button>(new Button(function));
	}();

	this->characterSheetButton = []()
	{
		auto function = [](GameState *gameState)
		{
			std::unique_ptr<Panel> sheetPanel(new CharacterPanel(gameState));
			gameState->setPanel(std::move(sheetPanel));
		};
		return std::unique_ptr<Button>(new Button(function));
	}();

	this->logbookButton = []()
	{
		auto function = [](GameState *gameState)
		{
			std::unique_ptr<Panel> logbookPanel(new LogbookPanel(gameState));
			gameState->setPanel(std::move(logbookPanel));
		};
		return std::unique_ptr<Button>(new Button(function));
	}();

	this->pauseButton = []()
	{
		auto function = [](GameState *gameState)
		{
			std::unique_ptr<Panel> pausePanel(new PauseMenuPanel(gameState));
			gameState->setPanel(std::move(pausePanel));
		};
		return std::unique_ptr<Button>(new Button(function));
	}();

	this->worldMapButton = []()
	{
		auto function = [](GameState *gameState)
		{
			std::unique_ptr<Panel> mapPanel(new WorldMapPanel(gameState));
			gameState->setPanel(std::move(mapPanel));
		};
		return std::unique_ptr<Button>(new Button(function));
	}();

	// Set all of the cursor regions relative to the current window.
	const Int2 screenDims = gameState->getRenderer().getWindowDimensions();
	this->updateCursorRegions(screenDims.getX(), screenDims.getY());
}
Пример #7
0
void GameWorldPanel::render(Renderer &renderer)
{
	assert(this->getGameState()->gameDataIsActive());

	// Clear full screen.
	renderer.clearNative();
	renderer.clearOriginal();

	// Draw game world onto the native frame buffer. The game world buffer
	// might not completely fill up the native buffer (bottom corners), so 
	// clearing the native buffer beforehand is still necessary.
	renderer.renderWorld();

	// Set screen palette.
	auto &textureManager = this->getGameState()->getTextureManager();
	textureManager.setPalette(PaletteFile::fromName(PaletteName::Default));

	// Set original frame buffer blending to true.
	renderer.useTransparencyBlending(true);

	// Draw game world interface.
	const auto &gameInterface = textureManager.getTexture(
		TextureFile::fromName(TextureName::GameWorldInterface));
	renderer.drawToOriginal(gameInterface.get(), 0, 
		Renderer::ORIGINAL_HEIGHT - gameInterface.getHeight());

	// Draw player portrait.
	const auto &player = this->getGameState()->getGameData()->getPlayer();
	const auto &headsFilename = PortraitFile::getHeads(
		player.getGenderName(), player.getRaceName(), true);
	const auto &portrait = textureManager.getTextures(headsFilename)
		.at(player.getPortraitID());
	const auto &status = textureManager.getTextures(
		TextureFile::fromName(TextureName::StatusGradients)).at(0);
	renderer.drawToOriginal(status.get(), 14, 166);
	renderer.drawToOriginal(portrait.get(), 14, 166);

	// Draw compass slider (the actual headings). +X is north, +Z is east.
	// Should do some sin() and cos() functions to get the pixel offset.
	auto *compassSlider = textureManager.getSurface(
		TextureFile::fromName(TextureName::CompassSlider));

	Texture compassSliderSegment = [&renderer, &compassSlider]()
	{
		SDL_Surface *segmentTemp = Surface::createSurfaceWithFormat(32, 7,
			Renderer::DEFAULT_BPP, Renderer::DEFAULT_PIXELFORMAT);

		SDL_Rect clipRect;
		clipRect.x = 60; // Arbitrary offset until compass rotation works.
		clipRect.y = 0;
		clipRect.w = segmentTemp->w;
		clipRect.h = segmentTemp->h;

		SDL_BlitSurface(compassSlider, &clipRect, segmentTemp, nullptr);

		SDL_Texture *segment = renderer.createTextureFromSurface(segmentTemp);
		SDL_FreeSurface(segmentTemp);

		return Texture(segment);
	}();

	renderer.drawToOriginal(compassSliderSegment.get(),
		(Renderer::ORIGINAL_WIDTH / 2) - (compassSliderSegment.getWidth() / 2),
		compassSliderSegment.getHeight());

	// Draw compass frame over the headings.
	const auto &compassFrame = textureManager.getTexture(
		TextureFile::fromName(TextureName::CompassFrame));
	renderer.drawToOriginal(compassFrame.get(),
		(Renderer::ORIGINAL_WIDTH / 2) - (compassFrame.getWidth() / 2), 0);

	// If the player's class can't use magic, show the darkened spell icon.
	if (!player.getCharacterClass().canCastMagic())
	{
		const auto &nonMagicIcon = textureManager.getTexture(
			TextureFile::fromName(TextureName::NoSpell));
		renderer.drawToOriginal(nonMagicIcon.get(), 91, 177);
	}

	// Draw text: player name.
	renderer.drawToOriginal(this->playerNameTextBox->getTexture(),
		this->playerNameTextBox->getX(), this->playerNameTextBox->getY());

	// Scale the original frame buffer onto the native one.
	// This shouldn't be done for the game world interface because it needs to
	// clamp to the screen edges, not the letterbox edges. 
	// Fix this eventually... again.
	renderer.drawOriginalToNative();

	// Draw cursor, depending on its position on the screen.
	const Int2 mousePosition = this->getMousePosition();

	const Texture &cursor = [this, &mousePosition, &textureManager]()
		-> const Texture& // Interesting how this return type isn't deduced in MSVC.
	{
		// See which arrow cursor region the native mouse is in.
		for (int i = 0; i < this->nativeCursorRegions.size(); ++i)
		{
			if (this->nativeCursorRegions.at(i)->contains(mousePosition))
			{
				return textureManager.getTextures(
					TextureFile::fromName(TextureName::ArrowCursors)).at(i);
			}
		}

		// If not in any of the arrow regions, use the default sword cursor.
		return textureManager.getTexture(
			TextureFile::fromName(TextureName::SwordCursor));
	}();

	renderer.drawToNative(cursor.get(), mousePosition.getX(), mousePosition.getY(),
		static_cast<int>(cursor.getWidth() * this->getCursorScale()),
		static_cast<int>(cursor.getHeight() * this->getCursorScale()));

	// Set the transparency blending back to normal (off).
	renderer.useTransparencyBlending(false);
}
Пример #8
0
void GameWorldPanel::handleMouse(double dt)
{
	static_cast<void>(dt);

	const auto &renderer = this->getGameState()->getRenderer();

	const uint32_t mouse = SDL_GetRelativeMouseState(nullptr, nullptr);
	const bool leftClick = (mouse & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;

	if (leftClick)
	{
		// Horizontal camera movement rough draft. The original camera controls for 
		// Arena are bad, but I am simulating them before thinking of adding modern 
		// 3D camera support (like Daggerfall) as an option.
		const Int2 screenDimensions = renderer.getWindowDimensions();
		const Int2 mousePosition = this->getMousePosition();

		// Strength of turning is determined by proximity of the mouse cursor to
		// the left or right screen edge.
		const double dx = [this, &mousePosition, &screenDimensions]()
		{
			const int mouseX = mousePosition.getX();

			// Native cursor regions (scaled to the current window).
			const Rect &middleLeft = *this->nativeCursorRegions.at(3).get();
			const Rect &middleRight = *this->nativeCursorRegions.at(5).get();

			// Measure the magnitude of rotation. -1.0 is left, 1.0 is right.
			double percent = 0.0;
			if (middleLeft.contains(mousePosition))
			{
				percent = -1.0 + (static_cast<double>(mouseX) / middleLeft.getWidth());
			}
			else if (middleRight.contains(mousePosition))
			{
				percent = static_cast<double>(mouseX - middleRight.getLeft()) /
					middleRight.getWidth();
			}

			// Reduce the magnitude by a lot as a baseline. Sensitivity can be 
			// tweaked in the options.
			percent *= 0.010;

			// No NaNs or infinities allowed.
			return std::isfinite(percent) ? percent : 0.0;
		}();

		auto &player = this->getGameState()->getGameData()->getPlayer();
		const auto &options = this->getGameState()->getOptions();

		// Yaw the camera left or right. No vertical movement in classic camera mode.
		player.rotate(dx, 0.0, options.getHorizontalSensitivity(),
			options.getVerticalSensitivity(), options.getVerticalFOV());
	}

	// Later in development, a 3D camera would be fun (more like Daggerfall), but 
	// for now the objective is to more closely resemble the original game, so the
	// rough draft 3D camera code below is commented out as a result.

	// Make the camera look around.
	/*int dx, dy;
	const auto mouse = SDL_GetRelativeMouseState(&dx, &dy);

	bool leftClick = (mouse & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
	bool rightClick = (mouse & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
	bool turning = ((dx != 0) || (dy != 0)) && leftClick;

	if (turning)
	{
		auto dimensions = this->getGameState()->getRenderer().getWindowDimensions();
		double dxx = static_cast<double>(dx) / static_cast<double>(dimensions.getX());
		double dyy = static_cast<double>(dy) / static_cast<double>(dimensions.getY());

		// Pitch and/or yaw the camera.
		const auto &options = this->getGameState()->getOptions();
		this->getGameState()->getGameData()->getPlayer().rotate(dxx, -dyy,
			options.getHorizontalSensitivity(), options.getVerticalSensitivity(),
			options.getVerticalFOV());
	}*/
}