/** * 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; } }
/** * 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; } }
/** * 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(); } }
/** * Can the user press the 'remove' button at the path GUI? * @return Button is enabled. */ bool PathBuildManager::GetRemoveIsEnabled() const { if (this->state == PBS_IDLE || this->state == PBS_WAIT_VOXEL) return false; /* If current tile has a path, it can be removed. */ const Voxel *v = _world.GetVoxel(this->pos); if (v != nullptr && HasValidPath(v)) return true; return this->state == PBS_WAIT_BUY; }
/** * 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); }
/** * (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; }
/** * 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]; }
/** 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(); } }
/** * (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; }
/** * User clicked somewhere. Check whether it is a good tile or path, and if so, move the building process forward. * @param click_pos Coordinate of the voxel. */ void PathBuildManager::TileClicked(const XYZPoint16 &click_pos) { if (this->state == PBS_IDLE || this->state > PBS_WAIT_BUY) return; if (this->state == PBS_SINGLE) { this->pos = click_pos; const Voxel *v = _world.GetCreateVoxel(this->pos, false); if (v != nullptr && HasValidPath(v)) { ChangePath(this->pos, this->path_type, false); } else { TileSlope ts = ExpandTileSlope(v->GetGroundSlope()); if (ts == SL_FLAT) { BuildFlatPath(this->pos, this->path_type, false); } else { TileEdge edge = INVALID_EDGE; switch (ts) { case TSB_NORTHEAST: edge = EDGE_SW; break; case TSB_NORTHWEST: edge = EDGE_SE; break; case TSB_SOUTHEAST: edge = EDGE_NW; break; case TSB_SOUTHWEST: edge = EDGE_NE; break; default: return; } BuildUpwardPath(this->pos, edge, this->path_type, false); } } _additions.Commit(); } else { uint8 dirs = GetPathAttachPoints(click_pos); if (dirs == 0) return; this->pos = click_pos; this->allowed_arrows = dirs; this->SetState(PBS_WAIT_ARROW); } }
/** * 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; }
/** * 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(); } }
/** * 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(); } }