From 271601221e42faff5bff936e45c81106e87a2dc2 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Thu, 28 Sep 2023 17:22:47 +0300 Subject: [PATCH] Add tile bounds (#105) --- src/game/gmouse.cc | 5 + src/game/map.cc | 4 + src/game/object.cc | 30 +++++- src/game/tile.cc | 235 +++++++++++++++++++++++++++++++++++++++++++-- src/game/tile.h | 6 ++ 5 files changed, 269 insertions(+), 11 deletions(-) diff --git a/src/game/gmouse.cc b/src/game/gmouse.cc index be6841a..73dfd7a 100644 --- a/src/game/gmouse.cc +++ b/src/game/gmouse.cc @@ -859,6 +859,11 @@ void gmouse_handle_event(int mouseX, int mouseY, int mouseState) return; } + // CE: Make sure we cannot go outside of the map. + if (!tile_point_inside_bound(mouseX, mouseY)) { + return; + } + if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_REPEAT) == 0) { if (gmouse_3d_is_on()) { diff --git a/src/game/map.cc b/src/game/map.cc index d709cb1..4eaf457 100644 --- a/src/game/map.cc +++ b/src/game/map.cc @@ -455,6 +455,9 @@ int map_set_elevation(int elevation) gmouse_set_cursor(MOUSE_CURSOR_NONE); map_elevation = elevation; + // CE: Recalculate bounds. + tile_update_bounds_base(); + register_clear(obj_dude); dude_stand(obj_dude, obj_dude->rotation, obj_dude->fid); partyMemberSyncPosition(); @@ -1611,6 +1614,7 @@ static void map_scroll_refresh_game(Rect* rect) grid_render(&rectToUpdate, map_elevation); obj_render_pre_roof(&rectToUpdate, map_elevation); square_render_roof(&rectToUpdate, map_elevation); + bounds_render(&rectToUpdate, map_elevation); obj_render_post_roof(&rectToUpdate, map_elevation); } diff --git a/src/game/object.cc b/src/game/object.cc index a0aec06..e5adb7a 100644 --- a/src/game/object.cc +++ b/src/game/object.cc @@ -772,6 +772,19 @@ void obj_render_pre_roof(Rect* rect, int elevation) return; } + // CE: Constrain rect to tile bounds so that we don't draw outside. + if (tile_inside_bound(&updatedRect) != 0) { + // Mouse hex cursor is a special case - should be shown as outline when + // out of bounds (see `obj_render_outline`). + outlineCount = 0; + if ((obj_mouse_flat->flags & OBJECT_HIDDEN) == 0 + && (obj_mouse_flat->outline & OUTLINE_TYPE_MASK) != 0 + && (obj_mouse_flat->outline & OUTLINE_DISABLED) == 0) { + outlinedObjects[outlineCount++] = obj_mouse_flat; + } + return; + } + int ambientIntensity = light_get_ambient(); int minX = updatedRect.ulx - 320; int minY = updatedRect.uly - 240; @@ -874,8 +887,23 @@ void obj_render_post_roof(Rect* rect, int elevation) return; } + // CE: Constrain rect to tile bounds so that we don't draw outside. + Rect constrainedRect = updatedRect; + if (tile_inside_bound(&constrainedRect) != 0) { + constrainedRect.ulx = 0; + constrainedRect.uly = 0; + constrainedRect.lrx = 0; + constrainedRect.lry = 0; + } + for (int index = 0; index < outlineCount; index++) { - obj_render_outline(outlinedObjects[index], &updatedRect); + // Mouse hex cursor is a special case - should be shown without + // constraining otherwise its hidden. + if (outlinedObjects[index] == obj_mouse_flat) { + obj_render_outline(outlinedObjects[index], &updatedRect); + } else { + obj_render_outline(outlinedObjects[index], &constrainedRect); + } } text_object_render(&updatedRect); diff --git a/src/game/tile.cc b/src/game/tile.cc index a52d49f..97968b2 100644 --- a/src/game/tile.cc +++ b/src/game/tile.cc @@ -1,6 +1,7 @@ #include "game/tile.h" #include +#include #include #define _USE_MATH_DEFINES @@ -557,6 +558,9 @@ int tile_set_center(int tile, int flags) tile_center_tile = tile; + // CE: Updates bounds screen coordinates. + tile_update_bounds_rect(); + if ((flags & TILE_SET_CENTER_REFRESH_WINDOW) != 0) { // NOTE: Uninline. tile_refresh_display(); @@ -606,6 +610,7 @@ static void refresh_game(Rect* rect, int elevation) square_render_floor(&rectToUpdate, elevation); obj_render_pre_roof(&rectToUpdate, elevation); square_render_roof(&rectToUpdate, elevation); + bounds_render(&rectToUpdate, elevation); obj_render_post_roof(&rectToUpdate, elevation); blit(&rectToUpdate); } @@ -1154,10 +1159,16 @@ void square_render_roof(Rect* rect, int elevation) int maxX; int maxY; - square_xy_roof(rect->ulx, rect->uly, elevation, &temp, &minY); - square_xy_roof(rect->lrx, rect->uly, elevation, &minX, &temp); - square_xy_roof(rect->ulx, rect->lry, elevation, &maxX, &temp); - square_xy_roof(rect->lrx, rect->lry, elevation, &temp, &maxY); + // CE: Constrain rect to tile bounds so that we don't draw outside. + Rect constrainedRect = *rect; + if (tile_inside_bound(&constrainedRect) != 0) { + return; + } + + square_xy_roof(constrainedRect.ulx, constrainedRect.uly, elevation, &temp, &minY); + square_xy_roof(constrainedRect.lrx, constrainedRect.uly, elevation, &minX, &temp); + square_xy_roof(constrainedRect.ulx, constrainedRect.lry, elevation, &maxX, &temp); + square_xy_roof(constrainedRect.lrx, constrainedRect.lry, elevation, &temp, &maxY); if (minX < 0) { minX = 0; @@ -1191,7 +1202,7 @@ void square_render_roof(Rect* rect, int elevation) int screenX; int screenY; square_coord_roof(squareTile, &screenX, &screenY, elevation); - roof_draw(fid, screenX, screenY, rect, light); + roof_draw(fid, screenX, screenY, &constrainedRect, light); } } } @@ -1378,10 +1389,16 @@ void square_render_floor(Rect* rect, int elevation) int minX; int temp; - square_xy(rect->ulx, rect->uly, elevation, &temp, &minY); - square_xy(rect->lrx, rect->uly, elevation, &minX, &temp); - square_xy(rect->ulx, rect->lry, elevation, &maxX, &temp); - square_xy(rect->lrx, rect->lry, elevation, &temp, &maxY); + // CE: Constrain rect to tile bounds so that we don't draw outside. + Rect constrainedRect = *rect; + if (tile_inside_bound(&constrainedRect) != 0) { + return; + } + + square_xy(constrainedRect.ulx, constrainedRect.uly, elevation, &temp, &minY); + square_xy(constrainedRect.lrx, constrainedRect.uly, elevation, &minX, &temp); + square_xy(constrainedRect.ulx, constrainedRect.lry, elevation, &maxX, &temp); + square_xy(constrainedRect.lrx, constrainedRect.lry, elevation, &temp, &maxY); if (minX < 0) { minX = 0; @@ -1412,7 +1429,7 @@ void square_render_floor(Rect* rect, int elevation) int tileScreenY; square_coord(squareTile, &tileScreenX, &tileScreenY, elevation); int fid = art_id(OBJ_TYPE_TILE, frmId & 0xFFF, 0, 0, 0); - floor_draw(fid, tileScreenX, tileScreenY, rect); + floor_draw(fid, tileScreenX, tileScreenY, &constrainedRect); } } baseSquareTile += square_width; @@ -1960,4 +1977,202 @@ int tile_scroll_to(int tile, int flags) return rc; } +static Rect tile_bounds_rect; +static int tile_bounds_left_off; +static int tile_bounds_top_off; +static int tile_bounds_right_off; +static int tile_bounds_bottom_off; + +void tile_update_bounds_base() +{ + int min_x = INT_MAX; + int min_y = INT_MAX; + int max_x = INT_MIN; + int max_y = INT_MIN; + + // Determine bounding rectangle of scroll blocking objects. + for (int tile = 0; tile < grid_size; tile++) { + if (obj_scroll_blocking_at(tile, map_elevation) == 0) { + int x; + int y; + tile_coord(tile, &x, &y, map_elevation); + x += 16; + y += 8; + + if (x < min_x) { + min_x = x; + } + + if (y < min_y) { + min_y = y; + } + + if (x > max_x) { + max_x = x; + } + + if (y > max_y) { + max_y = y; + } + } + } + + // Translate bounding rectangle in screen coordinates (which are relative + // to screen center tile) to offsets from reference tile (geometric center + // of the map). + + int geometric_center_x; + int geometric_center_y; + tile_coord(20100, &geometric_center_x, &geometric_center_y, map_elevation); + geometric_center_x += 16; + geometric_center_y += 8; + + tile_bounds_left_off = min_x - geometric_center_x; + tile_bounds_top_off = min_y - geometric_center_y; + tile_bounds_right_off = max_x - geometric_center_x; + tile_bounds_bottom_off = max_y - geometric_center_y; +} + +void tile_update_bounds_rect() +{ + // Translate offsets from reference tile to screen coordinates. + + int geometric_center_x; + int geometric_center_y; + tile_coord(20100, &geometric_center_x, &geometric_center_y, map_elevation); + geometric_center_x += 16; + geometric_center_y += 8; + + tile_bounds_rect.ulx = tile_bounds_left_off + geometric_center_x; + tile_bounds_rect.uly = tile_bounds_top_off + geometric_center_y; + tile_bounds_rect.lrx = tile_bounds_right_off + geometric_center_x; + tile_bounds_rect.lry = tile_bounds_bottom_off + geometric_center_y; + + // The bounding rectangle' corners are centers from scroll blocking objects. + // Since we're dealing with hex map where each row is shifted, we have two + // sets of blockers on each edge - to handle odd and even rows. Depending + // on scroll blockers location we can either have center tile to "touch" + // one scroll blocker or be "surrounded" by three of them. This requires + // bounds to be multiple of scroll steps. + + int tile_center_x; + int tile_center_y; + tile_coord(tile_center_tile, &tile_center_x, &tile_center_y, map_elevation); + tile_center_x += 16; + tile_center_y += 8; + + tile_bounds_rect.ulx -= (tile_bounds_rect.ulx - tile_center_x) % 32; + tile_bounds_rect.uly -= (tile_bounds_rect.uly - tile_center_y) % 24; + tile_bounds_rect.lrx -= (tile_bounds_rect.lrx - tile_center_x) % 32; + tile_bounds_rect.lry -= (tile_bounds_rect.lry - tile_center_y) % 24; + + // Scroll blocker itself cannot become center tile, so inset bounds for one + // full tile size. + tile_bounds_rect.ulx += 32; + tile_bounds_rect.uly += 16; + tile_bounds_rect.lrx -= 32; + tile_bounds_rect.lry -= 16; + + // Scroll blockers where placed for 640x480 resolution, which means visible + // rect is half of than amount in each direction. + tile_bounds_rect.ulx -= 640 / 2; + tile_bounds_rect.uly -= (480 - 100) / 2; + tile_bounds_rect.lrx += 640 / 2; + tile_bounds_rect.lry += (480 - 100) / 2; + + // Adjust for vertical layout. + tile_bounds_rect.uly += 8; + tile_bounds_rect.lry -= 8; + + // Decrement one px to make sure rect is what engine expects it to be. + tile_bounds_rect.lrx -= 1; + tile_bounds_rect.lry -= 1; +} + +int tile_inside_bound(Rect* rect) +{ + return rect_inside_bound(rect, &tile_bounds_rect, rect); +} + +bool tile_point_inside_bound(int x, int y) +{ + return x >= tile_bounds_rect.ulx && x <= tile_bounds_rect.lrx + && y >= tile_bounds_rect.uly && y <= tile_bounds_rect.lry; +} + +void bounds_render(Rect* rect, int elevation) +{ + constexpr int kShadowSize = 16; + + Rect edge; + + // Left. + edge.ulx = tile_bounds_rect.ulx; + edge.uly = tile_bounds_rect.uly; + edge.lrx = tile_bounds_rect.ulx + kShadowSize; + edge.lry = tile_bounds_rect.lry; + if (rect_inside_bound(&edge, rect, &edge) == 0) { + for (int y = edge.uly; y <= edge.lry; y++) { + unsigned char* dest = buf + buf_full * y + edge.ulx; + int step = edge.ulx - tile_bounds_rect.ulx; + for (int x = edge.ulx; x <= edge.lrx; x++) { + unsigned char color = *dest; + *dest++ = intensityColorTable[color][step * 128 / kShadowSize]; + step++; + } + } + } + + // Top. + edge.ulx = tile_bounds_rect.ulx; + edge.uly = tile_bounds_rect.uly; + edge.lrx = tile_bounds_rect.lrx; + edge.lry = tile_bounds_rect.uly + kShadowSize; + if (rect_inside_bound(&edge, rect, &edge) == 0) { + int step = edge.uly - tile_bounds_rect.uly; + for (int y = edge.uly; y <= edge.lry; y++) { + unsigned char* dest = buf + buf_full * y + edge.ulx; + for (int x = edge.ulx; x <= edge.lrx; x++) { + unsigned char color = *dest; + *dest++ = intensityColorTable[color][step * 128 / kShadowSize]; + } + step++; + } + } + + // Right. + edge.ulx = tile_bounds_rect.lrx - kShadowSize; + edge.uly = tile_bounds_rect.uly; + edge.lrx = tile_bounds_rect.lrx; + edge.lry = tile_bounds_rect.lry; + if (rect_inside_bound(&edge, rect, &edge) == 0) { + for (int y = edge.uly; y <= edge.lry; y++) { + unsigned char* dest = buf + buf_full * y + edge.lrx; + int step = tile_bounds_rect.lrx - edge.lrx; + for (int x = edge.lrx; x >= edge.ulx; x--) { + unsigned char color = *dest; + *dest-- = intensityColorTable[color][step * 128 / kShadowSize]; + step++; + } + } + } + + // Bottom. + edge.ulx = tile_bounds_rect.ulx; + edge.uly = tile_bounds_rect.lry - kShadowSize; + edge.lrx = tile_bounds_rect.lrx; + edge.lry = tile_bounds_rect.lry; + if (rect_inside_bound(&edge, rect, &edge) == 0) { + int step = tile_bounds_rect.lry - edge.lry; + for (int y = edge.lry; y >= edge.uly; y--) { + unsigned char* dest = buf + buf_full * y + edge.ulx; + for (int x = edge.ulx; x <= edge.lrx; x++) { + unsigned char color = *dest; + *dest++ = intensityColorTable[color][step * 128 / kShadowSize]; + } + step++; + } + } +} + } // namespace fallout diff --git a/src/game/tile.h b/src/game/tile.h index 99a57d2..de7dcbb 100644 --- a/src/game/tile.h +++ b/src/game/tile.h @@ -63,6 +63,12 @@ void floor_draw(int fid, int x, int y, Rect* rect); int tile_make_line(int currentCenterTile, int newCenterTile, int* tiles, int tilesCapacity); int tile_scroll_to(int tile, int flags); +void tile_update_bounds_base(); +void tile_update_bounds_rect(); +int tile_inside_bound(Rect* rect); +bool tile_point_inside_bound(int x, int y); +void bounds_render(Rect* rect, int elevation); + } // namespace fallout #endif /* FALLOUT_GAME_TILE_H_ */