Skip to content

Commit 1f6990f

Browse files
committed
Maniacs Patch - SetPicturePixel Command
Implements the CommandManiacSetPicturePixel function to allow direct pixel manipulation of game pictures via event commands. Handles bitmap uniqueness, format conversion, and window picture detachment to ensure safe editing. Updates command dispatch logic and header declaration.
1 parent df2d93c commit 1f6990f

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

src/game_interpreter.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
#include "transition.h"
7070
#include "baseui.h"
7171
#include "algo.h"
72+
#include "sprite_picture.h"
73+
#include "bitmap.h"
7274

7375
using namespace Game_Interpreter_Shared;
7476

@@ -810,6 +812,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) {
810812
return CmdSetup<&Game_Interpreter::CommandManiacCallCommand, 6>(com);
811813
case Cmd::Maniac_GetGameInfo:
812814
return CmdSetup<&Game_Interpreter::CommandManiacGetGameInfo, 8>(com);
815+
case static_cast<Cmd>(3025):
816+
return CmdSetup<&Game_Interpreter::CommandManiacSetPicturePixel, 8>(com);
813817
case Cmd::EasyRpg_SetInterpreterFlag:
814818
return CmdSetup<&Game_Interpreter::CommandEasyRpgSetInterpreterFlag, 2>(com);
815819
case Cmd::EasyRpg_ProcessJson:
@@ -4426,6 +4430,136 @@ bool Game_Interpreter::CommandManiacGetGameInfo(lcf::rpg::EventCommand const& co
44264430
return true;
44274431
}
44284432

4433+
bool Game_Interpreter::CommandManiacSetPicturePixel(lcf::rpg::EventCommand const& com) {
4434+
if (!Player::IsPatchManiac()) {
4435+
return true;
4436+
}
4437+
4438+
int pic_id = ValueOrVariableBitfield(com.parameters[0], 0, com.parameters[1]);
4439+
if (pic_id <= 0) {
4440+
Output::Warning("ManiacSetPicturePixel: Invalid picture ID {}", pic_id);
4441+
return true;
4442+
}
4443+
4444+
auto& pic = Main_Data::game_pictures->GetPicture(pic_id);
4445+
4446+
if (pic.IsRequestPending()) {
4447+
pic.MakeRequestImportant();
4448+
_async_op = AsyncOp::MakeYieldRepeat();
4449+
return true;
4450+
}
4451+
4452+
auto* sprite = pic.sprite.get();
4453+
if (!sprite) return true;
4454+
4455+
auto bitmap = sprite->GetBitmap();
4456+
if (!bitmap) return true;
4457+
4458+
// 1. Calculate Spritesheet Offset
4459+
// Maniacs operations are relative to the currently active cell.
4460+
int offset_x = 0;
4461+
int offset_y = 0;
4462+
4463+
const auto& data = pic.data;
4464+
if (data.spritesheet_cols > 1 || data.spritesheet_rows > 1) {
4465+
int frame_width = bitmap->GetWidth() / data.spritesheet_cols;
4466+
int frame_height = bitmap->GetHeight() / data.spritesheet_rows;
4467+
4468+
// Map current frame index to X/Y coords
4469+
offset_x = (data.spritesheet_frame % data.spritesheet_cols) * frame_width;
4470+
offset_y = (data.spritesheet_frame / data.spritesheet_cols) * frame_height;
4471+
}
4472+
4473+
// 2. COW & Window Detach Logic
4474+
BitmapRef writeable_bitmap = bitmap;
4475+
4476+
bool is_cached = !bitmap->GetId().empty() && !StartsWith(bitmap->GetId(), "Canvas:");
4477+
bool wrong_format = bitmap->bpp() != 4;
4478+
bool is_window = data.easyrpg_type == lcf::rpg::SavePicture::EasyRpgType_window;
4479+
4480+
if (is_cached || wrong_format || is_window) {
4481+
writeable_bitmap = Bitmap::Create(bitmap->GetWidth(), bitmap->GetHeight());
4482+
writeable_bitmap->BlitFast(0, 0, *bitmap, bitmap->GetRect(), 255);
4483+
4484+
writeable_bitmap->SetId("Canvas:" + std::to_string(pic_id));
4485+
4486+
sprite->SetBitmap(writeable_bitmap);
4487+
4488+
if (is_window) {
4489+
pic.data.easyrpg_type = lcf::rpg::SavePicture::EasyRpgType_default;
4490+
}
4491+
4492+
// Force sprite to recalculate its clipping rectangle immediately.
4493+
// Otherwise, SetBitmap resets src_rect to full size, showing the whole sheet.
4494+
sprite->OnPictureShow();
4495+
}
4496+
4497+
// 3. Parameters
4498+
int x = ValueOrVariableBitfield(com.parameters[0], 1, com.parameters[2]);
4499+
int y = ValueOrVariableBitfield(com.parameters[0], 2, com.parameters[3]);
4500+
int w = ValueOrVariableBitfield(com.parameters[0], 3, com.parameters[4]);
4501+
int h = ValueOrVariableBitfield(com.parameters[0], 4, com.parameters[5]);
4502+
4503+
int src_var_start = ValueOrVariableBitfield(com.parameters[0], 5, com.parameters[6]);
4504+
4505+
int flags = com.parameters[7];
4506+
bool flag_opaq = (flags & 1) != 0;
4507+
bool flag_skip_trans = (flags & 2) != 0;
4508+
4509+
// 4. Drawing Loop
4510+
int bmp_w = writeable_bitmap->GetWidth();
4511+
int bmp_h = writeable_bitmap->GetHeight();
4512+
4513+
if (w <= 0 || h <= 0) return true;
4514+
4515+
uint32_t* pixels = static_cast<uint32_t*>(writeable_bitmap->pixels());
4516+
int pitch = writeable_bitmap->pitch() / 4;
4517+
4518+
int current_var = src_var_start;
4519+
auto& vars = *Main_Data::game_variables;
4520+
4521+
for (int iy = 0; iy < h; ++iy) {
4522+
// Apply Y Offset here
4523+
int py = y + iy + offset_y;
4524+
4525+
if (py < 0 || py >= bmp_h) {
4526+
current_var += w;
4527+
continue;
4528+
}
4529+
4530+
for (int ix = 0; ix < w; ++ix) {
4531+
int32_t color_val = vars.Get(current_var++);
4532+
4533+
// Apply X Offset here
4534+
int px = x + ix + offset_x;
4535+
4536+
if (px < 0 || px >= bmp_w) continue;
4537+
4538+
uint8_t a = (color_val >> 24) & 0xFF;
4539+
uint8_t r = (color_val >> 16) & 0xFF;
4540+
uint8_t g = (color_val >> 8) & 0xFF;
4541+
uint8_t b = (color_val) & 0xFF;
4542+
4543+
if (flag_skip_trans && a == 0) continue;
4544+
4545+
if (flag_opaq) {
4546+
a = 0xFF;
4547+
}
4548+
4549+
if (a < 255) {
4550+
r = (r * a) / 255;
4551+
g = (g * a) / 255;
4552+
b = (b * a) / 255;
4553+
}
4554+
4555+
uint32_t final_pixel = Bitmap::pixel_format.rgba_to_uint32_t(r, g, b, a);
4556+
pixels[py * pitch + px] = final_pixel;
4557+
}
4558+
}
4559+
4560+
return true;
4561+
}
4562+
44294563
bool Game_Interpreter::CommandManiacGetSaveInfo(lcf::rpg::EventCommand const& com) {
44304564
if (!Player::IsPatchManiac()) {
44314565
return true;

src/game_interpreter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ class Game_Interpreter : public Game_BaseInterpreterContext
311311
bool CommandEasyRpgCloneMapEvent(lcf::rpg::EventCommand const& com);
312312
bool CommandEasyRpgDestroyMapEvent(lcf::rpg::EventCommand const& com);
313313
bool CommandManiacGetGameInfo(lcf::rpg::EventCommand const& com);
314+
bool CommandManiacSetPicturePixel(lcf::rpg::EventCommand const& com);
314315

315316
void SetSubcommandIndex(int indent, int idx);
316317
uint8_t& ReserveSubcommandIndex(int indent);

0 commit comments

Comments
 (0)