/** * 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(); } }
/** Update the state of the path build process. */ void PathBuildManager::UpdateState() { Viewport *vp = GetViewport(); if (this->state == PBS_IDLE || this->state == PBS_SINGLE) { DisableWorldAdditions(); this->selected_arrow = INVALID_EDGE; this->selected_slope = this->state == PBS_IDLE ? TSL_INVALID : TSL_FLAT; } /* The tile cursor is controlled by the viewport if waiting for a voxel or earlier. */ if (vp != nullptr && this->state > PBS_WAIT_VOXEL && this->state <= PBS_WAIT_BUY) { vp->tile_cursor.SetCursor(this->pos, CUR_TYPE_TILE); } /* See whether the PBS_WAIT_ARROW state can be left automatically. */ if (this->state == PBS_WAIT_ARROW) { this->allowed_arrows = GetPathAttachPoints(this->pos); /* If a valid selection has been made, or if only one choice exists, take it. */ if (this->selected_arrow != INVALID_EDGE && ((0x11 << this->selected_arrow) & this->allowed_arrows) != 0) { this->state = PBS_WAIT_SLOPE; } else if (this->allowed_arrows == (1 << EDGE_NE) || this->allowed_arrows == (0x10 << EDGE_NE)) { this->selected_arrow = EDGE_NE; this->state = PBS_WAIT_SLOPE; } else if (this->allowed_arrows == (1 << EDGE_NW) || this->allowed_arrows == (0x10 << EDGE_NW)) { this->selected_arrow = EDGE_NW; this->state = PBS_WAIT_SLOPE; } else if (this->allowed_arrows == (1 << EDGE_SE) || this->allowed_arrows == (0x10 << EDGE_SE)) { this->selected_arrow = EDGE_SE; this->state = PBS_WAIT_SLOPE; } else if (this->allowed_arrows == (1 << EDGE_SW) || this->allowed_arrows == (0x10 << EDGE_SW)) { this->selected_arrow = EDGE_SW; this->state = PBS_WAIT_SLOPE; } } /* Set the arrow cursor. Note that display is controlled later. */ if (vp != nullptr) { if (this->state > PBS_WAIT_ARROW && this->state <= PBS_WAIT_BUY) { XYZPoint16 arrow_pos = this->ComputeArrowCursorPosition(); vp->arrow_cursor.SetCursor(arrow_pos, (CursorType)(CUR_TYPE_ARROW_NE + this->selected_arrow)); } else { vp->arrow_cursor.SetInvalid(); } } /* See whether the PBS_WAIT_SLOPE state can be left automatically. */ if (this->state == PBS_WAIT_SLOPE) { /* Compute allowed slopes. */ XYZPoint16 arrow_pos = this->ComputeArrowCursorPosition(); this->allowed_slopes = CanBuildPathFromEdge(arrow_pos, (TileEdge)((this->selected_arrow + 2) % 4)); /* If a valid selection has been made, or if only one choice exists, take it. */ if (this->selected_slope != TSL_INVALID && ((1 << this->selected_slope) & this->allowed_slopes) != 0) { this->state = PBS_WAIT_BUY; } else if (this->allowed_slopes == (1 << TSL_DOWN)) { this->selected_slope = TSL_DOWN; this->state = PBS_WAIT_BUY; } else if (this->allowed_slopes == (1 << TSL_FLAT)) { this->selected_slope = TSL_FLAT; this->state = PBS_WAIT_BUY; } else if (this->allowed_slopes == (1 << TSL_UP)) { this->selected_slope = TSL_UP; this->state = PBS_WAIT_BUY; } } /* Handle _additions display. */ if (vp != nullptr) { if (this->state == PBS_SINGLE || this->state == PBS_WAIT_SLOPE) { _additions.Clear(); vp->EnableWorldAdditions(); } else if (this->state == PBS_WAIT_BUY) { _additions.Clear(); this->ComputeWorldAdditions(); vp->EnableWorldAdditions(); vp->EnsureAdditionsAreVisible(); } else { if (this->state != PBS_LONG_BUILD && this->state != PBS_LONG_BUY) vp->DisableWorldAdditions(); } } NotifyChange(WC_PATH_BUILDER, ALL_WINDOWS_OF_TYPE, CHG_UPDATE_BUTTONS, 0); }