/** * 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; }
/** * 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; }
/** * Find all edges that are an exit for a path in the given voxel. No investigation is performed whether the exits connect to anything. * @param v %Voxel to investigate. * @return Exits for a path in the queried voxel. Lower 4 bits are exits at the bottom; upper 4 bits are exits at the top. */ uint8 GetPathExits(const Voxel *v) { SmallRideInstance instance = v->GetInstance(); if (instance != SRI_PATH) return 0; uint16 inst_data = v->GetInstanceData(); if (!HasValidPath(inst_data)) return 0; PathSprites slope = GetImplodedPathSlope(inst_data); if (slope < PATH_FLAT_COUNT) { // At a flat path tile. return (_path_expand[slope] >> PATHBIT_NE) & 0xF; } switch (slope) { case PATH_RAMP_NE: return (1 << EDGE_NE) | (0x10 << EDGE_SW); case PATH_RAMP_NW: return (1 << EDGE_NW) | (0x10 << EDGE_SE); case PATH_RAMP_SE: return (1 << EDGE_SE) | (0x10 << EDGE_NW); case PATH_RAMP_SW: return (1 << EDGE_SW) | (0x10 << EDGE_NE); default: NOT_REACHED(); } }
/** * Find all edges that are an exit for a path in the given voxel. No investigation is performed whether the exits connect to anything. * @param v %Voxel to investigate. * @return Exits for a path in the queried voxel. Lower 4 bits are exits at the bottom; upper 4 bits are exits at the top. */ uint8 GetPathExits(const Voxel *v) { SmallRideInstance instance = v->GetInstance(); if (instance != SRI_PATH) return 0; uint16 inst_data = v->GetInstanceData(); if (!HasValidPath(inst_data)) return 0; return GetPathExits(GetImplodedPathSlope(inst_data), true); }
/** * 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]; }
/** * Examine, and perhaps modify a neighbouring path edge or ride connection, to make it connect (or not if not \a add_edges) * to the centre examined tile. * @param voxel_pos Coordinate of the neighbouring voxel. * @param edge Edge to examine, and/or connected to. * @param add_edges If set, add edges (else, remove them). * @param at_bottom Whether a path connection is expected at the bottom (if \c false, it should be at the top). * @param dest_voxel [out] %Voxel containing the neighbouring path, or \c nullptr. * @param dest_inst_data [out] New instance of the voxel. Only valid if \a dest_voxel is not \c nullptr. * @param dest_status [out] Status of the neighbouring path. * @return Neighbouring voxel was (logically) connected to the centre tile. */ static bool ExamineNeighbourPathEdge(const XYZPoint16 &voxel_pos, TileEdge edge, bool add_edges, bool at_bottom, Voxel **dest_voxel, uint16 *dest_inst_data, PathStatus *dest_status) { Voxel *v; *dest_voxel = nullptr; *dest_status = PAS_UNUSED; *dest_inst_data = PATH_INVALID; v = _world.GetCreateVoxel(voxel_pos, false); if (v == nullptr) return false; uint16 fences = v->GetFences(); FenceType fence_type = GetFenceType(fences, edge); if (fence_type != FENCE_TYPE_INVALID) return false; uint16 number = v->GetInstance(); if (number == SRI_PATH) { uint16 instance_data = v->GetInstanceData(); if (!HasValidPath(instance_data)) return false; uint8 slope = GetImplodedPathSlope(instance_data); if (at_bottom) { if (slope >= PATH_FLAT_COUNT && slope != _path_up_from_edge[edge]) return false; } else { if (slope != _path_down_from_edge[edge]) return false; } PathStatus status = _sprite_manager.GetPathStatus(GetPathType(instance_data)); if (add_edges && status == PAS_QUEUE_PATH) { // Only try to connect to queue paths if they are not yet connected to 2 (or more) neighbours. if (GetQuePathEdgeConnectCount(slope) > 1) return false; } slope = SetPathEdge(slope, edge, add_edges); *dest_status = status; *dest_voxel = v; *dest_inst_data = SetImplodedPathSlope(instance_data, slope); return true; } else if (number >= SRI_FULL_RIDES) { // A ride instance. Does it have an entrance here? if ((v->GetInstanceData() & (1 << edge)) != 0) { *dest_status = PAS_QUEUE_PATH; return true; } } return false; }
/** * (Try to) change the path type of the current path at the given voxel. * @param voxel_pos Coordinate of the voxel. * @param path_type For changing (ie not \a test_only), the type of path to change to. * @param test_only Only test whether it could be changed. * @return Whether the path's type could be changed. */ static bool ChangePath(const XYZPoint16 &voxel_pos, PathType path_type, bool test_only) { 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) ChangePathAtTile(voxel_pos, path_type, ps); 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; }
/** * User clicked 'forward' or 'back'. Figure out which direction to move in. * @param move_forward Move forward (if \c false, move back). */ void PathBuildManager::SelectMovement(bool move_forward) { if (this->state <= PBS_WAIT_ARROW || this->state > PBS_WAIT_BUY) return; TileEdge edge = (move_forward) ? this->selected_arrow : (TileEdge)((this->selected_arrow + 2) % 4); bool move_up; const Voxel *v = _world.GetVoxel(this->pos); if (v == nullptr) return; if (HasValidPath(v)) { move_up = (GetImplodedPathSlope(v) == _path_down_from_edge[edge]); } else if (v->GetGroundType() != GTP_INVALID) { TileSlope ts = ExpandTileSlope(v->GetGroundSlope()); if ((ts & TSB_STEEP) != 0) return; move_up = ((ts & _corners_at_edge[edge]) != 0); } else { return; // Surface type but no valid ground/path surface. } this->MoveCursor(edge, move_up); }
/** * User selected 'buy' or 'remove'. Perform the action, and update the path build state. * @param buying If \c true, user selected 'buy'. */ void PathBuildManager::SelectBuyRemove(bool buying) { if (buying) { // Buy a long path. if (this->state == PBS_LONG_BUY) { _additions.Commit(); this->pos = this->long_pos; this->SetState(PBS_WAIT_ARROW); return; } // Buying a path tile. if (this->state != PBS_WAIT_BUY) return; _additions.Commit(); this->SelectMovement(true); } else { // Removing a path tile. if (this->state <= PBS_WAIT_VOXEL || this->state > PBS_WAIT_BUY) return; TileEdge edge = (TileEdge)((this->selected_arrow + 2) % 4); const Voxel *v = _world.GetVoxel(this->pos); if (v == nullptr || !HasValidPath(v)) { this->MoveCursor(edge, false); this->UpdateState(); return; } PathSprites ps = GetImplodedPathSlope(v); _additions.Clear(); if (RemovePath(this->pos, false)) { _additions.Commit(); } /* Short-cut version of this->SelectMovement(false), as that function fails after removing the path. */ bool move_up = (ps == _path_down_from_edge[edge]); this->MoveCursor(edge, move_up); this->UpdateState(); } }
/** * (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; }