Files
scummvm-cursorfix/engines/ultima/ultima4/views/dungeonview.cpp
2026-02-02 04:50:13 +01:00

449 lines
15 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 "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/tileanim.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/views/dungeonview.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
DungeonView *DungeonView::_instance = nullptr;
DungeonView::DungeonView(int x, int y, int columns, int rows) : TileView(x, y, rows, columns)
, _screen3dDungeonViewEnabled(true) {
}
DungeonView *DungeonView::getInstance() {
if (!_instance) {
_instance = new DungeonView(BORDER_WIDTH, BORDER_HEIGHT, VIEWPORT_W, VIEWPORT_H);
}
return _instance;
}
void DungeonView::display(Context *c, TileView *view) {
int x, y;
// 1st-person perspective
if (_screen3dDungeonViewEnabled) {
// Note: This shouldn't go above 4, unless we check opaque tiles each step of the way.
const int farthest_non_wall_tile_visibility = 4;
Std::vector<MapTile> tiles;
g_screen->screenEraseMapArea();
if (c->_party->getTorchDuration() > 0) {
for (y = 3; y >= 0; y--) {
DungeonGraphicType type;
// FIXME: Maybe this should be in a loop
tiles = getTiles(y, -1);
type = tilesToGraphic(tiles);
drawWall(-1, y, (Direction)g_ultima->_saveGame->_orientation, type);
tiles = getTiles(y, 1);
type = tilesToGraphic(tiles);
drawWall(1, y, (Direction)g_ultima->_saveGame->_orientation, type);
tiles = getTiles(y, 0);
type = tilesToGraphic(tiles);
drawWall(0, y, (Direction)g_ultima->_saveGame->_orientation, type);
// This only checks that the tile at y==3 is opaque
if (y == 3 && !tiles.front().getTileType()->isOpaque()) {
for (int y_obj = farthest_non_wall_tile_visibility; y_obj > y; y_obj--) {
Std::vector<MapTile> distant_tiles = getTiles(y_obj , 0);
DungeonGraphicType distant_type = tilesToGraphic(distant_tiles);
if ((distant_type == DNGGRAPHIC_DNGTILE) || (distant_type == DNGGRAPHIC_BASETILE))
drawTile(c->_location->_map->_tileSet->get(distant_tiles.front().getId()), 0, y_obj, Direction(g_ultima->_saveGame->_orientation));
}
}
if ((type == DNGGRAPHIC_DNGTILE) || (type == DNGGRAPHIC_BASETILE))
drawTile(c->_location->_map->_tileSet->get(tiles.front().getId()), 0, y, Direction(g_ultima->_saveGame->_orientation));
}
}
}
// 3rd-person perspective
else {
Std::vector<MapTile> tiles;
static MapTile black = c->_location->_map->_tileSet->getByName("black")->getId();
static MapTile avatar = c->_location->_map->_tileSet->getByName("avatar")->getId();
for (y = 0; y < VIEWPORT_H; y++) {
for (x = 0; x < VIEWPORT_W; x++) {
tiles = getTiles((VIEWPORT_H / 2) - y, x - (VIEWPORT_W / 2));
// Only show blackness if there is no light
if (c->_party->getTorchDuration() <= 0)
view->drawTile(black, false, x, y);
else if (x == VIEWPORT_W / 2 && y == VIEWPORT_H / 2)
view->drawTile(avatar, false, x, y);
else
view->drawTile(tiles, false, x, y);
}
}
}
}
void DungeonView::drawInDungeon(Tile *tile, int x_offset, int distance, Direction orientation, bool tiledWall) {
Image *scaled;
const static int nscale_vga[] = { 12, 8, 4, 2, 1};
const static int nscale_ega[] = { 8, 4, 2, 1, 0};
const int lscale_vga[] = { 22, 18, 10, 4, 1};
const int lscale_ega[] = { 22, 14, 6, 3, 1};
const int *lscale;
const int *nscale;
int offset_multiplier = 0;
int offset_adj = 0;
if (settings._videoType != "EGA") {
lscale = & lscale_vga[0];
nscale = & nscale_vga[0];
offset_multiplier = 1;
offset_adj = 2;
} else {
lscale = & lscale_ega[0];
nscale = & nscale_ega[0];
offset_adj = 1;
offset_multiplier = 4;
}
const int *dscale = tiledWall ? lscale : nscale;
// Clear scratchpad and set a background color
_animated->initializeToBackgroundColor();
// Put tile on animated scratchpad
if (tile->getAnim()) {
MapTile mt = tile->getId();
tile->getAnim()->draw(_animated, tile, mt, orientation);
} else {
tile->getImage()->drawOn(_animated, 0, 0);
}
_animated->makeBackgroundColorTransparent();
// This process involving the background color is only required for drawing in the dungeon.
// It will not play well with semi-transparent graphics.
/* scale is based on distance; 1 means half size, 2 regular, 4 means scale by 2x, etc. */
if (dscale[distance] == 0)
return;
else if (dscale[distance] == 1)
scaled = g_screen->screenScaleDown(_animated, 2);
else {
scaled = g_screen->screenScale(_animated, dscale[distance] / 2, 1, 0);
}
if (tiledWall) {
int i_x = SCALED((VIEWPORT_W * _tileWidth / 2) + _bounds.left) - (scaled->width() / 2);
int i_y = SCALED((VIEWPORT_H * _tileHeight / 2) + _bounds.top) - (scaled->height() / 2);
int f_x = i_x + scaled->width();
int f_y = i_y + scaled->height();
int d_x = _animated->width();
int d_y = _animated->height();
for (int x = i_x; x < f_x; x += d_x)
for (int y = i_y; y < f_y; y += d_y)
_animated->drawSubRectOn(this->_screen,
x, y, 0, 0, f_x - x, f_y - y);
} else {
int y_offset = MAX(0, (dscale[distance] - offset_adj) * offset_multiplier);
int x = SCALED((VIEWPORT_W * _tileWidth / 2) + _bounds.left) - (scaled->width() / 2);
int y = SCALED((VIEWPORT_H * _tileHeight / 2) + _bounds.top + y_offset) - (scaled->height() / 8);
scaled->drawSubRectOn(this->_screen, x, y, 0, 0,
SCALED(_tileWidth * VIEWPORT_W + _bounds.left) - x,
SCALED(_tileHeight * VIEWPORT_H + _bounds.top) - y);
}
delete scaled;
}
int DungeonView::graphicIndex(int xoffset, int distance, Direction orientation, DungeonGraphicType type) {
int index;
index = 0;
if (type == DNGGRAPHIC_LADDERUP && xoffset == 0)
return 48 +
(distance * 2) +
(DIR_IN_MASK(orientation, MASK_DIR_SOUTH | MASK_DIR_NORTH) ? 1 : 0);
if (type == DNGGRAPHIC_LADDERDOWN && xoffset == 0)
return 56 +
(distance * 2) +
(DIR_IN_MASK(orientation, MASK_DIR_SOUTH | MASK_DIR_NORTH) ? 1 : 0);
if (type == DNGGRAPHIC_LADDERUPDOWN && xoffset == 0)
return 64 +
(distance * 2) +
(DIR_IN_MASK(orientation, MASK_DIR_SOUTH | MASK_DIR_NORTH) ? 1 : 0);
// FIXME
if (type != DNGGRAPHIC_WALL && type != DNGGRAPHIC_DOOR)
return -1;
if (type == DNGGRAPHIC_DOOR)
index += 24;
index += (xoffset + 1) * 2;
index += distance * 6;
if (DIR_IN_MASK(orientation, MASK_DIR_SOUTH | MASK_DIR_NORTH))
index++;
return index;
}
void DungeonView::drawTile(Tile *tile, int x_offset, int distance, Direction orientation) {
// Draw the tile to the screen
DungeonViewer.drawInDungeon(tile, x_offset, distance, orientation, tile->isTiledInDungeon());
}
Std::vector<MapTile> DungeonView::getTiles(int fwd, int side) {
MapCoords coords = g_context->_location->_coords;
switch (g_ultima->_saveGame->_orientation) {
case DIR_WEST:
coords.x -= fwd;
coords.y -= side;
break;
case DIR_NORTH:
coords.x += side;
coords.y -= fwd;
break;
case DIR_EAST:
coords.x += fwd;
coords.y += side;
break;
case DIR_SOUTH:
coords.x -= side;
coords.y += fwd;
break;
case DIR_ADVANCE:
case DIR_RETREAT:
default:
error("Invalid dungeon orientation");
}
// Wrap the coordinates if necessary
coords.wrap(g_context->_location->_map);
bool focus;
return g_context->_location->tilesAt(coords, focus);
}
DungeonGraphicType DungeonView::tilesToGraphic(const Std::vector<MapTile> &tiles) {
MapTile tile = tiles.front();
if (!_tiles._loaded) {
_tiles._corridor = g_context->_location->_map->_tileSet->getByName("brick_floor")->getId();
_tiles._upLadder = g_context->_location->_map->_tileSet->getByName("up_ladder")->getId();
_tiles._downLadder = g_context->_location->_map->_tileSet->getByName("down_ladder")->getId();
_tiles._upDownLadder = g_context->_location->_map->_tileSet->getByName("up_down_ladder")->getId();
_tiles._loaded = true;
}
/*
* check if the dungeon tile has an annotation or object on top
* (always displayed as a tile, unless a ladder)
*/
if (tiles.size() > 1) {
if (tile._id == _tiles._upLadder._id)
return DNGGRAPHIC_LADDERUP;
else if (tile._id == _tiles._downLadder._id)
return DNGGRAPHIC_LADDERDOWN;
else if (tile._id == _tiles._upDownLadder._id)
return DNGGRAPHIC_LADDERUPDOWN;
else if (tile._id == _tiles._corridor._id)
return DNGGRAPHIC_NONE;
else
return DNGGRAPHIC_BASETILE;
}
/*
* if not an annotation or object, then the tile is a dungeon
* token
*/
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dungeon);
DungeonToken token = dungeon->tokenForTile(tile);
switch (token) {
case DUNGEON_TRAP:
case DUNGEON_CORRIDOR:
return DNGGRAPHIC_NONE;
case DUNGEON_WALL:
case DUNGEON_SECRET_DOOR:
return DNGGRAPHIC_WALL;
case DUNGEON_ROOM:
case DUNGEON_DOOR:
return DNGGRAPHIC_DOOR;
case DUNGEON_LADDER_UP:
return DNGGRAPHIC_LADDERUP;
case DUNGEON_LADDER_DOWN:
return DNGGRAPHIC_LADDERDOWN;
case DUNGEON_LADDER_UPDOWN:
return DNGGRAPHIC_LADDERUPDOWN;
default:
return DNGGRAPHIC_DNGTILE;
}
}
const struct {
const char *subimage;
int ega_x2, ega_y2;
int vga_x2, vga_y2;
const char *subimage2;
} DNG_GRAPHIC_INFO[] = {
{ "dung0_lft_ew", -1, -1, -1, -1, nullptr },
{ "dung0_lft_ns", -1, -1, -1, -1, nullptr },
{ "dung0_mid_ew", -1, -1, -1, -1, nullptr },
{ "dung0_mid_ns", -1, -1, -1, -1, nullptr },
{ "dung0_rgt_ew", -1, -1, -1, -1, nullptr },
{ "dung0_rgt_ns", -1, -1, -1, -1, nullptr },
{ "dung1_lft_ew", 0, 32, 0, 8, "dung1_xxx_ew" },
{ "dung1_lft_ns", 0, 32, 0, 8, "dung1_xxx_ns" },
{ "dung1_mid_ew", -1, -1, -1, -1, nullptr },
{ "dung1_mid_ns", -1, -1, -1, -1, nullptr },
{ "dung1_rgt_ew", 144, 32, 160, 8, "dung1_xxx_ew" },
{ "dung1_rgt_ns", 144, 32, 160, 8, "dung1_xxx_ns" },
{ "dung2_lft_ew", 0, 64, 0, 48, "dung2_xxx_ew" },
{ "dung2_lft_ns", 0, 64, 0, 48, "dung2_xxx_ns" },
{ "dung2_mid_ew", -1, -1, -1, -1, nullptr },
{ "dung2_mid_ns", -1, -1, -1, -1, nullptr },
{ "dung2_rgt_ew", 112, 64, 128, 48, "dung2_xxx_ew" },
{ "dung2_rgt_ns", 112, 64, 128, 48, "dung2_xxx_ns" },
{ "dung3_lft_ew", 0, 80, 48, 72, "dung3_xxx_ew" },
{ "dung3_lft_ns", 0, 80, 48, 72, "dung3_xxx_ns" },
{ "dung3_mid_ew", -1, -1, -1, -1, nullptr },
{ "dung3_mid_ns", -1, -1, -1, -1, nullptr },
{ "dung3_rgt_ew", 96, 80, 104, 72, "dung3_xxx_ew" },
{ "dung3_rgt_ns", 96, 80, 104, 72, "dung3_xxx_ns" },
{ "dung0_lft_ew_door", -1, -1, -1, -1, nullptr },
{ "dung0_lft_ns_door", -1, -1, -1, -1, nullptr },
{ "dung0_mid_ew_door", -1, -1, -1, -1, nullptr },
{ "dung0_mid_ns_door", -1, -1, -1, -1, nullptr },
{ "dung0_rgt_ew_door", -1, -1, -1, -1, nullptr },
{ "dung0_rgt_ns_door", -1, -1, -1, -1, nullptr },
{ "dung1_lft_ew_door", 0, 32, 0, 8, "dung1_xxx_ew" },
{ "dung1_lft_ns_door", 0, 32, 0, 8, "dung1_xxx_ns" },
{ "dung1_mid_ew_door", -1, -1, -1, -1, nullptr },
{ "dung1_mid_ns_door", -1, -1, -1, -1, nullptr },
{ "dung1_rgt_ew_door", 144, 32, 160, 8, "dung1_xxx_ew" },
{ "dung1_rgt_ns_door", 144, 32, 160, 8, "dung1_xxx_ns" },
{ "dung2_lft_ew_door", 0, 64, 0, 48, "dung2_xxx_ew" },
{ "dung2_lft_ns_door", 0, 64, 0, 48, "dung2_xxx_ns" },
{ "dung2_mid_ew_door", -1, -1, -1, -1, nullptr },
{ "dung2_mid_ns_door", -1, -1, -1, -1, nullptr },
{ "dung2_rgt_ew_door", 112, 64, 128, 48, "dung2_xxx_ew" },
{ "dung2_rgt_ns_door", 112, 64, 128, 48, "dung2_xxx_ns" },
{ "dung3_lft_ew_door", 0, 80, 48, 72, "dung3_xxx_ew" },
{ "dung3_lft_ns_door", 0, 80, 48, 72, "dung3_xxx_ns" },
{ "dung3_mid_ew_door", -1, -1, -1, -1, nullptr },
{ "dung3_mid_ns_door", -1, -1, -1, -1, nullptr },
{ "dung3_rgt_ew_door", 96, 80, 104, 72, "dung3_xxx_ew" },
{ "dung3_rgt_ns_door", 96, 80, 104, 72, "dung3_xxx_ns" },
{ "dung0_ladderup", -1, -1, -1, -1, nullptr },
{ "dung0_ladderup_side", -1, -1, -1, -1, nullptr },
{ "dung1_ladderup", -1, -1, -1, -1, nullptr },
{ "dung1_ladderup_side", -1, -1, -1, -1, nullptr },
{ "dung2_ladderup", -1, -1, -1, -1, nullptr },
{ "dung2_ladderup_side", -1, -1, -1, -1, nullptr },
{ "dung3_ladderup", -1, -1, -1, -1, nullptr },
{ "dung3_ladderup_side", -1, -1, -1, -1, nullptr },
{ "dung0_ladderdown", -1, -1, -1, -1, nullptr },
{ "dung0_ladderdown_side", -1, -1, -1, -1, nullptr },
{ "dung1_ladderdown", -1, -1, -1, -1, nullptr },
{ "dung1_ladderdown_side", -1, -1, -1, -1, nullptr },
{ "dung2_ladderdown", -1, -1, -1, -1, nullptr },
{ "dung2_ladderdown_side", -1, -1, -1, -1, nullptr },
{ "dung3_ladderdown", -1, -1, -1, -1, nullptr },
{ "dung3_ladderdown_side", -1, -1, -1, -1, nullptr },
{ "dung0_ladderupdown", -1, -1, -1, -1, nullptr },
{ "dung0_ladderupdown_side", -1, -1, -1, -1, nullptr },
{ "dung1_ladderupdown", -1, -1, -1, -1, nullptr },
{ "dung1_ladderupdown_side", -1, -1, -1, -1, nullptr },
{ "dung2_ladderupdown", -1, -1, -1, -1, nullptr },
{ "dung2_ladderupdown_side", -1, -1, -1, -1, nullptr },
{ "dung3_ladderupdown", -1, -1, -1, -1, nullptr },
{ "dung3_ladderupdown_side", -1, -1, -1, -1, nullptr },
};
void DungeonView::drawWall(int xoffset, int distance, Direction orientation, DungeonGraphicType type) {
int index;
index = graphicIndex(xoffset, distance, orientation, type);
if (index == -1 || distance >= 4)
return;
int x = 0, y = 0;
SubImage *subimage = imageMgr->getSubImage(DNG_GRAPHIC_INFO[index].subimage);
if (subimage) {
x = subimage->left;
y = subimage->top;
}
g_screen->screenDrawImage(DNG_GRAPHIC_INFO[index].subimage, (BORDER_WIDTH + x) * settings._scale,
(BORDER_HEIGHT + y) * settings._scale);
if (DNG_GRAPHIC_INFO[index].subimage2 != nullptr) {
// FIXME: subimage2 is a horrible hack, needs to be cleaned up
if (settings._videoType == "EGA")
g_screen->screenDrawImage(DNG_GRAPHIC_INFO[index].subimage2,
(8 + DNG_GRAPHIC_INFO[index].ega_x2) * settings._scale,
(8 + DNG_GRAPHIC_INFO[index].ega_y2) * settings._scale);
else
g_screen->screenDrawImage(DNG_GRAPHIC_INFO[index].subimage2,
(8 + DNG_GRAPHIC_INFO[index].vga_x2) * settings._scale,
(8 + DNG_GRAPHIC_INFO[index].vga_y2) * settings._scale);
}
}
} // End of namespace Ultima4
} // End of namespace Ultima