Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,448 @@
/* 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

View File

@@ -0,0 +1,97 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_DUNGEONVIEW_H
#define ULTIMA4_VIEWS_DUNGEONVIEW_H
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/map/dungeon.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/views/tileview.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/location.h"
namespace Ultima {
namespace Ultima4 {
typedef enum {
DNGGRAPHIC_NONE,
DNGGRAPHIC_WALL,
DNGGRAPHIC_LADDERUP,
DNGGRAPHIC_LADDERDOWN,
DNGGRAPHIC_LADDERUPDOWN,
DNGGRAPHIC_DOOR,
DNGGRAPHIC_DNGTILE,
DNGGRAPHIC_BASETILE
} DungeonGraphicType;
Std::vector<MapTile> dungeonViewGetTiles(int fwd, int side);
DungeonGraphicType dungeonViewTilesToGraphic(const Std::vector<MapTile> &tiles);
#define DungeonViewer (*DungeonView::getInstance())
/**
* @todo
* <ul>
* <li>move the rest of the dungeon drawing logic here from screen_sdl</li>
* </ul>
*/
class DungeonView : public TileView {
struct MapTiles {
MapTile _corridor;
MapTile _upLadder;
MapTile _downLadder;
MapTile _upDownLadder;
bool _loaded;
MapTiles() : _loaded(false) {
}
};
private:
bool _screen3dDungeonViewEnabled;
MapTiles _tiles;
private:
DungeonView(int x, int y, int columns, int rows);
public:
static DungeonView *_instance;
static DungeonView *getInstance();
void drawInDungeon(Tile *tile, int x_offset, int distance, Direction orientation, bool tiled);
int graphicIndex(int xoffset, int distance, Direction orientation, DungeonGraphicType type);
void drawTile(Tile *tile, int x_offset, int distance, Direction orientation);
void drawWall(int xoffset, int distance, Direction orientation, DungeonGraphicType type);
void display(Context *c, TileView *view);
DungeonGraphicType tilesToGraphic(const Std::vector<MapTile> &tiles);
bool toggle3DDungeonView() {
return _screen3dDungeonViewEnabled = !_screen3dDungeonViewEnabled;
}
Std::vector<MapTile> getTiles(int fwd, int side);
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,61 @@
/* 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/gfx/image.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/views/imageview.h"
namespace Ultima {
namespace Ultima4 {
ImageView::ImageView(int x, int y, int width, int height) : View(x, y, width, height) {
}
ImageView::~ImageView() {
}
void ImageView::draw(const Common::String &imageName, int x, int y) {
ImageInfo *info = imageMgr->get(imageName);
if (info) {
info->_image->draw(SCALED(_bounds.left + x), SCALED(_bounds.top + y));
return;
}
SubImage *subimage = imageMgr->getSubImage(imageName);
if (subimage) {
info = imageMgr->get(subimage->_srcImageName);
if (info) {
info->_image->drawSubRect(SCALED(_bounds.left + x), SCALED(_bounds.top + y),
SCALED(subimage->left) / info->_prescale,
SCALED(subimage->top) / info->_prescale,
SCALED(subimage->width()) / info->_prescale,
SCALED(subimage->height()) / info->_prescale);
return;
}
}
error("ERROR 1005: Unable to load the image \"%s\"", imageName.c_str());
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,47 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_IMAGEVIEW_H
#define ULTIMA4_VIEWS_IMAGEVIEW_H
#include "ultima/ultima4/views/view.h"
namespace Ultima {
namespace Ultima4 {
/**
* A view for displaying bitmap images.
*/
class ImageView : public View {
public:
ImageView(int x = 0, int y = 0, int width = 320, int height = 200);
virtual ~ImageView();
/**
* Draw the image at the optionally specified offset.
*/
void draw(const Common::String &imageName, int x = 0, int y = 0);
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,289 @@
/* 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/views/menu.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/views/textview.h"
namespace Ultima {
namespace Ultima4 {
Menu::Menu() :
_closed(false),
_title(""),
_titleX(0),
_titleY(0) {
}
Menu::~Menu() {
for (auto *i : _items)
delete i;
}
void Menu::removeAll() {
_items.clear();
}
void Menu::add(int id, Common::String text, short x, short y, int sc) {
MenuItem *item = new MenuItem(text, x, y, sc);
item->setId(id);
_items.push_back(item);
}
MenuItem *Menu::add(int id, MenuItem *item) {
item->setId(id);
_items.push_back(item);
return item;
}
void Menu::addShortcutKey(int id, int shortcutKey) {
for (auto *i : _items) {
if (i->getId() == id) {
i->addShortcutKey(shortcutKey);
break;
}
}
}
void Menu::setClosesMenu(int id) {
for (auto *i : _items) {
if (i->getId() == id) {
i->setClosesMenu(true);
break;
}
}
}
Menu::MenuItemList::iterator Menu::getCurrent() {
return _selected;
}
void Menu::setCurrent(MenuItemList::iterator i) {
_selected = i;
highlight(*_selected);
MenuEvent event(this, MenuEvent::SELECT);
setChanged();
notifyObservers(event);
}
void Menu::setCurrent(int id) {
setCurrent(getById(id));
}
void Menu::show(TextView *view) {
if (_title.size() > 0)
view->textAt(_titleX, _titleY, "%s", _title.c_str());
for (auto *mi : _items) {
if (mi->isVisible()) {
Common::String text(mi->getText());
if (mi->isSelected()) {
text.setChar('\010', 0);
}
if (mi->isHighlighted()) {
view->textSelectedAt(mi->getX(), mi->getY(), view->colorizeString(text.c_str(), FG_YELLOW, mi->getScOffset(), 1).c_str());
// hack for the custom U5 mix reagents menu
// places cursor 1 column over, rather than 2.
view->setCursorPos(mi->getX() - (view->getWidth() == 15 ? 1 : 2), mi->getY(), true);
view->enableCursor();
} else {
view->textAt(mi->getX(), mi->getY(), "%s", view->colorizeString(text.c_str(), FG_YELLOW, mi->getScOffset(), 1).c_str());
}
}
}
}
bool Menu::isVisible() {
bool visible = false;
for (const auto *mi : _items) {
if (mi->isVisible())
visible = true;
}
return visible;
}
void Menu::next() {
MenuItemList::iterator i = _selected;
if (isVisible()) {
if (++i == _items.end())
i = _items.begin();
while (!(*i)->isVisible()) {
if (++i == _items.end())
i = _items.begin();
}
}
setCurrent(i);
}
void Menu::prev() {
MenuItemList::iterator i = _selected;
if (isVisible()) {
if (i == _items.begin())
i = _items.end();
i--;
while (!(*i)->isVisible()) {
if (i == _items.begin())
i = _items.end();
i--;
}
}
setCurrent(i);
}
void Menu::highlight(MenuItem *item) {
// unhighlight all menu items first
for (auto *mi : _items) {
mi->setHighlighted(false);
}
if (item)
item->setHighlighted(true);
}
Menu::MenuItemList::iterator Menu::begin() {
return _items.begin();
}
Menu::MenuItemList::iterator Menu::end() {
return _items.end();
}
Menu::MenuItemList::iterator Menu::begin_visible() {
if (!isVisible())
return _items.end();
for (Menu::MenuItemList::iterator it = _items.begin(); it != _items.end(); it++) {
if (!(*it)->isVisible())
return it;
}
return _items.end();
}
void Menu::reset(bool highlightFirst) {
_closed = false;
/* get the first visible menu item */
_selected = begin_visible();
/* un-highlight and deselect each menu item */
for (auto *mi : _items) {
mi->setHighlighted(false);
mi->setSelected(false);
}
/* highlight the first visible menu item */
if (highlightFirst)
highlight(*_selected);
MenuEvent event(this, MenuEvent::RESET);
setChanged();
notifyObservers(event);
}
Menu::MenuItemList::iterator Menu::getById(int id) {
if (id == -1)
return getCurrent();
for (Menu::MenuItemList::iterator it = _items.begin(); it != _items.end(); it++) {
if ((*it)->getId() == id)
return it;
}
return _items.end();
}
MenuItem *Menu::getItemById(int id) {
Menu::MenuItemList::iterator it = getById(id);
if (it != _items.end())
return *it;
return nullptr;
}
void Menu::activateItemAtPos(TextView *view, const Common::Point &pt) {
for (Menu::MenuItemList::iterator it = begin(); it != end(); ++it) {
Common::Rect r = view->getTextBounds((*it)->getX(), (*it)->getY(),
(*it)->getText().size());
if (r.contains(pt)) {
activateItem((*it)->getId(), MenuEvent::ACTIVATE);
}
}
}
void Menu::activateItem(int id, MenuEvent::Type action) {
MenuItem *mi;
/* find the given menu item by id */
if (id >= 0)
mi = getItemById(id);
/* or use the current item */
else
mi = *getCurrent();
if (!mi)
error("Error: Unable to find menu item with id '%d'", id);
/* make sure the action given will activate the menu item */
if (mi->getClosesMenu())
setClosed(true);
MenuEvent event(this, (MenuEvent::Type)action, mi);
mi->activate(event);
setChanged();
notifyObservers(event);
}
bool Menu::activateItemByShortcut(int key, MenuEvent::Type action) {
for (auto *i : _items) {
if (i->hasShortcutKey(key)) {
activateItem(i->getId(), action);
// if the selection doesn't close the menu, highlight the selection
if (!i->getClosesMenu())
setCurrent(i->getId());
return true;
}
}
return false;
}
bool Menu::getClosed() const {
return _closed;
}
void Menu::setClosed(bool closed) {
this->_closed = closed;
}
void Menu::setTitle(const Common::String &text, int x, int y) {
_title = text;
_titleX = x;
_titleY = y;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,201 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_MENU_H
#define ULTIMA4_VIEWS_MENU_H
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/views/menuitem.h"
#include "ultima/ultima4/core/observable.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class Menu;
class TextView;
class MenuEvent {
public:
enum Type {
ACTIVATE,
INCREMENT,
DECREMENT,
SELECT,
RESET
};
MenuEvent(const Menu *menu, Type type, const MenuItem *item = nullptr) {
this->_menu = menu;
this->_type = type;
this->_item = item;
}
const Menu *getMenu() {
return _menu;
}
Type getType() {
return _type;
}
const MenuItem *getMenuItem() {
return _item;
}
private:
const Menu *_menu;
Type _type;
const MenuItem *_item;
};
/**
* Menu class definition
*/
class Menu : public Observable<Menu *, MenuEvent &> {
public:
typedef Common::List<MenuItem *> MenuItemList;
public:
Menu();
~Menu();
void removeAll();
/**
* Adds an item to the menu list and returns the menu
*/
void add(int id, Common::String text, short x, short y, int shortcutKey = -1);
MenuItem *add(int id, MenuItem *item);
void addShortcutKey(int id, int shortcutKey);
void setClosesMenu(int id);
/**
* Returns the menu item that is currently selected/highlighted
*/
MenuItemList::iterator getCurrent();
/**
* Sets the current menu item to the one indicated by the iterator
*/
void setCurrent(MenuItemList::iterator i);
void setCurrent(int id);
void show(TextView *view);
/**
* Checks the menu to ensure that there is at least 1 visible
* item in the list. Returns true if there is at least 1 visible
* item, false if nothing is visible.
*/
bool isVisible();
/**
* Sets the selected iterator to the next visible menu item and highlights it
*/
void next();
/**
* Sets the selected iterator to the previous visible menu item and highlights it
*/
void prev();
/**
* Highlights a single menu item, un-highlighting any others
*/
void highlight(MenuItem *item);
/**
* Returns an iterator pointing to the first menu item
*/
MenuItemList::iterator begin();
/**
* Returns an iterator pointing just past the last menu item
*/
MenuItemList::iterator end();
/**
* Returns an iterator pointing to the first visible menu item
*/
MenuItemList::iterator begin_visible();
/**
* 'Resets' the menu. This does the following:
* - un-highlights all menu items
* - highlights the first menu item
* - selects the first visible menu item
*/
void reset(bool highlightFirst = true);
/**
* Returns an iterator pointing to the item associated with the given 'id'
*/
MenuItemList::iterator getById(int id);
/**
* Returns the menu item associated with the given 'id'
*/
MenuItem *getItemById(int id);
/**
* Activates any menu item at a given position
*/
void activateItemAtPos(TextView *view, const Common::Point &pt);
/**
* Activates the menu item given by 'id', using 'action' to
* activate it. If the menu item cannot be activated using
* 'action', then it is not activated. This also un-highlights
* the menu item given by 'menu' and highlights the new menu
* item that was found for 'id'.
*/
void activateItem(int id, MenuEvent::Type action);
/**
* Activates a menu item by it's shortcut key. True is returned if a
* menu item get activated, false otherwise.
*/
bool activateItemByShortcut(int key, MenuEvent::Type action);
/**
* Returns true if the menu has been closed.
*/
bool getClosed() const;
/**
* Update whether the menu has been closed.
*/
void setClosed(bool closed);
void setTitle(const Common::String &text, int x, int y);
private:
MenuItemList _items;
MenuItemList::iterator _selected;
bool _closed;
Common::String _title;
int _titleX, _titleY;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,258 @@
/* 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/views/menu.h"
#include "ultima/ultima4/views/menuitem.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
namespace Ultima {
namespace Ultima4 {
MenuItem::MenuItem(const Common::String &t, short x, short y, int sc) :
_id(-1), _x(x), _y(y), _text(t), _highlighted(false),
_selected(false), _visible(true), _scOffset(sc), _closesMenu(false) {
// if the sc/scOffset is outside the range of the text string, assert
assertMsg(sc == -1 || (sc >= 0 && sc <= (int)_text.size()), "sc value of %d out of range!", sc);
if (sc != -1) addShortcutKey(tolower(_text[sc]));
}
int MenuItem::getId() const {
return _id;
}
short MenuItem::getX() const {
return _x;
}
short MenuItem::getY() const {
return _y;
}
int MenuItem::getScOffset() const {
return _scOffset;
}
Common::String MenuItem::getText() const {
return _text;
}
bool MenuItem::isHighlighted() const {
return _highlighted;
}
bool MenuItem::isSelected() const {
return _selected;
}
bool MenuItem::isVisible() const {
return _visible;
}
bool MenuItem::hasShortcutKey(int sc) const {
Common::Array<int>::const_iterator begin = _shortcutKeys.begin();
Common::Array<int>::const_iterator end = _shortcutKeys.end();
return (Common::find(begin, end, sc) != end);
}
bool MenuItem::getClosesMenu() const {
return _closesMenu;
}
void MenuItem::setId(int i) {
_id = i;
}
void MenuItem::setX(int x) {
_x = x;
}
void MenuItem::setY(int y) {
_y = y;
}
void MenuItem::setText(const Common::String &t) {
_text = t;
}
void MenuItem::setHighlighted(bool h) {
_highlighted = h;
}
void MenuItem::setSelected(bool s) {
_selected = s;
}
void MenuItem::setVisible(bool v) {
_visible = v;
}
void MenuItem::addShortcutKey(int sc) {
if (!hasShortcutKey(sc))
_shortcutKeys.push_back(sc);
}
void MenuItem::setClosesMenu(bool closesMenu) {
this->_closesMenu = closesMenu;
}
BoolMenuItem::BoolMenuItem(const Common::String &text, short xp, short yp, int shortcutKey, bool *val) :
MenuItem(text, xp, yp, shortcutKey),
_val(val),
_on("On"),
_off("Off") {
}
BoolMenuItem *BoolMenuItem::setValueStrings(const Common::String &onString, const Common::String &offString) {
_on = onString;
_off = offString;
return this;
}
Common::String BoolMenuItem::getText() const {
char buffer[64];
snprintf(buffer, sizeof(buffer), _text.c_str(), *_val ? _on.c_str() : _off.c_str());
return buffer;
}
void BoolMenuItem::activate(MenuEvent &event) {
if (event.getType() == MenuEvent::DECREMENT ||
event.getType() == MenuEvent::INCREMENT ||
event.getType() == MenuEvent::ACTIVATE)
*_val = !(*_val);
}
StringMenuItem::StringMenuItem(const Common::String &text, short xp, short yp, int shortcutKey,
Common::String *val, const Std::vector<Common::String> &validSettings) :
MenuItem(text, xp, yp, shortcutKey),
_val(val),
_validSettings(validSettings) {
}
Common::String StringMenuItem::getText() const {
char buffer[64];
snprintf(buffer, sizeof(buffer), _text.c_str(), _val->c_str());
return buffer;
}
void StringMenuItem::activate(MenuEvent &event) {
Std::vector<Common::String>::const_iterator current =
find(_validSettings.begin(), _validSettings.end(), *_val);
if (current == _validSettings.end())
error("Error: menu Common::String '%s' not a valid choice", _val->c_str());
if (event.getType() == MenuEvent::INCREMENT || event.getType() == MenuEvent::ACTIVATE) {
/* move to the next valid choice, wrapping if necessary */
current++;
if (current == _validSettings.end())
current = _validSettings.begin();
*_val = *current;
} else if (event.getType() == MenuEvent::DECREMENT) {
/* move back one, wrapping if necessary */
if (current == _validSettings.begin())
current = _validSettings.end();
current--;
*_val = *current;
}
}
IntMenuItem::IntMenuItem(const Common::String &text, short xp, short yp, int shortcutKey, int *val,
int min, int max, int increment, menuOutputType output) :
MenuItem(text, xp, yp, shortcutKey),
_val(val),
_min(min),
_max(max),
_increment(increment),
_output(output) {
}
Common::String IntMenuItem::getText() const {
// do custom formatting for some menu entries,
// and generate a Common::String of the results
char outputBuffer[20];
switch (_output) {
case MENU_OUTPUT_REAGENT:
snprintf(outputBuffer, sizeof(outputBuffer), "%2d", static_cast<short>(*_val));
break;
case MENU_OUTPUT_GAMMA:
snprintf(outputBuffer, sizeof(outputBuffer), "%.1f", static_cast<float>(*_val) / 100);
break;
case MENU_OUTPUT_SHRINE:
/*
* is this code really necessary? the increments/decrements can be handled by IntMenuItem(),
* as well as the looping once the max is reached. more importantly, the minimum value is
* inconstant, and based upon another setting that can be changed independent of this one.
* This variable could be set to it's minimum value, but when the gameCyclesPerSecond setting
* is changed, the value of this setting could become out of bounds.
*
* settings.shrineTime is only used in one function within shrine.cpp, and that code appears
* to handle the min value, caping the minimum interval at 1.
*
// make sure that the setting we're trying for is even possible
if (event.getType() == MenuEvent::INCREMENT || event.getType() == MenuEvent::ACTIVATE) {
settingsChanged.shrineTime++;
if (settingsChanged.shrineTime > MAX_SHRINE_TIME)
settingsChanged.shrineTime = MEDITATION_MANTRAS_PER_CYCLE / settingsChanged.gameCyclesPerSecond;
} else if (event.getType() == MenuEvent::DECREMENT) {
settingsChanged.shrineTime--;
if (settingsChanged.shrineTime < (MEDITATION_MANTRAS_PER_CYCLE / settingsChanged.gameCyclesPerSecond))
settingsChanged.shrineTime = MAX_SHRINE_TIME;
}
*
*/
snprintf(outputBuffer, sizeof(outputBuffer), "%d sec", *_val);
break;
case MENU_OUTPUT_SPELL:
snprintf(outputBuffer, sizeof(outputBuffer), "%3g sec", static_cast<double>(*_val) / 5);
break;
case MENU_OUTPUT_VOLUME:
if (*_val == 0) {
snprintf(outputBuffer, sizeof(outputBuffer), "Disabled");
} else if (*_val == MAX_VOLUME) {
snprintf(outputBuffer, sizeof(outputBuffer), "Full");
} else {
snprintf(outputBuffer, sizeof(outputBuffer), "%d%s%s", *_val * 10, "%", "%");
}
break;
default:
break;
}
// the buffer must contain a field character %d or %s depending
// on the menuOutputType selected. MENU_OUTPUT_INT always uses
// %d, whereas all others use %s
char buffer[64];
if (_output != MENU_OUTPUT_INT)
snprintf(buffer, sizeof(buffer), _text.c_str(), outputBuffer);
else
snprintf(buffer, sizeof(buffer), _text.c_str(), *_val);
return buffer;
}
void IntMenuItem::activate(MenuEvent &event) {
if (event.getType() == MenuEvent::INCREMENT || event.getType() == MenuEvent::ACTIVATE) {
*_val += _increment;
if (*_val > _max)
*_val = _min;
} else if (event.getType() == MenuEvent::DECREMENT) {
*_val -= _increment;
if (*_val < _min)
*_val = _max;
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,146 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_MENUITEM_H
#define ULTIMA4_VIEWS_MENUITEM_H
#include "common/str.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class MenuEvent;
/**
* custom output types for with menu items that need
* to perform special calculations before displaying
* its associated value
*/
enum menuOutputType {
MENU_OUTPUT_INT,
MENU_OUTPUT_GAMMA,
MENU_OUTPUT_SHRINE,
MENU_OUTPUT_SPELL,
MENU_OUTPUT_VOLUME,
MENU_OUTPUT_REAGENT
};
class MenuItem {
public:
/**
* MenuItem class
*/
MenuItem(const Common::String &text, short x, short y, int shortcutKey = -1);
virtual ~MenuItem() {}
virtual void activate(MenuEvent &event) {}
// Accessor Methods
int getId() const;
short getX() const;
short getY() const;
int getScOffset() const;
virtual Common::String getText() const;
bool isHighlighted() const;
bool isSelected() const;
bool isVisible() const;
bool hasShortcutKey(int key) const;
bool getClosesMenu() const;
void setId(int id);
void setX(int x);
void setY(int y);
void setText(const Common::String &text);
void setHighlighted(bool h = true);
void setSelected(bool s = true);
void setVisible(bool v = true);
void addShortcutKey(int shortcutKey);
void setClosesMenu(bool closesMenu);
protected:
int _id;
short _x, _y;
Common::String _text;
bool _highlighted;
bool _selected;
bool _visible;
int _scOffset;
Common::Array<int> _shortcutKeys;
bool _closesMenu;
};
/**
* A menu item that toggles a boolean value, and displays the current
* setting as part of the text.
*/
class BoolMenuItem : public MenuItem {
public:
BoolMenuItem(const Common::String &text, short xp, short yp, int shortcutKey, bool *val);
BoolMenuItem *setValueStrings(const Common::String &onString, const Common::String &offString);
void activate(MenuEvent &event) override;
Common::String getText() const override;
protected:
bool *_val;
Common::String _on, _off;
};
/**
* A menu item that cycles through a list of possible Common::String values, and
* displays the current setting as part of the text.
*/
class StringMenuItem : public MenuItem {
public:
StringMenuItem(const Common::String &text, short xp, short yp, int shortcutKey, Common::String *val, const Std::vector<Common::String> &validSettings);
void activate(MenuEvent &event) override;
Common::String getText() const override;
protected:
Common::String *_val;
Std::vector<Common::String> _validSettings;
};
/**
* A menu item that cycles through a list of possible integer values,
* and displays the current setting as part of the text.
*/
class IntMenuItem : public MenuItem {
public:
IntMenuItem(const Common::String &text, short xp, short yp, int shortcutKey, int *val, int min, int max, int increment, menuOutputType output = MENU_OUTPUT_INT);
void activate(MenuEvent &event) override;
Common::String getText() const override;
protected:
int *_val;
int _min, _max, _increment;
menuOutputType _output;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,412 @@
/* 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/ultima4.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/game/armor.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/views/menu.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/game/spell.h"
#include "ultima/ultima4/map/tile.h"
#include "ultima/ultima4/game/weapon.h"
namespace Ultima {
namespace Ultima4 {
/**
* StatsArea class implementation
*/
StatsArea::StatsArea() :
_title(STATS_AREA_X * CHAR_WIDTH, 0 * CHAR_HEIGHT, STATS_AREA_WIDTH, 1),
_mainArea(STATS_AREA_X * CHAR_WIDTH, STATS_AREA_Y * CHAR_HEIGHT, STATS_AREA_WIDTH, STATS_AREA_HEIGHT),
_summary(STATS_AREA_X * CHAR_WIDTH, (STATS_AREA_Y + STATS_AREA_HEIGHT + 1) * CHAR_HEIGHT, STATS_AREA_WIDTH, 1),
_view(STATS_PARTY_OVERVIEW) {
// Generate a formatted Common::String for each menu item,
// and then add the item to the menu. The Y value
// for each menu item will be filled in later.
for (int count = 0; count < 8; count++) {
char outputBuffer[16];
snprintf(outputBuffer, sizeof(outputBuffer), "-%-11s%%s", getReagentName((Reagent)count));
_reagentsMixMenu.add(count, new IntMenuItem(outputBuffer, 1, 0, -1, (int *)g_context->_party->getReagentPtr((Reagent)count), 0, 99, 1, MENU_OUTPUT_REAGENT));
}
_reagentsMixMenu.addObserver(this);
}
void StatsArea::setView(StatsView view) {
this->_view = view;
update();
}
void StatsArea::prevItem() {
_view = (StatsView)(_view - 1);
if (_view < STATS_CHAR1)
_view = STATS_MIXTURES;
if (_view <= STATS_CHAR8 && (_view - STATS_CHAR1 + 1) > g_context->_party->size())
_view = (StatsView)(STATS_CHAR1 - 1 + g_context->_party->size());
update();
}
void StatsArea::nextItem() {
_view = (StatsView)(_view + 1);
if (_view > STATS_MIXTURES)
_view = STATS_CHAR1;
if (_view <= STATS_CHAR8 && (_view - STATS_CHAR1 + 1) > g_context->_party->size())
_view = STATS_WEAPONS;
update();
}
void StatsArea::update(bool avatarOnly) {
clear();
/*
* update the upper stats box
*/
switch (_view) {
case STATS_PARTY_OVERVIEW:
showPartyView(avatarOnly);
break;
case STATS_CHAR1:
case STATS_CHAR2:
case STATS_CHAR3:
case STATS_CHAR4:
case STATS_CHAR5:
case STATS_CHAR6:
case STATS_CHAR7:
case STATS_CHAR8:
showPlayerDetails();
break;
case STATS_WEAPONS:
showWeapons();
break;
case STATS_ARMOR:
showArmor();
break;
case STATS_EQUIPMENT:
showEquipment();
break;
case STATS_ITEMS:
showItems();
break;
case STATS_REAGENTS:
showReagents();
break;
case STATS_MIXTURES:
showMixtures();
break;
case MIX_REAGENTS:
showReagents(true);
break;
}
/*
* update the lower stats box (food, gold, etc.)
*/
if (g_context->_transportContext == TRANSPORT_SHIP)
_summary.textAt(0, 0, "F:%04d SHP:%02d", g_ultima->_saveGame->_food / 100, g_ultima->_saveGame->_shipHull);
else
_summary.textAt(0, 0, "F:%04d G:%04d", g_ultima->_saveGame->_food / 100, g_ultima->_saveGame->_gold);
update(g_context->_aura);
redraw();
}
void StatsArea::update(Aura *observable, NoArg *arg) {
Observer<Aura *>::update(observable, arg);
}
void StatsArea::update(Aura *aura) {
byte mask = 0xff;
for (int i = 0; i < VIRT_MAX; i++) {
if (g_ultima->_saveGame->_karma[i] == 0)
mask &= ~(1 << i);
}
switch (aura->getType()) {
case Aura::NONE:
_summary.drawCharMasked(0, STATS_AREA_WIDTH / 2, 0, mask);
break;
case Aura::HORN:
_summary.drawChar(CHARSET_REDDOT, STATS_AREA_WIDTH / 2, 0);
break;
case Aura::JINX:
_summary.drawChar('J', STATS_AREA_WIDTH / 2, 0);
break;
case Aura::NEGATE:
_summary.drawChar('N', STATS_AREA_WIDTH / 2, 0);
break;
case Aura::PROTECTION:
_summary.drawChar('P', STATS_AREA_WIDTH / 2, 0);
break;
case Aura::QUICKNESS:
_summary.drawChar('Q', STATS_AREA_WIDTH / 2, 0);
break;
}
_summary.update();
}
void StatsArea::update(Party *party, PartyEvent &event) {
update(); // Do a full update
}
void StatsArea::update(Menu *menu, MenuEvent &event) {
update(); // Do a full update
}
void StatsArea::highlightPlayer(int player) {
assertMsg(player < g_context->_party->size(), "player number out of range: %d", player);
_mainArea.highlight(0, player * CHAR_HEIGHT, STATS_AREA_WIDTH * CHAR_WIDTH, CHAR_HEIGHT);
#ifdef IOS_ULTIMA4
U4IOS::updateActivePartyMember(player);
#endif
}
void StatsArea::clear() {
for (int i = 0; i < STATS_AREA_WIDTH; i++)
_title.drawChar(CHARSET_HORIZBAR, i, 0);
_mainArea.clear();
_summary.clear();
}
void StatsArea::redraw() {
_title.update();
_mainArea.update();
_summary.update();
}
void StatsArea::setTitle(const Common::String &s) {
int titleStart = (STATS_AREA_WIDTH / 2) - ((s.size() + 2) / 2);
_title.textAt(titleStart, 0, "%c%s%c", 16, s.c_str(), 17);
}
void StatsArea::showPartyView(bool avatarOnly) {
const char *format = "%d%c%-9.8s%3d%s";
PartyMember *p = nullptr;
int activePlayer = g_context->_party->getActivePlayer();
assertMsg(g_context->_party->size() <= 8, "party members out of range: %d", g_context->_party->size());
if (!avatarOnly) {
for (int i = 0; i < g_context->_party->size(); i++) {
p = g_context->_party->member(i);
_mainArea.textAt(0, i, format, i + 1, (i == activePlayer) ? CHARSET_BULLET : '-', p->getName().c_str(), p->getHp(), _mainArea.colorizeStatus(p->getStatus()).c_str());
}
} else {
p = g_context->_party->member(0);
_mainArea.textAt(0, 0, format, 1, (activePlayer == 0) ? CHARSET_BULLET : '-', p->getName().c_str(), p->getHp(), _mainArea.colorizeStatus(p->getStatus()).c_str());
}
}
void StatsArea::showPlayerDetails() {
int player = _view - STATS_CHAR1;
assertMsg(player < 8, "character number out of range: %d", player);
PartyMember *p = g_context->_party->member(player);
setTitle(p->getName());
_mainArea.textAt(0, 0, "%c %c", p->getSex(), p->getStatus());
Common::String classStr = getClassName(p->getClass());
int classStart = (STATS_AREA_WIDTH / 2) - (classStr.size() / 2);
_mainArea.textAt(classStart, 0, "%s", classStr.c_str());
_mainArea.textAt(0, 2, " MP:%02d LV:%d", p->getMp(), p->getRealLevel());
_mainArea.textAt(0, 3, "STR:%02d HP:%04d", p->getStr(), p->getHp());
_mainArea.textAt(0, 4, "DEX:%02d HM:%04d", p->getDex(), p->getMaxHp());
_mainArea.textAt(0, 5, "INT:%02d EX:%04d", p->getInt(), p->getExp());
_mainArea.textAt(0, 6, "W:%s", p->getWeapon()->getName().c_str());
_mainArea.textAt(0, 7, "A:%s", p->getArmor()->getName().c_str());
}
void StatsArea::showWeapons() {
setTitle("Weapons");
int line = 0;
int col = 0;
_mainArea.textAt(0, line++, "A-%s", g_weapons->get(WEAP_HANDS)->getName().c_str());
for (int w = WEAP_HANDS + 1; w < WEAP_MAX; w++) {
int n = g_ultima->_saveGame->_weapons[w];
if (n >= 100)
n = 99;
if (n >= 1) {
const char *format = (n >= 10) ? "%c%d-%s" : "%c-%d-%s";
_mainArea.textAt(col, line++, format, w - WEAP_HANDS + 'A', n, g_weapons->get((WeaponType) w)->getAbbrev().c_str());
if (line >= (STATS_AREA_HEIGHT)) {
line = 0;
col += 8;
}
}
}
}
void StatsArea::showArmor() {
setTitle("Armour");
int line = 0;
_mainArea.textAt(0, line++, "A -No Armour");
for (int a = ARMR_NONE + 1; a < ARMR_MAX; a++) {
if (g_ultima->_saveGame->_armor[a] > 0) {
const char *format = (g_ultima->_saveGame->_armor[a] >= 10) ? "%c%d-%s" : "%c-%d-%s";
_mainArea.textAt(0, line++, format, a - ARMR_NONE + 'A', g_ultima->_saveGame->_armor[a], g_armors->get((ArmorType) a)->getName().c_str());
}
}
}
void StatsArea::showEquipment() {
setTitle("Equipment");
int line = 0;
_mainArea.textAt(0, line++, "%2d Torches", g_ultima->_saveGame->_torches);
_mainArea.textAt(0, line++, "%2d Gems", g_ultima->_saveGame->_gems);
_mainArea.textAt(0, line++, "%2d Keys", g_ultima->_saveGame->_keys);
if (g_ultima->_saveGame->_sextants > 0)
_mainArea.textAt(0, line++, "%2d Sextants", g_ultima->_saveGame->_sextants);
}
void StatsArea::showItems() {
int i, j;
char buffer[17];
setTitle("Items");
int line = 0;
if (g_ultima->_saveGame->_stones != 0) {
j = 0;
for (i = 0; i < 8; i++) {
if (g_ultima->_saveGame->_stones & (1 << i))
buffer[j++] = getStoneName((Virtue) i)[0];
}
buffer[j] = '\0';
_mainArea.textAt(0, line++, "Stones:%s", buffer);
}
if (g_ultima->_saveGame->_runes != 0) {
j = 0;
for (i = 0; i < 8; i++) {
if (g_ultima->_saveGame->_runes & (1 << i))
buffer[j++] = getVirtueName((Virtue) i)[0];
}
buffer[j] = '\0';
_mainArea.textAt(0, line++, "Runes:%s", buffer);
}
if (g_ultima->_saveGame->_items & (ITEM_CANDLE | ITEM_BOOK | ITEM_BELL)) {
buffer[0] = '\0';
if (g_ultima->_saveGame->_items & ITEM_BELL) {
Common::strcat_s(buffer, getItemName(ITEM_BELL));
Common::strcat_s(buffer, " ");
}
if (g_ultima->_saveGame->_items & ITEM_BOOK) {
Common::strcat_s(buffer, getItemName(ITEM_BOOK));
Common::strcat_s(buffer, " ");
}
if (g_ultima->_saveGame->_items & ITEM_CANDLE) {
Common::strcat_s(buffer, getItemName(ITEM_CANDLE));
buffer[15] = '\0';
}
_mainArea.textAt(0, line++, "%s", buffer);
}
if (g_ultima->_saveGame->_items & (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T)) {
j = 0;
if (g_ultima->_saveGame->_items & ITEM_KEY_T)
buffer[j++] = getItemName(ITEM_KEY_T)[0];
if (g_ultima->_saveGame->_items & ITEM_KEY_L)
buffer[j++] = getItemName(ITEM_KEY_L)[0];
if (g_ultima->_saveGame->_items & ITEM_KEY_C)
buffer[j++] = getItemName(ITEM_KEY_C)[0];
buffer[j] = '\0';
_mainArea.textAt(0, line++, "3 Part Key:%s", buffer);
}
if (g_ultima->_saveGame->_items & ITEM_HORN)
_mainArea.textAt(0, line++, "%s", getItemName(ITEM_HORN));
if (g_ultima->_saveGame->_items & ITEM_WHEEL)
_mainArea.textAt(0, line++, "%s", getItemName(ITEM_WHEEL));
if (g_ultima->_saveGame->_items & ITEM_SKULL)
_mainArea.textAt(0, line++, "%s", getItemName(ITEM_SKULL));
}
void StatsArea::showReagents(bool active) {
setTitle("Reagents");
int line = 0,
r = REAG_ASH;
Common::String shortcut("A");
_reagentsMixMenu.show(&_mainArea);
for (const auto *item : _reagentsMixMenu) {
if (item->isVisible()) {
// Insert the reagent menu item shortcut character
shortcut.setChar('A' + r, 0);
if (active)
_mainArea.textAt(0, line++, "%s", _mainArea.colorizeString(shortcut, FG_YELLOW, 0, 1).c_str());
else
_mainArea.textAt(0, line++, "%s", shortcut.c_str());
}
r++;
}
}
void StatsArea::showMixtures() {
setTitle("Mixtures");
int line = 0;
int col = 0;
for (int s = 0; s < SPELL_MAX; s++) {
int n = g_ultima->_saveGame->_mixtures[s];
if (n >= 100)
n = 99;
if (n >= 1) {
_mainArea.textAt(col, line++, "%c-%02d", s + 'A', n);
if (line >= (STATS_AREA_HEIGHT)) {
if (col >= 10)
break;
line = 0;
col += 5;
}
}
}
}
void StatsArea::resetReagentsMenu() {
int i = 0, row = 0;
for (auto *item : _reagentsMixMenu) {
if (g_ultima->_saveGame->_reagents[i++] > 0) {
item->setVisible(true);
item->setY(row++);
} else {
item->setVisible(false);
}
}
_reagentsMixMenu.reset(false);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,167 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_STATS_H
#define ULTIMA4_VIEWS_STATS_H
#include "ultima/ultima4/core/observable.h"
#include "ultima/ultima4/core/observer.h"
#include "ultima/ultima4/views/menu.h"
#include "ultima/ultima4/views/textview.h"
namespace Ultima {
namespace Ultima4 {
struct SaveGame;
class Aura;
class Ingredients;
class Menu;
class MenuEvent;
class Party;
class PartyEvent;
#define STATS_AREA_WIDTH 15
#define STATS_AREA_HEIGHT 8
#define STATS_AREA_X TEXT_AREA_X
#define STATS_AREA_Y 1
enum StatsView {
STATS_PARTY_OVERVIEW,
STATS_CHAR1,
STATS_CHAR2,
STATS_CHAR3,
STATS_CHAR4,
STATS_CHAR5,
STATS_CHAR6,
STATS_CHAR7,
STATS_CHAR8,
STATS_WEAPONS,
STATS_ARMOR,
STATS_EQUIPMENT,
STATS_ITEMS,
STATS_REAGENTS,
STATS_MIXTURES,
MIX_REAGENTS
};
class StatsArea : public Observer<Aura *>, public Observer<Party *, PartyEvent &>,
public Observer<Menu *, MenuEvent &>, public Observable<StatsArea *, Common::String> {
public:
StatsArea();
void setView(StatsView view);
void clear();
/**
* Sets the stats item to the previous in sequence.
*/
void prevItem();
/**
* Sets the stats item to the next in sequence.
*/
void nextItem();
/**
* Update the stats (ztats) box on the upper right of the screen.
*/
virtual void update(bool avatarOnly = false);
void update(Aura *observable, NoArg *arg) override;
void update(Aura *aura) override;
void update(Party *party, PartyEvent &event) override;
void update(Menu *menu, MenuEvent &event) override;
void highlightPlayer(int player);
/**
* Redraws the entire stats area
*/
void redraw();
TextView *getMainArea() {
return &_mainArea;
}
void resetReagentsMenu();
Menu *getReagentsMenu() {
return &_reagentsMixMenu;
}
private:
/**
* The basic party view.
*/
void showPartyView(bool avatarOnly);
/**
* The individual character view.
*/
void showPlayerDetails();
/**
* Weapons in inventory.
*/
void showWeapons();
/**
* Armor in inventory.
*/
void showArmor();
/**
* Equipment: touches, gems, keys, and sextants.
*/
void showEquipment();
/**
* Items: runes, stones, and other miscellaneous quest items.
*/
void showItems();
/**
* Unmixed reagents in inventory.
*/
void showReagents(bool active = false);
/**
* Mixed reagents in inventory.
*/
void showMixtures();
/**
* Sets the title of the stats area.
*/
void setTitle(const Common::String &s);
TextView _title;
TextView _mainArea;
TextView _summary;
StatsView _view;
Menu _reagentsMixMenu;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,312 @@
/* 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/events/event_handler.h"
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/views/textview.h"
namespace Ultima {
namespace Ultima4 {
Image *TextView::_charset = nullptr;
TextView::TextView(int x, int y, int columns, int rows) : View(x, y, columns * CHAR_WIDTH, rows * CHAR_HEIGHT) {
this->_columns = columns;
this->_rows = rows;
this->_cursorEnabled = false;
this->_cursorFollowsText = false;
this->_cursorX = 0;
this->_cursorY = 0;
this->_cursorPhase = 0;
if (_charset == nullptr)
_charset = imageMgr->get(BKGD_CHARSET)->_image;
eventHandler->getTimer()->add(&cursorTimer, /*SCR_CYCLE_PER_SECOND*/4, this);
}
TextView::~TextView() {
eventHandler->getTimer()->remove(&cursorTimer, this);
}
void TextView::reinit() {
View::reinit();
_charset = imageMgr->get(BKGD_CHARSET)->_image;
}
void TextView::drawChar(int chr, int x, int y) {
assertMsg(x < _columns, "x value of %d out of range", x);
assertMsg(y < _rows, "y value of %d out of range", y);
_charset->drawSubRect(SCALED(_bounds.left + (x * CHAR_WIDTH)),
SCALED(_bounds.top + (y * CHAR_HEIGHT)),
0, SCALED(chr * CHAR_HEIGHT),
SCALED(CHAR_WIDTH),
SCALED(CHAR_HEIGHT));
}
void TextView::drawCharMasked(int chr, int x, int y, byte mask) {
drawChar(chr, x, y);
for (int i = 0; i < 8; i++) {
if (mask & (1 << i)) {
_screen->fillRect(SCALED(_bounds.left + (x * CHAR_WIDTH)),
SCALED(_bounds.top + (y * CHAR_HEIGHT) + i),
SCALED(CHAR_WIDTH),
SCALED(1),
0, 0, 0);
}
}
}
void TextView::textSelectedAt(int x, int y, const char *text) {
if (!settings._enhancements || !settings._enhancementsOptions._textColorization) {
this->textAt(x, y, "%s", text);
return;
}
this->setFontColorBG(BG_BRIGHT);
for (int i = 0; i < this->getWidth() - 1; i++)
this->textAt(x - 1 + i, y, " ");
this->textAt(x, y, "%s", text);
this->setFontColorBG(BG_NORMAL);
}
Common::String TextView::colorizeStatus(char statustype) {
Common::String output;
if (!settings._enhancements || !settings._enhancementsOptions._textColorization) {
output = statustype;
return output;
}
switch (statustype) {
case 'P':
output = FG_GREEN;
break;
case 'S':
output = FG_PURPLE;
break;
case 'D':
output = FG_RED;
break;
default:
output = statustype;
return output;
}
output += statustype;
output += FG_WHITE;
return output;
}
Common::String TextView::colorizeString(Common::String input, ColorFG color, uint colorstart, uint colorlength) {
if (!settings._enhancements || !settings._enhancementsOptions._textColorization)
return input;
Common::String output = "";
size_t length = input.size();
size_t i;
bool colorization = false;
// loop through the entire Common::String and
for (i = 0; i < length; i++) {
if (i == colorstart) {
output += color;
colorization = true;
}
output += input[i];
if (colorization) {
colorlength--;
if (colorlength == 0) {
output += FG_WHITE;
colorization = false;
}
}
}
// if we reached the end of the Common::String without
// resetting the color to white, do it now
if (colorization)
output += FG_WHITE;
return output;
}
void TextView::setFontColor(ColorFG fg, ColorBG bg) {
_charset->setFontColorFG(fg);
_charset->setFontColorBG(bg);
}
void TextView::setFontColorFG(ColorFG fg) {
_charset->setFontColorFG(fg);
}
void TextView::setFontColorBG(ColorBG bg) {
_charset->setFontColorBG(bg);
}
void TextView::textAt(int x, int y, const char *fmt, ...) {
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
optionAt(x, y, '\0', "%s", buffer);
}
void TextView::optionAt(int x, int y, char key, const char *fmt, ...) {
char buffer[1024];
uint i;
uint offset = 0;
bool reenableCursor = false;
if (_cursorFollowsText && _cursorEnabled) {
disableCursor();
reenableCursor = true;
}
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
for (i = 0; i < strlen(buffer); i++) {
switch (buffer[i]) {
case FG_GREY:
case FG_BLUE:
case FG_PURPLE:
case FG_GREEN:
case FG_RED:
case FG_YELLOW:
case FG_WHITE:
setFontColorFG((ColorFG)buffer[i]);
offset++;
break;
default:
drawChar(buffer[i], x + (i - offset), y);
}
}
if (_cursorFollowsText)
setCursorPos(x + i, y, true);
if (reenableCursor)
enableCursor();
if (key) {
Common::Rect r(
SCALED(_bounds.left + (x * CHAR_WIDTH)),
SCALED(_bounds.top + (y * CHAR_HEIGHT)),
SCALED(_bounds.left + (x + strlen(buffer) - offset) * CHAR_WIDTH),
SCALED(_bounds.top + (y + 1) * CHAR_HEIGHT)
);
_options.push_back(Option(r, key));
}
}
void TextView::scroll() {
_screen->drawSubRectOn(_screen,
SCALED(_bounds.left),
SCALED(_bounds.top),
SCALED(_bounds.left),
SCALED(_bounds.top) + SCALED(CHAR_HEIGHT),
SCALED(_bounds.width()),
SCALED(_bounds.height()) - SCALED(CHAR_HEIGHT));
_screen->fillRect(SCALED(_bounds.left),
SCALED(_bounds.top + (CHAR_HEIGHT * (_rows - 1))),
SCALED(_bounds.width()),
SCALED(CHAR_HEIGHT),
0, 0, 0);
update();
}
void TextView::setCursorPos(int x, int y, bool clearOld) {
while (x >= _columns) {
x -= _columns;
y++;
}
assertMsg(y < _rows, "y value of %d out of range", y);
if (clearOld && _cursorEnabled) {
drawChar(' ', _cursorX, _cursorY);
update(_cursorX * CHAR_WIDTH, _cursorY * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
}
_cursorX = x;
_cursorY = y;
drawCursor();
}
void TextView::enableCursor() {
_cursorEnabled = true;
drawCursor();
}
void TextView::disableCursor() {
_cursorEnabled = false;
drawChar(' ', _cursorX, _cursorY);
update(_cursorX * CHAR_WIDTH, _cursorY * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
}
void TextView::drawCursor() {
assertMsg(_cursorPhase >= 0 && _cursorPhase < 4, "invalid cursor phase: %d", _cursorPhase);
if (!_cursorEnabled)
return;
drawChar(31 - _cursorPhase, _cursorX, _cursorY);
update(_cursorX * CHAR_WIDTH, _cursorY * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
}
void TextView::cursorTimer(void *data) {
TextView *thiz = static_cast<TextView *>(data);
thiz->_cursorPhase = (thiz->_cursorPhase + 1) % 4;
thiz->drawCursor();
}
char TextView::getOptionAt(const Common::Point &mousePos) {
for (uint idx = 0; idx < _options.size(); ++idx) {
if (_options[idx].contains(mousePos))
return _options[idx]._key;
}
return '\0';
}
void TextView::clearOptions() {
_options.clear();
}
Common::Rect TextView::getTextBounds(int x, int y, int textWidth) const {
return Common::Rect(
SCALED(_bounds.left + (x * CHAR_WIDTH)),
SCALED(_bounds.top + (y * CHAR_HEIGHT)),
SCALED(_bounds.left + (x + textWidth * CHAR_WIDTH)),
SCALED(_bounds.top + (y + 1) * CHAR_HEIGHT)
);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,149 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_TEXTVIEW_H
#define ULTIMA4_VIEWS_TEXTVIEW_H
#include "ultima/ultima4/views/view.h"
#include "ultima/ultima4/gfx/image.h"
#include "common/array.h"
#include "common/rect.h"
namespace Ultima {
namespace Ultima4 {
#define PRINTF_LIKE(x,y)
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 8
/**
* A view of a text area. Keeps track of the cursor position.
*/
class TextView : public View {
struct Option : public Common::Rect {
char _key;
Option() : Common::Rect(), _key('\0') {}
Option(const Common::Rect &r, char key) : Common::Rect(r), _key(key) {}
};
protected:
int _columns, _rows; /**< size of the view in character cells */
bool _cursorEnabled; /**< whether the cursor is enabled */
bool _cursorFollowsText; /**< whether the cursor is moved past the last character written */
int _cursorX, _cursorY; /**< current position of cursor */
int _cursorPhase; /**< the rotation state of the cursor */
static Image *_charset; /**< image containing font */
Common::Array<Option> _options;
public:
TextView(int x, int y, int columns, int rows);
virtual ~TextView();
void reinit();
int getCursorX() const {
return _cursorX;
}
int getCursorY() const {
return _cursorY;
}
bool getCursorEnabled() const {
return _cursorEnabled;
}
int getWidth() const {
return _columns;
}
/**
* Draw a character from the charset onto the view.
*/
void drawChar(int chr, int x, int y);
/**
* Draw a character from the charset onto the view, but mask it with
* horizontal lines. This is used for the avatar symbol in the
* statistics area, where a line is masked out for each virtue in
* which the player is not an avatar.
*/
void drawCharMasked(int chr, int x, int y, byte mask);
/**
* Draw text at the given position
*/
void textAt(int x, int y, const char *fmt, ...);
/**
* Draw an option at
*/
void optionAt(int x, int y, char key, const char *fmt, ...);
void scroll();
void setCursorFollowsText(bool follows) {
_cursorFollowsText = follows;
}
void setCursorPos(int x, int y, bool clearOld = true);
void enableCursor();
void disableCursor();
void drawCursor();
static void cursorTimer(void *data);
// functions to modify the charset font palette
void setFontColor(ColorFG fg, ColorBG bg);
void setFontColorFG(ColorFG fg);
void setFontColorBG(ColorBG bg);
// functions to add color to strings
/**
* Highlight the selected row using a background color
*/
void textSelectedAt(int x, int y, const char *text);
/**
* Depending on the status type, apply colorization to the character
*/
Common::String colorizeStatus(char statustype);
/**
* Depending on the status type, apply colorization to the character
*/
Common::String colorizeString(Common::String input, ColorFG color, uint colorstart, uint colorlength = 0);
/**
* Checks if a given position has an option
*/
char getOptionAt(const Common::Point &mousePos);
/**
* Clear the options list
*/
void clearOptions();
/**
* Returns the physical screen dimensions of text at a given
* text location
*/
Common::Rect getTextBounds(int x, int y, int textWidth) const;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,211 @@
/* 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/gfx/image.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/tile.h"
#include "ultima/ultima4/map/tileanim.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/views/tileview.h"
#include "ultima/ultima4/ultima4.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
TileView::TileView(int x, int y, int columns, int rows) :
View(x, y, columns * TILE_WIDTH, rows * TILE_HEIGHT) {
_columns = columns;
_rows = rows;
_tileWidth = TILE_WIDTH;
_tileHeight = TILE_HEIGHT;
_tileSet = g_tileSets->get("base");
_animated = Image::create(SCALED(_tileWidth), SCALED(_tileHeight), g_system->getScreenFormat());
_dest = nullptr;
}
TileView::TileView(int x, int y, int columns, int rows, const Common::String &tileset) :
View(x, y, columns * TILE_WIDTH, rows * TILE_HEIGHT) {
_columns = columns;
_rows = rows;
_tileWidth = TILE_WIDTH;
_tileHeight = TILE_HEIGHT;
_tileSet = g_tileSets->get(tileset);
_animated = Image::create(SCALED(_tileWidth), SCALED(_tileHeight), g_system->getScreenFormat());
_dest = nullptr;
}
TileView::~TileView() {
delete _animated;
}
void TileView::reinit() {
View::reinit();
_tileSet = g_tileSets->get("base");
// Scratchpad needs to be re-inited if we rescale...
if (_animated) {
delete _animated;
_animated = nullptr;
}
_animated = Image::create(SCALED(_tileWidth), SCALED(_tileHeight), _dest ? _dest->format() : g_system->getScreenFormat());
}
void TileView::loadTile(MapTile &mapTile) {
// This attempts to preload tiles in advance
Tile *tile = _tileSet->get(mapTile._id);
if (tile) {
tile->getImage();
}
// But may fail if the tiles don't exist directly in the expected imagesets
}
void TileView::drawTile(MapTile &mapTile, bool focus, int x, int y) {
Tile *tile = _tileSet->get(mapTile._id);
Image *image = tile->getImage();
assertMsg(x < _columns, "x value of %d out of range", x);
assertMsg(y < _rows, "y value of %d out of range", y);
// Blank scratch pad
_animated->fillRect(0, 0, SCALED(_tileWidth), SCALED(_tileHeight), 0, 0, 0, 255);
// Draw blackness on the tile.
_animated->drawSubRectOn(_dest, SCALED(x * _tileWidth + _bounds.left),
SCALED(y * _tileHeight + _bounds.top), 0, 0,
SCALED(_tileWidth), SCALED(_tileHeight));
// Draw the tile to the screen
if (tile->getAnim()) {
// First, create our animated version of the tile
#ifdef IOS_ULTIMA4
animated->clearImageContents();
#endif
tile->getAnim()->draw(_animated, tile, mapTile, DIR_NONE);
// Then draw it to the screen
_animated->drawSubRectOn(_dest, SCALED(x * _tileWidth + _bounds.left),
SCALED(y * _tileHeight + _bounds.top), 0, 0,
SCALED(_tileWidth), SCALED(_tileHeight));
} else {
image->drawSubRectOn(_dest, SCALED(x * _tileWidth + _bounds.left),
SCALED(y * _tileHeight + _bounds.top),
0, SCALED(_tileHeight * mapTile._frame),
SCALED(_tileWidth), SCALED(_tileHeight));
}
// Draw the focus around the tile if it has the focus
if (focus)
drawFocus(x, y);
}
void TileView::drawTile(Std::vector<MapTile> &tiles, bool focus, int x, int y) {
assertMsg(x < _columns, "x value of %d out of range", x);
assertMsg(y < _rows, "y value of %d out of range", y);
// Clear tile contents
_animated->fillRect(0, 0, SCALED(_tileWidth), SCALED(_tileHeight), 0, 0, 0, 255);
_animated->drawSubRectOn(_dest,
SCALED(x * _tileWidth + _bounds.left), SCALED(y * _tileHeight + _bounds.top),
0, 0,
SCALED(_tileWidth), SCALED(_tileHeight)
);
// Iterate through rendering each of the needed tiles
for (int t = tiles.size() - 1; t >= 0; --t) {
MapTile &frontTile = tiles[t];
Tile *frontTileType = _tileSet->get(frontTile._id);
if (!frontTileType) {
// TODO: This leads to an error. It happens after graphics mode changes.
return;
}
// Get the image for the tile
Image *image = frontTileType->getImage();
// Draw the tile to the screen
if (frontTileType->getAnim()) {
// First, create our animated version of the tile
frontTileType->getAnim()->draw(_animated, frontTileType, frontTile, DIR_NONE);
} else {
if (!image)
// FIXME: This is a problem, error message it.
return;
image->drawSubRectOn(_animated, 0, 0,
0, SCALED(_tileHeight * frontTile._frame),
SCALED(_tileWidth), SCALED(_tileHeight)
);
}
// Then draw it to the screen
_animated->drawSubRectOn(_dest, SCALED(x * _tileWidth + _bounds.left),
SCALED(y * _tileHeight + _bounds.top), 0, 0,
SCALED(_tileWidth), SCALED(_tileHeight)
);
}
// Draw the focus around the tile if it has the focus
if (focus)
drawFocus(x, y);
}
void TileView::drawFocus(int x, int y) {
assertMsg(x < _columns, "x value of %d out of range", x);
assertMsg(y < _rows, "y value of %d out of range", y);
// Draw the focus rectangle around the tile
if ((g_screen->_currentCycle * 4 / SCR_CYCLE_PER_SECOND) % 2) {
// left edge
_screen->fillRect(SCALED(x * _tileWidth + _bounds.left),
SCALED(y * _tileHeight + _bounds.top),
SCALED(2), SCALED(_tileHeight), 0xff, 0xff, 0xff);
// top edge
_screen->fillRect(SCALED(x * _tileWidth + _bounds.left),
SCALED(y * _tileHeight + _bounds.top),
SCALED(_tileWidth), SCALED(2),
0xff, 0xff, 0xff);
// Right edge
_screen->fillRect(SCALED((x + 1) * _tileWidth + _bounds.left - 2),
SCALED(y * _tileHeight + _bounds.top),
SCALED(2), SCALED(_tileHeight),
0xff, 0xff, 0xff);
// Bottom edge
_screen->fillRect(SCALED(x * _tileWidth + _bounds.left),
SCALED((y + 1) * _tileHeight + _bounds.top - 2),
SCALED(_tileWidth), SCALED(2),
0xff, 0xff, 0xff);
}
}
void TileView::setTileset(Tileset *tileset) {
this->_tileSet = tileset;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,73 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_TILEVIEW_H
#define ULTIMA4_VIEWS_TILEVIEW_H
#include "ultima/ultima4/views/view.h"
namespace Ultima {
namespace Ultima4 {
class Tile;
class Tileset;
class MapTile;
/**
* A view of a grid of tiles. Used to draw Maps.
* @todo
* <ul>
* <li>use for gem view</li>
* <li>intialize from a Layout?</li>
* </ul>
*/
class TileView : public View {
public:
TileView(int x, int y, int columns, int rows);
TileView(int x, int y, int columns, int rows, const Common::String &tileset);
virtual ~TileView();
void reinit();
void drawTile(MapTile &mapTile, bool focus, int x, int y);
void drawTile(Std::vector<MapTile> &tiles, bool focus, int x, int y);
/**
* Draw a focus rectangle around the tile
*/
void drawFocus(int x, int y);
void loadTile(MapTile &mapTile);
void setTileset(Tileset *tileset);
void setDest(Image *dest) {
_dest = dest;
}
protected:
int _columns, _rows;
int _tileWidth, _tileHeight;
Tileset *_tileSet;
Image *_animated; /**< a scratchpad image for drawing animations */
Image *_dest; // Dest surface, nullptr by default for screen
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,99 @@
/* 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/gfx/image.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/views/view.h"
namespace Ultima {
namespace Ultima4 {
Image *View::_screen = nullptr;
View::View(int x, int y, int width, int height) :
_bounds(Common::Rect(x, y, x + width, y + height)),
_highlighted(false) {
if (_screen == nullptr)
_screen = imageMgr->get("screen")->_image;
}
void View::reinit() {
_screen = imageMgr->get("screen")->_image;
}
void View::clear() {
unhighlight();
_screen->fillRect(
SCALED(_bounds.left), SCALED(_bounds.top),
SCALED(_bounds.width()), SCALED(_bounds.height()), 0, 0, 0);
}
void View::update() {
if (_highlighted)
drawHighlighted();
#ifdef IOS_ULTIMA4
U4IOS::updateView();
#endif
}
void View::update(int x, int y, int width, int height) {
if (_highlighted)
drawHighlighted();
#ifdef IOS_ULTIMA4
U4IOS::updateRectInView(x, y, width, height);
#endif
}
void View::highlight(int x, int y, int width, int height) {
_highlighted = true;
_highlightBounds = Common::Rect(x, y, x + width, y + height);
update(x, y, width, height);
}
void View::unhighlight() {
_highlighted = false;
update(_highlightBounds.left, _highlightBounds.top,
_highlightBounds.width(), _highlightBounds.height());
_highlightBounds = Common::Rect();
}
void View::drawHighlighted() {
Image *screen = imageMgr->get("screen")->_image;
Image *tmp = Image::create(SCALED(_highlightBounds.width()),
SCALED(_highlightBounds.height()), screen->format());
if (!tmp)
return;
screen->drawSubRectOn(tmp, 0, 0,
SCALED(_bounds.left + _highlightBounds.left),
SCALED(_bounds.top + _highlightBounds.top),
SCALED(_highlightBounds.width()),
SCALED(_highlightBounds.height()));
tmp->drawHighlighted();
tmp->draw(SCALED(_bounds.left + _highlightBounds.left), SCALED(_bounds.top + _highlightBounds.top));
delete tmp;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,81 @@
/* 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/>.
*
*/
#ifndef ULTIMA4_VIEWS_VIEW_H
#define ULTIMA4_VIEWS_VIEW_H
namespace Ultima {
namespace Ultima4 {
#define SCALED(n) ((n) * settings._scale)
class Image;
/**
* Generic base class for reflecting the state of a game object onto
* the screen.
*/
class View {
public:
View(int x, int y, int width, int height);
virtual ~View() {}
/**
* Hook for reinitializing when graphics reloaded.
*/
virtual void reinit();
/**
* Clear the view to black.
*/
virtual void clear();
/**
* Update the view to the screen.
*/
virtual void update();
/**
* Update a piece of the view to the screen.
*/
virtual void update(int x, int y, int width, int height);
/**
* Highlight a piece of the screen by drawing it in inverted colors.
*/
virtual void highlight(int x, int y, int width, int height);
virtual void unhighlight();
protected:
Common::Rect _bounds;
Common::Rect _highlightBounds;
bool _highlighted;
void drawHighlighted();
#ifdef IOS_ULTIMA4
friend void U4IOS::updateScreenView();
#endif
static Image *_screen;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif