Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,14 @@ namespace {

// EasyRPG extensions add support for large charsets; size is spoofed to ignore the error
if (!filename.empty() && filename.front() == '$' && T == Material::Charset && Player::HasEasyRpgExtensions()) {
w = 288;
h = 256;
w = min_w;
h = min_h;
}

// Maniac Patch adds support for more colors; size is spoofed to ignore the error
if (T == Material::System && Player::IsPatchManiac()) {
w = min_w;
h = min_h;
}

if (w < min_w || max_w < w || h < min_h || max_h < h) {
Expand Down
21 changes: 11 additions & 10 deletions src/font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -937,26 +937,27 @@ FontRef Font::exfont = std::make_shared<ExFont>();
Font::GlyphRet ExFont::vRender(char32_t glyph) const {
if (EP_UNLIKELY(!bm)) { bm = Bitmap::Create(WIDTH, HEIGHT, true); }
auto exfont = Cache::Exfont();
bm->Clear();

Rect rect(0, 0, 0, 0);

bool is_lower = (glyph >= 'a' && glyph <= 'z');
bool is_upper = (glyph >= 'A' && glyph <= 'Z');
// Glyph contains two packed coordinates (YX, 8 bits each)
int x = glyph & 0xFF;
int y = (glyph >> 8) & 0xFF;
rect = Rect(x * WIDTH, y * HEIGHT, WIDTH, HEIGHT);

if (!is_lower && !is_upper) {
// Invalid ExFont
if (rect.x + rect.width > exfont->GetWidth() || rect.y + rect.height > exfont->GetHeight()) {
// Coordinates are out of bounds for the ExFont sheet
return { bm, {WIDTH, 0}, {0, 0}, false };
}

glyph = is_lower ? (glyph - 'a' + 26) : (glyph - 'A');

Rect const rect((glyph % 13) * WIDTH, (glyph / 13) * HEIGHT, WIDTH, HEIGHT);
bm->Clear();
bm->Blit(0, 0, *exfont, rect, Opacity::Opaque());

// EasyRPG Extension: Support for colored ExFont
bool has_color = false;
const auto* pixels = reinterpret_cast<uint8_t*>(bm->pixels());
// For performance reasons only check the red channel of every 4th pixel (16 = 4 * 4 RGBA pixel) for color
for (int i = 0; i < bm->pitch() * bm->height(); i += 16) {
// For performance reasons only check the red channel of every pixel for color
for (int i = 0; i < bm->pitch() * bm->height(); i += 4) {
auto pixel = pixels[i];
if (pixel != 0 && pixel != 255) {
has_color = true;
Expand Down
24 changes: 18 additions & 6 deletions src/game_message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,31 +192,33 @@
const char* end,
uint32_t escape_char,
bool skip_prefix,
int max_recursion)
int max_recursion,
bool parse_array)
{
if (!skip_prefix) {
const auto begin = iter;
if (iter == end) {
return { begin, 0 };

Check warning on line 201 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 201 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 201 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 201 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 201 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]
}
auto ret = Utils::UTF8Next(iter, end);
// Invalid commands
if (ret.ch != escape_char) {
return { begin, 0 };

Check warning on line 206 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 206 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 206 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 206 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 206 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]
}
iter = ret.next;
if (iter == end || (*iter != upper && *iter != lower)) {
return { begin, 0 };

Check warning on line 210 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 210 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 210 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 210 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 210 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]
}
++iter;
}

// If no bracket, RPG_RT will return 0.
if (iter == end || *iter != '[') {
return { iter, 0 };

Check warning on line 217 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 217 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (arm64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 217 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 217 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]

Check warning on line 217 in src/game_message.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

missing initializer for member 'Game_Message::ParseParamResult::values' [-Wmissing-field-initializers]
}

int value = 0;
std::vector<int> values;
++iter;
bool stop_parsing = false;
bool got_valid_number = false;
Expand All @@ -241,6 +243,14 @@
auto ch = ret.ch;
iter = ret.next;

if (parse_array && ch == ',') {
// command contains a list of numbers
values.push_back(value);
value = 0;
got_valid_number = false;
continue;
}

// Recursive variable case.
if (ch == escape_char) {
if (iter != end && (*iter == 'V' || *iter == 'v')) {
Expand Down Expand Up @@ -273,15 +283,17 @@
++iter;
}

values.emplace_back(value);

// Actor 0 references the first party member
if (upper == 'N' && value == 0 && got_valid_number) {
if (upper == 'N' && values.front() == 0 && got_valid_number) {
auto* party = Main_Data::game_party.get();
if (party->GetBattlerCount() > 0) {
value = (*party)[0].GetId();
values.front() = (*party)[0].GetId();
}
}

return { iter, value };
return { iter, values.front(), values };
}

Game_Message::ParseParamStringResult Game_Message::ParseStringParam(
Expand Down Expand Up @@ -358,8 +370,8 @@
return ParseParam('T', 't', iter, end, escape_char, skip_prefix, max_recursion);
}

Game_Message::ParseParamResult Game_Message::ParseColor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix, int max_recursion) {
return ParseParam('C', 'c', iter, end, escape_char, skip_prefix, max_recursion);
Game_Message::ParseParamResult Game_Message::ParseColor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix, int max_recursion, bool parse_array) {
return ParseParam('C', 'c', iter, end, escape_char, skip_prefix, max_recursion, parse_array);
}

Game_Message::ParseParamResult Game_Message::ParseSpeed(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix, int max_recursion) {
Expand Down
8 changes: 6 additions & 2 deletions src/game_message.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ namespace Game_Message {
const char* next = nullptr;
/** value that was parsed */
int value = 0;
/** multiple values in case of array parsing. For compatibility first number is also stored in `value` */
std::vector<int> values;

bool is_array() const { return values.size() >= 2; }
};

/** Struct returned by parameter parsing methods */
Expand Down Expand Up @@ -156,7 +160,7 @@ namespace Game_Message {
*
* @return \refer ParseParamResult
*/
ParseParamResult ParseColor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion);
ParseParamResult ParseColor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion, bool parse_array = false);

/** Parse a \s[] speed string
*
Expand All @@ -182,7 +186,7 @@ namespace Game_Message {
*/
ParseParamResult ParseActor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion);

Game_Message::ParseParamResult ParseParam(char upper, char lower, const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion);
Game_Message::ParseParamResult ParseParam(char upper, char lower, const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion, bool parse_array = false);
// same as ParseParam but the parameter is of structure \x[some_word] instead of \x[1]
Game_Message::ParseParamStringResult ParseStringParam(char upper, char lower, const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion);
}
Expand Down
31 changes: 21 additions & 10 deletions src/game_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) {
case 'C':
{
// Color
text_index = Game_Message::ParseColor(text_index, end, Player::escape_char, true).next;
text_index = Game_Message::ParseColor(text_index, end, Player::escape_char, true, Game_Message::default_max_recursion, Player::IsPatchManiac()).next;
}
break;
}
Expand Down Expand Up @@ -429,16 +429,27 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) {

// Special message codes
switch (ch) {
case 'c':
case 'C':
{
// Color
auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true);
auto value = pres.value;
text_index = pres.next;
text_color = value > 19 ? 0 : value;
case 'c':
case 'C':
{
// Color
auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true, Game_Message::default_max_recursion, Player::IsPatchManiac());
text_index = pres.next;

if (Player::IsPatchManiac()) {
if (pres.is_array()) {
// Maniacs \C[x,y] -> y * 10 + x
text_color = pres.values[1] * 10 + pres.values[0];
} else {
// Maniacs \C[n] (arbitrary amount of colors)
text_color = pres.values[0];
}
}
else {
text_color = pres.value > 19 ? 0 : pres.value;
}
break;
}
break;
}
continue;
}
Expand Down
58 changes: 50 additions & 8 deletions src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
// Headers
#include "utils.h"
#include "compiler.h"
#include "game_message.h"
#include "player.h"

#include <cassert>
#include <cstdint>
#include <cinttypes>
Expand Down Expand Up @@ -408,17 +411,56 @@ int Utils::UTF8Length(std::string_view str) {

Utils::ExFontRet Utils::ExFontNext(const char* iter, const char* end) {
ExFontRet ret;
if (end - iter >= 2 && *iter == '$') {
auto next_ch = *(iter + 1);
// Don't use std::isalpha, because it's indirects based on locale.
bool is_lower = (next_ch >= 'a' && next_ch <= 'z');
bool is_upper = (next_ch >= 'A' && next_ch <= 'Z');
if (is_lower || is_upper) {
ret.next = iter + 2;
ret.value = next_ch;
if (end - iter < 2 || *iter != '$') {
return ret; // Not an ExFont command.
}

// The (0xFFu << 16) is to avoid detection as a control character by the
// font rendering code

// Maniacs Patch Extended Syntax $[x,y] Handling
if (Player::IsPatchManiac() && *(iter + 1) == '[') {
auto pres = Game_Message::ParseParam('$', '$', ++iter, end, Player::escape_char, true, Game_Message::default_max_recursion, true);
if (pres.values.empty()) {
// parse error
return ret;
}

ret.next = pres.next;

if (pres.is_array()) {
// XY Mode: $[x,y]
uint32_t x = pres.values[0];
uint32_t y = pres.values[1];
ret.value = (0xFFu << 16) | (y << 8) | x;
ret.is_valid = true;
return ret;
}
else if (pres.values.size() == 1) {
// Index Mode: $[n] or $[\V[n]]
int icon_index = pres.values[0];
int x = icon_index % 13;
int y = icon_index / 13;
ret.value = (0xFFu << 16) | (static_cast<uint32_t>(y) << 8) | static_cast<uint32_t>(x);
ret.is_valid = true;
return ret;
}
}

// Standard $A-Z Syntax
auto next_ch = *(iter + 1);
bool is_lower = (next_ch >= 'a' && next_ch <= 'z');
bool is_upper = (next_ch >= 'A' && next_ch <= 'Z');

if (is_lower || is_upper) {
ret.next = iter + 2;
char32_t adjusted_glyph = is_lower ? (next_ch - 'a' + 26) : (next_ch - 'A');
int x = adjusted_glyph % 13;
int y = adjusted_glyph / 13;
ret.value = (0xFFu << 16) | (static_cast<uint32_t>(y) << 8) | static_cast<uint32_t>(x);
ret.is_valid = true;
}

return ret;
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ namespace Utils {

struct ExFontRet {
const char* next = nullptr;
char value = '\0';
uint32_t value = 0;
bool is_valid = false;

explicit operator bool() const { return is_valid; }
Expand Down
30 changes: 21 additions & 9 deletions src/window_message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -574,16 +574,28 @@ void Window_Message::UpdateMessage() {
switch (ch) {
case 'c':
case 'C':
{
// Color
auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true);
auto value = pres.value;
text_index = pres.next;
DebugLogText("{}: MSG Color \\c[{}]", value);
SetWaitForNonPrintable(0);
text_color = value > 19 ? 0 : value;
{
// Color
auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true, Game_Message::default_max_recursion, Player::IsPatchManiac());
text_index = pres.next;

if (Player::IsPatchManiac()) {
if (pres.is_array()) {
// Maniacs \C[x,y] -> y * 10 + x
text_color = pres.values[1] * 10 + pres.values[0];
} else {
// Maniacs \C[n] (arbitrary amount of colors)
text_color = pres.values[0];
}
}
break;
else {
text_color = pres.value > 19 ? 0 : pres.value;
}

DebugLogText("{}: MSG Color \\c[{}]", text_color);
SetWaitForNonPrintable(0);
}
break;
case 's':
case 'S':
{
Expand Down
Loading
Loading