/** * Add edges of the neighbouring path tiles. * @param xpos X coordinate of the central voxel with a path tile. * @param ypos Y coordinate of the central voxel with a path tile. * @param zpos Z 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 use_additions Use #_additions rather than #_world. * @param add_edges If set, add edges (else, remove them). * @return Updated (imploded) slope at the central voxel. */ uint8 AddRemovePathEdges(uint16 xpos, uint16 ypos, uint8 zpos, uint8 slope, uint8 dirs, bool use_additions, bool add_edges) { 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]; if ((dxy.x < 0 && xpos == 0) || (dxy.x > 0 && xpos == _world.GetXSize() - 1)) continue; if ((dxy.y < 0 && ypos == 0) || (dxy.y > 0 && ypos == _world.GetYSize() - 1)) continue; TileEdge edge2 = (TileEdge)((edge + 2) % 4); bool modified = false; if (delta_z <= 0 || zpos < WORLD_Z_SIZE - 1) { Voxel *v; if (use_additions) { v = _additions.GetCreateVoxel(xpos + dxy.x, ypos + dxy.y, zpos + delta_z, false); } else { v = _world.GetCreateVoxel(xpos + dxy.x, ypos + dxy.y, zpos + delta_z, false); } if (v != nullptr) { uint16 number = v->GetInstance(); if (number == SRI_PATH) { // Valid path. v->SetInstanceData(SetPathEdge(v->GetInstanceData(), edge2, add_edges)); MarkVoxelDirty(xpos + dxy.x, ypos + dxy.y, zpos + delta_z); modified = true; } else if (number >= SRI_FULL_RIDES) { // A ride instance. Does it have an entrance here? if ((v->GetInstanceData() & (1 << edge2)) != 0) modified = true; } } } delta_z--; if (delta_z >= 0 || zpos > 0) { Voxel *v; if (use_additions) { v = _additions.GetCreateVoxel(xpos + dxy.x, ypos + dxy.y, zpos + delta_z, false); } else { v = _world.GetCreateVoxel(xpos + dxy.x, ypos + dxy.y, zpos + delta_z, false); } if (v != nullptr) { uint16 number = v->GetInstance(); if (number == SRI_PATH) { // Valid path. v->SetInstanceData(SetPathEdge(v->GetInstanceData(), edge2, add_edges)); MarkVoxelDirty(xpos + dxy.x, ypos + dxy.y, zpos + delta_z); modified = true; } else if (number >= SRI_FULL_RIDES) { // A ride instance. Does it have an entrance here? if ((v->GetInstanceData() & (1 << edge2)) != 0) modified = true; } } } if (modified && slope < PATH_FLAT_COUNT) slope = SetPathEdge(slope, edge, add_edges); } return slope; }
void FenceGui::SelectorMouseButtonEvent(uint8 state) { if (!IsLeftClick(state)) return; if (this->fence_sel.area.width != 1 || this->fence_sel.area.height != 1) return; if (this->fence_edge == INVALID_EDGE) return; if (_game_mode_mgr.InPlayMode() && _world.GetTileOwner(this->fence_base.x, this->fence_base.y) != OWN_PARK) return; VoxelStack *vs = _world.GetModifyStack(this->fence_base.x, this->fence_base.y); uint16 fences = GetGroundFencesFromMap(vs, this->fence_base.z); fences = SetFenceType(fences, this->fence_edge, this->fence_type); AddGroundFencesToMap(fences, vs, this->fence_base.z); MarkVoxelDirty(this->fence_base); }
/** * Change the path type of a currently existing path. * @param voxel_pos Coordinate of the voxel. * @param path_type The type of path to change to. * @param path_spr Imploded sprite number. */ static void ChangePathAtTile(const XYZPoint16 &voxel_pos, PathType path_type, uint8 path_spr) { VoxelStack *avs = _additions.GetModifyStack(voxel_pos.x, voxel_pos.y); Voxel *av = avs->GetCreate(voxel_pos.z, false); AddRemovePathEdges(voxel_pos, path_spr, EDGE_ALL, true, PAS_UNUSED); /* Reset flat path to one without edges or corners. */ if (path_spr < PATH_FLAT_COUNT) path_spr = PATH_EMPTY; uint8 slope = AddRemovePathEdges(voxel_pos, path_spr, EDGE_ALL, true, _sprite_manager.GetPathStatus(path_type)); av->SetInstanceData(MakePathInstanceData(slope, path_type)); MarkVoxelDirty(voxel_pos); }
/** * Remove a path from a tile, and free the voxels above it as well. * @param voxel_pos Coordinate of the voxel. * @param path_spr Imploded sprite number. * @see BuildPathAtTile */ static void RemovePathAtTile(const XYZPoint16 &voxel_pos, uint8 path_spr) { VoxelStack *avs = _additions.GetModifyStack(voxel_pos.x, voxel_pos.y); Voxel *av = avs->GetCreate(voxel_pos.z, false); av->SetInstance(SRI_FREE); av->SetInstanceData(0); AddRemovePathEdges(voxel_pos, path_spr, EDGE_ALL, true, PAS_UNUSED); MarkVoxelDirty(voxel_pos); av = avs->GetCreate(voxel_pos.z + 1, false); av->SetInstance(SRI_FREE); av->SetInstanceData(0); if (path_spr >= PATH_FLAT_COUNT) { av = avs->GetCreate(voxel_pos.z + 2, false); av->SetInstance(SRI_FREE); av->SetInstanceData(0); } }
/** * Build a path at a tile, and claim the voxels above it as well. * @param voxel_pos Coordinate of the voxel. * @param path_type The type of path to build. * @param path_spr Imploded sprite number. * @see RemovePathAtTile */ static void BuildPathAtTile(const XYZPoint16 &voxel_pos, PathType path_type, uint8 path_spr) { VoxelStack *avs = _additions.GetModifyStack(voxel_pos.x, voxel_pos.y); Voxel *av = avs->GetCreate(voxel_pos.z, true); av->SetInstance(SRI_PATH); uint8 slope = AddRemovePathEdges(voxel_pos, path_spr, EDGE_ALL, true, _sprite_manager.GetPathStatus(path_type)); av->SetInstanceData(MakePathInstanceData(slope, path_type)); av = avs->GetCreate(voxel_pos.z + 1, true); av->ClearVoxel(); av->SetInstance(SRI_PATH); av->SetInstanceData(PATH_INVALID); if (path_spr >= PATH_FLAT_COUNT) { // For non-flat sprites, add another voxel. av = avs->GetCreate(voxel_pos.z + 2, true); av->ClearVoxel(); av->SetInstance(SRI_PATH); av->SetInstanceData(PATH_INVALID); } MarkVoxelDirty(voxel_pos); }
/** Mark the voxel containing the voxel object as dirty, so it is repainted. */ void VoxelObject::MarkDirty() { MarkVoxelDirty(this->vox_pos); }
/** * 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(); } }