/** * Try to move the tile cursor to a new tile. * @param edge Direction of movement. * @param move_up Current tile seems to move up. */ void PathBuildManager::MoveCursor(TileEdge edge, bool move_up) { if (this->state <= PBS_WAIT_ARROW || this->state > PBS_WAIT_BUY || edge == INVALID_EDGE) return; /* Test whether we can move in the indicated direction. */ Point16 dxy = _tile_dxy[edge]; if ((dxy.x < 0 && this->pos.x == 0) || (dxy.x > 0 && this->pos.x == _world.GetXSize() - 1)) return; if ((dxy.y < 0 && this->pos.y == 0) || (dxy.y > 0 && this->pos.y == _world.GetYSize() - 1)) return; if (_game_mode_mgr.InPlayMode() && _world.GetTileOwner(this->pos.x + dxy.x, this->pos.y + dxy.y) != OWN_PARK) return; const Voxel *v_top, *v_bot; if (move_up) { /* Exit of current tile is at the top. */ v_top = (this->pos.z > WORLD_Z_SIZE - 2) ? nullptr : _world.GetVoxel(this->pos + XYZPoint16(dxy.x, dxy.y, 1)); v_bot = _world.GetVoxel(this->pos + XYZPoint16(dxy.x, dxy.y, 0)); } else { /* Exit of current tile is at the bottom. */ v_top = _world.GetVoxel(this->pos + XYZPoint16(dxy.x, dxy.y, 0)); v_bot = (this->pos.z == 0) ? nullptr : _world.GetVoxel(this->pos + XYZPoint16(dxy.x, dxy.y, -1)); } /* Try to find a voxel with a path. */ if (v_top != nullptr && HasValidPath(v_top)) { this->pos.x += dxy.x; this->pos.y += dxy.y; if (move_up) this->pos.z++; this->SetState(PBS_WAIT_ARROW); return; } if (v_bot != nullptr && HasValidPath(v_bot)) { this->pos.x += dxy.x; this->pos.y += dxy.y; if (!move_up) this->pos.z--; this->SetState(PBS_WAIT_ARROW); return; } /* Try to find a voxel with surface. */ if (v_top != nullptr && v_top->GetGroundType() != GTP_INVALID && !IsImplodedSteepSlope(v_top->GetGroundSlope())) { this->pos.x += dxy.x; this->pos.y += dxy.y; if (move_up) this->pos.z++; this->SetState(PBS_WAIT_ARROW); return; } if (v_bot != nullptr && v_bot->GetGroundType() != GTP_INVALID && !IsImplodedSteepSlope(v_bot->GetGroundSlope())) { this->pos.x += dxy.x; this->pos.y += dxy.y; if (!move_up) this->pos.z--; this->SetState(PBS_WAIT_ARROW); return; } }
/** * Search for a path to the destination. * @return Whether a path has been found. */ bool PathSearcher::Search() { this->dest_pos = nullptr; while (!open_points.empty()) { WalkedDistance wd = *open_points.begin(); open_points.erase(open_points.begin()); if (wd.traveled != wd.pos->traveled || wd.estimate != wd.pos->estimate) continue; // Invalid open point. /* Reached the destination? */ const WalkedPosition *wp = wd.pos; if (wp->cur_vox == this->dest_vox) { this->dest_pos = wp; return true; } /* Add new open points. */ const Voxel *v = _world.GetVoxel(wp->cur_vox); if (v == nullptr) continue; // No voxel at the expected point, don't bother. uint8 exits = GetPathExits(v); for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if ((exits & (0x11 << edge)) == 0) continue; /* There is an outgoing connection, is it also on the world? */ Point16 dxy = _tile_dxy[edge]; if (dxy.x < 0 && wp->cur_vox.x == 0) continue; if (dxy.x > 0 && wp->cur_vox.x + 1 == _world.GetXSize()) continue; if (dxy.y < 0 && wp->cur_vox.y == 0) continue; if (dxy.y > 0 && wp->cur_vox.y + 1 == _world.GetYSize()) continue; int extra_z = ((exits & (0x10 << edge)) != 0); if (wp->cur_vox.z + extra_z < 0 || wp->cur_vox.z + extra_z >= WORLD_Z_SIZE) continue; /* Now check the other side, new_z is the voxel where the path should be at the bottom. */ const Voxel *v2 = _world.GetVoxel(wp->cur_vox + XYZPoint16(dxy.x, dxy.y, extra_z)); if (v2 == nullptr) continue; uint8 other_exits = GetPathExits(v2); if ((other_exits & (1 << ((edge + 2) % 4))) == 0) { // No path here, try one voxel below extra_z--; if (wp->cur_vox.z + extra_z < 0) continue; v2 = _world.GetVoxel(wp->cur_vox + XYZPoint16(dxy.x, dxy.y, extra_z)); if (v2 == nullptr) continue; other_exits = GetPathExits(v2); if ((other_exits & (0x10 << ((edge + 2) % 4))) == 0) continue; } /* Add new open point to the path finder. */ this->AddOpen(wp->cur_vox + XYZPoint16(dxy.x, dxy.y, extra_z), wp->traveled + 1, wp); } } return false; }
/** * 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; }
/** * Can a shop be placed at the given voxel? * @param selected_shop Shop to place. * @param pos Coordinate of the voxel. * @param vp_orient Orientation of the viewport. * @pre voxel coordinate must be valid in the world. * @pre \a selected_shop may not be \c nullptr. * @return Shop can be placed at the given position. */ bool RideBuildWindow::CanPlaceShop(const ShopType *selected_shop, const XYZPoint16 &pos, ViewOrientation vp_orient) { /* 1. Can the position itself be used to build a shop? */ if (_world.GetTileOwner(pos.x, pos.y) != OWN_PARK) return false; const Voxel *vx = _world.GetVoxel(pos); if (vx != nullptr) { if (!vx->CanPlaceInstance()) return false; // Cannot build on a path or other ride. return vx->GetGroundType() != GTP_INVALID && vx->GetGroundSlope() == SL_FLAT; // Can build at a flat surface. } /* 2. Is the shop just above non-flat ground? */ if (pos.z > 0) { vx = _world.GetVoxel(pos + XYZPoint16(0, 0, -1)); if (vx != nullptr && vx->GetInstance() == SRI_FREE && vx->GetGroundType() != GTP_INVALID && vx->GetGroundSlope() != SL_FLAT) return true; } /* 3. Is there a path at the right place? */ for (TileEdge entrance = EDGE_BEGIN; entrance < EDGE_COUNT; entrance++) { // Loop over the 4 unrotated directions. if ((selected_shop->flags & (1 << entrance)) == 0) continue; // No entrance here. TileEdge entr = static_cast<TileEdge>((entrance + vp_orient + this->orientation) & 3); // Perform rotation specified by the user in the GUI. if (PathExistsAtBottomEdge(pos, entr)) return true; } return false; }
/** * Check that the voxel stack at the given coordinate is a good spot to use as entry point for new guests. * @param x X position at the edge. * @param y Y position at the edge. * @return The given position is a good starting point. */ static bool IsGoodEdgeRoad(int16 x, int16 y) { if (x < 0 || y < 0) return false; int16 z = _world.GetBaseGroundHeight(x, y); const Voxel *vs = _world.GetVoxel(XYZPoint16(x, y, z)); return vs && HasValidPath(vs) && GetImplodedPathSlope(vs) < PATH_FLAT_COUNT; }
/** * Mark the additions in the display as outdated, so they get repainted. * @param vp %Viewport displaying the world. */ void WorldAdditions::MarkDirty(Viewport *vp) { for (const auto &iter : this->modified_stacks) { const Point32 pt = iter.first; const VoxelStack *vstack = iter.second; if (vstack != nullptr) vp->MarkVoxelDirty(XYZPoint16(pt.x, pt.y, vstack->base), vstack->height); } }
/** * Add foundation bits from the bottom up to the given voxel. * @param world %Voxel storage. * @param xpos X position of the voxel stack. * @param ypos Y position of the voxel stack. * @param z Height of the ground. * @param bits Foundation bits to add. */ static void AddFoundations(VoxelWorld *world, uint16 xpos, uint16 ypos, int16 z, uint8 bits) { for (int16 zpos = 0; zpos < z; zpos++) { Voxel *v = world->GetCreateVoxel(XYZPoint16(xpos, ypos, zpos), true); if (v->GetFoundationType() == FDT_INVALID) { v->SetFoundationType(FDT_GROUND); v->SetFoundationSlope(bits); continue; } v->SetFoundationSlope(v->GetFoundationSlope() | bits); } }
/** * Compute the voxel to display the arrow cursor. * @return Computed position of the voxel that should contain the arrow cursor. */ XYZPoint16 PathBuildManager::ComputeArrowCursorPosition() { assert(this->state > PBS_WAIT_ARROW && this->state <= PBS_WAIT_BUY); assert(this->selected_arrow != INVALID_EDGE); Point16 dxy = _tile_dxy[this->selected_arrow]; XYZPoint16 arr_pos = this->pos + XYZPoint16(dxy.x, dxy.y, 0); uint8 bit = 1 << this->selected_arrow; if ((bit & this->allowed_arrows) == 0) { // Build direction is not at the bottom of the voxel. assert(((bit << 4) & this->allowed_arrows) != 0); // Should be available at the top of the voxel. arr_pos.z++; } /* Do some paranoia checking. */ assert(IsVoxelInsideWorld(arr_pos)); return arr_pos; }
/** * Creates a world of flat tiles. * @param z Height of the tiles. */ void VoxelWorld::MakeFlatWorld(int16 z) { for (uint16 xpos = 0; xpos < this->x_size; xpos++) { for (uint16 ypos = 0; ypos < this->y_size; ypos++) { Voxel *v = this->GetCreateVoxel(XYZPoint16(xpos, ypos, z), true); v->SetFoundationType(FDT_INVALID); v->SetGroundType(GTP_GRASS0); v->SetGroundSlope(ImplodeTileSlope(SL_FLAT)); v->ClearInstances(); } } for (uint16 xpos = 0; xpos < this->x_size; xpos++) { AddFoundations(this, xpos, 0, z, 0xC0); AddFoundations(this, xpos, this->y_size - 1, z, 0x0C); } for (uint16 ypos = 0; ypos < this->y_size; ypos++) { AddFoundations(this, 0, ypos, z, 0x03); AddFoundations(this, this->x_size - 1, ypos, z, 0x30); } }
/** Compute the new contents of the voxel where the path should be added from the #_world. */ void PathBuildManager::ComputeWorldAdditions() { assert(this->state == PBS_WAIT_BUY); // It needs selected_arrow and selected_slope. assert(this->selected_slope != TSL_INVALID); if (((1 << this->selected_slope) & this->allowed_slopes) == 0) return; XYZPoint16 arrow_pos = this->ComputeArrowCursorPosition(); const Voxel *v = _world.GetVoxel(arrow_pos); if (v != nullptr && HasValidPath(v)) { ChangePath(arrow_pos, this->path_type, false); return; } TileEdge edge = (TileEdge)((this->selected_arrow + 2) % 4); switch (this->selected_slope) { case TSL_DOWN: v = _world.GetVoxel(arrow_pos + XYZPoint16(0, 0, -1)); if (v != nullptr && HasValidPath(v)) { arrow_pos.z--; ChangePath(arrow_pos, this->path_type, false); } else { BuildDownwardPath(arrow_pos, edge, this->path_type, false); } break; case TSL_FLAT: BuildFlatPath(arrow_pos, this->path_type, false); break; case TSL_UP: BuildUpwardPath(arrow_pos, edge, this->path_type, false); break; default: NOT_REACHED(); } }
/** * Build a path from #_path_builder xpos/ypos to the mouse cursor position. * @param mousexy Mouse position. */ void PathBuildManager::ComputeNewLongPath(const Point32 &mousexy) { static const TrackSlope slope_prios_down[] = {TSL_DOWN, TSL_FLAT, TSL_UP, TSL_INVALID}; // Order of preference when going down. static const TrackSlope slope_prios_flat[] = {TSL_FLAT, TSL_UP, TSL_DOWN, TSL_INVALID}; // Order of preference when at the right height. static const TrackSlope slope_prios_up[] = {TSL_UP, TSL_FLAT, TSL_DOWN, TSL_INVALID}; // Order of preference when going up. Viewport *vp = GetViewport(); if (vp == nullptr) return; int c1, c2, c3; switch (vp->orientation) { case VOR_NORTH: c1 = 1; c2 = 2; c3 = 2; break; case VOR_EAST: c1 = -1; c2 = -2; c3 = 2; break; case VOR_SOUTH: c1 = 1; c2 = -2; c3 = -2; break; case VOR_WEST: c1 = -1; c2 = 2; c3 = -2; break; default: NOT_REACHED(); } XYZPoint16 path_pos(0, 0, 0); path_pos.y = this->pos.y * 256 + 128; int32 lambda_y = path_pos.y - mousexy.y; // Distance to constant Y plane at current tile cursor. path_pos.x = this->pos.x * 256 + 128; int32 lambda_x = path_pos.x - mousexy.x; // Distance to constant X plane at current tile cursor. if (abs(lambda_x) < abs(lambda_y)) { /* X constant. */ path_pos.x /= 256; path_pos.y = Clamp<int32>(mousexy.y + c1 * lambda_x, 0, _world.GetYSize() * 256 - 1) / 256; path_pos.z = Clamp<int32>(vp->view_pos.z + c3 * lambda_x, 0, WORLD_Z_SIZE * 256 - 1) / 256; } else { /* Y constant. */ path_pos.x = Clamp<int32>(mousexy.x + c1 * lambda_y, 0, _world.GetXSize() * 256 - 1) / 256; path_pos.y /= 256; path_pos.z = Clamp<int32>(vp->view_pos.z + c2 * lambda_y, 0, WORLD_Z_SIZE * 256 - 1) / 256; } if (this->long_pos != path_pos) { this->long_pos = path_pos; _additions.Clear(); path_pos = this->pos; /* Find the right direction from the selected tile to the current cursor location. */ TileEdge direction; Point16 dxy; for (direction = EDGE_BEGIN; direction < EDGE_COUNT; direction++) { dxy = _tile_dxy[direction]; if (!GoodDirection(dxy.x, path_pos.x, this->long_pos.x) || !GoodDirection(dxy.y, path_pos.y, this->long_pos.y)) continue; break; } if (direction == EDGE_COUNT) return; /* 'Walk' to the cursor as long as possible. */ while (path_pos.x != this->long_pos.x || path_pos.y != this->long_pos.y) { uint8 slopes = CanBuildPathFromEdge(path_pos, direction); const TrackSlope *slope_prio; /* Get order of slope preference. */ if (path_pos.z > this->long_pos.z) { slope_prio = slope_prios_down; } else if (path_pos.z == this->long_pos.z) { slope_prio = slope_prios_flat; } else { slope_prio = slope_prios_up; } /* Find best slope, and take it. */ while (*slope_prio != TSL_INVALID && (slopes & (1 << *slope_prio)) == 0) slope_prio++; if (*slope_prio == TSL_INVALID) break; path_pos.x += dxy.x; path_pos.y += dxy.y; const Voxel *v = _world.GetVoxel(path_pos); if (v != nullptr && HasValidPath(v)) { if (!ChangePath(path_pos, this->path_type, false)) break; if (*slope_prio == TSL_UP) path_pos.z++; } else { if (*slope_prio == TSL_UP) { if (!BuildUpwardPath(path_pos, static_cast<TileEdge>((direction + 2) & 3), this->path_type, false)) break; path_pos.z++; } else if (*slope_prio == TSL_DOWN) { v = _world.GetVoxel(path_pos + XYZPoint16(0, 0, -1)); if (v != nullptr && HasValidPath(v)) { if (!ChangePath(path_pos + XYZPoint16(0, 0, -1), this->path_type, false)) break; } else { if (!BuildDownwardPath(path_pos, static_cast<TileEdge>((direction + 2) & 3), this->path_type, false)) break; } path_pos.z--; } else { if (!BuildFlatPath(path_pos, this->path_type, false)) break; } } } vp->EnableWorldAdditions(); vp->EnsureAdditionsAreVisible(); } }