You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
6.7 KiB
286 lines
6.7 KiB
#include <algorithm> |
|
#include <memory> |
|
#include <vector> |
|
|
|
#include "devilution.h" |
|
#include "miniwin/ddraw.h" |
|
|
|
#include "DiabloUI/diabloui.h" |
|
#include "DiabloUI/credits_lines.h" |
|
#include "DiabloUI/art.h" |
|
#include "DiabloUI/art_draw.h" |
|
#include "DiabloUI/fonts.h" |
|
|
|
namespace dvl { |
|
|
|
namespace { |
|
|
|
const SDL_Rect VIEWPORT = { SCREEN_X, SCREEN_Y + 114, SCREEN_WIDTH, 251 }; |
|
constexpr int SHADOW_OFFSET_X = 2; |
|
constexpr int SHADOW_OFFSET_Y = 2; |
|
constexpr int LINE_H = 22; |
|
|
|
struct SurfaceDeleter { |
|
void operator()(SDL_Surface *surface) |
|
{ |
|
SDL_FreeSurface(surface); |
|
} |
|
}; |
|
|
|
using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>; |
|
|
|
struct CachedLine { |
|
CachedLine() = default; |
|
|
|
explicit CachedLine(std::size_t index, SurfacePtr surface) |
|
: index(index) |
|
, surface(std::move(surface)) |
|
, palette_version(pal_surface_palette_version) |
|
{ |
|
} |
|
|
|
std::size_t index; |
|
SurfacePtr surface; |
|
decltype(pal_surface_palette_version) palette_version; |
|
}; |
|
|
|
SurfacePtr RenderText(const char *text, SDL_Color color) |
|
{ |
|
if (text[0] == '\0') |
|
return nullptr; |
|
SDL_Surface *result = TTF_RenderUTF8_Solid(font, text, color); |
|
if (result == nullptr) |
|
SDL_Log(TTF_GetError()); |
|
return SurfacePtr(result); |
|
} |
|
|
|
CachedLine PrepareLine(std::size_t index) |
|
{ |
|
const char *contents = CREDITS_LINES[index]; |
|
if (contents[0] == '\t') |
|
++contents; |
|
|
|
const SDL_Color shadow_color = { 0, 0, 0, 0 }; |
|
auto text = RenderText(contents, shadow_color); |
|
|
|
// Precompose shadow and text: |
|
SurfacePtr surface; |
|
if (text != nullptr) { |
|
// Set up the target surface to have 3 colors: mask, text, and shadow. |
|
surface.reset( |
|
SDL_CreateRGBSurfaceWithFormat(0, text->w + SHADOW_OFFSET_X, text->h + SHADOW_OFFSET_Y, 8, SDL_PIXELFORMAT_INDEX8)); |
|
const SDL_Color &mask_color = palette->colors[50]; // Any color different from both shadow and text |
|
const SDL_Color &text_color = palette->colors[224]; |
|
SDL_Color colors[3] = { mask_color, text_color, shadow_color }; |
|
SDL_SetPaletteColors(surface->format->palette, colors, 0, 3); |
|
#ifdef USE_SDL1 |
|
SDL_SetColorKey(surface.get(), SDL_SRCCOLORKEY, 0); |
|
#else |
|
SDL_SetColorKey(surface.get(), SDL_TRUE, 0); |
|
#endif |
|
|
|
// Blit the shadow first: |
|
SDL_Rect shadow_rect = { SHADOW_OFFSET_X, SHADOW_OFFSET_Y, 0, 0 }; |
|
if (SDL_BlitSurface(text.get(), nullptr, surface.get(), &shadow_rect) <= -1) |
|
SDL_Log(SDL_GetError()); |
|
|
|
// Change the text surface color and blit again: |
|
#ifdef USE_SDL1 |
|
SDL_SetColorKey(text.get(), SDL_SRCCOLORKEY, 0); |
|
SDL_Color text_colors[2] = { mask_color, text_color }; |
|
if (SDL_SetPalette(text.get(), SDL_LOGPAL, text_colors, 0, 2) != 1) |
|
SDL_Log(SDL_GetError()); |
|
#else |
|
text->format->palette->colors[0] = mask_color; |
|
text->format->palette->colors[1] = text_color; |
|
SDL_SetColorKey(text.get(), SDL_TRUE, 0); |
|
#endif |
|
if (SDL_BlitSurface(text.get(), nullptr, surface.get(), nullptr) <= -1) |
|
SDL_Log(SDL_GetError()); |
|
} |
|
|
|
return CachedLine(index, std::move(surface)); |
|
} |
|
|
|
/** |
|
* Similar to std::deque<CachedLine> but simpler and backed by a single vector. |
|
*/ |
|
class LinesBuffer { |
|
public: |
|
LinesBuffer(std::size_t capacity) |
|
: start_(0) |
|
, end_(0) |
|
, empty_(true) |
|
{ |
|
data_.reserve(capacity); |
|
for (std::size_t i = 0; i < capacity; ++i) |
|
data_.push_back(CachedLine()); |
|
} |
|
|
|
bool empty() const |
|
{ |
|
return empty_; |
|
} |
|
|
|
CachedLine &front() |
|
{ |
|
return data_[start_]; |
|
} |
|
|
|
CachedLine &back() |
|
{ |
|
return data_[end_]; |
|
} |
|
|
|
CachedLine &operator[](std::size_t i) |
|
{ |
|
return data_[(start_ + i) % data_.size()]; |
|
} |
|
|
|
std::size_t size() const |
|
{ |
|
if (empty_) |
|
return 0; |
|
return start_ < end_ ? end_ - start_ : data_.size(); |
|
} |
|
|
|
void pop_front() |
|
{ |
|
start_ = (start_ + 1) % data_.size(); |
|
if (start_ == end_) |
|
empty_ = true; |
|
} |
|
|
|
void push_back(CachedLine &&line) |
|
{ |
|
end_ = (end_ + 1) % data_.size(); |
|
data_[end_] = std::move(line); |
|
empty_ = false; |
|
} |
|
|
|
private: |
|
std::size_t start_; |
|
std::size_t end_; |
|
bool empty_; |
|
std::vector<CachedLine> data_; |
|
}; |
|
|
|
class CreditsRenderer { |
|
|
|
public: |
|
CreditsRenderer() |
|
: lines_(VIEWPORT.h / LINE_H + 1) |
|
, finished_(false) |
|
, prev_offset_y_(0) |
|
|
|
{ |
|
LoadBackgroundArt("ui_art\\credits.pcx"); |
|
LoadTtfFont(); |
|
ticks_begin_ = SDL_GetTicks(); |
|
} |
|
|
|
~CreditsRenderer() |
|
{ |
|
ArtBackground.Unload(); |
|
UnloadTtfFont(); |
|
} |
|
|
|
void Render(); |
|
|
|
bool Finished() const |
|
{ |
|
return finished_; |
|
} |
|
|
|
private: |
|
LinesBuffer lines_; |
|
bool finished_; |
|
decltype(SDL_GetTicks()) ticks_begin_; |
|
int prev_offset_y_; |
|
}; |
|
|
|
void BlitToViewport(SDL_Surface *surface, int x, int y) |
|
{ |
|
SDL_Rect dest_rect = { |
|
static_cast<decltype(SDL_Rect().x)>(x), static_cast<decltype(SDL_Rect().y)>(y), 0, 0 |
|
}; |
|
if (SDL_BlitSurface(surface, nullptr, pal_surface, &dest_rect) <= -1) { |
|
SDL_Log(SDL_GetError()); |
|
} |
|
} |
|
|
|
void CreditsRenderer::Render() |
|
{ |
|
const int offset_y = -(VIEWPORT.y - LINE_H) + (SDL_GetTicks() - ticks_begin_) / 40; |
|
if (offset_y == prev_offset_y_) |
|
return; |
|
prev_offset_y_ = offset_y; |
|
|
|
DrawArt(0, 0, &ArtBackground); |
|
if (font == nullptr) |
|
return; |
|
|
|
const std::size_t lines_begin = std::max(offset_y / LINE_H, 0); |
|
const std::size_t lines_end = std::min(lines_begin + (VIEWPORT.h - 1) / LINE_H + 1, CREDITS_LINES_SIZE); |
|
|
|
if (lines_begin >= lines_end) { |
|
if (lines_end == CREDITS_LINES_SIZE) |
|
finished_ = true; |
|
return; |
|
} |
|
|
|
while (!lines_.empty() && lines_.front().index != lines_begin) |
|
lines_.pop_front(); |
|
if (lines_.empty()) |
|
lines_.push_back(PrepareLine(lines_begin)); |
|
while (lines_.back().index + 1 != lines_end) |
|
lines_.push_back(PrepareLine(lines_.back().index + 1)); |
|
|
|
SDL_SetClipRect(pal_surface, &VIEWPORT); |
|
int dest_y = VIEWPORT.y - (offset_y - lines_begin * LINE_H); |
|
for (std::size_t i = 0; i < lines_.size(); ++i, dest_y += LINE_H) { |
|
auto &line = lines_[i]; |
|
if (line.surface == nullptr) |
|
continue; |
|
|
|
// Still fading in: the cached line was drawn with a different fade level. |
|
if (line.palette_version != pal_surface_palette_version) |
|
line = PrepareLine(line.index); |
|
|
|
decltype(SDL_Rect().x) dest_x = VIEWPORT.x + 31; |
|
if (CREDITS_LINES[line.index][0] == '\t') |
|
dest_x += 40; |
|
|
|
BlitToViewport(line.surface.get(), dest_x, dest_y); |
|
} |
|
SDL_SetClipRect(pal_surface, nullptr); |
|
} |
|
|
|
} // namespace |
|
|
|
BOOL UiCreditsDialog(int a1) |
|
{ |
|
CreditsRenderer credits_renderer; |
|
bool endMenu = false; |
|
|
|
SDL_Event event; |
|
do { |
|
credits_renderer.Render(); |
|
UiFadeIn(); |
|
while (SDL_PollEvent(&event)) { |
|
switch (event.type) { |
|
case SDL_KEYDOWN: |
|
case SDL_MOUSEBUTTONDOWN: |
|
endMenu = true; |
|
break; |
|
case SDL_QUIT: |
|
exit(0); |
|
} |
|
} |
|
} while (!endMenu && !credits_renderer.Finished()); |
|
BlackPalette(); |
|
|
|
return true; |
|
} |
|
|
|
} // namespace dvl
|
|
|