|
69 | 69 | #include "transition.h" |
70 | 70 | #include "baseui.h" |
71 | 71 | #include "algo.h" |
| 72 | +#include "sprite_picture.h" |
| 73 | +#include "bitmap.h" |
72 | 74 |
|
73 | 75 | using namespace Game_Interpreter_Shared; |
74 | 76 |
|
@@ -810,6 +812,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { |
810 | 812 | return CmdSetup<&Game_Interpreter::CommandManiacCallCommand, 6>(com); |
811 | 813 | case Cmd::Maniac_GetGameInfo: |
812 | 814 | return CmdSetup<&Game_Interpreter::CommandManiacGetGameInfo, 8>(com); |
| 815 | + case static_cast<Cmd>(3025): |
| 816 | + return CmdSetup<&Game_Interpreter::CommandManiacSetPicturePixel, 8>(com); |
813 | 817 | case Cmd::EasyRpg_SetInterpreterFlag: |
814 | 818 | return CmdSetup<&Game_Interpreter::CommandEasyRpgSetInterpreterFlag, 2>(com); |
815 | 819 | case Cmd::EasyRpg_ProcessJson: |
@@ -4426,6 +4430,136 @@ bool Game_Interpreter::CommandManiacGetGameInfo(lcf::rpg::EventCommand const& co |
4426 | 4430 | return true; |
4427 | 4431 | } |
4428 | 4432 |
|
| 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 | + |
4429 | 4563 | bool Game_Interpreter::CommandManiacGetSaveInfo(lcf::rpg::EventCommand const& com) { |
4430 | 4564 | if (!Player::IsPatchManiac()) { |
4431 | 4565 | return true; |
|
0 commit comments