Files
CorsixTH/CorsixTH/Src/th_map.cpp
Stephen E. Baker 4a1c98a716 [RDY] Cpp fmt (#1562)
* Hand formatted changes before automatic formatting

* Break up / shorten some long lines

* Add some missing braces for clarity

* Multiline strings are merged to let clang-format split them
appropriately.

* sdl_core's frame_count changed from a C style array to std::array
which made the length checks simpler.

* Add includes and forward declairs to avoid transitive dependencies

* Remove th_gfx_font.h include from th_gfx.h - circular dependencies

* using to shorten lines in th_map.cpp

* Avoid non-portable reinterpret_cast for parcels in th_map.

* Use more constants in th_map.

* Use class initializer for th_map classes

* Add clang files to ignore list

* Add clang-format file

* Reformat all files with clang-format

Also includes some manual braces.

* Disable clang-format for backdrop.h

* Clang-format AnimView src files

* clang-format common code

* Fix anonymous struct in union warning

Anonymous structs in anonymous unions are not supported by the standard.

* Check clang-format in travis

* Bin pack parameters

* Bin pack arguments too

* Full Google style

2 space indent, no forced break on braces.

* A couple format overrides

Order of usings in config.h.in since 8 is smaller than 16.
Table layout, since 0x80 nicely fits in 8 columns.
2019-10-06 08:18:25 -04:00

1794 lines
58 KiB
C++

/*
Copyright (c) 2009 Peter "Corsix" Cawley
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "th_map.h"
#include "config.h"
#include <SDL.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <exception>
#include <fstream>
#include <new>
#include <string>
#include "run_length_encoder.h"
#include "th.h"
#include "th_gfx.h"
#include "th_map_overlays.h"
constexpr size_t max_player_count = 4;
map_tile_flags& map_tile_flags::operator=(uint32_t raw) {
using flags = map_tile_flags::key;
passable = (raw & static_cast<uint32_t>(flags::passable_mask)) != 0;
can_travel_n = (raw & static_cast<uint32_t>(flags::can_travel_n_mask)) != 0;
can_travel_e = (raw & static_cast<uint32_t>(flags::can_travel_e_mask)) != 0;
can_travel_s = (raw & static_cast<uint32_t>(flags::can_travel_s_mask)) != 0;
can_travel_w = (raw & static_cast<uint32_t>(flags::can_travel_w_mask)) != 0;
hospital = (raw & static_cast<uint32_t>(flags::hospital_mask)) != 0;
buildable = (raw & static_cast<uint32_t>(flags::buildable_mask)) != 0;
passable_if_not_for_blueprint =
(raw &
static_cast<uint32_t>(flags::passable_if_not_for_blueprint_mask)) != 0;
room = (raw & static_cast<uint32_t>(flags::room_mask)) != 0;
shadow_half = (raw & static_cast<uint32_t>(flags::shadow_half_mask)) != 0;
shadow_full = (raw & static_cast<uint32_t>(flags::shadow_full_mask)) != 0;
shadow_wall = (raw & static_cast<uint32_t>(flags::shadow_wall_mask)) != 0;
door_north = (raw & static_cast<uint32_t>(flags::door_north_mask)) != 0;
door_west = (raw & static_cast<uint32_t>(flags::door_west_mask)) != 0;
do_not_idle = (raw & static_cast<uint32_t>(flags::do_not_idle_mask)) != 0;
tall_north = (raw & static_cast<uint32_t>(flags::tall_north_mask)) != 0;
tall_west = (raw & static_cast<uint32_t>(flags::tall_west_mask)) != 0;
buildable_n = (raw & static_cast<uint32_t>(flags::buildable_n_mask)) != 0;
buildable_e = (raw & static_cast<uint32_t>(flags::buildable_e_mask)) != 0;
buildable_s = (raw & static_cast<uint32_t>(flags::buildable_s_mask)) != 0;
buildable_w = (raw & static_cast<uint32_t>(flags::buildable_w_mask)) != 0;
return *this;
}
bool& map_tile_flags::operator[](map_tile_flags::key key) {
using flags = map_tile_flags::key;
switch (key) {
case flags::passable_mask:
return passable;
case flags::can_travel_n_mask:
return can_travel_n;
case flags::can_travel_e_mask:
return can_travel_e;
case flags::can_travel_s_mask:
return can_travel_s;
case flags::can_travel_w_mask:
return can_travel_w;
case flags::hospital_mask:
return hospital;
case flags::buildable_mask:
return buildable;
case flags::passable_if_not_for_blueprint_mask:
return passable_if_not_for_blueprint;
case flags::room_mask:
return room;
case flags::shadow_half_mask:
return shadow_half;
case flags::shadow_full_mask:
return shadow_full;
case flags::shadow_wall_mask:
return shadow_wall;
case flags::door_north_mask:
return door_north;
case flags::door_west_mask:
return door_west;
case flags::do_not_idle_mask:
return do_not_idle;
case flags::tall_north_mask:
return tall_north;
case flags::tall_west_mask:
return tall_west;
case flags::buildable_n_mask:
return buildable_n;
case flags::buildable_e_mask:
return buildable_e;
case flags::buildable_s_mask:
return buildable_s;
case flags::buildable_w_mask:
return buildable_w;
default:
throw std::out_of_range("map tile flag is invalid");
}
}
const bool& map_tile_flags::operator[](map_tile_flags::key key) const {
using flags = map_tile_flags::key;
switch (key) {
case flags::passable_mask:
return passable;
case flags::can_travel_n_mask:
return can_travel_n;
case flags::can_travel_e_mask:
return can_travel_e;
case flags::can_travel_s_mask:
return can_travel_s;
case flags::can_travel_w_mask:
return can_travel_w;
case flags::hospital_mask:
return hospital;
case flags::buildable_mask:
return buildable;
case flags::passable_if_not_for_blueprint_mask:
return passable_if_not_for_blueprint;
case flags::room_mask:
return room;
case flags::shadow_half_mask:
return shadow_half;
case flags::shadow_full_mask:
return shadow_full;
case flags::shadow_wall_mask:
return shadow_wall;
case flags::door_north_mask:
return door_north;
case flags::door_west_mask:
return door_west;
case flags::do_not_idle_mask:
return do_not_idle;
case flags::tall_north_mask:
return tall_north;
case flags::tall_west_mask:
return tall_west;
case flags::buildable_n_mask:
return buildable_n;
case flags::buildable_e_mask:
return buildable_e;
case flags::buildable_s_mask:
return buildable_s;
case flags::buildable_w_mask:
return buildable_w;
default:
throw std::out_of_range("map tile flag is invalid");
}
}
map_tile_flags::operator uint32_t() const {
using flags = map_tile_flags::key;
uint32_t raw = 0;
if (passable) {
raw |= static_cast<uint32_t>(flags::passable_mask);
}
if (can_travel_n) {
raw |= static_cast<uint32_t>(flags::can_travel_n_mask);
}
if (can_travel_e) {
raw |= static_cast<uint32_t>(flags::can_travel_e_mask);
}
if (can_travel_s) {
raw |= static_cast<uint32_t>(flags::can_travel_s_mask);
}
if (can_travel_w) {
raw |= static_cast<uint32_t>(flags::can_travel_w_mask);
}
if (hospital) {
raw |= static_cast<uint32_t>(flags::hospital_mask);
}
if (buildable) {
raw |= static_cast<uint32_t>(flags::buildable_mask);
}
if (passable_if_not_for_blueprint) {
raw |= static_cast<uint32_t>(flags::passable_if_not_for_blueprint_mask);
}
if (room) {
raw |= static_cast<uint32_t>(flags::room_mask);
}
if (shadow_half) {
raw |= static_cast<uint32_t>(flags::shadow_half_mask);
}
if (shadow_full) {
raw |= static_cast<uint32_t>(flags::shadow_full_mask);
}
if (shadow_wall) {
raw |= static_cast<uint32_t>(flags::shadow_wall_mask);
}
if (door_north) {
raw |= static_cast<uint32_t>(flags::door_north_mask);
}
if (door_west) {
raw |= static_cast<uint32_t>(flags::door_west_mask);
}
if (do_not_idle) {
raw |= static_cast<uint32_t>(flags::do_not_idle_mask);
}
if (tall_north) {
raw |= static_cast<uint32_t>(flags::tall_north_mask);
}
if (tall_west) {
raw |= static_cast<uint32_t>(flags::tall_west_mask);
}
if (buildable_n) {
raw |= static_cast<uint32_t>(flags::buildable_n_mask);
}
if (buildable_e) {
raw |= static_cast<uint32_t>(flags::buildable_e_mask);
}
if (buildable_s) {
raw |= static_cast<uint32_t>(flags::buildable_s_mask);
}
if (buildable_w) {
raw |= static_cast<uint32_t>(flags::buildable_w_mask);
}
return raw;
}
map_tile::map_tile() : iParcelId(0), iRoomId(0), flags({}), objects() {
iBlock[0] = 0;
iBlock[1] = 0;
iBlock[2] = 0;
iBlock[3] = 0;
aiTemperature[0] = aiTemperature[1] = 8192;
}
map_tile::~map_tile() {}
level_map::level_map()
: cells(nullptr),
original_cells(nullptr),
blocks(nullptr),
overlay(nullptr),
owns_overlay(false),
plot_owner(nullptr),
width(0),
height(0),
player_count(0),
parcel_count(0),
current_temperature_index(0),
current_temperature_theme(temperature_theme::red),
parcel_tile_counts(nullptr),
parcel_adjacency_matrix(nullptr),
purchasable_matrix(nullptr) {}
level_map::~level_map() {
set_overlay(nullptr, false);
delete[] cells;
delete[] original_cells;
delete[] plot_owner;
delete[] parcel_tile_counts;
delete[] parcel_adjacency_matrix;
delete[] purchasable_matrix;
}
void level_map::set_overlay(map_overlay* pOverlay, bool bTakeOwnership) {
if (overlay && owns_overlay) {
delete overlay;
}
overlay = pOverlay;
owns_overlay = bTakeOwnership;
}
bool level_map::set_size(int iWidth, int iHeight) {
if (iWidth <= 0 || iHeight <= 0) {
return false;
}
delete[] cells;
delete[] original_cells;
delete[] parcel_adjacency_matrix;
delete[] purchasable_matrix;
width = iWidth;
height = iHeight;
cells = nullptr;
cells = new (std::nothrow) map_tile[iWidth * iHeight];
original_cells = nullptr;
original_cells = new (std::nothrow) map_tile[iWidth * iHeight];
parcel_adjacency_matrix = nullptr;
purchasable_matrix = nullptr;
if (cells == nullptr || original_cells == nullptr) {
delete[] cells;
delete[] original_cells;
original_cells = nullptr;
cells = nullptr;
width = 0;
height = 0;
return false;
}
return true;
}
namespace {
// NB: http://connection-endpoint.de/th-format-specification/
// gives a (slightly) incorrect array, which is why it differs from this one.
constexpr uint8_t gs_iTHMapBlockLUT[256] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x52, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D,
0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x84, 0x85, 0x88, 0x89,
0x8C, 0x8D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x8F, 0x00, 0x00,
0x00, 0x00, 0x8E, 0x8F, 0xD5, 0xD6, 0x9C, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0,
0xD1, 0xD2, 0xD3, 0xD4, 0xB3, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5,
0xB6, 0xB7, 0xB8, 0xB9, 0xB3, 0xB3, 0xB4, 0xB4, 0xBA, 0xBB, 0xBC, 0xBD,
0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
0xCA, 0xCB, 0x00, 0x82, 0x83, 0x86, 0x87, 0x8A, 0x8B, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x00, 0x9D, 0x9E, 0x9F, 0xA0,
0xA1, 0xA2, 0xA3, 0xA4, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
0xDF, 0xE0, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
} // namespace
void level_map::read_tile_index(const uint8_t* pData, int& iX, int& iY) const {
unsigned int iIndex = static_cast<unsigned int>(pData[1]);
iIndex = iIndex * 0x100 + static_cast<unsigned int>(pData[0]);
iX = iIndex % width;
iY = iIndex / width;
}
void level_map::write_tile_index(uint8_t* pData, int iX, int iY) const {
uint16_t iIndex = static_cast<uint16_t>(iY * width + iX);
pData[0] = static_cast<uint8_t>(iIndex & 0xFF);
pData[1] = static_cast<uint8_t>(iIndex >> 8);
}
bool level_map::load_blank() {
if (!set_size(128, 128)) {
return false;
}
player_count = 1;
initial_camera_x[0] = initial_camera_y[0] = 63;
heliport_x[0] = heliport_y[0] = 0;
parcel_count = 1;
delete[] plot_owner;
delete[] parcel_tile_counts;
plot_owner = nullptr;
parcel_tile_counts = nullptr;
map_tile* pNode = cells;
map_tile* pOriginalNode = original_cells;
for (int iY = 0; iY < height; ++iY) {
for (int iX = 0; iX < width; ++iX, ++pNode, ++pOriginalNode) {
pNode->iBlock[0] = static_cast<uint16_t>(2 + (iX % 2));
}
}
plot_owner = new int[1];
plot_owner[0] = 0;
parcel_tile_counts = new int[1];
parcel_tile_counts[0] = height * width;
return true;
}
namespace {
inline bool is_divider_wall(const uint8_t byte) { return (byte >> 1) == 70; }
} // namespace
bool level_map::load_from_th_file(const uint8_t* pData, size_t iDataLength,
map_load_object_callback_fn fnObjectCallback,
void* pCallbackToken) {
const size_t camera_offset = 163876;
const size_t heliport_offset = 163884;
const size_t parcel_offset = 131106;
if (iDataLength < 163948 || !set_size(128, 128)) {
return false;
}
player_count = pData[0] % max_player_count;
for (int i = 0; i < player_count; ++i) {
read_tile_index(pData + camera_offset + (i * 2), initial_camera_x[i],
initial_camera_y[i]);
read_tile_index(pData + heliport_offset + (i * 2), heliport_x[i],
heliport_y[i]);
}
parcel_count = 0;
delete[] plot_owner;
delete[] parcel_tile_counts;
plot_owner = nullptr;
parcel_tile_counts = nullptr;
map_tile* pNode = cells;
map_tile* pOriginalNode = original_cells;
const uint8_t* pParcel = pData + parcel_offset;
pData += 34;
pNode->objects.clear();
for (int iY = 0; iY < height; ++iY) {
for (int iX = 0; iX < width; ++iX) {
uint8_t iBaseTile = gs_iTHMapBlockLUT[pData[2]];
pNode->flags.can_travel_n = true;
pNode->flags.can_travel_e = true;
pNode->flags.can_travel_s = true;
pNode->flags.can_travel_w = true;
if (iX == 0) {
pNode->flags.can_travel_w = false;
} else if (iX == width - 1) {
pNode->flags.can_travel_e = false;
}
if (iY == 0) {
pNode->flags.can_travel_n = false;
} else if (iY == height - 1) {
pNode->flags.can_travel_s = false;
}
pNode->iBlock[0] = iBaseTile;
if (pData[3] == 0 || is_divider_wall(pData[3])) {
// Tiles 71, 72 and 73 (pond foliage) are used as floor tiles,
// but are too tall to be floor tiles, so move them to a wall,
// and replace the floor with something similar (pond base).
if (71 <= iBaseTile && iBaseTile <= 73) {
pNode->iBlock[1] = iBaseTile;
pNode->iBlock[0] = iBaseTile = 69;
} else {
pNode->iBlock[1] = 0;
}
} else {
pNode->iBlock[1] = gs_iTHMapBlockLUT[pData[3]];
pNode->flags.can_travel_n = false;
if (iY != 0) {
pNode[-128].flags.can_travel_s = false;
}
}
if (pData[4] == 0 || is_divider_wall(pData[4])) {
pNode->iBlock[2] = 0;
} else {
pNode->iBlock[2] = gs_iTHMapBlockLUT[pData[4]];
pNode->flags.can_travel_w = false;
if (iX != 0) {
pNode[-1].flags.can_travel_e = false;
}
}
pNode->iRoomId = 0;
pNode->iParcelId = bytes_to_uint16_le(pParcel);
if (pNode->iParcelId >= parcel_count) {
parcel_count = pNode->iParcelId + 1;
}
if (!(pData[5] & 1)) {
pNode->flags.passable = true;
if (!(pData[7] & 16)) {
pNode->flags.hospital = true;
if (!(pData[5] & 2)) {
pNode->flags.buildable = true;
}
if (!(pData[5] & 4) || pData[1] == 0) {
pNode->flags.buildable_n = true;
}
if (!(pData[5] & 8) || pData[1] == 0) {
pNode->flags.buildable_e = true;
}
if (!(pData[5] & 16) || pData[1] == 0) {
pNode->flags.buildable_s = true;
}
if (!(pData[5] & 32) || pData[1] == 0) {
pNode->flags.buildable_w = true;
}
}
}
*pOriginalNode = *pNode;
if (is_divider_wall(pData[3])) {
pOriginalNode->iBlock[1] = gs_iTHMapBlockLUT[pData[3]];
}
if (is_divider_wall(pData[4])) {
pOriginalNode->iBlock[2] = gs_iTHMapBlockLUT[pData[4]];
}
if (pData[1] != 0 && fnObjectCallback != nullptr) {
fnObjectCallback(pCallbackToken, iX, iY, (object_type)pData[1],
pData[0]);
}
++pNode;
++pOriginalNode;
pData += 8;
pParcel += 2;
}
}
plot_owner = new int[parcel_count];
plot_owner[0] = 0;
for (int i = 1; i < parcel_count; ++i) {
plot_owner[i] = 1;
}
update_shadows();
parcel_tile_counts = new int[parcel_count];
parcel_tile_counts[0] = 0;
for (int i = 1; i < parcel_count; ++i) {
parcel_tile_counts[i] = count_parcel_tiles(i);
}
return true;
}
void level_map::save(std::string filename) {
uint8_t aBuffer[256] = {0};
int iBufferNext = 0;
std::ofstream os(filename, std::ios_base::trunc | std::ios_base::binary);
// Header
aBuffer[0] = static_cast<uint8_t>(player_count);
// TODO: Determine correct contents for the next 33 bytes
os.write(reinterpret_cast<char*>(aBuffer), 34);
uint8_t aReverseBlockLUT[256] = {0};
for (int i = 0; i < 256; ++i) {
aReverseBlockLUT[gs_iTHMapBlockLUT[i]] = static_cast<uint8_t>(i);
}
aReverseBlockLUT[0] = 0;
for (map_tile *pNode = cells, *pLimitNode = pNode + width * height;
pNode != pLimitNode; ++pNode) {
// TODO: Nicer system for saving object data
aBuffer[iBufferNext++] = pNode->flags.tall_west ? 1 : 0;
aBuffer[iBufferNext++] =
static_cast<uint8_t>(pNode->objects.empty() ? object_type::no_object
: pNode->objects.front());
// Blocks
aBuffer[iBufferNext++] = aReverseBlockLUT[pNode->iBlock[0] & 0xFF];
aBuffer[iBufferNext++] = aReverseBlockLUT[pNode->iBlock[1] & 0xFF];
aBuffer[iBufferNext++] = aReverseBlockLUT[pNode->iBlock[2] & 0xFF];
// Flags (TODO: Set a few more flag bits?)
uint8_t iFlags = 63;
if (pNode->flags.passable) {
iFlags ^= 1;
}
if (pNode->flags.buildable) {
iFlags ^= 2;
}
if (pNode->flags.buildable_n) {
iFlags ^= 4;
}
if (pNode->flags.buildable_e) {
iFlags ^= 8;
}
if (pNode->flags.buildable_s) {
iFlags ^= 16;
}
if (pNode->flags.buildable_w) {
iFlags ^= 32;
}
aBuffer[iBufferNext++] = iFlags;
aBuffer[iBufferNext++] = 0;
iFlags = 16;
if (pNode->flags.hospital) {
iFlags ^= 16;
}
aBuffer[iBufferNext++] = iFlags;
if (iBufferNext == sizeof(aBuffer)) {
os.write(reinterpret_cast<char*>(aBuffer), sizeof(aBuffer));
iBufferNext = 0;
}
}
for (map_tile *pNode = cells, *pLimitNode = pNode + width * height;
pNode != pLimitNode; ++pNode) {
aBuffer[iBufferNext++] = static_cast<uint8_t>(pNode->iParcelId & 0xFF);
aBuffer[iBufferNext++] = static_cast<uint8_t>(pNode->iParcelId >> 8);
if (iBufferNext == sizeof(aBuffer)) {
os.write(reinterpret_cast<char*>(aBuffer), sizeof(aBuffer));
iBufferNext = 0;
}
}
// TODO: What are these two bytes?
aBuffer[iBufferNext++] = 3;
aBuffer[iBufferNext++] = 0;
os.write(reinterpret_cast<char*>(aBuffer), iBufferNext);
iBufferNext = 0;
std::memset(aBuffer, 0, 56);
for (int i = 0; i < player_count; ++i) {
write_tile_index(aBuffer + iBufferNext, initial_camera_x[i],
initial_camera_y[i]);
write_tile_index(aBuffer + iBufferNext + 8, heliport_x[i], heliport_y[i]);
iBufferNext += 2;
}
os.write(reinterpret_cast<char*>(aBuffer), 16);
std::memset(aBuffer, 0, 16);
// TODO: What are these 56 bytes?
os.write(reinterpret_cast<char*>(aBuffer), 56);
os.close();
}
namespace {
//! Add or remove divider wall for the given tile
/*!
If the given 'pNode' has an indoor border to another parcel in the 'delta'
direction:
* A divider wall is added in the layer specified by 'block' if the owners
of the two parcels are not the same, or
* A divider wall is removed if the owners are the same and 'iParcelId' is
involved. \return True if a border was removed, false otherwise
*/
bool addRemoveDividerWalls(level_map* pMap, map_tile* pNode,
const map_tile* pOriginalNode, int iXY, int delta,
int block, int iParcelId) {
if (iXY > 0 && pOriginalNode->flags.hospital &&
pOriginalNode[-delta].flags.hospital &&
pNode->iParcelId != pNode[-delta].iParcelId) {
int iOwner = pMap->get_parcel_owner(pNode->iParcelId);
int iOtherOwner = pMap->get_parcel_owner(pNode[-delta].iParcelId);
if (iOwner != iOtherOwner) {
pNode->iBlock[block] = block + (iOwner ? 143 : 141);
} else if (pNode->iParcelId == iParcelId ||
pNode[-delta].iParcelId == iParcelId) {
pNode->iBlock[block] = 0;
return true;
}
}
return false;
}
} // namespace
std::vector<std::pair<int, int>> level_map::set_parcel_owner(int iParcelId,
int iOwner) {
std::vector<std::pair<int, int>> vSplitTiles;
if (iParcelId <= 0 || parcel_count <= iParcelId || iOwner < 0) {
return vSplitTiles;
}
plot_owner[iParcelId] = iOwner;
map_tile* pNode = cells;
const map_tile* pOriginalNode = original_cells;
for (int iY = 0; iY < 128; ++iY) {
for (int iX = 0; iX < 128; ++iX, ++pNode, ++pOriginalNode) {
if (pNode->iParcelId == iParcelId) {
if (iOwner != 0) {
pNode->iBlock[0] = pOriginalNode->iBlock[0];
pNode->iBlock[1] = pOriginalNode->iBlock[1];
pNode->iBlock[2] = pOriginalNode->iBlock[2];
pNode->flags = pOriginalNode->flags;
} else {
// Nicely mown grass pattern
pNode->iBlock[0] = static_cast<uint16_t>(((iX & 1) << 1) + 1);
pNode->iBlock[1] = 0;
pNode->iBlock[2] = 0;
pNode->flags = {};
// Random decoration
if (((iX | iY) & 0x7) == 0) {
int iWhich = (iX ^ iY) % 9;
pNode->iBlock[1] = static_cast<uint16_t>(192 + iWhich);
}
}
}
if (addRemoveDividerWalls(this, pNode, pOriginalNode, iX, 1, 2,
iParcelId)) {
vSplitTiles.push_back(std::make_pair(iX, iY));
}
if (addRemoveDividerWalls(this, pNode, pOriginalNode, iY, 128, 1,
iParcelId)) {
vSplitTiles.push_back(std::make_pair(iX, iY));
}
}
}
update_pathfinding();
update_shadows();
update_purchase_matrix();
return vSplitTiles;
}
namespace {
void test_adj(bool* parcel_adjacency_matrix, int parcel_count,
const map_tile* original_node, int xy, ptrdiff_t delta) {
if (xy > 0 && original_node->iParcelId != original_node[-delta].iParcelId &&
original_node->flags.passable && original_node[-delta].flags.passable) {
parcel_adjacency_matrix[original_node->iParcelId * parcel_count +
original_node[-delta].iParcelId] = true;
parcel_adjacency_matrix[original_node->iParcelId +
original_node[-delta].iParcelId * parcel_count] =
true;
}
}
} // namespace
void level_map::make_adjacency_matrix() {
if (parcel_adjacency_matrix != nullptr) {
return;
}
parcel_adjacency_matrix = new bool[parcel_count * parcel_count];
for (int i = 0; i < parcel_count; ++i) {
for (int j = 0; j < parcel_count; ++j) {
parcel_adjacency_matrix[i * parcel_count + j] = (i == j);
}
}
const map_tile* pOriginalNode = original_cells;
for (int iY = 0; iY < 128; ++iY) {
for (int iX = 0; iX < 128; ++iX) {
++pOriginalNode;
test_adj(parcel_adjacency_matrix, parcel_count, pOriginalNode, iX, 1);
test_adj(parcel_adjacency_matrix, parcel_count, pOriginalNode, iY, 128);
}
}
}
void level_map::make_purchase_matrix() {
if (purchasable_matrix != nullptr) {
return; // Already made
}
purchasable_matrix = new bool[max_player_count * parcel_count];
update_purchase_matrix();
}
void level_map::update_purchase_matrix() {
if (purchasable_matrix == nullptr) {
return; // Nothing to update
}
for (int iPlayer = 1; iPlayer <= max_player_count; ++iPlayer) {
for (int iParcel = 0; iParcel < parcel_count; ++iParcel) {
bool bPurchasable = false;
if (iParcel != 0 && plot_owner[iParcel] == 0) {
for (int iParcel2 = 0; iParcel2 < parcel_count; ++iParcel2) {
if ((plot_owner[iParcel2] == iPlayer) || (iParcel2 == 0)) {
if (are_parcels_adjacent(iParcel, iParcel2)) {
bPurchasable = true;
break;
}
}
}
}
purchasable_matrix[iParcel * max_player_count + iPlayer - 1] =
bPurchasable;
}
}
}
bool level_map::are_parcels_adjacent(int iParcel1, int iParcel2) {
if (0 <= iParcel1 && iParcel1 < parcel_count && 0 <= iParcel2 &&
iParcel2 < parcel_count) {
make_adjacency_matrix();
return parcel_adjacency_matrix[iParcel1 * parcel_count + iParcel2];
}
return false;
}
bool level_map::is_parcel_purchasable(int iParcelId, int iPlayer) {
if (0 <= iParcelId && iParcelId < parcel_count && 1 <= iPlayer &&
iPlayer <= max_player_count) {
make_purchase_matrix();
return purchasable_matrix[iParcelId * max_player_count + iPlayer - 1];
}
return false;
}
void level_map::set_player_count(int count) {
if (count < 1 || count > max_player_count) {
throw std::out_of_range("Player count must be between 1 and 4");
}
player_count = count;
}
bool level_map::get_player_camera_tile(int iPlayer, int* pX, int* pY) const {
if (iPlayer < 0 || iPlayer >= get_player_count()) {
if (pX) {
*pX = 0;
}
if (pY) {
*pY = 0;
}
return false;
}
if (pX) {
*pX = initial_camera_x[iPlayer];
}
if (pY) {
*pY = initial_camera_y[iPlayer];
}
return true;
}
bool level_map::get_player_heliport_tile(int iPlayer, int* pX, int* pY) const {
if (iPlayer < 0 || iPlayer >= get_player_count()) {
if (pX) {
*pX = 0;
}
if (pY) {
*pY = 0;
}
return false;
}
if (pX) {
*pX = heliport_x[iPlayer];
}
if (pY) {
*pY = heliport_y[iPlayer];
}
return true;
}
void level_map::set_player_camera_tile(int iPlayer, int iX, int iY) {
if (0 <= iPlayer && iPlayer < get_player_count()) {
initial_camera_x[iPlayer] = iX;
initial_camera_y[iPlayer] = iY;
}
}
void level_map::set_player_heliport_tile(int iPlayer, int iX, int iY) {
if (0 <= iPlayer && iPlayer < get_player_count()) {
heliport_x[iPlayer] = iX;
heliport_y[iPlayer] = iY;
}
}
int level_map::get_parcel_tile_count(int iParcelId) const {
if (iParcelId < 1 || iParcelId >= parcel_count) {
return 0;
}
return parcel_tile_counts[iParcelId];
}
int level_map::count_parcel_tiles(int iParcelId) const {
int iTiles = 0;
for (int iY = 0; iY < height; ++iY) {
for (int iX = 0; iX < width; ++iX) {
const map_tile* pNode = get_tile_unchecked(iX, iY);
if (pNode->iParcelId == iParcelId) {
iTiles++;
}
}
}
return iTiles;
}
map_tile* level_map::get_tile(int iX, int iY) {
if (0 <= iX && iX < width && 0 <= iY && iY < height) {
return get_tile_unchecked(iX, iY);
} else {
return nullptr;
}
}
const map_tile* level_map::get_tile(int iX, int iY) const {
if (0 <= iX && iX < width && 0 <= iY && iY < height) {
return get_tile_unchecked(iX, iY);
} else {
return nullptr;
}
}
const map_tile* level_map::get_original_tile(int iX, int iY) const {
if (0 <= iX && iX < width && 0 <= iY && iY < height) {
return get_original_tile_unchecked(iX, iY);
} else {
return nullptr;
}
}
map_tile* level_map::get_tile_unchecked(int iX, int iY) {
return cells + iY * width + iX;
}
const map_tile* level_map::get_tile_unchecked(int iX, int iY) const {
return cells + iY * width + iX;
}
const map_tile* level_map::get_original_tile_unchecked(int iX, int iY) const {
return original_cells + iY * width + iX;
}
void level_map::set_block_sheet(sprite_sheet* pSheet) { blocks = pSheet; }
void level_map::set_all_wall_draw_flags(uint8_t iFlags) {
uint16_t iBlockOr = static_cast<uint16_t>(iFlags << 8);
map_tile* pNode = cells;
for (int i = 0; i < width * height; ++i, ++pNode) {
pNode->iBlock[1] =
static_cast<uint16_t>((pNode->iBlock[1] & 0xFF) | iBlockOr);
pNode->iBlock[2] =
static_cast<uint16_t>((pNode->iBlock[2] & 0xFF) | iBlockOr);
}
}
// Definition is in th_gfx.h so this should move
void clip_rect_intersection(clip_rect& rcClip, const clip_rect& rcIntersect) {
// The intersection of the rectangles is the higher of the lower bounds and
// the lower of the higher bounds, clamped to a zero size.
clip_rect::x_y_type maxX = static_cast<uint16_t>(
std::min(rcClip.x + rcClip.w, rcIntersect.x + rcIntersect.w));
clip_rect::x_y_type maxY = static_cast<uint16_t>(
std::min(rcClip.y + rcClip.h, rcIntersect.y + rcIntersect.h));
rcClip.x = std::max(rcClip.x, rcIntersect.x);
rcClip.y = std::max(rcClip.y, rcIntersect.y);
rcClip.w = maxX - rcClip.x;
rcClip.h = maxY - rcClip.y;
// Make sure that we clamp the values to 0.
if (rcClip.w <= 0) {
rcClip.w = rcClip.h = 0;
} else if (rcClip.h <= 0) {
rcClip.w = rcClip.h = 0;
}
}
void level_map::draw(render_target* pCanvas, int iScreenX, int iScreenY,
int iWidth, int iHeight, int iCanvasX,
int iCanvasY) const {
/*
The map is drawn in two passes, with each pass done one scanline at a
time (a scanline is a list of tiles with the same screen Y co-ordinate).
The first pass does floor tiles, as the entire floor needs to be painted
below anything else (for example, see the walking north through a door
animation, which needs to paint over the floor of the scanline below the
animation). On the second pass, walls and entities are drawn, with the
order controlled such that entites appear in the right order relative to
the walls around them. For each scanline, the following is done:
1st pass:
1) For each tile, left to right, the floor tile (layer 0)
2nd pass:
1) For each tile, right to left, the north wall, then the early entities
2) For each tile, left to right, the west wall, then the late entities
*/
if (blocks == nullptr || cells == nullptr) {
return;
}
clip_rect rcClip;
rcClip.x = static_cast<clip_rect::x_y_type>(iCanvasX);
rcClip.y = static_cast<clip_rect::x_y_type>(iCanvasY);
rcClip.w = static_cast<clip_rect::w_h_type>(iWidth);
rcClip.h = static_cast<clip_rect::w_h_type>(iHeight);
pCanvas->set_clip_rect(&rcClip);
// 1st pass
pCanvas->start_nonoverlapping_draws();
for (map_tile_iterator itrNode1(this, iScreenX, iScreenY, iWidth, iHeight);
itrNode1; ++itrNode1) {
unsigned int iH = 32;
unsigned int iBlock = itrNode1->iBlock[0];
blocks->get_sprite_size(iBlock & 0xFF, nullptr, &iH);
blocks->draw_sprite(
pCanvas, iBlock & 0xFF,
itrNode1.tile_x_position_on_screen() + iCanvasX - 32,
itrNode1.tile_y_position_on_screen() + iCanvasY - iH + 32, iBlock >> 8);
}
pCanvas->finish_nonoverlapping_draws();
bool bFirst = true;
map_scanline_iterator formerIterator;
// 2nd pass
for (map_tile_iterator itrNode2(this, iScreenX, iScreenY, iWidth, iHeight);
itrNode2; ++itrNode2) {
if (itrNode2->flags.shadow_full) {
blocks->draw_sprite(
pCanvas, 74, itrNode2.tile_x_position_on_screen() + iCanvasX - 32,
itrNode2.tile_y_position_on_screen() + iCanvasY, thdf_alpha_75);
} else if (itrNode2->flags.shadow_half) {
blocks->draw_sprite(
pCanvas, 75, itrNode2.tile_x_position_on_screen() + iCanvasX - 32,
itrNode2.tile_y_position_on_screen() + iCanvasY, thdf_alpha_75);
}
if (!itrNode2.is_last_on_scanline()) {
continue;
}
for (map_scanline_iterator itrNode(
itrNode2, map_scanline_iterator_direction::backward, iCanvasX,
iCanvasY);
itrNode; ++itrNode) {
unsigned int iH;
unsigned int iBlock = itrNode->iBlock[1];
if (iBlock != 0 && blocks->get_sprite_size(iBlock & 0xFF, nullptr, &iH) &&
iH > 0) {
blocks->draw_sprite(pCanvas, iBlock & 0xFF, itrNode.x() - 32,
itrNode.y() - iH + 32, iBlock >> 8);
if (itrNode->flags.shadow_wall) {
clip_rect rcOldClip, rcNewClip;
pCanvas->get_clip_rect(&rcOldClip);
rcNewClip.x = static_cast<clip_rect::x_y_type>(itrNode.x() - 32);
rcNewClip.y =
static_cast<clip_rect::x_y_type>(itrNode.y() - iH + 32 + 4);
rcNewClip.w = static_cast<clip_rect::w_h_type>(64);
rcNewClip.h = static_cast<clip_rect::w_h_type>(86 - 4);
clip_rect_intersection(rcNewClip, rcOldClip);
pCanvas->set_clip_rect(&rcNewClip);
blocks->draw_sprite(pCanvas, 156, itrNode.x() - 32, itrNode.y() - 56,
thdf_alpha_75);
pCanvas->set_clip_rect(&rcOldClip);
}
}
drawable* pItem = (drawable*)(itrNode->oEarlyEntities.next);
while (pItem) {
pItem->draw_fn(pItem, pCanvas, itrNode.x(), itrNode.y());
pItem = (drawable*)(pItem->next);
}
}
map_scanline_iterator itrNode(
itrNode2, map_scanline_iterator_direction::forward, iCanvasX, iCanvasY);
if (!bFirst) {
// since the scanline count from one THMapScanlineIterator to
// another can differ synchronization between the current iterator
// and the former one is neeeded
if (itrNode.x() < -64) {
++itrNode;
}
while (formerIterator.x() < itrNode.x()) {
++formerIterator;
}
}
bool bPreviousTileNeedsRedraw = false;
for (; itrNode; ++itrNode) {
bool bNeedsRedraw = false;
unsigned int iH;
unsigned int iBlock = itrNode->iBlock[2];
if (iBlock != 0 && blocks->get_sprite_size(iBlock & 0xFF, nullptr, &iH) &&
iH > 0) {
blocks->draw_sprite(pCanvas, iBlock & 0xFF, itrNode.x() - 32,
itrNode.y() - iH + 32, iBlock >> 8);
}
iBlock = itrNode->iBlock[3];
if (iBlock != 0 && blocks->get_sprite_size(iBlock & 0xFF, nullptr, &iH) &&
iH > 0) {
blocks->draw_sprite(pCanvas, iBlock & 0xFF, itrNode.x() - 32,
itrNode.y() - iH + 32, iBlock >> 8);
}
iBlock = itrNode->iBlock[1];
if (iBlock != 0 && blocks->get_sprite_size(iBlock & 0xFF, nullptr, &iH) &&
iH > 0) {
bNeedsRedraw = true;
}
if (itrNode->oEarlyEntities.next) {
bNeedsRedraw = true;
}
bool bRedrawAnimations = false;
drawable* pItem = (drawable*)(itrNode->next);
while (pItem) {
pItem->draw_fn(pItem, pCanvas, itrNode.x(), itrNode.y());
if (pItem->is_multiple_frame_animation_fn(pItem)) {
bRedrawAnimations = true;
}
if (pItem->get_drawing_layer() == 1) {
bNeedsRedraw = true;
}
pItem = (drawable*)(pItem->next);
}
// if the current tile contained a multiple frame animation (e.g. a
// doctor walking) check to see if in the tile to its left and above
// it there are items that need to be redrawn (i.e. in the tile to
// its left side objects to the south of the tile and in the tile
// above it side objects to the east of the tile).
if (bRedrawAnimations && !bFirst) {
bool bTileNeedsRedraw = bPreviousTileNeedsRedraw;
// check if an object in the adjacent tile to the left of the
// current tile needs to be redrawn and if necessary draw it
pItem = (drawable*)(formerIterator.get_previous_tile()->next);
while (pItem) {
if (pItem->get_drawing_layer() == 9) {
pItem->draw_fn(pItem, pCanvas, formerIterator.x() - 64,
formerIterator.y());
bTileNeedsRedraw = true;
}
pItem = (drawable*)(pItem->next);
}
// check if an object in the adjacent tile above the current
// tile needs to be redrawn and if necessary draw it
pItem = formerIterator ? (drawable*)(formerIterator->next) : nullptr;
while (pItem) {
if (pItem->get_drawing_layer() == 8) {
pItem->draw_fn(pItem, pCanvas, formerIterator.x(),
formerIterator.y());
}
pItem = (drawable*)(pItem->next);
}
// if an object was redrawn in the tile to the left of the
// current tile or if the tile below it had an object in the
// north side or a wall to the north redraw that tile
if (bTileNeedsRedraw) {
// redraw the north wall
unsigned int iBlock = itrNode.get_previous_tile()->iBlock[1];
if (iBlock != 0 &&
blocks->get_sprite_size(iBlock & 0xFF, nullptr, &iH) && iH > 0) {
blocks->draw_sprite(pCanvas, iBlock & 0xFF, itrNode.x() - 96,
itrNode.y() - iH + 32, iBlock >> 8);
if (itrNode.get_previous_tile()->flags.shadow_wall) {
clip_rect rcOldClip, rcNewClip;
pCanvas->get_clip_rect(&rcOldClip);
rcNewClip.x = static_cast<clip_rect::x_y_type>(itrNode.x() - 96);
rcNewClip.y =
static_cast<clip_rect::x_y_type>(itrNode.y() - iH + 32 + 4);
rcNewClip.w = static_cast<clip_rect::w_h_type>(64);
rcNewClip.h = static_cast<clip_rect::w_h_type>(86 - 4);
clip_rect_intersection(rcNewClip, rcOldClip);
pCanvas->set_clip_rect(&rcNewClip);
blocks->draw_sprite(pCanvas, 156, itrNode.x() - 96,
itrNode.y() - 56, thdf_alpha_75);
pCanvas->set_clip_rect(&rcOldClip);
}
}
pItem = (drawable*)(itrNode.get_previous_tile()->oEarlyEntities.next);
while (pItem) {
pItem->draw_fn(pItem, pCanvas, itrNode.x() - 64, itrNode.y());
pItem = (drawable*)(pItem->next);
}
pItem = (drawable*)(itrNode.get_previous_tile())->next;
for (; pItem; pItem = (drawable*)(pItem->next)) {
pItem->draw_fn(pItem, pCanvas, itrNode.x() - 64, itrNode.y());
}
}
}
bPreviousTileNeedsRedraw = bNeedsRedraw;
if (!bFirst) {
++formerIterator;
}
}
formerIterator = itrNode;
bFirst = false;
}
if (overlay) {
for (map_tile_iterator itrNode(this, iScreenX, iScreenY, iWidth, iHeight);
itrNode; ++itrNode) {
overlay->draw_cell(pCanvas,
itrNode.tile_x_position_on_screen() + iCanvasX - 32,
itrNode.tile_y_position_on_screen() + iCanvasY, this,
itrNode.tile_x(), itrNode.tile_y());
}
}
pCanvas->set_clip_rect(nullptr);
}
drawable* level_map::hit_test(int iTestX, int iTestY) const {
// This function needs to hitTest each drawable object, in the reverse
// order to that in which they would be drawn.
if (blocks == nullptr || cells == nullptr) {
return nullptr;
}
for (map_tile_iterator itrNode2(this, iTestX, iTestY, 1, 1,
map_scanline_iterator_direction::backward);
itrNode2; ++itrNode2) {
if (!itrNode2.is_last_on_scanline()) {
continue;
}
for (map_scanline_iterator itrNode(
itrNode2, map_scanline_iterator_direction::backward);
itrNode; ++itrNode) {
if (itrNode->next != nullptr) {
drawable* pResult =
hit_test_drawables(itrNode->next, itrNode.x(), itrNode.y(), 0, 0);
if (pResult) {
return pResult;
}
}
}
for (map_scanline_iterator itrNode(
itrNode2, map_scanline_iterator_direction::forward);
itrNode; ++itrNode) {
if (itrNode->oEarlyEntities.next != nullptr) {
drawable* pResult = hit_test_drawables(itrNode->oEarlyEntities.next,
itrNode.x(), itrNode.y(), 0, 0);
if (pResult) {
return pResult;
}
}
}
}
return nullptr;
}
drawable* level_map::hit_test_drawables(link_list* pListStart, int iXs, int iYs,
int iTestX, int iTestY) const {
link_list* pListEnd = pListStart;
while (pListEnd->next) {
pListEnd = pListEnd->next;
}
drawable* pList = (drawable*)pListEnd;
while (true) {
if (pList->hit_test_fn(pList, iXs, iYs, iTestX, iTestY)) return pList;
if (pList == pListStart) {
return nullptr;
} else {
pList = (drawable*)pList->prev;
}
}
}
int level_map::get_tile_owner(const map_tile* pNode) const {
return plot_owner[pNode->iParcelId];
}
int level_map::get_parcel_owner(int iParcel) const {
if (0 <= iParcel && iParcel < parcel_count) {
return plot_owner[iParcel];
} else {
return 0;
}
}
uint16_t level_map::get_tile_temperature(const map_tile* pNode) const {
return pNode->aiTemperature[current_temperature_index];
}
void level_map::set_temperature_display(temperature_theme eTempDisplay) {
current_temperature_theme = eTempDisplay;
}
uint32_t level_map::thermal_neighbour(uint32_t& iNeighbourSum, bool canTravel,
std::ptrdiff_t relative_idx,
map_tile* pNode, int prevTemp) const {
int iNeighbourCount = 0;
map_tile* pNeighbour = pNode + relative_idx;
// Ensure the neighbour is within the map bounds
map_tile* pLimitNode = cells + width * height;
if (pNeighbour < cells || pNeighbour >= pLimitNode) {
return 0;
}
if (canTravel) {
iNeighbourCount += 4;
iNeighbourSum += pNeighbour->aiTemperature[prevTemp] * 4;
} else {
bool bObjectPresent = false;
int iHospital1 = pNeighbour->flags.hospital;
int iHospital2 = pNode->flags.hospital;
if (iHospital1 == iHospital2) {
if (pNeighbour->flags.room == pNode->flags.room) {
bObjectPresent = true;
}
}
if (bObjectPresent) {
iNeighbourCount += 4;
iNeighbourSum += pNeighbour->aiTemperature[prevTemp] * 4;
} else {
iNeighbourCount += 1;
iNeighbourSum += pNeighbour->aiTemperature[prevTemp];
}
}
return iNeighbourCount;
}
namespace {
void merge_temperatures(map_tile& node, size_t new_temp_idx, int other_temp,
double ratio) {
const uint32_t node_temp = node.aiTemperature[new_temp_idx];
node.aiTemperature[new_temp_idx] =
static_cast<uint16_t>(((node_temp * (ratio - 1)) + other_temp) / ratio);
}
} // namespace
void level_map::update_temperatures(uint16_t iAirTemperature,
uint16_t iRadiatorTemperature) {
if (iRadiatorTemperature < iAirTemperature) {
iRadiatorTemperature = iAirTemperature;
}
const int iPrevTemp = current_temperature_index;
current_temperature_index ^= 1;
const int iNewTemp = current_temperature_index;
map_tile* pLimitNode = cells + width * height;
for (map_tile* pNode = cells; pNode != pLimitNode; ++pNode) {
// Get average temperature of neighbour cells
uint32_t iNeighbourSum = 0;
uint32_t iNeighbourCount = 0;
iNeighbourCount += thermal_neighbour(
iNeighbourSum, pNode->flags.can_travel_n, -width, pNode, iPrevTemp);
iNeighbourCount += thermal_neighbour(
iNeighbourSum, pNode->flags.can_travel_s, width, pNode, iPrevTemp);
iNeighbourCount += thermal_neighbour(
iNeighbourSum, pNode->flags.can_travel_e, 1, pNode, iPrevTemp);
iNeighbourCount += thermal_neighbour(
iNeighbourSum, pNode->flags.can_travel_w, -1, pNode, iPrevTemp);
uint32_t iRadiatorNumber = 0;
// Merge 1% against air temperature
// or 50% against radiator temperature
// or generally dissipate 0.1% of temperature.
uint32_t iMergeTemp = 0;
double iMergeRatio = 100;
if (pNode->flags.hospital) {
for (auto thob : pNode->objects) {
if (thob == object_type::radiator) {
iRadiatorNumber++;
}
}
if (iRadiatorNumber > 0) {
iMergeTemp = iRadiatorTemperature;
iMergeRatio = 2 - (iRadiatorNumber - 1) * 0.5;
} else {
iMergeRatio = 1000;
}
} else {
iMergeTemp = iAirTemperature;
}
// Diffuse 25% with neighbours
pNode->aiTemperature[iNewTemp] = pNode->aiTemperature[iPrevTemp];
if (iNeighbourCount != 0) {
merge_temperatures(
*pNode, iNewTemp, iNeighbourSum / iNeighbourCount,
4 - (iRadiatorNumber > 0 ? (iRadiatorNumber - 1) * 1.5 : 0));
}
merge_temperatures(*pNode, iNewTemp, iMergeTemp, iMergeRatio);
}
}
void level_map::update_pathfinding() {
map_tile* pNode = cells;
for (int iY = 0; iY < 128; ++iY) {
for (int iX = 0; iX < 128; ++iX, ++pNode) {
pNode->flags.can_travel_n = true;
pNode->flags.can_travel_e = true;
pNode->flags.can_travel_s = true;
pNode->flags.can_travel_w = true;
if (iX == 0) {
pNode->flags.can_travel_w = false;
} else if (iX == 127) {
pNode->flags.can_travel_e = false;
}
if (iY == 0) {
pNode->flags.can_travel_n = false;
} else if (iY == 127) {
pNode->flags.can_travel_s = false;
}
if (pNode->iBlock[1] & 0xFF) {
pNode->flags.can_travel_n = false;
if (iY != 0) {
pNode[-128].flags.can_travel_s = false;
}
}
if (pNode->iBlock[2] & 0xFF) {
pNode->flags.can_travel_w = false;
if (iX != 0) {
pNode[-1].flags.can_travel_e = false;
}
}
}
}
}
namespace {
//! For shadow casting, a tile is considered to have a wall on a direction
//! if it has a door in that direction, or the block is from the hardcoded
//! range of wall-like blocks.
bool is_wall(map_tile* tile, size_t block, bool flag) {
return flag || (82 <= (tile->iBlock[block] & 0xFF) &&
(tile->iBlock[block] & 0xFF) <= 164);
}
} // namespace
void level_map::update_shadows() {
map_tile* pNode = cells;
for (int iY = 0; iY < 128; ++iY) {
for (int iX = 0; iX < 128; ++iX, ++pNode) {
pNode->flags.shadow_full = false;
pNode->flags.shadow_half = false;
pNode->flags.shadow_wall = false;
if (is_wall(pNode, 2, pNode->flags.tall_west)) {
pNode->flags.shadow_half = true;
if (is_wall(pNode, 1, pNode->flags.tall_north)) {
pNode->flags.shadow_wall = true;
} else if (iY != 0) {
map_tile* pNeighbour = pNode - 128;
pNeighbour->flags.shadow_full = true;
if (iX != 0 && !is_wall(pNeighbour, 2, pNode->flags.tall_west)) {
// Wrap the shadow around a corner (no need to continue
// all the way along the wall, as the shadow would be
// occluded by the wall. If Debug->Transparent Walls is
// toggled on, then this optimisation becomes very
// visible, but it's a debug option, so it doesn't
// matter).
pNeighbour[-1].flags.shadow_full = true;
}
}
}
}
}
}
void level_map::persist(lua_persist_writer* pWriter) const {
lua_State* L = pWriter->get_stack();
integer_run_length_encoder oEncoder;
uint32_t iVersion = 4;
pWriter->write_uint(iVersion);
pWriter->write_uint(player_count);
for (int i = 0; i < player_count; ++i) {
pWriter->write_uint(initial_camera_x[i]);
pWriter->write_uint(initial_camera_y[i]);
pWriter->write_uint(heliport_x[i]);
pWriter->write_uint(heliport_y[i]);
}
pWriter->write_uint(parcel_count);
for (int i = 0; i < parcel_count; ++i) {
pWriter->write_uint(plot_owner[i]);
}
for (int i = 0; i < parcel_count; ++i) {
pWriter->write_uint(parcel_tile_counts[i]);
}
pWriter->write_uint(width);
pWriter->write_uint(height);
pWriter->write_uint(current_temperature_index);
oEncoder.initialise(6);
for (map_tile *pNode = cells, *pLimitNode = cells + width * height;
pNode != pLimitNode; ++pNode) {
oEncoder.write(pNode->iBlock[0]);
oEncoder.write(pNode->iBlock[1]);
oEncoder.write(pNode->iBlock[2]);
oEncoder.write(pNode->iBlock[3]);
oEncoder.write(pNode->iParcelId);
oEncoder.write(pNode->iRoomId);
// Flags include THOB values, and other things which do not work
// well with run-length encoding.
pWriter->write_uint(static_cast<uint32_t>(pNode->flags));
pWriter->write_uint(pNode->aiTemperature[0]);
pWriter->write_uint(pNode->aiTemperature[1]);
lua_rawgeti(L, luaT_upvalueindex(1), 2);
lua_pushlightuserdata(L, pNode->next);
lua_rawget(L, -2);
pWriter->write_stack_object(-1);
lua_pop(L, 1);
lua_pushlightuserdata(L, pNode->oEarlyEntities.next);
lua_rawget(L, -2);
pWriter->write_stack_object(-1);
lua_pop(L, 2);
}
oEncoder.finish();
oEncoder.pump_output(pWriter);
oEncoder.initialise(5);
for (map_tile *pNode = original_cells,
*pLimitNode = original_cells + width * height;
pNode != pLimitNode; ++pNode) {
oEncoder.write(pNode->iBlock[0]);
oEncoder.write(pNode->iBlock[1]);
oEncoder.write(pNode->iBlock[2]);
oEncoder.write(pNode->iParcelId);
oEncoder.write(static_cast<uint32_t>(pNode->flags));
}
oEncoder.finish();
oEncoder.pump_output(pWriter);
}
void level_map::depersist(lua_persist_reader* pReader) {
new (this) level_map; // Call constructor
lua_State* L = pReader->get_stack();
int iWidth, iHeight;
integer_run_length_decoder oDecoder;
uint32_t iVersion;
if (!pReader->read_uint(iVersion)) return;
if (iVersion != 4) {
if (iVersion < 2 || iVersion == 128) {
luaL_error(L,
"TODO: Write code to load map data from earlier "
"savegame versions (if really necessary).");
} else if (iVersion > 4) {
luaL_error(L, "Cannot load savegame from a newer version.");
}
}
if (!pReader->read_uint(player_count)) return;
for (int i = 0; i < player_count; ++i) {
if (!pReader->read_uint(initial_camera_x[i])) return;
if (!pReader->read_uint(initial_camera_y[i])) return;
if (!pReader->read_uint(heliport_x[i])) return;
if (!pReader->read_uint(heliport_y[i])) return;
}
if (!pReader->read_uint(parcel_count)) {
return;
}
delete[] plot_owner;
plot_owner = new int[parcel_count];
for (int i = 0; i < parcel_count; ++i) {
if (!pReader->read_uint(plot_owner[i])) {
return;
}
}
delete[] parcel_tile_counts;
parcel_tile_counts = new int[parcel_count];
parcel_tile_counts[0] = 0;
if (iVersion >= 3) {
for (int i = 0; i < parcel_count; ++i) {
if (!pReader->read_uint(parcel_tile_counts[i])) {
return;
}
}
}
if (!pReader->read_uint(iWidth) || !pReader->read_uint(iHeight)) {
return;
}
if (!set_size(iWidth, iHeight)) {
pReader->set_error("Unable to set size while depersisting map");
return;
}
if (iVersion >= 4) {
if (!pReader->read_uint(current_temperature_index)) {
return;
}
}
for (map_tile *pNode = cells, *pLimitNode = cells + width * height;
pNode != pLimitNode; ++pNode) {
uint32_t f;
if (!pReader->read_uint(f)) return;
pNode->flags = f;
if (iVersion >= 4) {
if (!pReader->read_uint(pNode->aiTemperature[0]) ||
!pReader->read_uint(pNode->aiTemperature[1])) {
return;
}
}
if (!pReader->read_stack_object()) {
return;
}
pNode->next = (link_list*)lua_touserdata(L, -1);
if (pNode->next) {
if (pNode->next->prev != nullptr) {
std::fprintf(stderr, "Warning: THMap linked-lists are corrupted.\n");
}
pNode->next->prev = pNode;
}
lua_pop(L, 1);
if (!pReader->read_stack_object()) return;
pNode->oEarlyEntities.next = (link_list*)lua_touserdata(L, -1);
if (pNode->oEarlyEntities.next) {
if (pNode->oEarlyEntities.next->prev != nullptr) {
std::fprintf(stderr, "Warning: THMap linked-lists are corrupted.\n");
}
pNode->oEarlyEntities.next->prev = &pNode->oEarlyEntities;
}
lua_pop(L, 1);
}
oDecoder.initialise(6, pReader);
for (map_tile *pNode = cells, *pLimitNode = cells + width * height;
pNode != pLimitNode; ++pNode) {
pNode->iBlock[0] = static_cast<uint16_t>(oDecoder.read());
pNode->iBlock[1] = static_cast<uint16_t>(oDecoder.read());
pNode->iBlock[2] = static_cast<uint16_t>(oDecoder.read());
pNode->iBlock[3] = static_cast<uint16_t>(oDecoder.read());
pNode->iParcelId = static_cast<uint16_t>(oDecoder.read());
pNode->iRoomId = static_cast<uint16_t>(oDecoder.read());
}
oDecoder.initialise(5, pReader);
for (map_tile *pNode = original_cells,
*pLimitNode = original_cells + width * height;
pNode != pLimitNode; ++pNode) {
pNode->iBlock[0] = static_cast<uint16_t>(oDecoder.read());
pNode->iBlock[1] = static_cast<uint16_t>(oDecoder.read());
pNode->iBlock[2] = static_cast<uint16_t>(oDecoder.read());
pNode->iParcelId = static_cast<uint16_t>(oDecoder.read());
pNode->flags = oDecoder.read();
}
if (iVersion < 3) {
for (int i = 1; i < parcel_count; ++i) {
parcel_tile_counts[i] = get_parcel_tile_count(i);
}
}
}
map_tile_iterator::map_tile_iterator()
: tile(nullptr),
container(nullptr),
screen_offset_x(0),
screen_offset_y(0),
screen_width(0),
screen_height(0) {}
map_tile_iterator::map_tile_iterator(
const level_map* pMap, int iScreenX, int iScreenY, int iWidth, int iHeight,
map_scanline_iterator_direction eScanlineDirection)
: container(pMap),
screen_offset_x(iScreenX),
screen_offset_y(iScreenY),
screen_width(iWidth),
screen_height(iHeight),
scanline_count(0),
direction(eScanlineDirection) {
if (direction == map_scanline_iterator_direction::forward) {
base_x = 0;
base_y = (iScreenY - 32) / 16;
if (base_y < 0) {
base_y = 0;
} else if (base_y >= container->get_height()) {
base_x = base_y - container->get_height() + 1;
base_y = container->get_height() - 1;
if (base_x >= container->get_width()) base_x = container->get_width() - 1;
}
} else {
base_x = container->get_width() - 1;
base_y = container->get_height() - 1;
}
world_x = base_x;
world_y = base_y;
advance_until_visible();
}
map_tile_iterator& map_tile_iterator::operator++() {
--world_y;
++world_x;
advance_until_visible();
return *this;
}
void map_tile_iterator::advance_until_visible() {
tile = nullptr;
while (true) {
x_relative_to_screen = world_x;
y_relative_to_screen = world_y;
container->world_to_screen(x_relative_to_screen, y_relative_to_screen);
x_relative_to_screen -= screen_offset_x;
y_relative_to_screen -= screen_offset_y;
if (direction == map_scanline_iterator_direction::forward
? y_relative_to_screen >= screen_height + margin_bottom
: y_relative_to_screen < -margin_top) {
return;
}
if (direction == map_scanline_iterator_direction::forward
? (y_relative_to_screen > -margin_top)
: (y_relative_to_screen < screen_height + margin_bottom)) {
while (world_y >= 0 && world_x < container->get_width()) {
if (x_relative_to_screen < -margin_left) {
// Nothing to do
} else if (x_relative_to_screen < screen_width + margin_right) {
++scanline_count;
tile = container->get_tile_unchecked(world_x, world_y);
return;
} else {
break;
}
--world_y;
++world_x;
x_relative_to_screen += 64;
}
}
scanline_count = 0;
if (direction == map_scanline_iterator_direction::forward) {
if (base_y == container->get_height() - 1) {
if (++base_x == container->get_width()) {
break;
}
} else {
++base_y;
}
} else {
if (base_x == 0) {
if (base_y == 0) {
break;
} else {
--base_y;
}
} else {
--base_x;
}
}
world_x = base_x;
world_y = base_y;
}
}
bool map_tile_iterator::is_last_on_scanline() const {
return world_y <= 0 || world_x + 1 >= container->get_width() ||
x_relative_to_screen + 64 >= screen_width + margin_right;
}
map_scanline_iterator::map_scanline_iterator()
: tile_step(0), x_step(0), steps_taken(0) {}
map_scanline_iterator::map_scanline_iterator(
const map_tile_iterator& itrNodes,
map_scanline_iterator_direction eDirection, int iXOffset, int iYOffset)
: tile_step((static_cast<int>(eDirection) - 1) *
(1 - itrNodes.container->get_width())),
x_step((static_cast<int>(eDirection) - 1) * 64),
steps_taken(0) {
if (eDirection == map_scanline_iterator_direction::backward) {
tile = itrNodes.tile;
x_relative_to_screen = itrNodes.tile_x_position_on_screen();
} else {
tile = itrNodes.tile - tile_step * (itrNodes.scanline_count - 1);
x_relative_to_screen = itrNodes.tile_x_position_on_screen() -
x_step * (itrNodes.scanline_count - 1);
}
x_relative_to_screen += iXOffset;
y_relative_to_screen = itrNodes.tile_y_position_on_screen() + iYOffset;
end_tile = tile + tile_step * itrNodes.scanline_count;
first_tile = tile;
}
map_scanline_iterator& map_scanline_iterator::operator++() {
tile += tile_step;
x_relative_to_screen += x_step;
steps_taken++;
return *this;
}
// copies the members of the given THMapScanlineIterator and resets the tile
// member to the first element.
map_scanline_iterator map_scanline_iterator::operator=(
const map_scanline_iterator& iterator) {
tile = iterator.first_tile;
end_tile = iterator.end_tile;
x_relative_to_screen =
iterator.x_relative_to_screen - iterator.steps_taken * iterator.x_step;
y_relative_to_screen = iterator.y_relative_to_screen;
x_step = iterator.x_step;
tile_step = iterator.tile_step;
return *this;
}