about summary refs log tree commit diff stats
path: root/data/maps/the_partial/rooms/Obverse Side.txtpb
blob: c0ce04bb8fe6364dc08813c35ee6a86a6ff326df (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
name: "Obverse Side"
panels {
  name: "PUN"
  path: "Panels/Main/panel_1"
  clue: "pun"
  answer: "run"
  symbols: SPARKLES
}
panels {
  name: "UP"
  path: "Panels/Main/panel_3"
  clue: "up"
  answer: "or"
  symbols: SPARKLES
}
panels {
  name: "PUT"
  path: "Panels/Main/panel_5"
  clue: "put"
  answer: "rot"
  symbols: SPARKLES
}
panels {
  name: "PUNT"
  path: "Panels/Main/panel_8"
  clue: "punt"
  answer: "runt"
  symbols: SPARKLES
}
panels {
  name: "FIGHT"
  path: "Panels/Main/panel_9"
  clue: "fight"
  answer: "right"
  symbols: SPARKLES
}
panels {
  name: "LINT"
  path: "Panels/Side 1/panel_2"
  clue: "lint"
  answer: "hint"
  symbols: SPARKLES
}
panels {
  name: "TURN"
  path: "Panels/Side 1/panel_4"
  clue: "turn"
  answer: "torn"
  symbols: SPARKLES
}
panels {
  name: "HOT"
  path: "Panels/Side 1/panel_6"
  clue: "hot"
  answer: "hut"
  symbols: SPARKLES
}
panels {
  name: "OUT"
  path: "Panels/Side 1/panel_8"
  clue: "out"
  answer: "cut"
  symbols: SPARKLES
}
panels {
  name: "TON"
  path: "Panels/Side 1/panel_9"
  clue: "ton"
  answer: "ion"
  symbols: SPARKLES
}
panels {
  name: "HUT"
  path: "Panels/Side 1/panel_10"
  clue: "hut"
  answer: "hot"
  symbols: SPARKLES
}
panels {
  name: "ION"
  path: "Panels/Side 1/panel_11"
  clue: "ion"
  answer: "ton"
  symbols: SPARKLES
}
panels {
  name: "CUT"
  path: "Panels/Side 1/panel_12"
  clue: "cut"
  answer: "out"
  symbols: SPARKLES
}
panels {
  name: "FUN"
  path: "Panels/Side 1/panel_7"
  clue: "fun"
  answer: "run"
  symbols: SPARKLES
}
ports {
  name: "GREAT"
  path: "Components/Warps/worldport"
  orientation: "west"
}
keyholders {
  # This is one of the ones that's misnamed within the game.
  name: "L"
  path: "Components/KeyHolders/keyHolderI"
  key: "l"
}
paintings {
  name: "F"
  path: "Components/Paintings/f"
  orientation: "south"
  exit_only: true
}
paintings {
  name: "P"
  path: "Components/Paintings/p"
  orientation: "south"
  exit_only: true
}
12 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
#include "renderer.h"
#include <iostream>
#include "consts.h"
#include "game.h"
#include "map.h"
#include "transform_system.h"
#include "camera_system.h"
#include "message_system.h"
#include "effect_system.h"
#include "input_system.h"

Renderer::Renderer() {
  win_ = window_ptr(
    SDL_CreateWindow(
      "Tanetane",
      100,
      100,
      GAME_WIDTH,
      GAME_HEIGHT,
      SDL_WINDOW_SHOWN));

  if (!win_)
  {
    throw sdl_error();
  }

  ren_ = renderer_ptr(
    SDL_CreateRenderer(
      win_.get(),
      -1,
      SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC));

  if (!ren_)
  {
    throw sdl_error();
  }
}

texture_ptr Renderer::renderMapLayer(const Map& map, bool above) {
  if (cachedTilesetName_ != map.getTilesetFilename()) {
    surface_ptr pfs(IMG_Load(map.getTilesetFilename().c_str()));
    if (!pfs) {
      throw img_error();
    }

    tilesetTex_ = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get()));
    cachedTilesetName_ = map.getTilesetFilename();
  }

  vec2i mapBounds = map.getMapSize() * map.getTileSize();

  texture_ptr canvas(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      mapBounds.w(),
      mapBounds.h()));

  SDL_SetTextureBlendMode(canvas.get(), SDL_BLENDMODE_BLEND);

  SDL_SetRenderTarget(ren_.get(), canvas.get());
  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND);
  SDL_SetRenderDrawColor(ren_.get(), 255, 255, 255, 0);
  SDL_RenderClear(ren_.get());

  const std::vector<std::vector<Tile>>* layers = nullptr;
  if (above) {
    layers = &map.getUpperLayers();
  } else {
    layers = &map.getLowerLayers();
  }

  for (const std::vector<Tile>& tiles : *layers) {
    for (int y = 0; y < map.getMapSize().h(); y++) {
      for (int x = 0; x < map.getMapSize().w(); x++) {
        const Tile& tile = tiles.at(x + y * map.getMapSize().w());

        SDL_Rect srcRect {
          static_cast<int>((tile.id % map.getTilesetColumns()) * map.getTileSize().w()),
          static_cast<int>((tile.id / map.getTilesetColumns()) * map.getTileSize().h()),
          map.getTileSize().w(),
          map.getTileSize().h() };

        SDL_Rect destRect {
          x * map.getTileSize().w(),
          y * map.getTileSize().h(),
          map.getTileSize().w(),
          map.getTileSize().h() };

        SDL_RendererFlip flip = SDL_FLIP_NONE;
        if (tile.flipHorizontal && tile.flipVertical) {
          flip = static_cast<SDL_RendererFlip>(SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL);
        } else if (tile.flipHorizontal) {
          flip = SDL_FLIP_HORIZONTAL;
        } else if (tile.flipVertical) {
          flip = SDL_FLIP_VERTICAL;
        }
        SDL_RenderCopyEx(ren_.get(), tilesetTex_.get(), &srcRect, &destRect, 0, nullptr, flip);
      }
    }
  }

  return canvas;
}

void Renderer::renderSprite(const Sprite& sprite) {
  if (sprite.isAnimated) {
    if (sprite.hasShadow) {
      int shadowTexId = loadImageFromFile("../res/shadow.png");
      const SDL_Rect shadowDest { sprite.loc.x() - 8, sprite.loc.y() - 8, 16, 8 };
      SDL_RenderCopy(ren_.get(), textures_.at(shadowTexId).get(), nullptr, &shadowDest);
    }

    const SpriteFrame& frame = sprite.frames.at(sprite.animations.at(sprite.animationId).frameIndices.at(sprite.animationFrame));
    const SDL_Rect& src = frame.srcRect;
    SDL_Rect dest { sprite.loc.x() - frame.center.x(), sprite.loc.y() - frame.center.y(), frame.size.w(), frame.size.h() };
    if (sprite.bobbing) {
      dest.y -= sprite.bobAmount;
    }
    SDL_RenderCopy(ren_.get(), textures_.at(loadImageFromFile(sprite.spritesheet)).get(), &src, &dest);
  }
}

void Renderer::render(Game& game) {
  auto& effects = game.getSystem<EffectSystem>();

  if (cachedMapName_ != game.getMap().getName()) {
    cachedMapName_ = game.getMap().getName();
    renLowerLayer_ = renderMapLayer(game.getMap(), false);
    renUpperLayer_ = renderMapLayer(game.getMap(), true);

    vec2i mapBounds = game.getMap().getMapSize() * game.getMap().getTileSize();
    mapSwapTex_.reset(SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      mapBounds.w(),
      mapBounds.h()));

    SDL_SetTextureBlendMode(mapSwapTex_.get(), SDL_BLENDMODE_BLEND);
  }

  vec2i mapBounds = game.getMap().getMapSize() * game.getMap().getTileSize();

  texture_ptr canvas(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      mapBounds.w(),
      mapBounds.h()));

  if (!canvas) {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), canvas.get());
  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
  SDL_SetRenderDrawColor(ren_.get(), 255, 255, 255, 255);
  SDL_RenderClear(ren_.get());

  // Render lower map layer
  SDL_RenderCopy(ren_.get(), renLowerLayer_.get(), nullptr, nullptr);

  if (effects.isMapFaded()) {
    SDL_SetRenderTarget(ren_.get(), mapSwapTex_.get());
    SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
    SDL_RenderClear(ren_.get());
    SDL_RenderCopy(ren_.get(), renLowerLayer_.get(), nullptr, nullptr);

    SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD);
    SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255);
    SDL_RenderFillRect(ren_.get(), nullptr);

    SDL_SetRenderTarget(ren_.get(), canvas.get());
    SDL_SetTextureAlphaMod(mapSwapTex_.get(), effects.getMapFadeProgress() * 255);
    SDL_RenderCopy(ren_.get(), mapSwapTex_.get(), nullptr, nullptr);
  }

  // Render normal sprite layer
  for (const Sprite& sprite : game.getSystem<TransformSystem>().getSpritesByY(SpriteLayer::Normal) | game.spriteView()) {
    renderSprite(sprite);
  }

  // Render upper map layer
  SDL_RenderCopy(ren_.get(), renUpperLayer_.get(), nullptr, nullptr);

  if (effects.isMapFaded()) {
    SDL_SetRenderTarget(ren_.get(), mapSwapTex_.get());
    SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
    SDL_RenderClear(ren_.get());
    SDL_RenderCopy(ren_.get(), renUpperLayer_.get(), nullptr, nullptr);

    SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD);
    SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255);
    SDL_RenderFillRect(ren_.get(), nullptr);

    SDL_SetRenderTarget(ren_.get(), canvas.get());
    SDL_SetTextureAlphaMod(mapSwapTex_.get(), effects.getMapFadeProgress() * 255);
    SDL_RenderCopy(ren_.get(), mapSwapTex_.get(), nullptr, nullptr);
  }

  // Render above sprite layer
  for (const Sprite& sprite : game.getSystem<TransformSystem>().getSpritesByY(SpriteLayer::Above) | game.spriteView()) {
    renderSprite(sprite);
  }

  vec2i viewport = game.getSystem<CameraSystem>().getCameraPosition() + effects.getCameraShakeOffset();

  SDL_Rect cameraField {
    viewport.x(),
    viewport.y(),
    game.getSystem<CameraSystem>().getFieldOfView().w(),
    game.getSystem<CameraSystem>().getFieldOfView().h()
  };

  texture_ptr cameraTex(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      CANVAS_WIDTH,
      CANVAS_HEIGHT));

  if (!cameraTex) {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), cameraTex.get());
  SDL_RenderCopy(ren_.get(), canvas.get(), &cameraField, nullptr);

  if (game.getSystem<MessageSystem>().getCutsceneBarsProgress() > 0) {
    SDL_SetRenderTarget(ren_.get(), cameraTex.get());
    SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
    SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255);

    int topBarHeight = 16.0 * game.getSystem<MessageSystem>().getCutsceneBarsProgress();
    SDL_Rect topBar { 0, 0, CANVAS_WIDTH, topBarHeight };
    SDL_RenderFillRect(ren_.get(), &topBar);

    int bottomBarHeight = 36.0 * game.getSystem<MessageSystem>().getCutsceneBarsProgress();
    SDL_Rect bottomBar { 0, CANVAS_HEIGHT - bottomBarHeight, CANVAS_WIDTH, bottomBarHeight };
    SDL_RenderFillRect(ren_.get(), &bottomBar);

    if (game.getSystem<MessageSystem>().getCutsceneBarsProgress() == 1.0 &&
        !game.getSystem<MessageSystem>().getLines().empty()) {
      if (!game.getSystem<MessageSystem>().getSpeaker().empty()) {
        if (speakerHeaderTex_ == -1) {
          speakerHeaderTex_ = loadImageFromFile("../res/speaker_header.png");
        }

        {
          SDL_Rect destRect { 0, 111, 57, 13 };
          SDL_SetRenderTarget(ren_.get(), cameraTex.get());
          SDL_RenderCopy(ren_.get(), textures_.at(speakerHeaderTex_).get(), nullptr, &destRect);
        }

        if (speakerHeaderLine_.line != game.getSystem<MessageSystem>().getSpeaker()) {
          renderMessageLine(speakerHeaderLine_, game.getSystem<MessageSystem>().getSpeaker(), game);
        }

        {
          SDL_Rect destRect {
            4,
            112,
            MESSAGE_TEXT_WIDTH,
            game.getFont().getCharacterHeight() };

          SDL_SetRenderTarget(ren_.get(), cameraTex.get());
          SDL_RenderCopy(ren_.get(), speakerHeaderLine_.renderedTex.get(), nullptr, &destRect);
        }
      }

      int lineIndex = 0;
      for (const MessageLine& line : game.getSystem<MessageSystem>().getLines()) {
        if (messageLines_[lineIndex].line != line.text) {
          renderMessageLine(messageLines_[lineIndex], line.text, game);
        }

        // 18x, 127y1, 143y2
        if (line.charsRevealed > 0) {
          {
            SDL_Rect srcRect {
              0, 0, messageLines_[lineIndex].charIndexToWidth[line.charsRevealed],
              game.getFont().getCharacterHeight()
            };
            SDL_Rect destRect {
              18,
              127 + 16 * lineIndex,
              srcRect.w,
              srcRect.h };

            SDL_SetRenderTarget(ren_.get(), cameraTex.get());
            SDL_RenderCopy(ren_.get(), messageLines_[lineIndex].renderedTex.get(), &srcRect, &destRect);
          }

          if (line.bulleted) {
            vec2i charLoc = game.getFont().getCharacterLocation('^');
            vec2i charSize = game.getFont().getCharacterSize('^');
            SDL_Rect srcRect { charLoc.x(), charLoc.y(), charSize.w(), charSize.h() };
            SDL_Rect destRect { 13, 127 + 16 * lineIndex, charSize.w(), charSize.h() };

            SDL_SetRenderTarget(ren_.get(), cameraTex.get());
            SDL_RenderCopy(ren_.get(), textures_.at(game.getFont().getTextureId()).get(), &srcRect, &destRect);
          }
        }

        lineIndex++;
      }

      // 216, 138, 142
      if (game.getSystem<MessageSystem>().isNextArrowShowing()) {
        if (game.getSystem<MessageSystem>().isChoiceActive()) {
          if (choiceArrowTex_ == -1) {
            choiceArrowTex_ = loadImageFromFile("../res/select_choice_arrow.png");
          }

          int selection = game.getSystem<MessageSystem>().getChoiceSelection();
          int charIndex = game.getSystem<MessageSystem>().getLines().back().choicePos[selection];
          int baseX = messageLines_[1].charIndexToWidth[charIndex];

          SDL_Rect destRect {
            18 + baseX - game.getSystem<MessageSystem>().getNextArrowBob()-8,
            144,
            8,
            8 };

          SDL_SetRenderTarget(ren_.get(), cameraTex.get());
          SDL_RenderCopy(ren_.get(), textures_.at(choiceArrowTex_).get(), nullptr, &destRect);
        } else {
          if (advMsgArrowTex_ == -1) {
            advMsgArrowTex_ = loadImageFromFile("../res/advance_text_arrow.png");
          }

          SDL_Rect destRect {
            220,
            142 + game.getSystem<MessageSystem>().getNextArrowBob(),
            8,
            8 };

          SDL_SetRenderTarget(ren_.get(), cameraTex.get());
          SDL_RenderCopy(ren_.get(), textures_.at(advMsgArrowTex_).get(), nullptr, &destRect);
        }
      }
    }
  }

  if (effects.isScreenFaded()) {
    SDL_SetRenderTarget(ren_.get(), cameraTex.get());
    SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND);
    SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, effects.getScreenFadeProgress() * 255);
    SDL_RenderFillRect(ren_.get(), nullptr);
  }

  if (effects.isCircleTransitionActive()) {
    if (effects.getCircleTransitionProgress() == 1.0) {
      SDL_SetRenderTarget(ren_.get(), cameraTex.get());
      SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
      SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255);
      SDL_RenderClear(ren_.get());
    } else {
      texture_ptr circleEffectTex(
        SDL_CreateTexture(
          ren_.get(),
          SDL_PIXELFORMAT_RGBA8888,
          SDL_TEXTUREACCESS_TARGET,
          CANVAS_WIDTH,
          CANVAS_HEIGHT));

      if (!circleEffectTex) {
        throw sdl_error();
      }

      SDL_SetRenderTarget(ren_.get(), circleEffectTex.get());
      SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
      SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255);
      SDL_RenderClear(ren_.get());

      int circleDiameter = 290 * (1.0 - effects.getCircleTransitionProgress());
      SDL_Rect circleSize {
        CANVAS_WIDTH / 2 - circleDiameter / 2,
        CANVAS_HEIGHT / 2 - circleDiameter / 2,
        circleDiameter,
        circleDiameter
      };

      SDL_Rect copyFromCamera = circleSize;
      if (circleDiameter > CANVAS_WIDTH) {
        copyFromCamera.x = 0;
        copyFromCamera.w = CANVAS_WIDTH;
      }
      if (circleDiameter > CANVAS_HEIGHT) {
        copyFromCamera.y = 0;
        copyFromCamera.h = CANVAS_HEIGHT;
      }

      SDL_RenderCopy(ren_.get(), cameraTex.get(), &copyFromCamera, &copyFromCamera);

      int pinholeTexId = loadImageFromFile("../res/pinhole.png");
      SDL_SetTextureBlendMode(textures_[pinholeTexId].get(), SDL_BLENDMODE_MOD);
      SDL_RenderCopy(ren_.get(), textures_[pinholeTexId].get(), nullptr, &circleSize);

      SDL_SetRenderTarget(ren_.get(), cameraTex.get());
      SDL_SetTextureBlendMode(circleEffectTex.get(), SDL_BLENDMODE_NONE);
      SDL_RenderCopy(ren_.get(), circleEffectTex.get(), nullptr, nullptr);
    }
  }

  if (game.getSystem<InputSystem>().isDebugConsoleOpen()) {
    // Not sure why I'm supposed to copy the cached texture to the screen
    // BEFORE rendering it, but we get flickering if we don't, so.
    SDL_SetRenderTarget(ren_.get(), cameraTex.get());
    SDL_RenderCopy(ren_.get(), debugConsoleTex_.get(), nullptr, &debugDestRect_);

    if (!debugConsoleTex_ || cachedDebugText_ != game.getSystem<InputSystem>().getDebugText()) {
      cachedDebugText_ = game.getSystem<InputSystem>().getDebugText();

      std::vector<texture_ptr> textLines;
      std::string remaining = "`" + cachedDebugText_;

      while (!remaining.empty()) {
        MessageCache output;
        renderMessageLine(output, remaining, game);
        textLines.push_back(std::move(output.renderedTex));
        remaining = output.overflow;

        if (!remaining.empty()) {
          remaining = "   " + remaining;
        }
      }

      int height = 16 * textLines.size() + 4 + 4;

      debugConsoleTex_.reset(
        SDL_CreateTexture(
          ren_.get(),
          SDL_PIXELFORMAT_RGBA8888,
          SDL_TEXTUREACCESS_TARGET,
          CANVAS_WIDTH,
          height));

      SDL_SetRenderTarget(ren_.get(), debugConsoleTex_.get());
      SDL_SetTextureBlendMode(debugConsoleTex_.get(), SDL_BLENDMODE_BLEND);
      SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND);
      SDL_SetRenderDrawColor(ren_.get(), 30, 0, 110, 127);
      //SDL_RenderFillRect(ren_.get(), nullptr);
      SDL_RenderClear(ren_.get());

      for (int i=0; i<textLines.size(); i++) {
        SDL_Rect destRect {
          18,
          4 + 16 * i,
          MESSAGE_TEXT_WIDTH,
          game.getFont().getCharacterHeight() };

        SDL_RenderCopy(ren_.get(), textLines[i].get(), nullptr, &destRect);
      }

      //SDL_SetRenderTarget(ren_.get(), nullptr);

      debugDestRect_ = SDL_Rect { 0, 0, CANVAS_WIDTH, height };
    }
  }

  SDL_SetRenderTarget(ren_.get(), nullptr);
  SDL_RenderCopy(ren_.get(), cameraTex.get(), nullptr, nullptr);
  SDL_RenderPresent(ren_.get());
}

int Renderer::loadImageFromFile(std::string filename) {
  if (!filenameToTexId_.count(filename)) {
    surface_ptr pfs(IMG_Load(filename.data()));
    if (!pfs)
    {
      throw img_error();
    }

    texture_ptr tex = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get()));
    //SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);

    int texId = textures_.size();
    textures_.push_back(std::move(tex));

    filenameToTexId_[filename] = texId;
  }

  return filenameToTexId_[filename];
}

void Renderer::renderMessageLine(MessageCache& line, const std::string& text, Game& game) {
  line.line = text;

  line.renderedTex.reset(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      MESSAGE_TEXT_WIDTH,
      game.getFont().getCharacterHeight()));

  SDL_SetTextureBlendMode(line.renderedTex.get(), SDL_BLENDMODE_BLEND);

  SDL_SetRenderTarget(ren_.get(), line.renderedTex.get());
  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND);
  SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
  SDL_RenderClear(ren_.get());

  line.charIndexToWidth.clear();
  line.charIndexToWidth.push_back(0);

  int length = 0;

  for (int i=0; i<line.line.size(); i++) {
    if (line.line[i] != ' ') {
      if (length + game.getFont().getCharacterWidth(line.line[i]) > MESSAGE_TEXT_WIDTH) {
        line.overflow = line.line.substr(i);
        break;
      }

      vec2i charLoc = game.getFont().getCharacterLocation(line.line[i]);
      vec2i charSize = game.getFont().getCharacterSize(line.line[i]);
      SDL_Rect srcRect { charLoc.x(), charLoc.y(), charSize.w(), charSize.h() };
      SDL_Rect destRect { length, 0, charSize.w(), charSize.h() };

      SDL_RenderCopy(ren_.get(), textures_.at(game.getFont().getTextureId()).get(), &srcRect, &destRect);

      length++;
    }

    length += game.getFont().getCharacterWidth(line.line[i]);
    line.charIndexToWidth.push_back(length);
  }

  line.charIndexToWidth.back()--;
}