示例#1
0
int LuaArchive::GetArchiveInfo(lua_State* L)
{
	const std::string archiveName = luaL_checksstring(L, 1);
	CArchiveScanner::ArchiveData archiveData = archiveScanner->GetArchiveData(archiveName);
	if (archiveData.IsEmpty()) {
		return 0;
	}

	lua_createtable(L, 0, archiveData.GetInfo().size());

	for (const auto& pair: archiveData.GetInfo()) {
		const std::string& itemName = pair.first;
		const InfoItem&    itemData = pair.second;

		lua_pushsstring(L, itemName);
		switch (itemData.valueType) {
			case INFO_VALUE_TYPE_STRING: {
				lua_pushsstring(L, itemData.valueTypeString);
			} break;
			case INFO_VALUE_TYPE_INTEGER: {
				lua_pushinteger(L, itemData.value.typeInteger);
			} break;
			case INFO_VALUE_TYPE_FLOAT: {
				lua_pushnumber(L,  itemData.value.typeFloat);
			} break;
			case INFO_VALUE_TYPE_BOOL: {
				lua_pushboolean(L, itemData.value.typeBool);
			} break;
			default: assert(false);
		}
		lua_rawset(L, -3);
	}

	return 1;
}
示例#2
0
int LuaArchive::HasArchive(lua_State* L)
{
	const std::string archiveName = luaL_checksstring(L, 1);
	CArchiveScanner::ArchiveData archiveData = archiveScanner->GetArchiveData(archiveName);
	lua_pushboolean(L, !archiveData.IsEmpty());
	return 1;
}
示例#3
0
int LuaArchive::GetArchiveReplaces(lua_State* L)
{
	const std::string archiveName = luaL_checksstring(L, 1);
	CArchiveScanner::ArchiveData archiveData = archiveScanner->GetArchiveData(archiveName);
	if (archiveData.IsEmpty()) {
		return 0;
	}

	const std::vector<std::string>& replaces = archiveData.GetReplaces();

	unsigned int count = 0;
	lua_createtable(L, replaces.size(), 0);

	for (const std::string& replace: replaces) {
		lua_pushsstring(L, replace);
		lua_rawseti(L, -2, ++count);
	}
	return 1;
}
static bool archNameCompare(const CArchiveScanner::ArchiveData& a, const CArchiveScanner::ArchiveData& b)
{
	return (a.GetName() < b.GetName());
}
示例#5
0
文件: ModInfo.cpp 项目: AMDmi3/spring
void CModInfo::Init(const char* modArchive)
{
	filename = modArchive;
	humanNameVersioned = archiveScanner->NameFromArchive(modArchive);

	const CArchiveScanner::ArchiveData md = archiveScanner->GetArchiveData(humanNameVersioned);

	humanName   = md.GetName();
	shortName   = md.GetShortName();
	version     = md.GetVersion();
	mutator     = md.GetMutator();
	description = md.GetDescription();

	// initialize the parser
	LuaParser parser("gamedata/modrules.lua",
	                 SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP);
	// customize the defs environment
	parser.GetTable("Spring");
	parser.AddFunc("GetModOptions", LuaSyncedRead::GetModOptions);
	parser.EndTable();
	parser.Execute();

	if (!parser.IsValid()) {
		LOG_L(L_ERROR, "Failed loading mod-rules, using defaults; error: %s", parser.GetErrorLog().c_str());
	}

	const LuaTable& root = parser.GetRoot();

	{
		// system
		const LuaTable& system = root.SubTable("system");
		const size_t numThreads = std::max(0, configHandler->GetInt("MultiThreadCount"));

		bool disableGML = (numThreads == 1);

		pathFinderSystem = system.GetInt("pathFinderSystem", PFS_TYPE_DEFAULT) % PFS_NUM_TYPES;
		luaThreadingModel = system.GetInt("luaThreadingModel", MT_LUA_SINGLE_BATCH);

		//FIXME: remove unsave modes
		if (luaThreadingModel > 2) {
			LOG_L(L_WARNING, "Experimental luaThreadingModel %d selected! This is currently unmaintained and may be deprecated and/or removed in the future!", luaThreadingModel);
			LOG_L(L_WARNING, "Automaticly disabled to prevent desyncs / crashes / hangs / graphical errors!");
			if (!configHandler->GetBool("EnableUnsafeAndBrokenMT")) {
				luaThreadingModel = 2;
			} else {
				LOG_L(L_WARNING, "MT enforced: expect desyncs / crashes / hangs / graphical errors!");
			}
		}

		if (numThreads == 0) {
			disableGML |= (Threading::GetAvailableCores() <= 1     );
			disableGML |= (luaThreadingModel == MT_LUA_NONE        );
			disableGML |= (luaThreadingModel == MT_LUA_SINGLE      );
			disableGML |= (luaThreadingModel == MT_LUA_SINGLE_BATCH);
		}

		if (disableGML) {
			// single core, or this game did not make any effort to
			// specifically support MT ==> disable it by default
			GML::Enable(false);
		}

		GML::SetCheckCallChain(globalConfig->GetMultiThreadLua() == MT_LUA_SINGLE_BATCH);
	}

	{
		// movement
		const LuaTable& movementTbl = root.SubTable("movement");

		allowAircraftToLeaveMap = movementTbl.GetBool("allowAirPlanesToLeaveMap", true);
		allowAircraftToHitGround = movementTbl.GetBool("allowAircraftToHitGround", true);
		allowPushingEnemyUnits = movementTbl.GetBool("allowPushingEnemyUnits", false);
		allowCrushingAlliedUnits = movementTbl.GetBool("allowCrushingAlliedUnits", false);
		allowUnitCollisionDamage = movementTbl.GetBool("allowUnitCollisionDamage", false);
		allowUnitCollisionOverlap = movementTbl.GetBool("allowUnitCollisionOverlap", true);
		allowGroundUnitGravity = movementTbl.GetBool("allowGroundUnitGravity", true);
		allowHoverUnitStrafing = movementTbl.GetBool("allowHoverUnitStrafing", (pathFinderSystem == PFS_TYPE_QTPFS));
		useClassicGroundMoveType = movementTbl.GetBool("useClassicGroundMoveType", false);
	}

	{
		// construction
		const LuaTable& constructionTbl = root.SubTable("construction");

		constructionDecay = constructionTbl.GetBool("constructionDecay", true);
		constructionDecayTime = (int)(constructionTbl.GetFloat("constructionDecayTime", 6.66) * GAME_SPEED);
		constructionDecaySpeed = std::max(constructionTbl.GetFloat("constructionDecaySpeed", 0.03), 0.01f);
	}

	{
		// reclaim
		const LuaTable& reclaimTbl = root.SubTable("reclaim");

		multiReclaim  = reclaimTbl.GetInt("multiReclaim",  0);
		reclaimMethod = reclaimTbl.GetInt("reclaimMethod", 1);
		reclaimUnitMethod = reclaimTbl.GetInt("unitMethod", 1);
		reclaimUnitEnergyCostFactor = reclaimTbl.GetFloat("unitEnergyCostFactor", 0.0);
		reclaimUnitEfficiency = reclaimTbl.GetFloat("unitEfficiency", 1.0);
		reclaimFeatureEnergyCostFactor = reclaimTbl.GetFloat("featureEnergyCostFactor", 0.0);
		reclaimAllowEnemies = reclaimTbl.GetBool("allowEnemies", true);
		reclaimAllowAllies = reclaimTbl.GetBool("allowAllies", true);
	}

	{
		// repair
		const LuaTable& repairTbl = root.SubTable("repair");
		repairEnergyCostFactor = repairTbl.GetFloat("energyCostFactor", 0.0);
	}

	{
		// resurrect
		const LuaTable& resurrectTbl = root.SubTable("resurrect");
		resurrectEnergyCostFactor  = resurrectTbl.GetFloat("energyCostFactor",  0.5);
	}

	{
		// capture
		const LuaTable& captureTbl = root.SubTable("capture");
		captureEnergyCostFactor = captureTbl.GetFloat("energyCostFactor", 0.0);
	}

	{
		// paralyze
		const LuaTable& paralyzeTbl = root.SubTable("paralyze");
		paralyzeOnMaxHealth = paralyzeTbl.GetBool("paralyzeOnMaxHealth", true);
	}

	{
		// fire-at-dead-units
		const LuaTable& fireAtDeadTbl = root.SubTable("fireAtDead");

		fireAtKilled   = fireAtDeadTbl.GetBool("fireAtKilled", false);
		fireAtCrashing = fireAtDeadTbl.GetBool("fireAtCrashing", false);
	}

	{
		// transportability
		const LuaTable& transportTbl = root.SubTable("transportability");

		transportAir    = transportTbl.GetBool("transportAir",   false);
		transportShip   = transportTbl.GetBool("transportShip",  false);
		transportHover  = transportTbl.GetBool("transportHover", false);
		transportGround = transportTbl.GetBool("transportGround", true);

		targetableTransportedUnits = transportTbl.GetBool("targetableTransportedUnits", false);
	}

	{
		// experience
		const LuaTable& experienceTbl = root.SubTable("experience");

		CUnit::SetExpMultiplier (experienceTbl.GetFloat("experienceMult", 1.0f));
		CUnit::SetExpPowerScale (experienceTbl.GetFloat("powerScale",  1.0f));
		CUnit::SetExpHealthScale(experienceTbl.GetFloat("healthScale", 0.7f));
		CUnit::SetExpReloadScale(experienceTbl.GetFloat("reloadScale", 0.4f));
	}

	{
		// flanking bonus
		const LuaTable& flankingBonusTbl = root.SubTable("flankingBonus");
		flankingBonusModeDefault = flankingBonusTbl.GetInt("defaultMode", 1);
	}

	{
		// feature visibility
		const LuaTable& featureLOS = root.SubTable("featureLOS");

		featureVisibility = featureLOS.GetInt("featureVisibility", FEATURELOS_ALL);
		featureVisibility = Clamp(featureVisibility, int(FEATURELOS_NONE), int(FEATURELOS_ALL));
	}

	{
		// sensors, line-of-sight
		const LuaTable& sensors = root.SubTable("sensors");
		const LuaTable& los = sensors.SubTable("los");

		requireSonarUnderWater = sensors.GetBool("requireSonarUnderWater", true);

		// losMipLevel is used as index to readMap->mipHeightmaps,
		// so the max value is CReadMap::numHeightMipMaps - 1
		losMipLevel = los.GetInt("losMipLevel", 1);
		losMul = los.GetFloat("losMul", 1.0f);
		// airLosMipLevel doesn't have such restrictions, it's just used in various
		// bitshifts with signed integers
		airMipLevel = los.GetInt("airMipLevel", 2);
		airLosMul = los.GetFloat("airLosMul", 1.0f);

		if ((losMipLevel < 0) || (losMipLevel > 6)) {
			throw content_error("Sensors\\Los\\LosMipLevel out of bounds. "
				                "The minimum value is 0. The maximum value is 6.");
		}

		if ((airMipLevel < 0) || (airMipLevel > 30)) {
			throw content_error("Sensors\\Los\\AirLosMipLevel out of bounds. "
				                "The minimum value is 0. The maximum value is 30.");
		}
	}
}
示例#6
0
文件: ModInfo.cpp 项目: 9heart/spring
void CModInfo::Init(const char* modArchive)
{
	filename = modArchive;
	humanNameVersioned = archiveScanner->NameFromArchive(modArchive);

	const CArchiveScanner::ArchiveData md = archiveScanner->GetArchiveData(humanNameVersioned);

	humanName   = md.GetName();
	shortName   = md.GetShortName();
	version     = md.GetVersion();
	mutator     = md.GetMutator();
	description = md.GetDescription();

	// initialize the parser
	LuaParser parser("gamedata/modrules.lua",
	                 SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP);
	// customize the defs environment
	parser.GetTable("Spring");
	parser.AddFunc("GetModOptions", LuaSyncedRead::GetModOptions);
	parser.EndTable();
	parser.Execute();

	if (!parser.IsValid()) {
		LOG_L(L_ERROR, "Failed loading mod-rules, using defaults; error: %s", parser.GetErrorLog().c_str());
	}

	const LuaTable& root = parser.GetRoot();

	{
		// system
		const LuaTable& system = root.SubTable("system");
		pathFinderSystem = system.GetInt("pathFinderSystem", PFS_TYPE_DEFAULT) % PFS_NUM_TYPES;

	}

	{
		// movement
		const LuaTable& movementTbl = root.SubTable("movement");

		allowAircraftToLeaveMap = movementTbl.GetBool("allowAirPlanesToLeaveMap", true);
		allowAircraftToHitGround = movementTbl.GetBool("allowAircraftToHitGround", true);
		allowPushingEnemyUnits = movementTbl.GetBool("allowPushingEnemyUnits", false);
		allowCrushingAlliedUnits = movementTbl.GetBool("allowCrushingAlliedUnits", false);
		allowUnitCollisionDamage = movementTbl.GetBool("allowUnitCollisionDamage", false);
		allowUnitCollisionOverlap = movementTbl.GetBool("allowUnitCollisionOverlap", true);
		allowGroundUnitGravity = movementTbl.GetBool("allowGroundUnitGravity", true);
		allowHoverUnitStrafing = movementTbl.GetBool("allowHoverUnitStrafing", (pathFinderSystem == PFS_TYPE_QTPFS));
		useClassicGroundMoveType = movementTbl.GetBool("useClassicGroundMoveType", false);
	}

	{
		// construction
		const LuaTable& constructionTbl = root.SubTable("construction");

		constructionDecay = constructionTbl.GetBool("constructionDecay", true);
		constructionDecayTime = (int)(constructionTbl.GetFloat("constructionDecayTime", 6.66) * GAME_SPEED);
		constructionDecaySpeed = std::max(constructionTbl.GetFloat("constructionDecaySpeed", 0.03), 0.01f);
	}

	{
		// reclaim
		const LuaTable& reclaimTbl = root.SubTable("reclaim");

		multiReclaim  = reclaimTbl.GetInt("multiReclaim",  0);
		reclaimMethod = reclaimTbl.GetInt("reclaimMethod", 1);
		reclaimUnitMethod = reclaimTbl.GetInt("unitMethod", 1);
		reclaimUnitEnergyCostFactor = reclaimTbl.GetFloat("unitEnergyCostFactor", 0.0);
		reclaimUnitEfficiency = reclaimTbl.GetFloat("unitEfficiency", 1.0);
		reclaimFeatureEnergyCostFactor = reclaimTbl.GetFloat("featureEnergyCostFactor", 0.0);
		reclaimAllowEnemies = reclaimTbl.GetBool("allowEnemies", true);
		reclaimAllowAllies = reclaimTbl.GetBool("allowAllies", true);
	}

	{
		// repair
		const LuaTable& repairTbl = root.SubTable("repair");
		repairEnergyCostFactor = repairTbl.GetFloat("energyCostFactor", 0.0);
	}

	{
		// resurrect
		const LuaTable& resurrectTbl = root.SubTable("resurrect");
		resurrectEnergyCostFactor  = resurrectTbl.GetFloat("energyCostFactor",  0.5);
	}

	{
		// capture
		const LuaTable& captureTbl = root.SubTable("capture");
		captureEnergyCostFactor = captureTbl.GetFloat("energyCostFactor", 0.0);
	}

	{
		// paralyze
		const LuaTable& paralyzeTbl = root.SubTable("paralyze");
		paralyzeOnMaxHealth = paralyzeTbl.GetBool("paralyzeOnMaxHealth", true);
	}

	{
		// fire-at-dead-units
		const LuaTable& fireAtDeadTbl = root.SubTable("fireAtDead");

		fireAtKilled   = fireAtDeadTbl.GetBool("fireAtKilled", false);
		fireAtCrashing = fireAtDeadTbl.GetBool("fireAtCrashing", false);
	}

	{
		// transportability
		const LuaTable& transportTbl = root.SubTable("transportability");

		transportAir    = transportTbl.GetBool("transportAir",   false);
		transportShip   = transportTbl.GetBool("transportShip",  false);
		transportHover  = transportTbl.GetBool("transportHover", false);
		transportGround = transportTbl.GetBool("transportGround", true);

		targetableTransportedUnits = transportTbl.GetBool("targetableTransportedUnits", false);
	}

	{
		// experience
		const LuaTable& experienceTbl = root.SubTable("experience");

		CUnit::SetExpMultiplier (experienceTbl.GetFloat("experienceMult", 1.0f));
		CUnit::SetExpPowerScale (experienceTbl.GetFloat("powerScale",  1.0f));
		CUnit::SetExpHealthScale(experienceTbl.GetFloat("healthScale", 0.7f));
		CUnit::SetExpReloadScale(experienceTbl.GetFloat("reloadScale", 0.4f));
	}

	{
		// flanking bonus
		const LuaTable& flankingBonusTbl = root.SubTable("flankingBonus");
		flankingBonusModeDefault = flankingBonusTbl.GetInt("defaultMode", 1);
	}

	{
		// feature visibility
		const LuaTable& featureLOS = root.SubTable("featureLOS");

		featureVisibility = featureLOS.GetInt("featureVisibility", FEATURELOS_ALL);
		featureVisibility = Clamp(featureVisibility, int(FEATURELOS_NONE), int(FEATURELOS_ALL));
	}

	{
		// sensors, line-of-sight
		const LuaTable& sensors = root.SubTable("sensors");
		const LuaTable& los = sensors.SubTable("los");

		requireSonarUnderWater = sensors.GetBool("requireSonarUnderWater", true);

		// losMipLevel is used as index to readMap->mipHeightmaps,
		// so the max value is CReadMap::numHeightMipMaps - 1
		losMipLevel = los.GetInt("losMipLevel", 1);
		losMul = los.GetFloat("losMul", 1.0f);
		// airLosMipLevel doesn't have such restrictions, it's just used in various
		// bitshifts with signed integers
		airMipLevel = los.GetInt("airMipLevel", 2);
		airLosMul = los.GetFloat("airLosMul", 1.0f);

		if ((losMipLevel < 0) || (losMipLevel > 6)) {
			throw content_error("Sensors\\Los\\LosMipLevel out of bounds. "
				                "The minimum value is 0. The maximum value is 6.");
		}

		if ((airMipLevel < 0) || (airMipLevel > 30)) {
			throw content_error("Sensors\\Los\\AirLosMipLevel out of bounds. "
				                "The minimum value is 0. The maximum value is 30.");
		}
	}
}
示例#7
0
void CModInfo::Init(const char* modArchive)
{
	filename = modArchive;
	humanName = archiveScanner->NameFromArchive(modArchive);

	const CArchiveScanner::ArchiveData md = archiveScanner->GetArchiveData(humanName);

	shortName   = md.GetShortName();
	version     = md.GetVersion();
	mutator     = md.GetMutator();
	description = md.GetDescription();

	// initialize the parser
	LuaParser parser("gamedata/modrules.lua",
	                 SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP);
	// customize the defs environment
	parser.GetTable("Spring");
	parser.AddFunc("GetModOptions", LuaSyncedRead::GetModOptions);
	parser.EndTable();
	parser.Execute();
	if (!parser.IsValid()) {
		LOG_L(L_ERROR, "Failed loading mod-rules, using defaults; error: %s",
				parser.GetErrorLog().c_str());
	}
	const LuaTable root = parser.GetRoot();

	// determine if bombers are allowed to leave map boundaries
	const LuaTable movementTbl = root.SubTable("movement");
	allowAirPlanesToLeaveMap = movementTbl.GetBool("allowAirPlanesToLeaveMap", true);
	allowPushingEnemyUnits = movementTbl.GetBool("allowPushingEnemyUnits", false);

	// determine whether the modder allows the user to use team coloured nanospray
	const LuaTable nanosprayTbl = root.SubTable("nanospray");
	allowTeamColors = nanosprayTbl.GetBool("allow_team_colors", true);
	if (allowTeamColors) {
		// Load the users preference for team coloured nanospray
		globalRendering->teamNanospray = configHandler->GetBool("TeamNanoSpray");
	}

	// constructions
	const LuaTable constructionTbl = root.SubTable("construction");
	constructionDecay = constructionTbl.GetBool("constructionDecay", true);
	constructionDecayTime = (int)(constructionTbl.GetFloat("constructionDecayTime", 6.66) * 30);
	constructionDecaySpeed = constructionTbl.GetFloat("constructionDecaySpeed", 0.03);

	// reclaim
	const LuaTable reclaimTbl = root.SubTable("reclaim");
	multiReclaim  = reclaimTbl.GetInt("multiReclaim",  0);
	reclaimMethod = reclaimTbl.GetInt("reclaimMethod", 1);
	reclaimUnitMethod = reclaimTbl.GetInt("unitMethod", 1);
	reclaimUnitEnergyCostFactor = reclaimTbl.GetFloat("unitEnergyCostFactor", 0.0);
	reclaimUnitEfficiency = reclaimTbl.GetFloat("unitEfficiency", 1.0);
	reclaimFeatureEnergyCostFactor = reclaimTbl.GetFloat("featureEnergyCostFactor", 0.0);
	reclaimAllowEnemies = reclaimTbl.GetBool("allowEnemies", true);
	reclaimAllowAllies = reclaimTbl.GetBool("allowAllies", true);

	// repair
	const LuaTable repairTbl = root.SubTable("repair");
	repairEnergyCostFactor = repairTbl.GetFloat("energyCostFactor", 0.0);

	// resurrect
	const LuaTable resurrectTbl = root.SubTable("resurrect");
	resurrectEnergyCostFactor  = resurrectTbl.GetFloat("energyCostFactor",  0.5);

	// capture
	const LuaTable captureTbl = root.SubTable("capture");
	captureEnergyCostFactor = captureTbl.GetFloat("energyCostFactor", 0.0);

	// paralyze
	const LuaTable paralyzeTbl = root.SubTable("paralyze");
	paralyzeOnMaxHealth = paralyzeTbl.GetBool("paralyzeOnMaxHealth", true);

	// fire-at-dead-units
	const LuaTable fireAtDeadTbl = root.SubTable("fireAtDead");
	fireAtKilled   = fireAtDeadTbl.GetBool("fireAtKilled", false);
	fireAtCrashing = fireAtDeadTbl.GetBool("fireAtCrashing", false);

	// transportability
	const LuaTable transportTbl = root.SubTable("transportability");
	transportAir    = transportTbl.GetInt("transportAir",   false);
	transportShip   = transportTbl.GetInt("transportShip",  false);
	transportHover  = transportTbl.GetInt("transportHover", false);
	transportGround = transportTbl.GetInt("transportGround", true);

	// experience
	const LuaTable experienceTbl = root.SubTable("experience");
	CUnit::SetExpMultiplier (experienceTbl.GetFloat("experienceMult", 1.0f));
	CUnit::SetExpPowerScale (experienceTbl.GetFloat("powerScale",  1.0f));
	CUnit::SetExpHealthScale(experienceTbl.GetFloat("healthScale", 0.7f));
	CUnit::SetExpReloadScale(experienceTbl.GetFloat("reloadScale", 0.4f));

	// flanking bonus
	const LuaTable flankingBonusTbl = root.SubTable("flankingBonus");
	flankingBonusModeDefault = flankingBonusTbl.GetInt("defaultMode", 1);

	// feature visibility
	const LuaTable featureLOS = root.SubTable("featureLOS");
	featureVisibility = featureLOS.GetInt("featureVisibility", 3);
	if (featureVisibility < 0 || featureVisibility > 3)
		throw content_error("invalid modinfo: featureVisibility, valid range is 0..3");

	// sensors
	const LuaTable sensors = root.SubTable("sensors");
	requireSonarUnderWater = sensors.GetBool("requireSonarUnderWater", true);
	/// LoS
	const LuaTable los = sensors.SubTable("los");
	// losMipLevel is used as index to readmap->mipHeightmaps,
	// so the max value is CReadMap::numHeightMipMaps - 1
	losMipLevel = los.GetInt("losMipLevel", 1);
	losMul = los.GetFloat("losMul", 1.0f);
	if ((losMipLevel < 0) || (losMipLevel > 6)) {
		throw content_error("Sensors\\Los\\LosMipLevel out of bounds. "
		                    "The minimum value is 0. The maximum value is 6.");
	}
	// airLosMipLevel doesn't have such restrictions, it's just used in various
	// bitshifts with signed integers
	airMipLevel = los.GetInt("airMipLevel", 2);
	if ((airMipLevel < 0) || (airMipLevel > 30)) {
		throw content_error("Sensors\\Los\\AirLosMipLevel out of bounds. "
		                    "The minimum value is 0. The maximum value is 30.");
	}
	airLosMul = los.GetFloat("airLosMul", 1.0f);

	const LuaTable system = root.SubTable("system");
	luaThreadingModel = system.GetInt("luaThreadingModel", MT_LUA_SINGLE_BATCH);
}