/* 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 .
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/misc/u6_llist.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/gui/widgets/msg_scroll_new_ui.h"
#include "ultima/nuvie/core/effect.h" /* for initial fade-in */
#include "ultima/nuvie/core/tile_manager.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/gui/gui.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/screen/game_palette.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/script/script.h"
#include "ultima/nuvie/core/u6_objects.h"
#include "ultima/nuvie/gui/widgets/command_bar.h"
#include "ultima/nuvie/views/actor_view.h"
#include "ultima/nuvie/views/inventory_view.h"
#include "ultima/nuvie/gui/widgets/background.h"
#include "ultima/nuvie/keybinding/keys.h"
namespace Ultima {
namespace Nuvie {
#define TMP_MAP_BORDER 3
#define WRAP_VIEWP(p,p1,s) ((p1-p) < 0 ? (p1-p) + s : p1-p)
// This should make the mouse-cursor hovering identical to that in U6.
static const uint8 movement_array[9 * 9] = {
9, 9, 2, 2, 2, 2, 2, 3, 3,
9, 9, 9, 2, 2, 2, 3, 3, 3,
8, 9, 9, 2, 2, 2, 3, 3, 4,
8, 8, 8, 9, 2, 3, 4, 4, 4,
8, 8, 8, 8, 1, 4, 4, 4, 4,
8, 8, 8, 7, 6, 5, 4, 4, 4,
8, 7, 7, 6, 6, 6, 5, 5, 4,
7, 7, 7, 6, 6, 6, 5, 5, 5,
7, 7, 6, 6, 6, 6, 6, 5, 5
};
static const Tile grid_tile = {
0,
false,
false,
false,
false,
false,
true,
false,
false,
0,
//uint8 qty;
//uint8 flags;
0,
0,
0,
{
54, 255, 255, 255, 58, 255, 255, 255, 58, 255, 255, 255, 58, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
58, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
58, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
58, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
}
};
MapWindow::MapWindow(const Configuration *cfg, Map *m): GUI_Widget(nullptr, 0, 0, 0, 0), config(cfg),
map(m), anim_manager(nullptr), cur_x(0), cur_y(0), mousecenter_x(0),
mousecenter_y(0), cur_x_add(0), cur_y_add(0), vel_x(0), vel_y(0),
last_boundary_fill_x(0), last_boundary_fill_y(0), cursor_x(0), cursor_y(0),
show_cursor(false), show_use_cursor(false), show_grid(false), x_ray_view(X_RAY_OFF),
freeze_blacking_location(false), enable_blacking(true), new_thumbnail(false),
thumbnail(nullptr), overlay(nullptr), overlay_level(MAP_OVERLAY_DEFAULT),
cur_level(0), tmp_map_buf(nullptr), selected_obj(nullptr), look_obj(nullptr),
look_actor(nullptr), walking(false), looking(false),
original_obj_loc(MapCoord(0, 0, 0)), roof_tiles(nullptr),
draw_brit_lens_anim(false), draw_garg_lens_anim(false), window_updated(true),
roof_display(ROOF_DISPLAY_NORMAL), lighting_update_required(true), game(nullptr),
custom_actor_tiles(false), tmp_map_width(0), tmp_map_height(0), tile_manager(nullptr),
obj_manager(nullptr), actor_manager(nullptr), map_center_xoff(0), cursor_tile(nullptr),
use_tile(nullptr), win_width(0), win_height(0), border_width(0), hackmove(false),
game_started(false), wizard_eye_info({nullptr, 0, 0, 0, nullptr}) {
config->value("config/GameType", game_type);
uint16 x_off = Game::get_game()->get_game_x_offset();
uint16 y_off = Game::get_game()->get_game_y_offset();
GUI_Widget::Init(nullptr, x_off, y_off, 0, 0);
screen = nullptr;
map_width = map->get_width(cur_level);
config->value(config_get_game_key(config) + "/map_tile_lighting", using_map_tile_lighting, game_type == NUVIE_GAME_MD ? false : true);
config->value("config/input/enable_doubleclick", enable_doubleclick, true);
config->value("config/input/look_on_left_click", look_on_left_click, true);
set_use_left_clicks();
config->value("config/input/walk_with_left_button", walk_with_left_button, true);
set_walk_button_mask();
config->value("config/cheats/min_brightness", min_brightness, 0);
roof_mode = Game::get_game()->is_roof_mode();
set_interface();
}
MapWindow::~MapWindow() {
set_overlay(nullptr); // free
free(tmp_map_buf);
delete anim_manager;
if (roof_tiles)
delete roof_tiles;
}
bool MapWindow::init(TileManager *tm, ObjManager *om, ActorManager *am) {
game = Game::get_game();
tile_manager = tm;
obj_manager = om;
actor_manager = am;
uint16 map_w = 11, map_h = 11;
border_width = game->get_background()->get_border_width();
if (!game->is_orig_style()) {
uint16 game_width = game->get_game_width();
uint16 game_height = game->get_game_height();
if (game->is_original_plus_cutoff_map()) {
map_center_xoff = 0;
game_width -= border_width; // don't go over border
} else if (game->is_original_plus_full_map()) {
map_center_xoff = (border_width / 16) % 16;
} else { // new style
map_center_xoff = 0;
}
map_w = game_width / 16;
map_h = game_height / 16;
if (game_width % 16 != 0 || map_w % 2 == 0) { // not just the right size
map_w += 1;
if (map_w % 2 == 0) // need odd number of tiles to center properly
map_w += 1;
}
if (game_height % 16 != 0 || map_h % 2 == 0) { // not just the right size
map_h += 1;
if (map_h % 2 == 0) // need odd number of tiles to center properly
map_h += 1;
}
offset_x -= (map_w * 16 - game_width) / 2;
offset_y -= (map_h * 16 - game_height) / 2;
} else
map_center_xoff = 0;
anim_manager = new AnimManager(offset_x, offset_y);
cursor_tile = tile_manager->get_cursor_tile();
use_tile = tile_manager->get_use_tile();
area.left = offset_x;
area.top = offset_y;
set_windowSize(map_w, map_h);
// hide the window until game is fully loaded and does fade-in
get_overlay(); // this allocates `overlay`
overlay_level = MAP_OVERLAY_ONTOP;
assert(SDL_FillRect(overlay, nullptr, game->get_palette()->get_bg_color()) == 0);
wizard_eye_info.eye_tile = tile_manager->get_tile(TILE_U6_WIZARD_EYE);
wizard_eye_info.moves_left = 0;
wizard_eye_info.caller = nullptr;
if (roof_mode)
loadRoofTiles();
return true;
}
void MapWindow::set_use_left_clicks() {
if (enable_doubleclick || look_on_left_click)
set_accept_mouseclick(true, USE_BUTTON); // allow left clicks
else
set_accept_mouseclick(false, USE_BUTTON); // disallow left clicks
}
bool MapWindow::set_windowSize(uint16 width, uint16 height) {
win_width = width;
win_height = height;
area.setWidth(win_width * 16);
area.setHeight(win_height * 16);
// We make the temp map +1 bigger on the top and left edges
// and +2 bigger on the bottom and right edges
// The +1 is for the boundary fill function
// The additional +1 on the right/bottom edges is needed
// to hide objects on boundarys when wall is in darkness
tmp_map_width = win_width + (TMP_MAP_BORDER * 2);// + 1;
tmp_map_height = win_height + (TMP_MAP_BORDER * 2);// + 1;
tmp_map_buf = (uint16 *)nuvie_realloc(tmp_map_buf, tmp_map_width * tmp_map_height * sizeof(uint16));
if (tmp_map_buf == nullptr)
return false;
// if(surface != nullptr)
// delete surface;
// surface = new Surface;
// if(surface->init(win_width*16,win_height*16) == false)
// return false;
if (game->is_orig_style()) {
clip_rect.left = area.left + 8;
clip_rect.setWidth((win_width - 1) * 16);
if (game_type == NUVIE_GAME_U6) {
clip_rect.top = area.top + 8;
clip_rect.setHeight((win_height - 1) * 16);
} else {
clip_rect.top = area.top + 16;
clip_rect.setHeight((win_height - 2) * 16);
}
} else {
clip_rect.left = game->get_game_x_offset();
clip_rect.top = game->get_game_y_offset();
if (game->is_original_plus_cutoff_map())
clip_rect.setWidth(game->get_game_width() - border_width - 1);
else
clip_rect.setWidth(game->get_game_width());
clip_rect.setHeight(game->get_game_height());
}
anim_manager->set_area(clip_rect);
Screen *const gameScreen = Game::get_game()->get_screen();
assert(gameScreen);
_mapWinSubSurf.create(*gameScreen->get_sdl_surface(), clip_rect);
reset_mousecenter();
updateBlacking();
return true;
}
void MapWindow::set_walk_button_mask() {
if (walk_with_left_button)
walk_button_mask = (BUTTON_MASK(USE_BUTTON) | BUTTON_MASK(WALK_BUTTON));
else
walk_button_mask = BUTTON_MASK(WALK_BUTTON);
}
void MapWindow::set_show_cursor(bool state) {
ActorView *actor_view = game->get_view_manager()->get_actor_view();
InventoryView *inventory_view = game->get_view_manager()->get_inventory_view();
if (actor_view)
actor_view->set_show_cursor(false);
if (inventory_view)
inventory_view->set_show_cursor(false);
show_cursor = state;
}
void MapWindow::set_show_use_cursor(bool state) {
ActorView *actor_view = game->get_view_manager()->get_actor_view();
InventoryView *inventory_view = game->get_view_manager()->get_inventory_view();
if (actor_view)
actor_view->set_show_cursor(false);
if (inventory_view)
inventory_view->set_show_cursor(false);
show_use_cursor = state;
}
void MapWindow::set_show_grid(bool state) {
show_grid = state;
}
/**
* cheat_off is needed to turn the X-ray cheat off (set to X_RAY_OFF)
* Convoluted logic is so we don't slow down boundaryFill with an extra check
*/
void MapWindow::set_x_ray_view(X_RayType state, bool cheat_off) {
if (x_ray_view == X_RAY_CHEAT_ON) {
if (state == X_RAY_ON) // X_RAY_CHEAT_ON takes precedence to preserve X-ray cheat
return;
else if (state == X_RAY_OFF) {
if (!cheat_off) { // not turning X-ray cheat off
if (game->are_cheats_enabled()) // don't turn off when cheats are enabled
return;
else // need to preserve X-ray cheat setting when cheats are off
state = X_RAY_CHEAT_OFF;
}
}
} else if (x_ray_view == X_RAY_CHEAT_OFF) {
if (state == X_RAY_OFF) // X_RAY_CHEAT_OFF takes precedence to preserve X-ray cheat
return;
else if (state == X_RAY_ON) // need to preserve X-ray cheat setting when cheats are off
state = X_RAY_CHEAT_ON;
}
x_ray_view = state;
updateBlacking();
}
void MapWindow::set_freeze_blacking_location(bool state) {
freeze_blacking_location = state;
}
void MapWindow::set_enable_blacking(bool state) {
enable_blacking = state;
updateBlacking();
}
void MapWindow::set_walking(bool state) {
if (state && game->get_view_manager()->gumps_are_active()) //we don't allow walking while gumps are active.
return;
walking = state;
}
void MapWindow::moveLevel(uint8 new_level) {
cur_level = new_level;
updateBlacking();
}
void MapWindow::moveMap(sint16 new_x, sint16 new_y, sint8 new_level, uint8 new_x_add, uint8 new_y_add) {
map_width = map->get_width(new_level);
if (new_x < 0) {
new_x = map_width + new_x;
} else {
new_x %= map_width;
}
//printf("cur_x = %d\n",new_x);
cur_x = new_x;
cur_y = new_y;
cur_level = new_level;
cur_x_add = new_x_add;
cur_y_add = new_y_add;
updateBlacking();
}
void MapWindow::moveMapRelative(sint16 rel_x, sint16 rel_y) {
moveMap(cur_x + rel_x, cur_y + rel_y, cur_level);
}
/* Move map by relative pixel amount.
*/
void MapWindow::shiftMapRelative(sint16 rel_x, sint16 rel_y) {
const uint8 tile_pitch = 16;
uint32 total_px = (cur_x * tile_pitch) + cur_x_add,
total_py = (cur_y * tile_pitch) + cur_y_add;
total_px += rel_x;
total_py += rel_y;
moveMap(total_px / tile_pitch, total_py / tile_pitch, cur_level,
total_px % tile_pitch, total_py % tile_pitch);
}
/* Center MapWindow on a location.
*/
void MapWindow::centerMap(uint16 x, uint16 y, uint8 z) {
moveMap(x - ((win_width - 1 - map_center_xoff) / 2) , y - ((win_height - 1) / 2), z);
}
void MapWindow::centerMapOnActor(Actor *actor) {
uint16 x;
uint16 y;
uint8 z;
actor->get_location(&x, &y, &z);
centerMap(x, y, z);
return;
}
void MapWindow::centerCursor() {
cursor_x = (win_width - 1 - map_center_xoff) / 2;
cursor_y = (win_height - 1) / 2;
return;
}
void MapWindow::moveCursor(sint16 new_x, sint16 new_y) {
if (new_x < 0 || new_x >= win_width)
return;
if (new_y < 0 || new_y >= win_height)
return;
cursor_x = new_x;
cursor_y = new_y;
return;
}
void MapWindow::moveCursorRelative(sint16 rel_x, sint16 rel_y) {
moveCursor(cursor_x + rel_x, cursor_y + rel_y);
}
bool MapWindow::is_on_screen(uint16 x, uint16 y, uint8 z) {
if (z == cur_level && WRAP_VIEWP(cur_x, x, map_width) < win_width && y >= cur_y && y < cur_y + win_height) {
if (tile_is_black(x, y) == false) {
return true;
}
}
return false;
}
/**
* Can we display an object at this location on the tmp map buffer.
*
* @param x coord on the tmp buf
* @param y coord on the tmp buf
* @param obj object to display
* @return
*/
bool MapWindow::can_display_obj(uint16 x, uint16 y, Obj *obj) {
uint16 tile_num = tmp_map_buf[y * tmp_map_width + x];
if (tile_num == 0) //don't draw object if area is in darkness.
return false;
else {
if (x >= tmp_map_width - 1 || y >= tmp_map_height - 1)
return false;
// We don't show objects on walls if the area to the right or bottom of the wall is in darkness
if (tmp_map_buf[y * tmp_map_width + (x + 1)] == 0 || tmp_map_buf[(y + 1)*tmp_map_width + x] == 0) {
Tile *tile = tile_manager->get_tile(tile_num);
if (((tile->flags1 & TILEFLAG_WALL) || (game_type == NUVIE_GAME_U6 && obj->obj_n == OBJ_U6_BARS)))
return false;
}
}
return true;
}
bool MapWindow::tile_is_black(uint16 x, uint16 y, const Obj *obj) const {
if (game->using_hackmove())
return false;
if (!MapCoord(x, y, cur_level).is_visible()) // tmpBufTileIsBlack will crash if called (doesn't happen in gdb)
return true;
uint16 wrapped_x = WRAP_VIEWP(cur_x, x, map_width);
if (tmpBufTileIsBlack(wrapped_x + TMP_MAP_BORDER, y - cur_y + TMP_MAP_BORDER))
return true;
else if (obj) {
const Tile *tile = tile_manager->get_original_tile(obj_manager->get_obj_tile_num(obj->obj_n) + obj->frame_n);
if (!tile || (tmpBufTileIsBlack(wrapped_x + TMP_MAP_BORDER + 1, y - cur_y + TMP_MAP_BORDER) && !(tile->flags1 & TILEFLAG_WALL))
|| (tmpBufTileIsBlack(wrapped_x + TMP_MAP_BORDER, y - cur_y + TMP_MAP_BORDER + 1) && !(tile->flags1 & TILEFLAG_WALL)))
return true;
}
return false;
}
const char *MapWindow::look(uint16 x, uint16 y, bool show_prefix) {
if (tmp_map_buf[(y + TMP_MAP_BORDER) * tmp_map_width + (x + TMP_MAP_BORDER)] == 0) //black area
return "darkness."; // nothing to see here. ;)
uint16 wrapped_x = WRAPPED_COORD(cur_x + x, cur_level);
const Actor *actor = actor_manager->get_actor(wrapped_x, cur_y + y, cur_level);
if (actor != nullptr && actor->is_visible())
return actor_manager->look_actor(actor, show_prefix);
return map->look(wrapped_x, cur_y + y, cur_level);
}
Obj *MapWindow::get_objAtCursor(bool for_use /* = false */) {
MapCoord coord = get_cursorCoord();
return get_objAtCoord(coord, OBJ_SEARCH_TOP, OBJ_EXCLUDE_IGNORED, for_use);
}
Obj *MapWindow::get_objAtCoord(MapCoord coord, bool top_obj, bool include_ignored_objects, bool for_use /* = false */) {
if (tile_is_black(coord.x, coord.y))
return nullptr; // nothing to see here. ;)
Obj *obj = obj_manager->get_obj(coord.x, coord.y, coord.z, top_obj, include_ignored_objects);
// Savage Empire Create Object from Tile
if (for_use && game_type == NUVIE_GAME_SE && obj == nullptr) {
Script *script = game->get_script();
uint16 map_win_x = WRAP_VIEWP(cur_x, coord.x, map_width);
uint16 map_win_y = coord.y - cur_y;
// Check that x,y is in tmp_map_buf
if (is_on_screen(coord.x, coord.y, coord.z)) {
uint16 tile_n = tmp_map_buf[(map_win_y + TMP_MAP_BORDER) * tmp_map_width + (map_win_x + TMP_MAP_BORDER)];
uint16 obj_n = script->call_get_tile_to_object_mapping(tile_n);
if (obj_n != 0) {
obj = obj_manager->get_tile_obj(obj_n);
obj->x = coord.x;
obj->y = coord.y;
obj->z = coord.z;
}
}
}
return obj;
}
Actor *MapWindow::get_actorAtCursor() {
if (tmp_map_buf[(cursor_y + TMP_MAP_BORDER) * tmp_map_width + (cursor_x + TMP_MAP_BORDER)] == 0) //black area
return nullptr; // nothing to see here. ;)
return actor_manager->get_actor(WRAPPED_COORD(cur_x + cursor_x, cur_level), WRAPPED_COORD(cur_y + cursor_y, cur_level), cur_level);
}
MapCoord MapWindow::get_cursorCoord() {
return MapCoord(WRAPPED_COORD(cur_x + cursor_x, cur_level), WRAPPED_COORD(cur_y + cursor_y, cur_level), cur_level);
}
void MapWindow::get_level(uint8 *level) const {
*level = cur_level;
}
void MapWindow::get_pos(uint16 *x, uint16 *y, uint8 *px, uint8 *py) const {
*x = cur_x;
*y = cur_y;
if (px)
*px = cur_x_add;
if (py)
*py = cur_y_add;
}
void MapWindow::get_windowSize(uint16 *width, uint16 *height) const {
*width = win_width;
*height = win_height;
}
/* Returns true if the location at the coordinates is visible on the map window.
*/
bool MapWindow::in_window(uint16 x, uint16 y, uint8 z) const {
return ((z == cur_level && WRAP_VIEWP(cur_x, x, map_width) < win_width
&& y >= cur_y && y <= (cur_y + win_height)));
}
/* Update player position if walking to mouse cursor. Update map position.
*/
void MapWindow::update() {
GameClock *clock = game->get_clock();
Events *event = game->get_event();
static uint32 last_update_time = clock->get_ticks();
uint32 update_time = clock->get_ticks();
// do fade-in on the first update (game has loaded now)
if (game_started == false) {
new GameFadeInEffect(game->get_palette()->get_bg_color());
game_started = true;
}
anim_manager->update(); // update animations
if (vel_x || vel_y) { // this slides the map
if ((update_time - last_update_time) >= 100) { // only move every 10th sec
sint32 sx = vel_x / 10, sy = vel_y / 10;
if (vel_x && !sx) // move even if vel_x/vel_y was < 10
sx = (vel_x < 0) ? -1 : 1;
if (vel_y && !sy)
sy = (vel_y < 0) ? -1 : 1;
shiftMapRelative(sx, sy);
last_update_time = update_time;
}
}
if (walking) {
if (Events::get()->getButtonState() & walk_button_mask) {
if (game->user_paused())
return;
int mx, my; // bit-AND buttons with mouse state to test
screen->get_mouse_location(&mx, &my);
if (is_wizard_eye_mode()) {
// int wx, wy;
// mouseToWorldCoords(mx, my, wx, wy);
sint16 rx, ry;
get_movement_direction((uint16)mx, (uint16)my, rx, ry);
moveMapRelative((rx == 0) ? 0 : rx < 0 ? -1 : 1,
(ry == 0) ? 0 : ry < 0 ? -1 : 1);
wizard_eye_update();
} else {
//DEBUG(0, LEVEL_DEBUGGING, "MOUSE WALKING...\n");
event->walk_to_mouse_cursor((uint32)mx,
(uint32)my);
}
} else
walking = false;
}
KeyBinder *keybinder = game->get_keybinder();
if (keybinder->is_joy_repeat_enabled() && (event->get_mode() == MOVE_MODE || is_wizard_eye_mode())
&& keybinder->get_next_joy_repeat_time() < clock->get_ticks()) {
Common::KeyCode key = keybinder->get_key_from_joy_walk_axes();
if (key != Common::KEYCODE_INVALID) {
Common::Event sdl_event;
sdl_event.type = Common::EVENT_KEYDOWN;
sdl_event.kbd.keycode = key;
sdl_event.kbd.flags = 0;
if (GUI::get_gui()->HandleEvent(&sdl_event) == GUI_PASS)
event->handleEvent(&sdl_event);
}
}
}
// moved from updateBlacking() so you don't have to update all blacking (SB-X)
void MapWindow::updateAmbience() {
lighting_update_required = true;
}
// moved from updateBlacking() so you don't have to update all blacking (SB-X)
void MapWindow::createLightOverlay() {
//Dusk starts at 19:00
//It's completely dark by 20:00
//Dawn starts at 5:00
//It's completely bright by 6:00
//Dusk and dawn operate by changing the ambient light, not by changing the radius of the avatar's light globe
if (!screen)
return;
uint8 cur_min_brightness;
if (game->are_cheats_enabled())
cur_min_brightness = min_brightness;
else
cur_min_brightness = 0;
GameClock *clock = game->get_clock();
Weather *weather = game->get_weather();
bool dawn_or_dusk = false;
int h = clock->get_hour();
int a;
if (x_ray_view >= X_RAY_ON)
a = 255;
else if (in_dungeon_level())
a = cur_min_brightness;
else if (weather->is_eclipse()) //solar eclipse
a = cur_min_brightness;
else if (h == 19) { //Dusk -- Smooth transition between 255 and min_brightness during first 59 minutes
if (screen->get_lighting_style() == LightingSmooth) {
dawn_or_dusk = true;
a = 255 - (uint8)((255.0f - cur_min_brightness) * (float)clock->get_minute() / 59.0f);
} else {
a = 20 * (6 - clock->get_minute() / 10);
if (a < cur_min_brightness)
a = cur_min_brightness;
}
} else if (h == 5) { //Dawn -- Smooth transition between min_brightness and 255 during first 59 minutes
if (screen->get_lighting_style() == LightingSmooth) {
dawn_or_dusk = true;
a = cur_min_brightness + (255.0f - cur_min_brightness) * (float)clock->get_minute() / 59.0f;
} else {
a = 20 * (1 + clock->get_minute() / 10);
if (a < cur_min_brightness)
a = cur_min_brightness;
}
} else if (h > 5 && h < 19) //Day
a = 255;
else //Night
a = cur_min_brightness;
if (a > 255)
a = 255;
bool party_light_source;
// smooth seems to need an enormous range in order to have smooth transitions
if (a < (screen->get_lighting_style() == LightingSmooth ? 248 : 81) &&
(game->get_party()->has_light_source() || clock->get_timer(GAMECLOCK_TIMER_U6_LIGHT) != 0)) { //FIXME U6 specific
party_light_source = true;
if (screen->get_lighting_style() == LightingSmooth) {
if (!dawn_or_dusk) // preserve a when dusk or dawn so we have the correct opacity
a = cur_min_brightness;
} else
a = 80;
} else
party_light_source = false;
screen->set_ambient(a);
//Clear the opacity map
screen->clearalphamap8(0, 0, win_width, win_height, screen->get_ambient(), party_light_source);
updateLighting();
lighting_update_required = false;
}
void MapWindow::updateLighting() {
if (using_map_tile_lighting) {
uint16 *ptr = tmp_map_buf;
for (uint16 y = 0; y < tmp_map_height; y++) {
for (uint16 x = 0; x < tmp_map_width; x++) {
if (tmp_map_buf[x + y * tmp_map_width] != 0) {
Tile *tile = tile_manager->get_tile(*ptr);
if (GET_TILE_LIGHT_LEVEL(tile) > 0)
screen->drawalphamap8globe(x - TMP_MAP_BORDER, y - TMP_MAP_BORDER, GET_TILE_LIGHT_LEVEL(tile));
U6LList *obj_list = obj_manager->get_obj_list(cur_x - TMP_MAP_BORDER + x, cur_y - TMP_MAP_BORDER + y, cur_level); //FIXME wrapped coords.
if (obj_list) {
for (U6Link *link = obj_list->start(); link != nullptr; link = link->next) {
Obj *obj = (Obj *)link->data;
tile = tile_manager->get_tile(obj_manager->get_obj_tile_num(obj) + obj->frame_n); //FIXME do we need to check the light for each tile in a multi-tile object.
if (GET_TILE_LIGHT_LEVEL(tile) > 0 && can_display_obj(x, y, obj))
screen->drawalphamap8globe(x - TMP_MAP_BORDER, y - TMP_MAP_BORDER, GET_TILE_LIGHT_LEVEL(tile));
}
}
}
ptr++;
}
}
for (const TileInfo &ti : m_ViewableMapTiles) {
if (GET_TILE_LIGHT_LEVEL(ti.t) > 0)
screen->drawalphamap8globe(ti.x, ti.y, GET_TILE_LIGHT_LEVEL(ti.t));
}
}
/* draw light coming from the actor
Wisps can change the light level depending on their current tile so we can't use actor->light for an actor's innate lighting.
*/
for (uint16 i = 0; i < 256; i++) {
const Actor *actor = actor_manager->get_actor(i);
if (actor->z == cur_level) {
if (actor->x >= cur_x - TMP_MAP_BORDER && actor->x < cur_x + win_width + TMP_MAP_BORDER) {
if (actor->y >= cur_y - TMP_MAP_BORDER && actor->y < cur_y + win_height + TMP_MAP_BORDER) {
if (tmp_map_buf[(actor->y - cur_y + TMP_MAP_BORDER) * tmp_map_width + (actor->x - cur_x + TMP_MAP_BORDER)] != 0) {
uint8 light = actor->get_light_level();
if (light > 0) {
screen->drawalphamap8globe(actor->x - cur_x, actor->y - cur_y, light);
}
}
}
}
}
}
}
void MapWindow::updateBlacking() {
generateTmpMap();
updateAmbience();
m_ViewableObjects.clear();
/// m_ViewableObjTiles.clear();
draw_brit_lens_anim = false;
draw_garg_lens_anim = false;
window_updated = true;
}
void MapWindow::Display(bool full_redraw) {
if (lighting_update_required) {
createLightOverlay();
}
uint16 *map_ptr = tmp_map_buf;
map_ptr += (TMP_MAP_BORDER * tmp_map_width + TMP_MAP_BORDER);// * sizeof(uint16); //remember our tmp map is TMP_MAP_BORDER bigger all around.
for (uint16 i = 0; i < win_height; i++) {
for (uint16 j = 0; j < win_width; j++) {
sint16 draw_x = area.left + (j * 16), draw_y = area.top + (i * 16);
//draw_x -= (cur_x_add <= draw_x) ? cur_x_add : draw_x;
//draw_y -= (cur_y_add <= draw_y) ? cur_y_add : draw_y;
draw_x -= cur_x_add;
draw_y -= cur_y_add;
if (map_ptr[j] == 0) {
screen->clear(draw_x, draw_y, 16, 16, &clip_rect); //blackout tile.
} else {
const Tile *tile;
if (map_ptr[j] >= 16 && map_ptr[j] < 48) { //lay down the base tile for shoreline tiles
tile = tile_manager->get_anim_base_tile(map_ptr[j]);
screen->blit(draw_x, draw_y, (const byte *)tile->data, 8, 16, 16, 16, tile->transparent, &clip_rect);
}
tile = tile_manager->get_tile(map_ptr[j]);
screen->blit(draw_x, draw_y, (const byte *)tile->data, 8, 16, 16, 16, tile->transparent, &clip_rect);
}
}
//map_ptr += map_width;
map_ptr += tmp_map_width ;//* sizeof(uint16);
}
drawObjs();
//drawAnims();
if (roof_mode && roof_display != ROOF_DISPLAY_OFF) {
drawRoofs();
}
if (game->get_clock()->get_timer(GAMECLOCK_TIMER_U6_STORM) != 0) //FIXME u6 specific.
drawRain();
if (show_grid) {
drawGrid();
}
if (show_cursor) {
screen->blit(area.left + cursor_x * 16, area.top + cursor_y * 16, (byte *)cursor_tile->data, 8, 16, 16, 16, true, &clip_rect);
}
if (show_use_cursor) {
screen->blit(area.left + cursor_x * 16, area.top + cursor_y * 16, (byte *)use_tile->data, 8, 16, 16, 16, true, &clip_rect);
}
// screen->fill(0,8,8,win_height*16-16,win_height*16-16);
screen->blitalphamap8(area.left, area.top, &clip_rect);
if (game->get_clock()->get_timer(GAMECLOCK_TIMER_U6_INFRAVISION) != 0)
drawActors();
if (overlay && overlay_level == MAP_OVERLAY_DEFAULT)
screen->blit(area.left, area.top, (byte *)(overlay->getPixels()), overlay->format.bpp(), overlay->w, overlay->h, overlay->pitch, true, &clip_rect);
drawAnims(true);
if (new_thumbnail)
create_thumbnail();
if (is_wizard_eye_mode()) {
uint16 we_x = mousecenter_x * 16 + area.left;
if (game->is_original_plus_full_map())
we_x -= ((map_center_xoff + 1) / 2) * 16;
screen->blit(we_x, mousecenter_y * 16 + area.top, (byte *)wizard_eye_info.eye_tile->data, 8, 16, 16, 16, true, &clip_rect);
}
if (game->is_orig_style())
drawBorder();
if (overlay && overlay_level == MAP_OVERLAY_ONTOP)
screen->blit(area.left, area.top, (byte *)(overlay->getPixels()), overlay->format.bpp(), overlay->w, overlay->h, overlay->pitch, true, &clip_rect);
// ptr = (byte *)screen->get_pixels();
// ptr += 8 * screen->get_pitch() + 8;
// screen->blit(8,8,ptr,8,(win_width-1) * 16,(win_height-1) * 16, win_width * 16, false);
if (game->is_orig_style())
screen->update(area.left + 8, area.top + 8, win_width * 16 - 16, win_height * 16 - 16);
else if (game->is_original_plus_cutoff_map())
screen->update(Game::get_game()->get_game_x_offset(), Game::get_game()->get_game_y_offset(), game->get_game_width() - border_width - 1, game->get_game_height());
else
screen->update(Game::get_game()->get_game_x_offset(), Game::get_game()->get_game_y_offset(), game->get_game_width(), game->get_game_height());
if (window_updated) {
window_updated = false;
game->get_sound_manager()->update_map_sfx();
}
}
void MapWindow::drawActors() {
for (uint16 i = 0; i < 256; i++) {
Actor *actor = actor_manager->get_actor(i);
if (actor->z == cur_level) {
uint8 x = WRAP_VIEWP(cur_x, actor->x, map_width);
if (x < win_width) { //actor->x >= cur_x && actor->x < cur_x + win_width)
if (actor->y >= cur_y && actor->y < cur_y + win_height) {
if (tmp_map_buf[(actor->y - cur_y + TMP_MAP_BORDER) * tmp_map_width + (x + TMP_MAP_BORDER)] != 0) {
drawActor(actor);
}
}
}
}
}
}
//FIX need a function for multi-tile actors.
inline void MapWindow::drawActor(const Actor *actor) {
if (actor->is_visible() /* && actor->obj_n != 0*/
&& (!(actor->obj_flags & OBJ_STATUS_INVISIBLE) || actor->is_in_party() || actor == actor_manager->get_player())
&& actor->get_corpser_flag() == false) {
Tile *tile = tile_manager->get_tile(actor->get_tile_num() + actor->frame_n);
Tile *rtile = nullptr;
if (actor->obj_flags & OBJ_STATUS_INVISIBLE) {
rtile = new Tile(*tile);
for (int x = 0; x < 256; x++)
if (rtile->data[x] != 0x00)
rtile->data[x] = 0xFF;
else
rtile->data[x] = 0x0B;
} else if (actor->status_flags & ACTOR_STATUS_PROTECTED) { // actually this doesn't appear when using a protection ring
rtile = new Tile(*tile);
for (int x = 0; x < 256; x++)
if (rtile->data[x] == 0x00)
rtile->data[x] = 0x0C;
} else if (actor->is_cursed()) {
rtile = new Tile(*tile);
for (int x = 0; x < 256; x++)
if (rtile->data[x] == 0x00)
rtile->data[x] = 0x9;
}
uint16 wrapped_x = WRAP_VIEWP(cur_x, actor->x, map_width);
if (rtile != 0) {
drawNewTile(rtile, wrapped_x, actor->y - cur_y, false);
drawNewTile(rtile, wrapped_x, actor->y - cur_y, true);
delete rtile;
} else {
drawTile(tile, wrapped_x, actor->y - cur_y, false);
drawTile(tile, wrapped_x, actor->y - cur_y, true);
if (game->get_clock()->get_timer(GAMECLOCK_TIMER_U6_INFRAVISION) != 0) {
const Std::list &surrounding_objs = actor->get_surrounding_obj_list();
for (Obj *obj : surrounding_objs) {
const Tile *t = tile_manager->get_original_tile(obj_manager->get_obj_tile_num(obj->obj_n) + obj->frame_n);
uint16 wrapped_obj_x = WRAP_VIEWP(cur_x, obj->x, map_width);
drawTile(t, wrapped_obj_x, obj->y - cur_y, false);
drawTile(t, wrapped_obj_x, obj->y - cur_y, true); // doesn't seem needed but will do it anyway (for now)
}
}
}
}
}
void MapWindow::drawObjs() {
//FIXME: we need to make this more efficient.
drawObjSuperBlock(true, false); //draw force lower objects
drawObjSuperBlock(false, false); //draw lower objects
drawActors();
drawAnims(false);
drawObjSuperBlock(false, true); //draw top objects
drawLensAnim();
return;
}
inline void MapWindow::drawLensAnim() {
if (draw_brit_lens_anim) {
if (cur_x < 0x399)
drawTile(tile_manager->get_tile(TILE_U6_BRITANNIAN_LENS_ANIM_2), 0x398 - cur_x, 0x353 - cur_y, true);
if (cur_x + win_width > 0x39a)
drawTile(tile_manager->get_tile(TILE_U6_BRITANNIAN_LENS_ANIM_1), 0x39a - cur_x, 0x353 - cur_y, true);
}
if (draw_garg_lens_anim) {
if (cur_x < 0x39d)
drawTile(tile_manager->get_tile(TILE_U6_GARGOYLE_LENS_ANIM_2), 0x39c - cur_x, 0x353 - cur_y, true);
if (cur_x + win_width > 0x39e)
drawTile(tile_manager->get_tile(TILE_U6_GARGOYLE_LENS_ANIM_1), 0x39e - cur_x, 0x353 - cur_y, true);
}
}
void MapWindow::drawObjSuperBlock(bool draw_lowertiles, bool toptile) {
uint16 stop_x, stop_y;
if (cur_x < 0)
stop_x = 0;
else
stop_x = cur_x;
if (cur_y < 0)
stop_y = 0;
else
stop_y = cur_y;
for (sint16 y = cur_y + win_height; y >= stop_y; y--) {
for (sint16 x = cur_x + win_width; x >= stop_x; x--) {
U6LList *obj_list = obj_manager->get_obj_list(x, y, cur_level);
if (obj_list) {
for (U6Link *link = obj_list->start(); link != nullptr; link = link->next) {
Obj *obj = (Obj *)link->data;
drawObj(obj, draw_lowertiles, toptile);
}
}
}
}
}
inline void MapWindow::drawObj(const Obj *obj, bool draw_lowertiles, bool toptile) {
sint16 y = obj->y - cur_y;
sint16 x = WRAP_VIEWP(cur_x, obj->x, map_width);
if (x < 0 || y < 0)
return;
if (window_updated) {
m_ViewableObjects.push_back(obj);
if (game_type == NUVIE_GAME_U6 && cur_level == 0 && obj->y == 0x353 && tmp_map_buf[(y + TMP_MAP_BORDER)*tmp_map_width + (x + TMP_MAP_BORDER)] != 0) {
if (obj->obj_n == 394 && obj->x == 0x399) {
draw_brit_lens_anim = true;
} else if (obj->obj_n == 396 && obj->x == 0x39d) {
draw_garg_lens_anim = true;
}
}
}
//don't show invisible objects.
if (obj->status & OBJ_STATUS_INVISIBLE)
return;
Tile *tile = tile_manager->get_original_tile(obj_manager->get_obj_tile_num(obj) + obj->frame_n);
if (draw_lowertiles == false && (tile->flags3 & 0x4) && toptile == false) //don't display force lower tiles.
return;
if (draw_lowertiles == true && !(tile->flags3 & 0x4))
return;
if (tmp_map_buf[(y + TMP_MAP_BORDER)*tmp_map_width + (x + TMP_MAP_BORDER)] == 0) //don't draw object if area is in darkness.
return;
else {
// We don't show objects on walls if the area to the right or bottom of the wall is in darkness
if (tmp_map_buf[(y + TMP_MAP_BORDER)*tmp_map_width + (x + TMP_MAP_BORDER + 1)] == 0 || tmp_map_buf[(y + TMP_MAP_BORDER + 1)*tmp_map_width + (x + TMP_MAP_BORDER)] == 0) {
if ((!(tile->flags1 & TILEFLAG_WALL) || (game_type == NUVIE_GAME_U6 && obj->obj_n == OBJ_U6_BARS)))
return;
}
}
drawTile(tile, x, obj->y - cur_y, toptile);
}
/* The pixeldata in the passed Tile pointer will be used if use_tile_data is
* true, otherwise the current tile is derived from tile_num. This can't be
* used with animated tiles. It only applies to the base tile in multi-tiles.
*/
inline void MapWindow::drawTile(const Tile *tile, uint16 x, uint16 y, bool toptile,
bool use_tile_data) {
uint16 tile_num = tile->tile_num;
//don't show special marker tiles in MD unless "show eggs" is turned on.
if (game_type == NUVIE_GAME_MD
&& tile_num > 2040 && tile_num < 2048
&& !obj_manager->is_showing_eggs())
return;
/* shouldn't be needed for in_town check
if(window_updated)
{
TileInfo ti;
ti.t=tile;
ti.left=x;
ti.top=y;
m_ViewableObjTiles.push_back(ti);
}
*/
bool dbl_width = tile->dbl_width;
bool dbl_height = tile->dbl_height;
if (x < win_width && y < win_height)
drawTopTile(use_tile_data ? tile : tile_manager->get_tile(tile_num), x, y, toptile);
if (dbl_width) {
tile_num--;
if (x > 0 && y < win_height) {
tile = tile_manager->get_tile(tile_num);
drawTopTile(tile, x - 1, y, toptile);
}
}
if (dbl_height) {
tile_num--;
if (y > 0 && x < win_width) {
tile = tile_manager->get_tile(tile_num);
drawTopTile(tile, x, y - 1, toptile);
}
}
if (x > 0 && dbl_width && y > 0 && dbl_height) {
tile_num--;
tile = tile_manager->get_tile(tile_num);
drawTopTile(tile, x - 1, y - 1, toptile);
}
}
inline void MapWindow::drawNewTile(const Tile *tile, uint16 x, uint16 y, bool toptile) {
drawTile(tile, x, y, toptile, true);
}
inline void MapWindow::drawTopTile(const Tile *tile, uint16 x, uint16 y, bool toptile) {
// if(tile->boundary)
// {
// screen->blit(cursor_tile->data,8,x*16,y*16,16,16,false);
// }
// FIXME: Don't use pixel offset (x_add,y_add) here, pass it via params?
if (toptile) {
if (tile->toptile)
// screen->blit(x*16,y*16,tile->data,8,16,16,16,tile->transparent,&clip_rect);
screen->blit(area.left + (x * 16) - cur_x_add, area.top + (y * 16) - cur_y_add, tile->data, 8, 16, 16, 16, tile->transparent, &clip_rect);
} else {
if (!tile->toptile)
// screen->blit(x*16,y*16,tile->data,8,16,16,16,tile->transparent,&clip_rect);
screen->blit(area.left + (x * 16) - cur_x_add, area.top + (y * 16) - cur_y_add, tile->data, 8, 16, 16, 16, tile->transparent, &clip_rect);
}
}
void MapWindow::drawBorder() {
const Tile *tile;
const Tile *tile1;
uint16 i;
if (game_type != NUVIE_GAME_U6)
return;
const uint16 orig_win_w = 11;
const uint16 orig_win_h = 11;
uint16 x_off = Game::get_game()->get_game_x_offset();
uint16 y_off = Game::get_game()->get_game_y_offset();
tile = tile_manager->get_tile(432);
screen->blit(x_off, y_off, tile->data, 8, 16, 16, 16, true, &clip_rect); // upper left corner
tile = tile_manager->get_tile(434);
screen->blit(x_off + (orig_win_w - 1) * 16, y_off, tile->data, 8, 16, 16, 16, true); // upper right corner (got rid of &clip_rect for original+)
tile = tile_manager->get_tile(435);
screen->blit(x_off, y_off + (orig_win_h - 1) * 16, tile->data, 8, 16, 16, 16, true, &clip_rect); // lower left corner
tile = tile_manager->get_tile(437);
screen->blit(x_off + (orig_win_w - 1) * 16, y_off + (orig_win_h - 1) * 16, tile->data, 8, 16, 16, 16, true); // lower right corner (got rid of &clip_rect for original+)
tile = tile_manager->get_tile(433);
tile1 = tile_manager->get_tile(436);
for (i = 1; i < orig_win_w - 1; i++) {
screen->blit(x_off + i * 16, y_off, tile->data, 8, 16, 16, 16, true, &clip_rect); // top row
screen->blit(x_off + i * 16, y_off + (orig_win_h - 1) * 16, tile1->data, 8, 16, 16, 16, true, &clip_rect); // bottom row
}
tile = tile_manager->get_tile(438);
tile1 = tile_manager->get_tile(439);
for (i = 1; i < orig_win_h - 1; i++) {
screen->blit(x_off, y_off + i * 16, tile->data, 8, 16, 16, 16, true, &clip_rect); // left column
screen->blit(x_off + (orig_win_w - 1) * 16, y_off + i * 16, tile1->data, 8, 16, 16, 16, true); // right column (got rid of &clip_rect for original+)
}
}
//FIXME this won't work for wrapped maps like MD.
void MapWindow::drawRoofs() {
if (cur_y < 1 || cur_y > 760) // FIXME We need to handle this properly
return;
if (roof_display == ROOF_DISPLAY_NORMAL && map->has_roof(WRAPPED_COORD(cur_x + (win_width - 1 - map_center_xoff) / 2, cur_level), cur_y + (win_height - 1) / 2, cur_level)) //Don't draw roof tiles if player is underneath.
return;
if (x_ray_view >= X_RAY_ON)
return;
bool orig_style = game->is_orig_style();
uint16 *roof_map_ptr = map->get_roof_data(cur_level);
Common::Rect src(16, 16), dst(16, 16);
if (roof_map_ptr) {
roof_map_ptr += cur_y * 1024 + cur_x;
for (uint16 i = 0; i < win_height; i++) {
for (uint16 j = 0; j < win_width; j++) {
if (roof_map_ptr[j] != 0) {
dst.left = area.left + (j * 16);
dst.top = area.top + (i * 16);
dst.left -= cur_x_add;
dst.top -= cur_y_add;
src.left = (roof_map_ptr[j] % MAPWINDOW_ROOFTILES_IMG_W) * 16;
src.top = (roof_map_ptr[j] / MAPWINDOW_ROOFTILES_IMG_W) * 16;
if (orig_style) {
src.setWidth(16);
src.setHeight(16);
dst.setWidth(16);
dst.setHeight(16);
if (i == 0) {
src.top += 8;
src.setHeight(8);
dst.top += 8;
dst.setHeight(8);
} else if (i == win_height - 1) {
src.setHeight(8);
dst.setHeight(8);
}
if (j == 0) {
src.left += 8;
src.setWidth(8);
dst.left += 8;
dst.setWidth(8);
} else if (j == win_width - 1) {
src.setWidth(8);
dst.setWidth(8);
}
SDL_BlitSurface(roof_tiles, &src, surface, &dst);
} else {
src.setWidth(16);
src.setHeight(16);
_mapWinSubSurf.blitFrom(*roof_tiles, src, Common::Point(dst.left, dst.top));
}
}
}
roof_map_ptr += 1024;
}
}
}
void MapWindow::drawRain() {
int c;
if (game->is_orig_style())
c = win_width * win_height;
else if (game->is_original_plus_cutoff_map())
c = ((game->get_game_width() - border_width) * game->get_game_height()) / 256;
else
c = (game->get_game_width() * game->get_game_height()) / 256;
for (int i = 0; i < c; i++) {
uint16 x;
uint16 y;
if (game->is_orig_style()) {
x = area.left + NUVIE_RAND() % ((win_width - 1) * 16 - 2) + 8;
y = area.top + NUVIE_RAND() % ((win_height - 1) * 16 - 2) + 8;
} else {
if (game->is_original_plus_cutoff_map())
x = game->get_game_x_offset() + NUVIE_RAND() % (game->get_game_width() - border_width - 2);
else
x = game->get_game_x_offset() + NUVIE_RAND() % (game->get_game_width() - 2);
y = game->get_game_y_offset() + NUVIE_RAND() % (game->get_game_height() - 2);
}
//FIXME the original does something with the palette if a pixel is black then draw gray etc.
//We can't do this easily here because we don't have the original 8 bit display surface.
screen->put_pixel(118, x, y);
screen->put_pixel(118, x + 1, y + 1);
screen->put_pixel(0, x + 2, y + 2);
}
}
void MapWindow::AddMapTileToVisibleList(uint16 tile_num, uint16 x, uint16 y) {
if (x >= TMP_MAP_BORDER &&
y >= TMP_MAP_BORDER &&
x < tmp_map_width - TMP_MAP_BORDER &&
y < tmp_map_height - TMP_MAP_BORDER) {
TileInfo ti;
ti.t = tile_manager->get_tile(tile_num);
ti.x = (uint16)(x - TMP_MAP_BORDER);
ti.y = (uint16)(y - TMP_MAP_BORDER);
m_ViewableMapTiles.push_back(ti);
}
}
void MapWindow::drawGrid() {
for (uint16 i = 0; i < win_height; i++) {
for (uint16 j = 0; j < win_width; j++) {
screen->blit(area.left + (j * 16) - cur_x_add, area.top + (i * 16) - cur_y_add,
(const byte *)grid_tile.data, 8, 16, 16, 16, true);
}
}
}
void MapWindow::generateTmpMap() {
uint16 x, y;
m_ViewableMapTiles.clear();
const byte *map_ptr = map->get_map_data(cur_level);
uint16 pitch = map->get_width(cur_level);
if (enable_blacking == false) {
uint16 *ptr = tmp_map_buf;
for (y = 0; y < tmp_map_height; y++) {
for (x = 0; x < tmp_map_width; x++) {
uint16 x1 = cur_x + x - TMP_MAP_BORDER;
uint16 y1 = cur_y + y - TMP_MAP_BORDER;
WRAP_COORD(x1, cur_level);
WRAP_COORD(y1, cur_level);
*ptr = map_ptr[y1 * pitch + x1];
AddMapTileToVisibleList(*ptr, x, y);
ptr++;
}
}
return;
}
roof_display = ROOF_DISPLAY_NORMAL;
memset(tmp_map_buf, 0, tmp_map_width * tmp_map_height * sizeof(uint16));
if (freeze_blacking_location == false) {
x = cur_x + ((win_width - 1 - map_center_xoff) / 2);
y = cur_y + ((win_height - 1) / 2);
} else { // SB-X
x = last_boundary_fill_x;
y = last_boundary_fill_y;
}
WRAP_COORD(x, cur_level);
WRAP_COORD(y, cur_level);
//This is for U6. Sherry needs to pass through walls
//We shift the boundary fill start location off the wall tile so it flood
//fills correctly. We move east for vertical wall tiles and south for
//horizontal wall tiles.
if (game_type == NUVIE_GAME_U6 && obj_manager->is_boundary(x, y, cur_level)) {
const Tile *tile = obj_manager->get_obj_tile(x, y, cur_level, false);
if ((tile->flags1 & TILEFLAG_WALL_MASK) == (TILEFLAG_WALL_NORTH | TILEFLAG_WALL_SOUTH))
x = WRAPPED_COORD(x + 1, cur_level);
else
y = WRAPPED_COORD(y + 1, cur_level);
}
last_boundary_fill_x = x;
last_boundary_fill_y = y;
boundaryFill(map_ptr, pitch, x, y);
reshapeBoundary();
if (roof_mode && floorTilesVisible())
roof_display = ROOF_DISPLAY_OFF; // hide roof if a building's floor is showing.
}
void MapWindow::boundaryFill(const byte *map_ptr, uint16 pitch, uint16 x, uint16 y) {
uint16 pos;
uint16 tmp_x, tmp_y;
uint16 p_cur_x = WRAPPED_COORD(cur_x - TMP_MAP_BORDER, cur_level);
uint16 p_cur_y = WRAPPED_COORD(cur_y - TMP_MAP_BORDER, cur_level);
if (x == WRAPPED_COORD(p_cur_x - 1, cur_level) || x == WRAPPED_COORD(p_cur_x + tmp_map_width, cur_level))
return;
if (y == WRAPPED_COORD(p_cur_y - 1, cur_level) || y == WRAPPED_COORD(p_cur_y + tmp_map_height, cur_level))
return;
if (p_cur_y > y)
tmp_y = pitch - p_cur_y + y;
else
tmp_y = y - p_cur_y;
pos = tmp_y * tmp_map_width;
if (p_cur_x > x)
tmp_x = pitch - p_cur_x + x;
else
tmp_x = x - p_cur_x;
pos += tmp_x;
uint16 *ptr = &tmp_map_buf[pos];
if (*ptr != 0)
return;
byte current = map_ptr[y * pitch + x];
*ptr = (uint16)current;
AddMapTileToVisibleList(current, tmp_x, tmp_y);
if (x_ray_view <= X_RAY_OFF && map->is_boundary(x, y, cur_level)) { //hit the boundary wall tiles
if (boundaryLookThroughWindow(*ptr, x, y) == false)
return;
else
roof_display = ROOF_DISPLAY_OFF; //hide roof tiles if player is looking through window.
}
uint16 xp1 = WRAPPED_COORD(x + 1, cur_level);
uint16 xm1 = WRAPPED_COORD(x - 1, cur_level);
uint16 yp1 = WRAPPED_COORD(y + 1, cur_level);
uint16 ym1 = WRAPPED_COORD(y - 1, cur_level);
boundaryFill(map_ptr, pitch, xp1, y);
boundaryFill(map_ptr, pitch, x, yp1);
boundaryFill(map_ptr, pitch, xp1, yp1);
boundaryFill(map_ptr, pitch, xm1, ym1);
boundaryFill(map_ptr, pitch, xm1, y);
boundaryFill(map_ptr, pitch, x, ym1);
boundaryFill(map_ptr, pitch, xp1, ym1);
boundaryFill(map_ptr, pitch, xm1, yp1);
return;
}
bool MapWindow::floorTilesVisible() {
Actor *actor = actor_manager->get_player();
if (!actor)
return false;
uint16 a_x, a_y;
uint8 a_z;
actor->get_location(&a_x, &a_y, &a_z);
uint16 cX = WRAPPED_COORD(a_x - 1, cur_level), eX = WRAPPED_COORD(a_x + 2, cur_level);
uint16 cY = WRAPPED_COORD(a_y - 1, cur_level), eY = WRAPPED_COORD(a_y + 2, cur_level);
for (; cY != eY;) {
for (; cX != eX;) {
if (map->has_roof(cX, cY, cur_level) && !map->is_boundary(cX, cY, cur_level)) {
const Tile *t = obj_manager->get_obj_tile(cX, cY, cur_level, false);
if (t && (t->flags1 & TILEFLAG_WALL))
return true;
}
cX = WRAPPED_COORD(cX + 1, cur_level);
}
cX = WRAPPED_COORD(a_x - 1, cur_level);
cY = WRAPPED_COORD(cY + 1, cur_level);
}
return false;
}
bool MapWindow::boundaryLookThroughWindow(uint16 tile_num, uint16 x, uint16 y) {
Tile *tile = tile_manager->get_tile(tile_num);
if (!(tile->flags2 & TILEFLAG_WINDOW)) {
Obj *obj = obj_manager->get_objBasedAt(x, y, cur_level, true);
if (obj) { //check for a windowed object.
tile = tile_manager->get_tile(obj_manager->get_obj_tile_num(obj->obj_n) + obj->frame_n);
if (!(tile->flags2 & TILEFLAG_WINDOW))
return false;
} else
return false;
}
Actor *actor = actor_manager->get_player();
uint16 a_x, a_y;
uint8 a_z;
actor->get_location(&a_x, &a_y, &a_z);
if (a_x == x) {
if (a_y == WRAPPED_COORD(y - 1, cur_level) || a_y == WRAPPED_COORD(y + 1, cur_level))
return true;
}
if (a_y == y) {
if (a_x == WRAPPED_COORD(x - 1, cur_level) || a_x == WRAPPED_COORD(x + 1, cur_level))
return true;
}
return false;
}
//reshape walls based on new blacked out areas.
void MapWindow::reshapeBoundary() {
for (uint16 y = 1; y < tmp_map_height - 1; y++) {
for (uint16 x = 1; x < tmp_map_width - 1; x++) {
if (tmpBufTileIsBoundary(x, y)) {
const Tile *tile = tile_manager->get_tile(tmp_map_buf[y * tmp_map_width + x]);
uint8 flag, original_flag;
if ((tile->tile_num >= 140 && tile->tile_num <= 187)) { //main U6 wall tiles FIX for WOU games
flag = 0;
original_flag = tile->flags1 & TILEFLAG_WALL_MASK;
} else
continue;
//generate the required wall flags
if (tmpBufTileIsWall(x, y - 1, NUVIE_DIR_N))
flag |= TILEFLAG_WALL_NORTH;
if (tmpBufTileIsWall(x + 1, y, NUVIE_DIR_E))
flag |= TILEFLAG_WALL_EAST;
if (tmpBufTileIsWall(x, y + 1, NUVIE_DIR_S))
flag |= TILEFLAG_WALL_SOUTH;
if (tmpBufTileIsWall(x - 1, y, NUVIE_DIR_W))
flag |= TILEFLAG_WALL_WEST;
//we want to keep existing tile if it is pointing to non-wall tiles which are not blacked
//this is used to support cookfire walls which aren't considered walls in tileflags.
if (tmpBufTileIsBlack(x, y - 1) == false && (original_flag & TILEFLAG_WALL_NORTH))
flag |= TILEFLAG_WALL_NORTH;
if (tmpBufTileIsBlack(x + 1, y) == false && (original_flag & TILEFLAG_WALL_EAST))
flag |= TILEFLAG_WALL_EAST;
if (tmpBufTileIsBlack(x, y + 1) == false && (original_flag & TILEFLAG_WALL_SOUTH))
flag |= TILEFLAG_WALL_SOUTH;
if (tmpBufTileIsBlack(x - 1, y) == false && (original_flag & TILEFLAG_WALL_WEST))
flag |= TILEFLAG_WALL_WEST;
if (flag == 0) //isolated border tiles
continue;
if (flag == 48) { // 0011 top right corner
if (tmpBufTileIsBlack(x, y - 1) && tmpBufTileIsBlack(x + 1, y)) { //replace with blacked corner tile
//Oh dear! this is evil. FIX
tmp_map_buf[y * tmp_map_width + x] = 266 + 2 * (((tile->tile_num - tile->tile_num % 16) - 140) / 16);
continue;
}
}
if (flag == 192) { // 1100 bottom left corner
if (tmpBufTileIsBlack(x, y + 1) && tmpBufTileIsBlack(x - 1, y)) { //replace with blacked corner tile
//Oh dear! this is evil. FIX
tmp_map_buf[y * tmp_map_width + x] = 266 + 1 + 2 * (((tile->tile_num - tile->tile_num % 16) - 140) / 16);
continue;
}
}
if ((tile->flags1 & TILEFLAG_WALL_MASK) == flag) // complete match no work needed
continue;
// Look for a suitable tile to transform into
// ERIC 05/03/05 flag |= TILEFLAG_WALL_NORTH | TILEFLAG_WALL_WEST;
if (((tile->flags1) & TILEFLAG_WALL_MASK) > flag && flag != 144) { // 1001 _| corner
//flag |= TILEFLAG_WALL_NORTH | TILEFLAG_WALL_WEST;
for (; ((tile->flags1) & TILEFLAG_WALL_MASK) != flag && (tile->flags1 & TILEFLAG_WALL_MASK);)
tile = tile_manager->get_tile(tile->tile_num - 1);
} else {
//flag |= TILEFLAG_WALL_NORTH | TILEFLAG_WALL_WEST;
for (; ((tile->flags1) & TILEFLAG_WALL_MASK) != flag && (tile->flags1 & TILEFLAG_WALL_MASK);)
tile = tile_manager->get_tile(tile->tile_num + 1);
}
if ((tile->flags1 & TILEFLAG_WALL_MASK) == flag)
tmp_map_buf[y * tmp_map_width + x] = tile->tile_num;
}
}
}
}
inline bool MapWindow::tmpBufTileIsBlack(uint16 x, uint16 y) const {
if (tmp_map_buf[y * tmp_map_width + x] == 0)
return true;
return false;
}
bool MapWindow::tmpBufTileIsBoundary(uint16 x, uint16 y) {
uint16 tile_num = tmp_map_buf[y * tmp_map_width + x];
if (tile_num == 0)
return false;
const Tile *tile = tile_manager->get_tile(tile_num);
if (tile->boundary)
return true;
if (obj_manager->is_boundary(WRAPPED_COORD(cur_x - TMP_MAP_BORDER + x, cur_level), WRAPPED_COORD(cur_y - TMP_MAP_BORDER + y, cur_level), cur_level))
return true;
return false;
}
bool MapWindow::tmpBufTileIsWall(uint16 x, uint16 y, NuvieDir direction) {
uint16 tile_num = tmp_map_buf[y * tmp_map_width + x];
if (tile_num == 0)
return false;
uint8 mask = 0;
switch (direction) {
case NUVIE_DIR_N :
mask = TILEFLAG_WALL_SOUTH;
break;
case NUVIE_DIR_S :
mask = TILEFLAG_WALL_NORTH;
break;
case NUVIE_DIR_E :
mask = TILEFLAG_WALL_WEST;
break;
case NUVIE_DIR_W :
mask = TILEFLAG_WALL_EAST;
break;
default:
error("invalid direction in MapWindow::tmpBufferIsWall");
}
const Tile *tile = tile_manager->get_tile(tile_num);
if (tile->flags1 & TILEFLAG_WALL) {
if (tile->flags1 & mask)
return true;
}
// if(obj_manager->is_boundary(cur_x-1+x, cur_y-1+y, cur_level))
// return true;
tile = obj_manager->get_obj_tile(WRAPPED_COORD(cur_x - TMP_MAP_BORDER + x, cur_level), WRAPPED_COORD(cur_y - TMP_MAP_BORDER + y, cur_level), cur_level, false);
if (tile != nullptr) {
if (tile->flags2 & TILEFLAG_BOUNDARY) {
if (tile->flags1 & mask)
return true;
}
}
return false;
}
/* Returns MSG_SUCCESS if the obj can be dropped or moved by the actor at world coordinates x,y or an error msg.
* There must be a direct path between the actor to the obj and also between the obj and destination.
* Objs that can't be picked up will be blocked actors and unpassable areas. (z is always cur_level)
*/
CanDropOrMoveMsg MapWindow::can_drop_or_move_obj(uint16 x, uint16 y, Actor *actor, Obj *obj) {
bool in_inventory = obj->is_in_inventory();
if (!in_inventory && obj->x == x && obj->y == y)
return MSG_NOT_POSSIBLE;
if (game->using_hackmove())
return MSG_SUCCESS;
if (tile_is_black(x, y, obj)) {
if (tile_is_black(x, y))
return MSG_NOT_POSSIBLE;
else
return MSG_BLOCKED;
}
MapCoord actor_loc = actor->get_location();
// Can only drop onto non-blocking actors
// Message: "Blocked" if pushing, "Not possible" if dropping
if (actor_manager->findActorAt(x, y, actor_loc.z, [](const Actor *a) {return !a->isNonBlocking();}, true, false))
return in_inventory ? MSG_NOT_POSSIBLE : MSG_BLOCKED;
Obj *dest_obj = nullptr;
if (game_type == NUVIE_GAME_U6) {
dest_obj = obj_manager->get_obj(x, y, actor_loc.z); //FIXME this might not be right. We might want to exclude obj.
} else {
dest_obj = obj_manager->get_obj(x, y, actor_loc.z, OBJ_SEARCH_TOP, OBJ_EXCLUDE_IGNORED, obj);
}
bool can_go_in_water = (game_type == NUVIE_GAME_U6
&& (obj->obj_n == OBJ_U6_SKIFF || obj->obj_n == OBJ_U6_RAFT));
if (can_go_in_water && dest_obj) // it is drawn underneath so only allow on hackmove
return MSG_NOT_POSSIBLE;
LineTestResult lt;
MapCoord target_loc(x, y, actor_loc.z);
MapCoord obj_loc(obj->x, obj->y, actor_loc.z);
if (in_inventory && !obj->get_actor_holding_obj()->is_onscreen()
&& obj->get_actor_holding_obj()->get_location().distance(target_loc) > 5)
return MSG_OUT_OF_RANGE;
if (get_interface() == INTERFACE_IGNORE_BLOCK && map->can_put_obj(x, y, cur_level))
return MSG_SUCCESS;
if (actor_loc.distance(target_loc) > 5 && get_interface() == INTERFACE_NORMAL)
return MSG_OUT_OF_RANGE;
bool blocked = false;
uint8 lt_flags = (game_type == NUVIE_GAME_U6) ? LT_HitMissileBoundary : 0; //FIXME this probably isn't quite right for MD/SE
if (map->lineTest(actor_loc.x, actor_loc.y, x, y, actor_loc.z, lt_flags, lt, 0, obj)) {
MapCoord hit_loc = MapCoord(lt.hit_x, lt.hit_y, lt.hit_level);
if (in_inventory || obj_loc.distance(target_loc) != 1 || hit_loc.distance(target_loc) != 1)
blocked = true; // Just set a bool and don't return yet: blocker might be a suitable container.
else // trying to push object one tile away from actor
blocked = map->lineTest(obj->x, obj->y, x, y, actor_loc.z, lt_flags, lt, 0, obj);
}
const Obj* potentialContainer = nullptr;
if (blocked) {
if (lt.hitObj && MapCoord(lt.hitObj) == target_loc)
potentialContainer = lt.hitObj;
} else
potentialContainer = dest_obj;
if (potentialContainer && obj_manager->can_store_obj(potentialContainer, obj)) //if we are moving onto a container.
return MSG_SUCCESS;
if (blocked)
return MSG_BLOCKED;
const Tile *tile;
if (dest_obj)
tile = obj_manager->get_obj_tile(dest_obj->obj_n, dest_obj->frame_n);
else
tile = map->get_tile(x, y, actor_loc.z);
if (!tile) // shouldn't happen
return MSG_NO_TILE;
if ((can_go_in_water || !map->is_water(x, y, actor_loc.z))
&& ((tile->flags3 & TILEFLAG_CAN_PLACE_ONTOP)
|| (tile->passable && !map->is_boundary(x, y, actor_loc.z))))
return MSG_SUCCESS;
return MSG_NOT_POSSIBLE;
}
void MapWindow::display_can_drop_or_move_msg(CanDropOrMoveMsg msg, string msg_text) {
if (msg == MSG_NOT_POSSIBLE)
msg_text += "Not possible\n";
else if (msg == MSG_BLOCKED)
msg_text += "Blocked\n";
else if (msg == MSG_OUT_OF_RANGE)
msg_text += "Out of range\n";
/* else if(msg == MSG_NO_TILE) // shouldn't be needed now that blacked out areas are checked first
msg_text += "ERROR: No tile. Report me\n";*/
game->get_scroll()->display_string(msg_text);
}
bool MapWindow::can_get_obj(const Actor *actor, Obj *obj) {
if (!obj)
return false;
if (get_interface() == INTERFACE_IGNORE_BLOCK)
return true;
if (obj->is_in_inventory())
return false;
if (obj->is_in_container())
obj = obj->get_container_obj(true);
if (actor->get_z() != obj->z)
return false;
LineTestResult lt;
if (map->lineTest(actor->get_x(), actor->get_y(), obj->x, obj->y, obj->z, LT_HitUnpassable, lt, 0, obj)) {
// Skip Check for SE Tile Objects - We are actually using the blocking item/tree
Script *script = game->get_script();
if (game_type != NUVIE_GAME_SE || !script->call_is_tile_object(obj->obj_n)) {
return false;
}
}
if (game_type == NUVIE_GAME_U6 && obj->obj_n == OBJ_U6_SECRET_DOOR)
return true;
return !blocked_by_wall(actor, obj);
}
/*
* Check to make sure the obj isn't on the other side of a wall or trying to push through it.
* The original engine didn't bother to check.
*/
bool MapWindow::blocked_by_wall(const Actor *actor, const Obj *obj) {
if (game_type == NUVIE_GAME_U6 && obj->x == 282 && obj->y == 438 && cur_level == 0) // HACK for buggy location
return false;
const Tile *tile = map->get_tile(obj->x, obj->y, cur_level);
if (((tile->flags1 & TILEFLAG_WALL) && !game->get_usecode()->is_door(obj))
&& (((tile->flags1 & TILEFLAG_WALL_MASK) == 208 && actor->get_y() < obj->y) // can't get items that are south
|| ((tile->flags1 & TILEFLAG_WALL_MASK) == 176 && actor->get_x() < obj->x) // can't get items that are east
|| ((tile->flags1 & TILEFLAG_WALL_MASK) == 240 // northwest corner - used in SE (not sure if used in other games)
&& (actor->get_y() < obj->y || actor->get_x() < obj->x))))
return true;
return false;
}
bool MapWindow::drag_accept_drop(int x, int y, int message, void *data) {
DEBUG(0, LEVEL_DEBUGGING, "MapWindow::drag_accept_drop()\n");
x -= area.left;
y -= area.top;
x /= 16;
y /= 16;
GUI::get_gui()->force_full_redraw();
if (message == GUI_DRAG_OBJ) {
if (game->get_player()->is_in_vehicle() && !game->using_hackmove()) {
game->get_event()->display_not_aboard_vehicle();
return false;
}
uint16 mapWidth = map->get_width(cur_level);
x = (cur_x + x) % mapWidth;
y = (cur_y + y) % mapWidth;
Obj *obj = (Obj *)data;
Actor *p = actor_manager->get_player();
Actor *target_actor = map->get_actor(x, y, cur_level);
if (obj->is_in_inventory() == false) { //obj on map.
if (can_get_obj(p, obj)) { //make sure there is a clear line from player to object
if (target_actor) {
game->get_event()->display_move_text(target_actor, obj);
if (target_actor == p || (target_actor->is_in_party())) {
if (obj_manager->obj_is_damaging(obj, p)) {
game->get_player()->subtract_movement_points(3);
return false;
}
if ((!game->get_usecode()->has_getcode(obj) || game->get_usecode()->get_obj(obj, target_actor))
&& game->get_event()->can_move_obj_between_actors(obj, p, target_actor))
return true;
else {
game->get_scroll()->message("\n\n");
return false;
}
} else {
game->get_scroll()->display_string("\n\nOnly within the party!");
game->get_scroll()->message("\n\n");
return false;
}
} else
return true;
}
} else {
if (game->get_usecode()->cannot_unready(obj)) {
game->get_event()->unready(obj);
return false;
}
if (target_actor) {
Actor *owner = obj->get_actor_holding_obj();
game->get_event()->display_move_text(target_actor, obj);
if (game->get_event()->can_move_obj_between_actors(obj, owner, target_actor) == false) {
game->get_scroll()->message("\n\n");
return false;
} else
return true;
} else
return true; //throw on ground
}
game->get_scroll()->display_string("Move-");
game->get_scroll()->display_string(obj_manager->look_obj(obj)); // getting obj name
game->get_scroll()->display_string("\nto ");
game->get_scroll()->display_string(get_direction_name(x - obj->x , y - obj->y));
game->get_scroll()->message(".\n\nCan't reach it\n\n");
}
return false;
}
void MapWindow::drag_perform_drop(int x, int y, int message, void *data) {
DEBUG(0, LEVEL_DEBUGGING, "MapWindow::drag_perform_drop()\n");
Events *event = game->get_event();
uint16 mapWidth = map->get_width(cur_level);
x -= area.left;
y -= area.top;
if (message == GUI_DRAG_OBJ) {
x = (uint)(cur_x + x / 16) % mapWidth;
y = (uint)(cur_y + y / 16) % mapWidth;
Obj *obj = (Obj *)data;
if (obj->obj_n == OBJ_U6_LOCK_PICK && game_type == NUVIE_GAME_U6)
game->get_usecode()->search_container(obj, false);
Actor *a = map->get_actor(x, y, cur_level);
if (a && (a->is_in_party() || a == actor_manager->get_player())) {
if (a == actor_manager->get_player()) // get
game->get_player()->subtract_movement_points(3);
else // get plus move
game->get_player()->subtract_movement_points(8);
obj_manager->moveto_inventory(obj, a);
game->get_scroll()->message("\n\n");
} else {
if (!obj->is_in_inventory() && !obj->is_in_container()) { // !need is_in_container to exclude container gumps
move_on_drop(obj); // no longer determines whether to drop or move but can call usecode
event->newAction(PUSH_MODE);
event->select_obj(obj);
event->pushTo(x - obj->x, y - obj->y, PUSH_FROM_OBJECT);
event->endAction();
return;
}
CanDropOrMoveMsg can_drop; // so we can skip quantity prompt
if ((can_drop = can_drop_or_move_obj(x, y, actor_manager->get_player(), obj)) != MSG_SUCCESS) {
game->get_scroll()->display_string("Drop-");
game->get_scroll()->display_string(obj_manager->look_obj(obj));
game->get_scroll()->display_string("\n\nlocation:\n\n");
display_can_drop_or_move_msg(can_drop, "");
game->get_scroll()->message("\n");
return;
}
// drop on ground or into a container
event->newAction(DROP_MODE);
event->select_obj(obj);
if (obj->qty <= 1 || !obj_manager->is_stackable(obj))
event->select_target(x, y);
else
event->set_drop_target(x, y); // pre-select target
}
}
}
// performs some usecode but used to decide whether to move or drop too
bool MapWindow::move_on_drop(Obj *obj) {
bool move = (get_interface() == INTERFACE_NORMAL);
/* if(drop_with_move && move)
return true;
if(obj_manager->obj_is_damaging(obj))
return move;
*/
if (game->get_usecode()->has_getcode(obj) && obj->is_in_inventory() == false) {
if (game_type == NUVIE_GAME_U6) {
switch (obj->obj_n) {
case OBJ_U6_CHEST:
case OBJ_U6_LOCK_PICK:
case OBJ_U6_MOONSTONE:
game->get_usecode()->get_obj(obj, actor_manager->get_player());
return false;
case OBJ_U6_SKIFF:
return false;
case OBJ_U6_TORCH:
if (obj->frame_n == 0)
return false;
break;
default :
break;
}
}
return move;
}
return false;
}
void MapWindow::set_interface() {
Std::string interface_str;
config->value("config/input/interface", interface_str, "normal");
if (interface_str == "ignore_block" || Game::get_game()->using_hackmove()) // game variable is not initialized
interface = INTERFACE_IGNORE_BLOCK;
else if (interface_str == "fullscreen")
interface = INTERFACE_FULLSCREEN;
else
interface = INTERFACE_NORMAL;
}
InterfaceType MapWindow::get_interface() {
// check is easily exploited but would be annoying if checking for nearby enemies
if (interface == INTERFACE_FULLSCREEN && game->get_party()->is_in_combat_mode())
return INTERFACE_NORMAL;
return interface;
}
bool MapWindow::is_interface_fullscreen_in_combat() {
if (interface == INTERFACE_FULLSCREEN && game->get_party()->is_in_combat_mode())
return true;
return false;
}
GUI_status MapWindow::Idle(void) {
return (GUI_Widget::Idle());
}
// single-click (press and release button)
GUI_status MapWindow::MouseClick(int x, int y, Events::MouseButton button) {
if (button == USE_BUTTON && look_on_left_click) {
wait_for_mouseclick(button); // see MouseDelayed
}
return (MouseUp(x, y, button)); // do MouseUp so selected_obj is cleared
}
// single-click; waited for double-click
GUI_status MapWindow::MouseDelayed(int x, int y, Events::MouseButton button) {
Events *event = game->get_event();
if (!looking || game->user_paused() || event->cursor_mode
|| (event->get_mode() != MOVE_MODE && event->get_mode() != EQUIP_MODE)) {
look_obj = nullptr;
look_actor = nullptr;
return GUI_PASS;
}
game->get_scroll()->display_string("Look-");
event->set_mode(LOOK_MODE);
event->lookAtCursor(true, original_obj_loc.x, original_obj_loc.y, original_obj_loc.z, look_obj, look_actor);
look_obj = nullptr;
look_actor = nullptr;
return (MouseUp(x, y, button)); // do MouseUp so selected_obj is cleared
}
// MouseDown; waited for MouseUp
GUI_status MapWindow::MouseHeld(int x, int y, Events::MouseButton button) {
looking = false;
if (walk_with_left_button)
set_walking(true);
return GUI_PASS;
}
// double-click
GUI_status MapWindow::MouseDouble(int x, int y, Events::MouseButton button) {
Events *event = game->get_event();
// only USE if not doing anything in event
if (enable_doubleclick && event->get_mode() == MOVE_MODE && !is_wizard_eye_mode()) {
int wx, wy;
mouseToWorldCoords(x, y, wx, wy);
event->multiuse((uint16)wx, (uint16)wy);
}
looking = false;
return (MouseUp(x, y, button)); // do MouseUp so selected_obj is cleared
}
GUI_status MapWindow::MouseWheel(sint32 x, sint32 y) {
Game *g = Game::get_game();
if (g->is_new_style()) {
if (y > 0)
g->get_scroll()->move_scroll_up();
if (y < 0)
g->get_scroll()->move_scroll_down();
} else {
if (y > 0)
g->get_scroll()->page_up();
if (y < 0)
g->get_scroll()->page_down();
}
return GUI_YUM;
}
GUI_status MapWindow::MouseDown(int x, int y, Events::MouseButton button) {
//DEBUG(0,LEVEL_DEBUGGING,"MapWindow::MouseDown, button = %i\n", button);
Events *event = game->get_event();
Actor *player = actor_manager->get_player();
Obj *obj = get_objAtMousePos(x, y);
if (is_wizard_eye_mode()) {
set_walking(true);
return GUI_YUM;
}
if (event->is_looking_at_spellbook()) {
event->cancelAction();
return GUI_YUM;
}
if (game->is_original_plus() && y <= Game::get_game()->get_game_y_offset() + 200
&& x >= Game::get_game()->get_game_x_offset() + game->get_game_width() - border_width) {
looking = false;
return GUI_PASS;
}
if (event->get_mode() == MOVE_MODE || event->get_mode() == EQUIP_MODE) {
int wx, wy;
mouseToWorldCoords(x, y, wx, wy);
if (button == WALK_BUTTON && game->get_command_bar()->get_selected_action() != -1) {
if (game->get_command_bar()->try_selected_action() == false) // start new action
return GUI_PASS; // false if new event doesn't need target
} else if (wx == player->x && wy == player->y // PASS if Avatar is hit
&& (button == WALK_BUTTON || !enable_doubleclick)) {
event->cancelAction(); // MOVE_MODE, so this should work
return GUI_PASS;
} else if (button == WALK_BUTTON
|| (!enable_doubleclick && button == USE_BUTTON
&& !game->is_dragging_enabled() && !look_on_left_click)) {
set_walking(true);
} else if (button == USE_BUTTON) { // you can also walk by holding the USE button
if (look_on_left_click && !event->cursor_mode) { // need to preserve location because of click delay
looking = true;
original_obj_loc = MapCoord(wx, wy , cur_level);
look_actor = actor_manager->get_actor(wx , wy, cur_level);
look_obj = obj_manager->get_obj(wx , wy, cur_level);
moveCursor(WRAP_VIEWP(cur_x, wx, map_width), wy - cur_y);
}
wait_for_mousedown(button);
}
}
if (event->get_mode() == INPUT_MODE || event->get_mode() == ATTACK_MODE) { // finish whatever action is being done, with mouse coordinates
if (button != USE_BUTTON && button != WALK_BUTTON)
return GUI_PASS;
looking = false;
select_target(x, y);
return GUI_PASS;
} else if (event->get_mode() != MOVE_MODE && event->get_mode() != EQUIP_MODE) {
return GUI_PASS;
}
if (!obj || button != DRAG_BUTTON)
return GUI_PASS;
original_obj_loc = MapCoord(obj->x, obj->y, obj->z);
int distance = player->get_location().distance(original_obj_loc);
float weight = obj_manager->get_obj_weight(obj, OBJ_WEIGHT_EXCLUDE_CONTAINER_ITEMS);
if ((weight == 0 || player->get_actor_num() == 0
|| tile_is_black(obj->x, obj->y, obj)) && !game->using_hackmove())
return GUI_PASS;
// checking interface directly to allow dragging in INTERFACE_FULLSCREEN when in combat
if (distance > 1 && interface == INTERFACE_NORMAL)
return GUI_PASS;
if (button == DRAG_BUTTON && game->is_dragging_enabled())
selected_obj = obj;
return GUI_PASS;
}
GUI_status MapWindow::MouseUp(int x, int y, Events::MouseButton button) {
// cancel dragging and movement no matter what button is released
if (selected_obj) {
selected_obj = nullptr;
}
walking = false;
dragging = false;
return GUI_PASS;
}
GUI_status MapWindow::MouseMotion(int x, int y, uint8 state) {
// Events *event = game->get_event();
Tile *tile;
update_mouse_cursor((uint32)x, (uint32)y);
// DEBUG(0,LEVEL_DEBUGGING,"MapWindow::MouseMotion\n");
// if(selected_obj) // We don't want to walk if we are selecting an object to move.
// walking = false;
if (walking) { // No, we don't want to select an object to move if we are walking.
selected_obj = nullptr;
dragging = false;
}
if (selected_obj && !dragging) {
int wx, wy;
// ensure that the player can reach the selected object before
// letting them drag it
//mouseToWorldCoords(x, y, wx, wy);
wx = selected_obj->x;
wy = selected_obj->y;
LineTestResult result;
Actor *player = actor_manager->get_player();
if (map->lineTest(player->x, player->y, wx, wy, cur_level, LT_HitUnpassable, result)
&& !(result.hitObj && result.hitObj->x == wx && result.hitObj->y == wy)
&& get_interface() == INTERFACE_NORMAL)
// something was in the way, so don't allow a drag
return GUI_PASS;
dragging = true;
set_mousedown(0, DRAG_BUTTON); // cancel MouseHeld
game->set_mouse_pointer(0); // arrow
tile = tile_manager->get_tile(obj_manager->get_obj_tile_num(selected_obj->obj_n) + selected_obj->frame_n);
bool out_of_range;
if (is_interface_fullscreen_in_combat() && player->get_location().distance(original_obj_loc) > 1)
out_of_range = true;
else
out_of_range = false;
return gui_drag_manager->start_drag(this, GUI_DRAG_OBJ, selected_obj, tile->data, 16, 16, 8, out_of_range);
}
return GUI_PASS;
}
void MapWindow::drag_drop_success(int x, int y, int message, void *data) {
//DEBUG(0,LEVEL_DEBUGGING,"MapWindow::drag_drop_success\n");
dragging = false;
// handled by drop target
// if (selected_obj)
// obj_manager->remove_obj (selected_obj);
selected_obj = nullptr;
Redraw();
}
void MapWindow::drag_drop_failed(int x, int y, int message, void *data) {
DEBUG(0, LEVEL_DEBUGGING, "MapWindow::drag_drop_failed\n");
dragging = false;
selected_obj = nullptr;
}
// this does nothing
GUI_status MapWindow::KeyDown(const Common::KeyState &key) {
if (is_wizard_eye_mode()) {
KeyBinder *keybinder = Game::get_game()->get_keybinder();
ActionType a = keybinder->get_ActionType(key);
switch (keybinder->GetActionKeyType(a)) {
case WEST_KEY:
moveMapRelative(-1, 0);
break;
case EAST_KEY:
moveMapRelative(1, 0);
break;
case SOUTH_KEY:
moveMapRelative(0, 1);
break;
case NORTH_KEY:
moveMapRelative(0, -1);
break;
case NORTH_EAST_KEY:
moveMapRelative(1, -1);
break;
case SOUTH_EAST_KEY:
moveMapRelative(1, 1);
break;
case NORTH_WEST_KEY:
moveMapRelative(-1, -1);
break;
case SOUTH_WEST_KEY:
moveMapRelative(-1, 1);
break;
case CANCEL_ACTION_KEY:
wizard_eye_stop();
break;
default:
keybinder->handle_always_available_keys(a);
return GUI_YUM;
}
if (keybinder->GetActionKeyType(a) <= SOUTH_WEST_KEY)
wizard_eye_update();
return GUI_YUM;
}
return GUI_PASS;
}
Obj *MapWindow::get_objAtMousePos(int mx, int my) {
int wx, wy;
mouseToWorldCoords(mx, my, wx, wy);
return obj_manager->get_obj(wx, wy, cur_level);
}
Actor *MapWindow::get_actorAtMousePos(int mx, int my) {
int wx, wy;
mouseToWorldCoords(mx, my, wx, wy);
return actor_manager->get_actor(wx, wy, cur_level);
}
void MapWindow::teleport_to_cursor() {
int mx, my, wx, wy;
screen->get_mouse_location(&mx, &my);
mouseToWorldCoords(mx, my, wx, wy);
game->get_player()->move(wx, wy, cur_level, true);
}
void MapWindow::select_target(int x, int y) {
int wx, wy;
mouseToWorldCoords(x, y, wx, wy);
moveCursor(WRAPPED_COORD(wx - cur_x, cur_level), WRAPPED_COORD(wy - cur_y, cur_level));
game->get_event()->select_target(uint16(wx), uint16(wy), cur_level);
}
void MapWindow::mouseToWorldCoords(int mx, int my, int &wx, int &wy) {
int x = mx - area.left;
int y = my - area.top;
int mapWidth = map->get_width(cur_level);
wx = (uint)(cur_x + x / 16) % mapWidth;
wy = (uint)(cur_y + y / 16) % mapWidth;
}
void MapWindow::drag_draw(int x, int y, int message, void *data) {
Tile *tile;
if (!selected_obj)
return;
tile = tile_manager->get_tile(obj_manager->get_obj_tile_num(selected_obj) + selected_obj->frame_n);
int nx = x - 8;
int ny = y - 8;
if (nx + 16 >= screen->get_width())
nx = screen->get_width() - 17;
else if (nx < 0)
nx = 0;
if (ny + 16 >= screen->get_height())
ny = screen->get_height() - 17;
else if (ny < 0)
ny = 0;
screen->blit(nx, ny, tile->data, 8, 16, 16, 16, true);
screen->update(nx, ny, 16, 16);
}
/* Display MapWindow animations. */
void MapWindow::drawAnims(bool top_anims) {
if (!screen) // screen should be set early on
return;
else if (!anim_manager->get_surface()) // screen must be assigned to AnimManager
anim_manager->set_surface(screen);
anim_manager->display(top_anims);
}
/* Set mouse pointer to a movement-arrow for walking, or a crosshair. */
void MapWindow::update_mouse_cursor(uint32 mx, uint32 my) {
Events *event = game->get_event();
int wx = 0, wy = 0;
sint16 rel_x = 0, rel_y = 0;
uint8 mptr = 0; // mouse-pointer is set here in get_movement_direction()
if (event->get_mode() != MOVE_MODE && event->get_mode() != INPUT_MODE)
return;
// MousePos->WorldCoord->Direction&MousePointer
if (game->is_orig_style())
mouseToWorldCoords((int)mx, (int)my, wx, wy);
get_movement_direction((uint16)mx, (uint16)my, rel_x, rel_y, &mptr);
if (event->get_mode() == INPUT_MODE && mousecenter_x == (win_width / 2) && mousecenter_y == (win_height / 2)
&& !event->dont_show_target_cursor())
game->set_mouse_pointer(1); // crosshairs
else if (dragging || (game->is_orig_style() && (wx == cur_x || wy == cur_y || wx == WRAP_VIEWP(cur_x, win_width - 1, map_width) || wy == (cur_y + win_height - 1)))
|| (game->is_original_plus() && (my <= (uint32)Game::get_game()->get_game_y_offset() + 200 || game->is_original_plus_cutoff_map())
&& mx >= (uint32)Game::get_game()->get_game_x_offset() + game->get_game_width() - border_width))
game->set_mouse_pointer(0); // arrow
else
game->set_mouse_pointer(mptr); // 1=crosshairs, 2to9=arrows
}
/* Get relative movement direction from the MouseCenter coordinates to the
* mouse coordinates mx,my, for walking with the mouse, etc. The mouse-pointer
* number that should be used for that direction will be set to mptr.
*/
void MapWindow::get_movement_direction(uint16 mx, uint16 my, sint16 &rel_x, sint16 &rel_y, uint8 *mptr) {
uint16 cent_x = mousecenter_x,
cent_y = mousecenter_y;
if (game->is_original_plus_full_map() && game->get_event()->get_mode() != INPUT_MODE)
cent_x -= (map_center_xoff + 1) / 2; // player is off center
mx = (mx - area.left) / 16;
my = (my - area.top) / 16;
uint16 dist_x = abs(mx - cent_x), dist_y = abs(my - cent_y);
rel_x = rel_y = 0;
if (dist_x <= 4 && dist_y <= 4) {
// use mapwindow coords (4,4 is center of mapwindow)
uint8 cursor_num = movement_array[(9 * (4 + (my - cent_y))) + (4 + (mx - cent_x))];
if (mptr) // set mouse-pointer number
*mptr = cursor_num;
if (cursor_num == 1) // nowhere
return;
if (cursor_num == 2) // up
rel_y = -1;
else if (cursor_num == 6) // down
rel_y = 1;
else if (cursor_num == 8) // left
rel_x = -1;
else if (cursor_num == 4) // right
rel_x = 1;
else if (cursor_num == 3) { // up-right
rel_x = 1;
rel_y = -1;
} else if (cursor_num == 5) { // down-right
rel_x = 1;
rel_y = 1;
} else if (cursor_num == 7) { // down-left
rel_x = -1;
rel_y = 1;
} else if (cursor_num == 9) { // up-left
rel_x = -1;
rel_y = -1;
}
} else { // mapwindow is larger than the array; use 4 squares around center array
if (dist_x <= 4 && my < cent_y) { // up
rel_y = -1;
if (mptr) *mptr = 2;
} else if (dist_x <= 4 && my > cent_y) { // down
rel_y = 1;
if (mptr) *mptr = 6;
} else if (mx < cent_x && dist_y <= 4) { // left
rel_x = -1;
if (mptr) *mptr = 8;
} else if (mx > cent_x && dist_y <= 4) { // right
rel_x = 1;
if (mptr) *mptr = 4;
} else if (mx > cent_x && my < cent_y) { // up-right
rel_x = 1;
rel_y = -1;
if (mptr) *mptr = 3;
} else if (mx > cent_x && my > cent_y) { // down-right
rel_x = 1;
rel_y = 1;
if (mptr) *mptr = 5;
} else if (mx < cent_x && my > cent_y) { // down-left
rel_x = -1;
rel_y = 1;
if (mptr) *mptr = 7;
} else if (mx < cent_x && my < cent_y) { // up-left
rel_x = -1;
rel_y = -1;
if (mptr) *mptr = 9;
}
}
}
/* Revert mouse cursor to normal arrow. Stop walking. */
GUI_status MapWindow::MouseLeave(uint8 state) {
if (game_type == NUVIE_GAME_MD) // magnifying glass - pointer 0 should be used too for some areas
game->set_mouse_pointer(1);
else
game->set_mouse_pointer(0);
walking = false;
dragging = false;
// NOTE: Don't clear selected_obj here! It's used to remove the object after
// dragging.
return GUI_PASS;
}
byte *MapWindow::make_thumbnail() {
if (thumbnail)
return nullptr;
new_thumbnail = true;
GUI::get_gui()->Display(); // this calls MapWindow::display() which in turn calls create_thumbnail(). :-)
return thumbnail;
}
void MapWindow::create_thumbnail() {
Common::Rect src_rect;
src_rect.setWidth(MAPWINDOW_THUMBNAIL_SIZE * MAPWINDOW_THUMBNAIL_SCALE);
src_rect.setHeight(src_rect.width());
src_rect.left = area.left + win_width * 8 - (src_rect.width() / 2); // area.left + (win_width * 16) / 2 - 120 / 2
src_rect.top = area.top + win_height * 8 - (src_rect.height() / 2); // area.top + (win_height * 16) / 2 - 120 / 2
thumbnail = screen->copy_area(&src_rect, MAPWINDOW_THUMBNAIL_SCALE); //scale down x3
new_thumbnail = false;
}
void MapWindow::free_thumbnail() {
if (thumbnail) {
delete[] thumbnail;
thumbnail = nullptr;
}
return;
}
/* Returns a new 8bit copy of the mapwindow as displayed. Caller must free it. */
Graphics::ManagedSurface *MapWindow::get_sdl_surface() {
return (get_sdl_surface(0, 0, area.width(), area.height()));
}
Graphics::ManagedSurface *MapWindow::get_sdl_surface(uint16 x, uint16 y, uint16 w, uint16 h) {
Graphics::ManagedSurface *new_surface = nullptr;
byte *screen_area;
Common::Rect copy_area(area.left + x, area.top + y, area.left + x + w, area.top + y + h);
GUI::get_gui()->Display();
screen_area = screen->copy_area(©_area);
new_surface = screen->create_sdl_surface_8(screen_area, copy_area.width(), copy_area.height());
// new_surface = screen->create_sdl_surface_from(screen_area, screen->get_bpp(),
// copy_area.w, copy_area.h,
// copy_area.w);
free(screen_area);
return new_surface;
}
/* Returns the overlay surface. A new 8bit overlay is created if necessary. */
Graphics::ManagedSurface *MapWindow::get_overlay() {
if (!overlay)
overlay = new Graphics::ManagedSurface(area.width(), area.height(),
Graphics::PixelFormat::createFormatCLUT8());
return overlay;
}
/* Set the overlay surface. The current overlay is deleted if necessary. */
void MapWindow::set_overlay(Graphics::ManagedSurface *surfpt) {
if (overlay && (overlay != surfpt))
delete overlay;
overlay = surfpt;
}
/* Returns true if town tiles are within 5 tiles of the player */
bool MapWindow::in_town() const {
const MapCoord player_loc = actor_manager->get_player()->get_location();
for (const TileInfo &ti : m_ViewableMapTiles)
if (MapCoord(ti.x + cur_x, ti.y + cur_y, cur_level).distance(player_loc) <= 5 && // make sure tile is close enough
(ti.t->flags1 & TILEFLAG_WALL) && (ti.t->flags1 & TILEFLAG_WALL_MASK)) { //only wall tiles with wall direction bits set.
return true;
}
return false;
}
void MapWindow::wizard_eye_start(const MapCoord &location, uint16 duration, CallBack *caller) {
wizard_eye_info.moves_left = duration;
wizard_eye_info.caller = caller;
wizard_eye_info.prev_x = cur_x;
wizard_eye_info.prev_y = cur_y;
set_x_ray_view(X_RAY_ON);
sint16 map_x = location.x - (win_width / 2);
if (game->is_original_plus_full_map())
map_x += ((map_center_xoff + 1) / 2);
moveMap(map_x, location.y - (win_height / 2) , cur_level); // FIXME - map should already be centered on the caster so why are we doing this?
grab_focus();
}
void MapWindow::wizard_eye_stop() {
if (wizard_eye_info.moves_left > 0) {
wizard_eye_info.moves_left = 0;
wizard_eye_update();
}
}
void MapWindow::wizard_eye_update() {
if (wizard_eye_info.moves_left > 0)
wizard_eye_info.moves_left--;
if (wizard_eye_info.moves_left == 0) {
set_x_ray_view(X_RAY_OFF);
moveMap(wizard_eye_info.prev_x, wizard_eye_info.prev_y, cur_level);
wizard_eye_info.caller->callback(EFFECT_CB_COMPLETE, (CallBack *)this, nullptr);
release_focus();
}
}
void MapWindow::set_roof_mode(bool roofs) {
roof_mode = roofs;
if (roof_mode) {
if (roof_tiles)
return;
else
loadRoofTiles();
} else {
if (roof_tiles) {
delete roof_tiles;
roof_tiles = nullptr;
}
}
}
void MapWindow::loadRoofTiles() {
const Common::Path imagefile = map->getRoofTilesetFilename();
roof_tiles = SDL_LoadBMP(imagefile);
if (roof_tiles) {
roof_tiles->setTransparentColor(roof_tiles->format.RGBToColor(0, 0x70, 0xfc));
}
}
bool MapWindow::in_dungeon_level() const {
if (game_type == NUVIE_GAME_MD) {
return (cur_level == 1 || cur_level > 3); //FIXME this should probably be moved into script.
}
return (cur_level != 0 && cur_level != 5);
}
} // End of namespace Nuvie
} // End of namespace Ultima