Ejemplo n.º 1
0
/* Edit3D::changeThingZ
 * Changes the Z height of selected 3d mode things by [amount]
 *******************************************************************/
void Edit3D::changeThingZ(int amount) const
{
	// Ignore for doom format
	if (context_.map().currentFormat() == MAP_DOOM)
		return;

	// Go through 3d selection
	auto& selection_3d = context_.selection();
	for (unsigned a = 0; a < selection_3d.size(); a++)
	{
		// Check if thing
		if (selection_3d[a].type == ItemType::Thing)
		{
			MapThing* thing = context_.map().getThing(selection_3d[a].index);
			if (thing)
			{
				// Change z height
				context_.recordPropertyChangeUndoStep(thing);
				double z = thing->intProperty("height");
				z += amount;
				thing->setIntProperty("height", z);
			}
		}
	}
}
Ejemplo n.º 2
0
/* MapSpecials::processZDoomSlopes
 * Process ZDoom slope specials
 *******************************************************************/
void MapSpecials::processZDoomSlopes(SLADEMap* map)
{
	// ZDoom has a variety of slope mechanisms, which must be evaluated in a
	// specific order.
	//  - Plane_Align, in line order
	//  - line slope + sector tilt + vavoom, in thing order
	//  - slope copy things, in thing order
	//  - overwrite vertex heights with vertex height things
	//  - vertex triangle slopes, in sector order
	//  - Plane_Copy, in line order

	// First things first: reset every sector to flat planes
	for (unsigned a = 0; a < map->nSectors(); a++)
	{
		MapSector* target = map->getSector(a);
		target->setPlane<FLOOR_PLANE>(plane_t::flat(target->getPlaneHeight<FLOOR_PLANE>()));
		target->setPlane<CEILING_PLANE>(plane_t::flat(target->getPlaneHeight<CEILING_PLANE>()));
	}

	// Plane_Align (line special 181)
	for (unsigned a = 0; a < map->nLines(); a++)
	{
		MapLine* line = map->getLine(a);
		if (line->getSpecial() != 181)
			continue;

		MapSector* sector1 = line->frontSector();
		MapSector* sector2 = line->backSector();
		if (!sector1 || !sector2)
		{
			LOG_MESSAGE(1, "Ignoring Plane_Align on one-sided line %d", line->getIndex());
			continue;
		}
		if (sector1 == sector2)
		{
			LOG_MESSAGE(1, "Ignoring Plane_Align on line %d, which has the same sector on both sides", line->getIndex());
			continue;
		}

		int floor_arg = line->intProperty("arg0");
		if (floor_arg == 1)
			applyPlaneAlign<FLOOR_PLANE>(line, sector1, sector2);
		else if (floor_arg == 2)
			applyPlaneAlign<FLOOR_PLANE>(line, sector2, sector1);

		int ceiling_arg = line->intProperty("arg1");
		if (ceiling_arg == 1)
			applyPlaneAlign<CEILING_PLANE>(line, sector1, sector2);
		else if (ceiling_arg == 2)
			applyPlaneAlign<CEILING_PLANE>(line, sector2, sector1);
	}

	// Line slope things (9500/9501), sector tilt things (9502/9503), and
	// vavoom things (1500/1501), all in the same pass
	for (unsigned a = 0; a < map->nThings(); a++)
	{
		MapThing* thing = map->getThing(a);

		// Line slope things
		if (thing->getType() == 9500)
			applyLineSlopeThing<FLOOR_PLANE>(map, thing);
		else if (thing->getType() == 9501)
			applyLineSlopeThing<CEILING_PLANE>(map, thing);
		// Sector tilt things
		else if (thing->getType() == 9502)
			applySectorTiltThing<FLOOR_PLANE>(map, thing);
		else if (thing->getType() == 9503)
			applySectorTiltThing<CEILING_PLANE>(map, thing);
		// Vavoom things
		else if (thing->getType() == 1500)
			applyVavoomSlopeThing<FLOOR_PLANE>(map, thing);
		else if (thing->getType() == 1501)
			applyVavoomSlopeThing<CEILING_PLANE>(map, thing);
	}

	// Slope copy things (9510/9511)
	for (unsigned a = 0; a < map->nThings(); a++)
	{
		MapThing* thing = map->getThing(a);

		if (thing->getType() == 9510 || thing->getType() == 9511)
		{
			int target_idx = map->sectorAt(thing->point());
			if (target_idx < 0)
				continue;
			MapSector* target = map->getSector(target_idx);

			// First argument is the tag of a sector whose slope should be copied
			int tag = thing->intProperty("arg0");
			if (!tag)
			{
				LOG_MESSAGE(1, "Ignoring slope copy thing in sector %d with no argument", target_idx);
				continue;
			}

			vector<MapSector*> tagged_sectors;
			map->getSectorsByTag(tag, tagged_sectors);
			if (tagged_sectors.empty())
			{
				LOG_MESSAGE(1, "Ignoring slope copy thing in sector %d; no sectors have target tag %d", target_idx, tag);
				continue;
			}

			if (thing->getType() == 9510)
				target->setFloorPlane(tagged_sectors[0]->getFloorPlane());
			else
				target->setCeilingPlane(tagged_sectors[0]->getCeilingPlane());
		}
	}

	// Vertex height things
	// These only affect the calculation of slopes and shouldn't be stored in
	// the map data proper, so instead of actually changing vertex properties,
	// we store them in a hashmap.
	VertexHeightMap vertex_floor_heights;
	VertexHeightMap vertex_ceiling_heights;
	for (unsigned a = 0; a < map->nThings(); a++)
	{
		MapThing* thing = map->getThing(a);
		if (thing->getType() == 1504 || thing->getType() == 1505)
		{
			// TODO there could be more than one vertex at this point
			MapVertex* vertex = map->vertexAt(thing->xPos(), thing->yPos());
			if (vertex)
			{
				if (thing->getType() == 1504)
					vertex_floor_heights[vertex] = thing->floatProperty("height");
				else if (thing->getType() == 1505)
					vertex_ceiling_heights[vertex] = thing->floatProperty("height");
			}
		}
	}

	// Vertex heights -- only applies for sectors with exactly three vertices.
	// Heights may be set by UDMF properties, or by a vertex height thing
	// placed exactly on the vertex (which takes priority over the prop).
	vector<MapVertex*> vertices;
	for (unsigned a = 0; a < map->nSectors(); a++)
	{
		MapSector* target = map->getSector(a);
		vertices.clear();
		target->getVertices(vertices);
		if (vertices.size() != 3)
			continue;

		applyVertexHeightSlope<FLOOR_PLANE>(target, vertices, vertex_floor_heights);
		applyVertexHeightSlope<CEILING_PLANE>(target, vertices, vertex_ceiling_heights);
	}

	// Plane_Copy
	vector<MapSector*> sectors;
	for (unsigned a = 0; a < map->nLines(); a++)
	{
		MapLine* line = map->getLine(a);
		if (line->getSpecial() != 118)
			continue;

		int tag;
		MapSector* front = line->frontSector();
		MapSector* back = line->backSector();
		if ((tag = line->intProperty("arg0")) && front)
		{
			sectors.clear();
			map->getSectorsByTag(tag, sectors);
			if (sectors.size())
				front->setFloorPlane(sectors[0]->getFloorPlane());
		}
		if ((tag = line->intProperty("arg1")) && front)
		{
			sectors.clear();
			map->getSectorsByTag(tag, sectors);
			if (sectors.size())
				front->setCeilingPlane(sectors[0]->getCeilingPlane());
		}
		if ((tag = line->intProperty("arg2")) && back)
		{
			sectors.clear();
			map->getSectorsByTag(tag, sectors);
			if (sectors.size())
				back->setFloorPlane(sectors[0]->getFloorPlane());
		}
		if ((tag = line->intProperty("arg3")) && back)
		{
			sectors.clear();
			map->getSectorsByTag(tag, sectors);
			if (sectors.size())
				back->setCeilingPlane(sectors[0]->getCeilingPlane());
		}

		// The fifth "share" argument copies from one side of the line to the
		// other
		if (front && back)
		{
			int share = line->intProperty("arg4");

			if ((share & 3) == 1)
				back->setFloorPlane(front->getFloorPlane());
			else if ((share & 3) == 2)
				front->setFloorPlane(back->getFloorPlane());

			if ((share & 12) == 4)
				back->setCeilingPlane(front->getCeilingPlane());
			else if ((share & 12) == 8)
				front->setCeilingPlane(back->getCeilingPlane());
		}
	}
}
Ejemplo n.º 3
0
/* InfoOverlay3D::update
 * Updates the info text for the object of [item_type] at [item_index]
 * in [map]
 *******************************************************************/
void InfoOverlay3D::update(int item_index, int item_type, SLADEMap* map)
{
	// Clear current info
	info.clear();
	info2.clear();

	// Setup variables
	current_type = item_type;
	texname = "";
	texture = NULL;
	thing_icon = false;
	int map_format = theMapEditor->currentMapDesc().format;

	// Wall
	if (item_type == MapEditor::SEL_SIDE_BOTTOM || item_type == MapEditor::SEL_SIDE_MIDDLE || item_type == MapEditor::SEL_SIDE_TOP)
	{
		// Get line and side
		MapSide* side = map->getSide(item_index);
		if (!side) return;
		MapLine* line = side->getParentLine();
		if (!line) return;
		object = side;

		// --- Line/side info ---
		info.push_back(S_FMT("Line #%d", line->getIndex()));
		if (side == line->s1())
			info.push_back(S_FMT("Front Side #%d", side->getIndex()));
		else
			info.push_back(S_FMT("Back Side #%d", side->getIndex()));

		// Relevant flags
		string flags = "";
		if (theGameConfiguration->lineBasicFlagSet("dontpegtop", line, map_format))
			flags += "Upper Unpegged, ";
		if (theGameConfiguration->lineBasicFlagSet("dontpegbottom", line, map_format))
			flags += "Lower Unpegged, ";
		if (theGameConfiguration->lineBasicFlagSet("blocking", line, map_format))
			flags += "Blocking, ";
		if (!flags.IsEmpty())
			flags.RemoveLast(2);
		info.push_back(flags);

		info.push_back(S_FMT("Length: %d", (int)line->getLength()));

		// Other potential info: special, sector#


		// --- Wall part info ---

		// Part
		if (item_type == MapEditor::SEL_SIDE_BOTTOM)
			info2.push_back("Lower Texture");
		else if (item_type == MapEditor::SEL_SIDE_MIDDLE)
			info2.push_back("Middle Texture");
		else
			info2.push_back("Upper Texture");

		// Offsets
		if (theGameConfiguration->udmfNamespace() == "zdoom")
		{
			// Get x offset info
			int xoff = side->intProperty("offsetx");
			double xoff_part = 0;
			if (item_type == MapEditor::SEL_SIDE_BOTTOM)
				xoff_part = side->floatProperty("offsetx_bottom");
			else if (item_type == MapEditor::SEL_SIDE_MIDDLE)
				xoff_part = side->floatProperty("offsetx_mid");
			else
				xoff_part = side->floatProperty("offsetx_top");

			// Add x offset string
			string xoff_info;
			if (xoff_part == 0)
				xoff_info = S_FMT("%d", xoff);
			else if (xoff_part > 0)
				xoff_info = S_FMT("%1.2f (%d+%1.2f)", (double)xoff+xoff_part, xoff, xoff_part);
			else
				xoff_info = S_FMT("%1.2f (%d-%1.2f)", (double)xoff+xoff_part, xoff, -xoff_part);

			// Get y offset info
			int yoff = side->intProperty("offsety");
			double yoff_part = 0;
			if (item_type == MapEditor::SEL_SIDE_BOTTOM)
				yoff_part = side->floatProperty("offsety_bottom");
			else if (item_type == MapEditor::SEL_SIDE_MIDDLE)
				yoff_part = side->floatProperty("offsety_mid");
			else
				yoff_part = side->floatProperty("offsety_top");

			// Add y offset string
			string yoff_info;
			if (yoff_part == 0)
				yoff_info = S_FMT("%d", yoff);
			else if (yoff_part > 0)
				yoff_info = S_FMT("%1.2f (%d+%1.2f)", (double)yoff+yoff_part, yoff, yoff_part);
			else
				yoff_info = S_FMT("%1.2f (%d-%1.2f)", (double)yoff+yoff_part, yoff, -yoff_part);

			info2.push_back(S_FMT("Offsets: %s, %s", xoff_info, yoff_info));
		}
		else
		{
			// Basic offsets
			info2.push_back(S_FMT("Offsets: %d, %d", side->intProperty("offsetx"), side->intProperty("offsety")));
		}

		// ZDoom UDMF extras
		if (theGameConfiguration->udmfNamespace() == "zdoom")
		{
			// Scale
			double xscale, yscale;
			if (item_type == MapEditor::SEL_SIDE_BOTTOM)
			{
				xscale = side->floatProperty("scalex_bottom");
				yscale = side->floatProperty("scaley_bottom");
			}
			else if (item_type == MapEditor::SEL_SIDE_MIDDLE)
			{
				xscale = side->floatProperty("scalex_mid");
				yscale = side->floatProperty("scaley_mid");
			}
			else
			{
				xscale = side->floatProperty("scalex_top");
				yscale = side->floatProperty("scaley_top");
			}
			info2.push_back(S_FMT("Scale: %1.2fx, %1.2fx", xscale, yscale));
		}
		else
		{
			info2.push_back("");
		}

		// Height of this section of the wall
		// TODO this is wrong in the case of slopes, but slope support only
		// exists in the 3.1.1 branch
		fpoint2_t left_point, right_point;
		MapSide* other_side;
		if (side == line->s1())
		{
			left_point = line->v1()->getPoint(0);
			right_point = line->v2()->getPoint(0);
			other_side = line->s2();
		}
		else
		{
			left_point = line->v2()->getPoint(0);
			right_point = line->v1()->getPoint(0);
			other_side = line->s1();
		}

		MapSector* this_sector = side->getSector();
		MapSector* other_sector = NULL;
		if (other_side)
			other_sector = other_side->getSector();

		double left_height, right_height;
		if (item_type == MapEditor::SEL_SIDE_MIDDLE && other_sector)
		{
			// A two-sided line's middle area is the smallest distance between
			// both sides' floors and ceilings, which is more complicated with
			// slopes.
			plane_t floor1 = this_sector->getFloorPlane();
			plane_t floor2 = other_sector->getFloorPlane();
			plane_t ceiling1 = this_sector->getCeilingPlane();
			plane_t ceiling2 = other_sector->getCeilingPlane();
			left_height = min(ceiling1.height_at(left_point), ceiling2.height_at(left_point))
			            - max(floor1.height_at(left_point), floor2.height_at(left_point));
			right_height = min(ceiling1.height_at(right_point), ceiling2.height_at(right_point))
			             - max(floor1.height_at(right_point), floor2.height_at(right_point));
		}
		else
		{
			plane_t top_plane, bottom_plane;
			if (item_type == MapEditor::SEL_SIDE_MIDDLE)
			{
				top_plane = this_sector->getCeilingPlane();
				bottom_plane = this_sector->getFloorPlane();
			}
			else
			{
				if (!other_sector) return;
				if (item_type == MapEditor::SEL_SIDE_TOP)
				{
					top_plane = this_sector->getCeilingPlane();
					bottom_plane = other_sector->getCeilingPlane();
				}
				else
				{
					top_plane = other_sector->getFloorPlane();
					bottom_plane = this_sector->getFloorPlane();
				}
			}

			left_height = top_plane.height_at(left_point) - bottom_plane.height_at(left_point);
			right_height = top_plane.height_at(right_point) - bottom_plane.height_at(right_point);
		}
		if (fabs(left_height - right_height) < 0.001)
			info2.push_back(S_FMT("Height: %d", (int)left_height));
		else
			info2.push_back(S_FMT("Height: %d ~ %d", (int)left_height, (int)right_height));

		// Texture
		if (item_type == MapEditor::SEL_SIDE_BOTTOM)
			texname = side->getTexLower();
		else if (item_type == MapEditor::SEL_SIDE_MIDDLE)
			texname = side->getTexMiddle();
		else
			texname = side->getTexUpper();
		texture = theMapEditor->textureManager().getTexture(texname, theGameConfiguration->mixTexFlats());
	}


	// Floor
	else if (item_type == MapEditor::SEL_FLOOR || item_type == MapEditor::SEL_CEILING)
	{
		// Get sector
		MapSector* sector = map->getSector(item_index);
		if (!sector) return;
		object = sector;

		// Get basic info
		int fheight = sector->intProperty("heightfloor");
		int cheight = sector->intProperty("heightceiling");

		// --- Sector info ---

		// Sector index
		info.push_back(S_FMT("Sector #%d", item_index));

		// Sector height
		info.push_back(S_FMT("Total Height: %d", cheight - fheight));

		// ZDoom UDMF extras
		/*
		if (theGameConfiguration->udmfNamespace() == "zdoom") {
			// Sector colour
			rgba_t col = sector->getColour(0, true);
			info.push_back(S_FMT("Colour: R%d, G%d, B%d", col.r, col.g, col.b));
		}
		*/


		// --- Flat info ---

		// Height
		if (item_type == MapEditor::SEL_FLOOR)
			info2.push_back(S_FMT("Floor Height: %d", fheight));
		else
			info2.push_back(S_FMT("Ceiling Height: %d", cheight));

		// Light
		int light = sector->intProperty("lightlevel");
		if (theGameConfiguration->udmfNamespace() == "zdoom")
		{
			// Get extra light info
			int fl = 0;
			bool abs = false;
			if (item_type == MapEditor::SEL_FLOOR)
			{
				fl = sector->intProperty("lightfloor");
				abs = sector->boolProperty("lightfloorabsolute");
			}
			else
			{
				fl = sector->intProperty("lightceiling");
				abs = sector->boolProperty("lightceilingabsolute");
			}

			// Set if absolute
			if (abs)
			{
				light = fl;
				fl = 0;
			}

			// Add info string
			if (fl == 0)
				info2.push_back(S_FMT("Light: %d", light));
			else if (fl > 0)
				info2.push_back(S_FMT("Light: %d (%d+%d)", light+fl, light, fl));
			else
				info2.push_back(S_FMT("Light: %d (%d-%d)", light+fl, light, -fl));
		}
		else
			info2.push_back(S_FMT("Light: %d", light));

		// ZDoom UDMF extras
		if (theGameConfiguration->udmfNamespace() == "zdoom")
		{
			// Offsets
			double xoff, yoff;
			if (item_type == MapEditor::SEL_FLOOR)
			{
				xoff = sector->floatProperty("xpanningfloor");
				yoff = sector->floatProperty("ypanningfloor");
			}
			else
			{
				xoff = sector->floatProperty("xpanningceiling");
				yoff = sector->floatProperty("ypanningceiling");
			}
			info2.push_back(S_FMT("Offsets: %1.2f, %1.2f", xoff, yoff));

			// Scaling
			double xscale, yscale;
			if (item_type == MapEditor::SEL_FLOOR)
			{
				xscale = sector->floatProperty("xscalefloor");
				yscale = sector->floatProperty("yscalefloor");
			}
			else
			{
				xscale = sector->floatProperty("xscaleceiling");
				yscale = sector->floatProperty("yscaleceiling");
			}
			info2.push_back(S_FMT("Scale: %1.2fx, %1.2fx", xscale, yscale));
		}

		// Texture
		if (item_type == MapEditor::SEL_FLOOR)
			texname = sector->getFloorTex();
		else
			texname = sector->getCeilingTex();
		texture = theMapEditor->textureManager().getFlat(texname, theGameConfiguration->mixTexFlats());
	}

	// Thing
	else if (item_type == MapEditor::SEL_THING)
	{
		// index, type, position, sector, zpos, height?, radius?

		// Get thing
		MapThing* thing = map->getThing(item_index);
		if (!thing) return;
		object = thing;

		// Index
		info.push_back(S_FMT("Thing #%d", item_index));

		// Position
		if (theMapEditor->currentMapDesc().format == MAP_HEXEN || theMapEditor->currentMapDesc().format == MAP_UDMF)
			info.push_back(S_FMT("Position: %d, %d, %d", (int)thing->xPos(), (int)thing->yPos(), (int)thing->floatProperty("height")));
		else
			info.push_back(S_FMT("Position: %d, %d", (int)thing->xPos(), (int)thing->yPos()));


		// Type
		ThingType* tt = theGameConfiguration->thingType(thing->getType());
		if (tt->getName() == "Unknown")
			info2.push_back(S_FMT("Type: %d", thing->getType()));
		else
			info2.push_back(S_FMT("Type: %s", tt->getName()));

		// Args
		if (theMapEditor->currentMapDesc().format == MAP_HEXEN ||
		        (theMapEditor->currentMapDesc().format == MAP_UDMF && theGameConfiguration->getUDMFProperty("arg0", MOBJ_THING)))
		{
			// Get thing args
			int args[5];
			args[0] = thing->intProperty("arg0");
			args[1] = thing->intProperty("arg1");
			args[2] = thing->intProperty("arg2");
			args[3] = thing->intProperty("arg3");
			args[4] = thing->intProperty("arg4");
			string argstr = tt->getArgsString(args);

			if (argstr.IsEmpty())
				info2.push_back("No Args");
			else
				info2.push_back(argstr);
		}

		// Sector
		int sector = map->sectorAt(thing->point());
		if (sector >= 0)
			info2.push_back(S_FMT("In Sector #%d", sector));
		else
			info2.push_back("No Sector");


		// Texture
		texture = theMapEditor->textureManager().getSprite(tt->getSprite(), tt->getTranslation(), tt->getPalette());
		if (!texture)
		{
			if (use_zeth_icons && tt->getZeth() >= 0)
				texture = theMapEditor->textureManager().getEditorImage(S_FMT("zethicons/zeth%02d", tt->getZeth()));
			if (!texture)
				texture = theMapEditor->textureManager().getEditorImage(S_FMT("thing/%s", tt->getIcon()));
			thing_icon = true;
		}
		texname = "";
	}

	last_update = theApp->runTimer();
}
Ejemplo n.º 4
0
/* Edit3D::changeHeight
 * Changes the height of objects, depending on type:
 * Things: Z height
 * Flat: height
 * Wall: vertical offset
 *******************************************************************/
void Edit3D::changeHeight(int amount) const
{
	// Get items to process
	vector<MapEditor::Item> items;
	auto& selection_3d = context_.selection();
	auto hilight_3d = context_.hilightItem();
	auto& map = context_.map();
	if (selection_3d.empty() && hilight_3d.index >= 0)
	{
		if (hilight_3d.type != ItemType::Thing || map.currentFormat() != MAP_DOOM)
			items.push_back(hilight_3d);
	}
	else for (unsigned a = 0; a < selection_3d.size(); a++)
	{
		if (selection_3d[a].type != ItemType::Thing || map.currentFormat() != MAP_DOOM)
			items.push_back(selection_3d[a]);
	}
	if (items.empty())
		return;

	// Begin undo level
	context_.beginUndoRecordLocked("Change Height", true, false, false);

	// Go through items
	for (unsigned a = 0; a < items.size(); a++)
	{
		auto type = items[a].type;

		// Thing
		if (type == ItemType::Thing)
		{
			MapThing* thing = map.getThing(items[a].index);
			if (thing)
			{
				double z = thing->intProperty("height");
				z += amount;
				thing->setIntProperty("height", z);
			}
		}

		// Wall
		if (type == ItemType::WallBottom ||
			type == ItemType::WallMiddle ||
			type == ItemType::WallTop)
		{
			auto side = map.getSide(items[a].index);

			if (side)
			{
				string ofs = "offsety";

				// If offsets are linked, just change the whole side offset
				if (link_offset_)
				{
					int offset = side->intProperty(ofs);
					side->setIntProperty(ofs, offset + amount);
					continue;
				}
				// Unlinked offsets, build string (offsety_[top/mid/bottom])
				else if (items[a].type == ItemType::WallBottom)
					ofs += "_bottom";
				else if (items[a].type == ItemType::WallTop)
					ofs += "_top";
				else
					ofs += "_mid";

				// Change the offset
				float offset = side->floatProperty(ofs);
				side->setFloatProperty(ofs, offset + amount);
			}
		}

		// Floor
		else if (type == ItemType::Floor)
		{
			// Get sector
			auto sector = map.getSector(items[a].index);

			// Change height
			if (sector)
				sector->setFloorHeight(sector->getFloorHeight() + amount);
		}

		// Ceiling
		else if (type == ItemType::Ceiling)
		{
			// Get sector
			auto sector = map.getSector(items[a].index);

			// Change height
			if (sector)
				sector->setCeilingHeight(sector->getCeilingHeight() + amount);
		}
	}

	// End undo level
	context_.endUndoRecord();

	// Editor message
	if (items.size() > 0)
	{
		if (amount > 0)
			context_.addEditorMessage(S_FMT("Height increased by %d", amount));
		else
			context_.addEditorMessage(S_FMT("Height decreased by %d", -amount));
	}
}