/** * In the given voxel, can a path be build in the voxel from the bottom at the given edge? * @param voxel_pos Coordinate of the voxel. * @param edge Entry edge. * @return Bit-set of track slopes, indicating the directions of building paths. */ static uint8 CanBuildPathFromEdge(const XYZPoint16 &voxel_pos, TileEdge edge) { if (!IsVoxelstackInsideWorld(voxel_pos.x, voxel_pos.y)) return 0; if (voxel_pos.z < 0 || voxel_pos.z >= WORLD_Z_SIZE - 1) return 0; /* If other side of the edge is not on-world or not owned, don't compute path options. */ Point16 dxy = _tile_dxy[edge]; if (!IsVoxelstackInsideWorld(voxel_pos.x + dxy.x, voxel_pos.y + dxy.y) || (_game_mode_mgr.InPlayMode() && _world.GetTileOwner(voxel_pos.x + dxy.x, voxel_pos.y + dxy.y) != OWN_PARK)) return 0; const Voxel *v = _world.GetVoxel(voxel_pos); if (v != nullptr && HasValidPath(v)) { PathSprites ps = GetImplodedPathSlope(v); if (ps < PATH_FLAT_COUNT) return 1 << TSL_FLAT; if (ps == _path_up_from_edge[edge]) return 1 << TSL_UP; } if (voxel_pos.z > 0) { v = _world.GetVoxel(voxel_pos + XYZPoint16(0, 0, -1)); if (v != nullptr && HasValidPath(v) && GetImplodedPathSlope(v) == _path_down_from_edge[edge]) return 1 << TSL_DOWN; } uint8 slopes = 0; // XXX Check for already existing paths. if (BuildDownwardPath(voxel_pos, edge, PAT_INVALID, true)) slopes |= 1 << TSL_DOWN; if (BuildFlatPath(voxel_pos, PAT_INVALID, true)) slopes |= 1 << TSL_FLAT; if (BuildUpwardPath(voxel_pos, edge, PAT_INVALID, true)) slopes |= 1 << TSL_UP; return slopes; }
/** * Walk over a queue path from the given entry edge at the given position. * If it leads to a new voxel edge, the provided position and edge is update with the exit point. * @param voxel_pos [inout] Start voxel position before the queue path, updated to last voxel position. * @param entry Direction used for entry to the path, updated to last edge exit direction. * @return Whether a (possibly) new last voxel could be found, \c false means the path leads to nowhere. * @note Parameter values may get changed during the call, do not rely on their values except when \c true is returned. */ bool TravelQueuePath(XYZPoint16 *voxel_pos, TileEdge *entry) { XYZPoint16 new_pos = *voxel_pos; TileEdge edge = *entry; /* Check that entry voxel actually exists. */ if (!IsVoxelstackInsideWorld(new_pos.x, new_pos.y)) return false; for (;;) { new_pos.x += _tile_dxy[edge].x; new_pos.y += _tile_dxy[edge].y; if (!IsVoxelstackInsideWorld(new_pos.x, new_pos.y)) return false; const Voxel *vx = _world.GetVoxel(new_pos); if (vx == nullptr || !HasValidPath(vx)) { /* No path here, check the voxel below. */ if (new_pos.z == 0) return true; // Path ends here. new_pos.z--; vx = _world.GetVoxel(new_pos); if (vx == nullptr || !HasValidPath(vx)) return true; // Path ends here. } if (new_pos == *voxel_pos) return false; // Cycle detected. /* Stop if we found a non-queue path. */ if (_sprite_manager.GetPathStatus(GetPathType(vx->GetInstanceData())) != PAS_QUEUE_PATH) return true; /* At this point: * voxel_pos, edge (and *entry) contain the last valid voxel edge. * new_pos, vx is the next queue path tile position. */ uint8 exits = GetPathExits(vx); /* Check that the new tile can go back to our last tile. */ uint8 rev_edge = (edge + 2) % 4; if (!((exits & (0x01 << rev_edge)) != 0 && new_pos.z == voxel_pos->z) && !((exits & (0x10 << rev_edge)) != 0 && new_pos.z == voxel_pos->z - 1)) { return false; } /* Find exit to the next path tile. */ for (edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if (edge == rev_edge) continue; // Skip the direction we came from. if ((exits & (0x01 << edge)) != 0) break; if ((exits & (0x10 << edge)) != 0) { new_pos.z++; break; } } if (edge == EDGE_COUNT) return false; // Queue path doesn't have a second exit. *voxel_pos = new_pos; *entry = edge; } }
/** * In the given voxel, can a flat path be build in the voxel? * @param voxel_pos Coordinate of the voxel. * @param path_type For building (ie not \a test_only), the type of path to build. * @param test_only Only test whether it could be created. * @return Whether the path is or could be built. */ static bool BuildFlatPath(const XYZPoint16 &voxel_pos, PathType path_type, bool test_only) { /* xy position should be valid, and allow path building. */ if (!IsVoxelstackInsideWorld(voxel_pos.x, voxel_pos.y)) return false; if (_game_mode_mgr.InPlayMode() && _world.GetTileOwner(voxel_pos.x, voxel_pos.y) != OWN_PARK) return false; if (voxel_pos.z < 0 || voxel_pos.z > WORLD_Z_SIZE - 2) return false; // Z range should be valid. const VoxelStack *vs = _world.GetStack(voxel_pos.x, voxel_pos.y); const Voxel *v = vs->Get(voxel_pos.z + 1); // Voxel above the path should be empty. if (v != nullptr && !v->IsEmpty()) return false; v = vs->Get(voxel_pos.z); if (v != nullptr) { if (v->GetInstance() != SRI_FREE) return false; // Voxel should have no other rides. if (v->GetGroundType() != GTP_INVALID) { if (v->GetGroundSlope() >= PATH_FLAT_COUNT) return false; // Non-flat surface. } else { /* No surface, but a foundation suggests a nearby hill. * Currently simply deny building here, in the future, consider making a tunnel. */ if (v->GetFoundationType() != FDT_INVALID) return false; } } if (!test_only) BuildPathAtTile(voxel_pos, path_type, PATH_EMPTY); return true; }
/** * In the given voxel, can an upward path be build in the voxel from the bottom at the given edge? * @param voxel_pos Coordinate of the voxel. * @param edge Entry edge. * @param path_type For building (ie not \a test_only), the type of path to build. * @param test_only Only test whether it could be created. * @return Whether the path is or could be built. */ static bool BuildUpwardPath(const XYZPoint16 &voxel_pos, TileEdge edge, PathType path_type, bool test_only) { /* xy position should be valid, and allow path building. */ if (!IsVoxelstackInsideWorld(voxel_pos.x, voxel_pos.y)) return false; if (_game_mode_mgr.InPlayMode() && _world.GetTileOwner(voxel_pos.x, voxel_pos.y) != OWN_PARK) return false; if (voxel_pos.z < 0 || voxel_pos.z > WORLD_Z_SIZE - 3) return false; // Z range should be valid. const VoxelStack *vs = _world.GetStack(voxel_pos.x, voxel_pos.y); const Voxel *v = vs->Get(voxel_pos.z + 1); // Voxel above the path should be empty. if (v != nullptr && !v->IsEmpty()) return false; v = vs->Get(voxel_pos.z + 2); // 2 voxels higher should also be empty. if (v != nullptr && !v->IsEmpty()) return false; v = vs->Get(voxel_pos.z); if (v != nullptr) { if (v->GetInstance() != SRI_FREE) return false; // Voxel should have no other rides. if (v->GetGroundType() != GTP_INVALID) { TileSlope slope = ExpandTileSlope(v->GetGroundSlope()); if ((slope & (TSB_STEEP | TSB_TOP)) == TSB_STEEP) return false; if ((slope & _corners_at_edge[edge]) != 0) return false; // A raised corner at 'edge'. } else { /* No surface, but a foundation suggests a nearby hill. * Currently simply deny building here, in the future, consider making a tunnel. */ if (v->GetFoundationType() != FDT_INVALID) return false; } } if (!test_only) BuildPathAtTile(voxel_pos, path_type, _path_up_from_edge[edge]); return true; }
/** * Compute the attach points of a path in a voxel. * @param voxel_pos Coordinate of the voxel. * @return Attach points for paths starting from the given voxel coordinates. * Upper 4 bits are the edges at the top of the voxel, lower 4 bits are the attach points for the bottom of the voxel. */ static uint8 GetPathAttachPoints(const XYZPoint16 &voxel_pos) { if (!IsVoxelstackInsideWorld(voxel_pos.x, voxel_pos.y)) return 0; if (voxel_pos.z >= WORLD_Z_SIZE - 1) return 0; // The voxel containing the flat path, and one above it. const Voxel *v = _world.GetVoxel(voxel_pos); if (v == nullptr) return 0; uint8 edges = 0; for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { uint16 x = voxel_pos.x + _tile_dxy[edge].x; uint16 y = voxel_pos.y + _tile_dxy[edge].y; if (!IsVoxelstackInsideWorld(x, y)) continue; const XYZPoint16 new_pos(x, y, voxel_pos.z); if (HasValidPath(v)) { PathSprites ps = GetImplodedPathSlope(v); if (ps < PATH_FLAT_COUNT) { if (CanBuildPathFromEdge(new_pos, (TileEdge)((edge + 2) % 4)) != 0) edges |= 1 << edge; } else { const XYZPoint16 new_above(x, y, voxel_pos.z + 1); if (_path_up_from_edge[edge] == ps && CanBuildPathFromEdge(new_pos, (TileEdge)((edge + 2) % 4)) != 0) edges |= 1 << edge; if (_path_down_from_edge[edge] == ps && CanBuildPathFromEdge(new_above, (TileEdge)((edge + 2) % 4)) != 0) edges |= (1 << edge) << 4; } continue; } if (v->GetGroundType() != GTP_INVALID) { TileSlope ts = ExpandTileSlope(v->GetGroundSlope()); if ((ts & TSB_STEEP) != 0) continue; if ((ts & _corners_at_edge[edge]) == 0) { if (CanBuildPathFromEdge(new_pos, (TileEdge)((edge + 2) % 4)) != 0) edges |= 1 << edge; } else { const XYZPoint16 new_above(x, y, voxel_pos.z + 1); if (CanBuildPathFromEdge(new_above, (TileEdge)((edge + 2) % 4)) != 0) edges |= (1 << edge) << 4; } continue; } /* No path and no ground -> Invalid, do next edge. */ } return edges; }
/** * Decide at which voxel to place a shop. It should be placed at a voxel intersecting with the view line through the given point in the world. * @param world_pos Coordinate of the point. * @param vp_orient Orientation of the viewport. * @return Result of the placement process. */ RidePlacementResult RideBuildWindow::ComputeShopVoxel(XYZPoint32 world_pos, ViewOrientation vp_orient) { ShopInstance *si = static_cast<ShopInstance *>(this->instance); assert(si != nullptr && si->GetKind() == RTK_SHOP); // It should be possible to set the position of a shop. const ShopType *st = si->GetShopType(); assert(st != nullptr); int dx, dy; // Change of xworld and yworld for every (zworld / 2) change. switch (vp_orient) { case VOR_NORTH: dx = 1; dy = 1; break; case VOR_WEST: dx = -1; dy = 1; break; case VOR_SOUTH: dx = -1; dy = -1; break; case VOR_EAST: dx = 1; dy = -1; break; default: NOT_REACHED(); } XYZPoint16 vox_pos; /* Move to the top voxel of the world. */ vox_pos.z = WORLD_Z_SIZE - 1; int dz = vox_pos.z * 256 - world_pos.z; world_pos.x += dx * dz / 2; world_pos.y += dy * dz / 2; while (vox_pos.z >= 0) { vox_pos.x = world_pos.x / 256; vox_pos.y = world_pos.y / 256; if (IsVoxelstackInsideWorld(vox_pos.x, vox_pos.y) && this->CanPlaceShop(st, vox_pos, vp_orient)) { /* Position of the shop the same as previously? */ if (si->vox_pos != vox_pos || si->orientation != this->orientation) { si->SetRide((this->orientation + vp_orient) & 3, vox_pos); return RPR_CHANGED; } return RPR_SAMEPOS; } else { /* Since z gets smaller, we subtract dx and dy, thus the checks reverse. */ if (vox_pos.x < 0 && dx > 0) break; if (vox_pos.x >= _world.GetXSize() && dx < 0) break; if (vox_pos.y < 0 && dy > 0) break; if (vox_pos.y >= _world.GetYSize() && dy < 0) break; } world_pos.x -= 128 * dx; world_pos.y -= 128 * dy; vox_pos.z--; } return RPR_FAIL; }
/** * Does a path run at/to the bottom the given voxel in the neighbouring voxel? * @param voxel_pos Coordinate of the voxel. * @param edge Direction to move to get the neighbouring voxel. * @return Whether a path exists at the bottom of the neighbouring voxel. * @pre voxel coordinate must be valid in the world. * @todo Merge with path computations in the path placement. */ bool PathExistsAtBottomEdge(XYZPoint16 voxel_pos, TileEdge edge) { voxel_pos.x += _tile_dxy[edge].x; voxel_pos.y += _tile_dxy[edge].y; if (!IsVoxelstackInsideWorld(voxel_pos.x, voxel_pos.y)) return false; const Voxel *vx = _world.GetVoxel(voxel_pos); if (vx == nullptr || !HasValidPath(vx)) { /* No path here, check the voxel below. */ if (voxel_pos.z == 0) return false; voxel_pos.z--; vx = _world.GetVoxel(voxel_pos); if (vx == nullptr || !HasValidPath(vx)) return false; /* Path must end at the top of the voxel. */ return GetImplodedPathSlope(vx) == _path_up_from_edge[edge]; } /* Path must end at the bottom of the voxel. */ return GetImplodedPathSlope(vx) < PATH_FLAT_COUNT || GetImplodedPathSlope(vx) == _path_down_from_edge[edge]; }
/** * (Try to) remove a path from the given voxel. * @param voxel_pos Coordinate of the voxel. * @param test_only Only test whether it could be removed. * @return Whether the path is or could be removed. */ static bool RemovePath(const XYZPoint16 &voxel_pos, bool test_only) { /* xy position should be valid, and allow path building. */ if (!IsVoxelstackInsideWorld(voxel_pos.x, voxel_pos.y)) return false; if (_game_mode_mgr.InPlayMode() && _world.GetTileOwner(voxel_pos.x, voxel_pos.y) != OWN_PARK) return false; if (voxel_pos.z <= 0 || voxel_pos.z > WORLD_Z_SIZE - 2) return false; // Z range should be valid. const VoxelStack *vs = _world.GetStack(voxel_pos.x, voxel_pos.y); const Voxel *v = vs->Get(voxel_pos.z); if (v == nullptr || !HasValidPath(v)) return false; PathSprites ps = GetImplodedPathSlope(v); assert(ps < PATH_COUNT); v = vs->Get(voxel_pos.z + 1); assert(v->GetInstance() == SRI_PATH && !HasValidPath(v->GetInstanceData())); if (ps >= PATH_FLAT_COUNT) { v = vs->Get(voxel_pos.z + 2); assert(v->GetInstance() == SRI_PATH && !HasValidPath(v->GetInstanceData())); } if (!test_only) RemovePathAtTile(voxel_pos, ps); return true; }
/** * Add edges of the neighbouring path tiles. * @param voxel_pos Coordinate of the central voxel with a path tile. * @param slope Imploded path slope of the central voxel. * @param dirs Edge directions to change (bitset of #TileEdge), usually #EDGE_ALL. * @param status Status of the path. #PAS_UNUSED means to remove the edges. * @return Updated (imploded) slope at the central voxel. */ uint8 AddRemovePathEdges(const XYZPoint16 &voxel_pos, uint8 slope, uint8 dirs, PathStatus status) { PathStatus ngb_status[4]; // Neighbour path status, #PAS_UNUSED means do not connect. Voxel *ngb_voxel[4]; // Neighbour voxels with path, may be null if it doesn't need changing. uint16 ngb_instance_data[4]; // New instance data, if the voxel exists. XYZPoint16 ngb_pos[4]; // Coordinate of the neighbouring voxel. Voxel *v = _world.GetCreateVoxel(voxel_pos, false); uint16 fences = v->GetFences(); std::fill_n(ngb_status, lengthof(ngb_status), PAS_UNUSED); // Clear path all statuses to prevent connecting to it if an edge is skipped. for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if ((dirs & (1 << edge)) == 0) continue; // Skip directions that should not be updated. int delta_z = 0; if (slope >= PATH_FLAT_COUNT) { if (_path_down_from_edge[edge] == slope) { delta_z = 1; } else if (_path_up_from_edge[edge] != slope) { continue; } } Point16 dxy = _tile_dxy[edge]; ngb_pos[edge].x = voxel_pos.x + dxy.x; ngb_pos[edge].y = voxel_pos.y + dxy.y; if (!IsVoxelstackInsideWorld(ngb_pos[edge].x, ngb_pos[edge].y)) continue; FenceType fence_type = GetFenceType(fences, edge); if (fence_type != FENCE_TYPE_INVALID) continue; TileEdge edge2 = (TileEdge)((edge + 2) % 4); bool modified = false; if (delta_z <= 0 || voxel_pos.z < WORLD_Z_SIZE - 1) { ngb_pos[edge].z = voxel_pos.z + delta_z; modified = ExamineNeighbourPathEdge(ngb_pos[edge], edge2, status != PAS_UNUSED, true, &ngb_voxel[edge], &ngb_instance_data[edge], &ngb_status[edge]); } delta_z--; if (!modified && (delta_z >= 0 || voxel_pos.z > 0)) { ngb_pos[edge].z = voxel_pos.z + delta_z; ExamineNeighbourPathEdge(ngb_pos[edge], edge2, status != PAS_UNUSED, false, &ngb_voxel[edge], &ngb_instance_data[edge], &ngb_status[edge]); } } switch (status) { case PAS_UNUSED: // All edges get removed. case PAS_NORMAL_PATH: // All edges get added. for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if (ngb_status[edge] != PAS_UNUSED) { if (slope < PATH_FLAT_COUNT) slope = SetPathEdge(slope, edge, status != PAS_UNUSED); if (ngb_voxel[edge] != nullptr) { ngb_voxel[edge]->SetInstanceData(ngb_instance_data[edge]); MarkVoxelDirty(ngb_pos[edge]); } } } return slope; case PAS_QUEUE_PATH: /* Pass 1: Try to add other queue paths. */ for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if (ngb_status[edge] == PAS_QUEUE_PATH) { if (GetQuePathEdgeConnectCount(slope) > 1) break; if (slope < PATH_FLAT_COUNT) slope = SetPathEdge(slope, edge, true); if (ngb_voxel[edge] != nullptr) { ngb_voxel[edge]->SetInstanceData(ngb_instance_data[edge]); MarkVoxelDirty(ngb_pos[edge]); } } } /* Pass 2: Add normal paths. */ for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if (ngb_status[edge] == PAS_NORMAL_PATH) { if (GetQuePathEdgeConnectCount(slope) > 1) break; if (slope < PATH_FLAT_COUNT) slope = SetPathEdge(slope, edge, true); if (ngb_voxel[edge] != nullptr) { ngb_voxel[edge]->SetInstanceData(ngb_instance_data[edge]); MarkVoxelDirty(ngb_pos[edge]); } } } return slope; default: NOT_REACHED(); } }