Files
scummvm-cursorfix/engines/ags/engine/ac/walk_behind.cpp
2026-02-02 04:50:13 +01:00

166 lines
5.6 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/engine/ac/walk_behind.h"
#include "ags/shared/ac/common.h"
#include "ags/shared/ac/common_defines.h"
#include "ags/engine/ac/room_status.h"
#include "ags/engine/ac/game_state.h"
#include "ags/engine/gfx/graphics_driver.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
using namespace AGS::Engine;
// Generates walk-behinds as separate sprites
void walkbehinds_generate_sprites() {
const Bitmap *mask = _GP(thisroom).WalkBehindMask.get();
const Bitmap *bg = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get();
const int coldepth = bg->GetColorDepth();
Bitmap wbbmp; // temp buffer
// Iterate through walk-behinds and generate a texture for each of them
for (int wb = 1 /* 0 is "no area" */; wb < MAX_WALK_BEHINDS; ++wb) {
const Rect pos = _G(walkBehindAABB)[wb];
if (pos.Right > 0) {
wbbmp.CreateTransparent(pos.GetWidth(), pos.GetHeight(), coldepth);
// Copy over all solid pixels belonging to this WB area
const int sx = pos.Left, ex = pos.Right, sy = pos.Top, ey = pos.Bottom;
for (int y = sy; y <= ey; ++y) {
const uint8_t *check_line = mask->GetScanLine(y);
const uint8_t *src_line = bg->GetScanLine(y);
uint8_t *dst_line = wbbmp.GetScanLineForWriting(y - sy);
for (int x = sx; x <= ex; ++x) {
if (check_line[x] != wb) continue;
switch (coldepth) {
case 8:
dst_line[(x - sx)] = src_line[x];
break;
case 16:
reinterpret_cast<uint16_t *>(dst_line)[(x - sx)] =
reinterpret_cast<const uint16_t *>(src_line)[x];
break;
case 32:
reinterpret_cast<uint32_t *>(dst_line)[(x - sx)] =
reinterpret_cast<const uint32_t *>(src_line)[x];
break;
default: assert(0); break;
}
}
}
// Add to walk-behinds image list
add_walkbehind_image(wb, &wbbmp, pos.Left, pos.Top);
}
}
_G(walkBehindsCachedForBgNum) = _GP(play).bg_frame;
}
// Edits the given game object's sprite, cutting out pixels covered by walk-behinds;
// returns whether any pixels were updated;
bool walkbehinds_cropout(Bitmap *sprit, int sprx, int spry, int basel) {
if (_G(noWalkBehindsAtAll))
return false;
const int maskcol = sprit->GetMaskColor();
const int spcoldep = sprit->GetColorDepth();
bool pixels_changed = false;
// pass along the sprite's pixels, but skip those that lie outside the mask
for (int x = MAX(0, 0 - sprx);
(x < sprit->GetWidth()) && (x + sprx < _GP(thisroom).WalkBehindMask->GetWidth()); ++x) {
// select the WB column at this x
const auto &wbcol = _G(walkBehindCols)[x + sprx];
// skip if no area, or sprite lies outside of all areas in this column
if ((!wbcol.Exists) ||
(wbcol.Y2 <= spry) ||
(wbcol.Y1 > spry + sprit->GetHeight()))
continue;
// ensure we only check within the valid areas (between Y1 and Y2)
// we assume that Y1 and Y2 are always within the mask
for (int y = MAX(0, wbcol.Y1 - spry);
(y < sprit->GetHeight()) && (y + spry < wbcol.Y2); ++y) {
const int wb = _GP(thisroom).WalkBehindMask->GetScanLine(y + spry)[x + sprx];
if (wb < 1) continue; // "no area"
if (_G(croom)->walkbehind_base[wb] <= basel) continue;
pixels_changed = true;
uint8_t *dst_line = sprit->GetScanLineForWriting(y);
switch (spcoldep) {
case 8:
dst_line[x] = maskcol;
break;
case 16:
reinterpret_cast<uint16_t *>(dst_line)[x] = maskcol;
break;
case 32:
reinterpret_cast<uint32_t *>(dst_line)[x] = maskcol;
break;
default:
assert(0);
break;
}
}
}
return pixels_changed;
}
void walkbehinds_recalc() {
// Reset all data
_G(walkBehindCols).clear();
for (int wb = 0; wb < MAX_WALK_BEHINDS; ++wb) {
_G(walkBehindAABB)[wb] = Rect(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN);
}
_G(noWalkBehindsAtAll) = true;
// Recalculate everything; note that mask is always 8-bit
const Bitmap *mask = _GP(thisroom).WalkBehindMask.get();
_G(walkBehindCols).resize(mask->GetWidth());
for (int col = 0; col < mask->GetWidth(); ++col) {
auto &wbcol = _G(walkBehindCols)[col];
for (int y = 0; y < mask->GetHeight(); ++y) {
int wb = mask->GetScanLine(y)[col];
// Valid areas start with index 1, 0 = no area
if ((wb >= 1) && (wb < MAX_WALK_BEHINDS)) {
if (!wbcol.Exists) {
wbcol.Y1 = y;
wbcol.Exists = true;
_G(noWalkBehindsAtAll) = false;
}
wbcol.Y2 = y + 1; // +1 to allow bottom line of screen to work (CHECKME??)
// resize the bounding rect
_G(walkBehindAABB)[wb].Left = MIN(col, _G(walkBehindAABB)[wb].Left);
_G(walkBehindAABB)[wb].Top = MIN(y, _G(walkBehindAABB)[wb].Top);
_G(walkBehindAABB)[wb].Right = MAX(col, _G(walkBehindAABB)[wb].Right);
_G(walkBehindAABB)[wb].Bottom = MAX(y, _G(walkBehindAABB)[wb].Bottom);
}
}
}
_G(walkBehindsCachedForBgNum) = -1;
}
} // namespace AGS3