/** * 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; }
/** * Add/remove land border fence based on current land ownership for the given tile rectangle. * @param x Base X coordinate of the rectangle. * @param y Base Y coordinate of the rectangle. * @param width Length in X direction of the rectangle. * @param height Length in Y direction of the rectangle. */ void UpdateLandBorderFence(uint16 x, uint16 y, uint16 width, uint16 height) { /* * Iterate over given rectangle plus one tile, unless the map border * is reached. */ uint16 x_min = x > 0 ? x-1 : x; uint16 y_min = y > 0 ? y-1 : y; uint16 x_max = std::min(_world.GetXSize()-1, x + width + 1); uint16 y_max = std::min(_world.GetYSize()-1, y + height + 1); for (uint16 ix = x_min; ix < x_max; ix++) { for (uint16 iy = y_min; iy < y_max; iy++) { VoxelStack *vs = _world.GetModifyStack(ix, iy); TileOwner owner = vs->owner; int16 height = vs->GetBaseGroundOffset() + vs->base; uint16 fences = GetGroundFencesFromMap(vs, height); for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { FenceType ftype = GetFenceType(fences, edge); /* Don't overwrite user-buildable fences. */ if (ftype >= FENCE_TYPE_BUILDABLE_BEGIN && ftype < FENCE_TYPE_COUNT) continue; /* Decide whether a fence is needed (add a border fence just outside player-owned land). */ ftype = FENCE_TYPE_INVALID; if (owner != OWN_PARK) { const Point16 pt = _tile_dxy[edge]; if ((ix > 0 || pt.x >= 0) && (iy > 0 || pt.y >= 0)) { uint16 nx = ix + pt.x; uint16 ny = iy + pt.y; if (nx < _world.GetXSize() && ny < _world.GetYSize() && _world.GetTileOwner(nx, ny) == OWN_PARK) { ftype = FENCE_TYPE_LAND_BORDER; } } } fences = SetFenceType(fences, edge, ftype); } AddGroundFencesToMap(fences, vs, height); } } }
/** * 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(); } }