Example #1
0
wxString Spring::WriteScriptTxt( IBattle& battle ) const
{
    wxLogMessage(_T("0 WriteScriptTxt called "));

    wxString ret;

    TDFWriter tdf(ret);

    // Start generating the script.
    tdf.EnterSection( _T("GAME") );

		tdf.Append( _T("HostIP"), battle.GetHostIp() );
			if ( battle.IsFounderMe() )
			{
					if ( battle.GetNatType() == NAT_Hole_punching ) tdf.Append( _T("HostPort"), battle.GetMyInternalUdpSourcePort() );
					else tdf.Append(_T("HostPort"), battle.GetHostPort() );
			}
			else
			{
					tdf.Append( _T("HostPort"), battle.GetHostPort() );
					if ( battle.GetNatType() == NAT_Hole_punching )
					{
						tdf.Append( _T("SourcePort"), battle.GetMyInternalUdpSourcePort() );
					}
					else if ( sett().GetClientPort() != 0)
					{
						tdf.Append( _T("SourcePort"), sett().GetClientPort() ); /// this allows to play with broken router by setting SourcePort to some forwarded port.
					}
			}
			tdf.Append( _T("IsHost"), battle.IsFounderMe() );

			tdf.Append(_T("MyPlayerName"), battle.GetMe().GetNick() );

			if ( !battle.IsFounderMe() )
			{
					tdf.LeaveSection();
					return ret;
			}

			/**********************************************************************************
																		Host-only section
			**********************************************************************************/

			tdf.AppendLineBreak();

			tdf.Append(_T("ModHash"), battle.LoadMod().hash );
			tdf.Append(_T("MapHash"), battle.LoadMap().hash );

			tdf.Append( _T("Mapname"), battle.GetHostMapName() );
			tdf.Append( _T("GameType"), battle.GetHostModName() );

			tdf.AppendLineBreak();

			switch ( battle.GetBattleType() )
			{
				case BT_Played: break;
				case BT_Replay:
				{
					wxString path = battle.GetPlayBackFilePath();
					if ( path.Find(_T("/")) != wxNOT_FOUND ) path.BeforeLast(_T('/'));
					tdf.Append( _T("DemoFile"), path );
					tdf.AppendLineBreak();
					break;
				}
				case BT_Savegame:
				{
					wxString path = battle.GetPlayBackFilePath();
					if ( path.Find(_T("/")) != wxNOT_FOUND ) path.BeforeLast(_T('/'));
					tdf.Append( _T("Savefile"), path );
					tdf.AppendLineBreak();
					break;
				}
				default:
                    wxLogDebugFunc( _T("") ); break;
			}

			long startpostype;
			battle.CustomBattleOptions().getSingleValue( _T("startpostype"), OptionsWrapper::EngineOption ).ToLong( &startpostype );

			std::vector<StartPos> remap_positions;
			if ( battle.IsProxy() && ( startpostype != IBattle::ST_Pick ) && ( startpostype != IBattle::ST_Choose ) )
			{
				std::set<int> parsedteams;
				unsigned int NumUsers = battle.GetNumUsers();
				unsigned int NumTeams = 0;
				for ( unsigned int i = 0; i < NumUsers; i++ )
				{
						User& usr = battle.GetUser( i );
						UserBattleStatus& status = usr.BattleStatus();
						if ( status.spectator ) continue;
						if ( parsedteams.find( status.team ) != parsedteams.end() ) continue; // skip duplicates
						parsedteams.insert( status.team );
						NumTeams++;
				}

				MapInfo infos = battle.LoadMap().info;
				unsigned int nummapstartpositions = infos.positions.size();
				unsigned int copysize = std::min( nummapstartpositions, NumTeams );
				remap_positions = std::vector<StartPos> ( infos.positions.begin(), infos.positions.begin() + copysize ); // only add the first x positions

				if ( startpostype == IBattle::ST_Random )
				{
					random_shuffle( remap_positions.begin(), remap_positions.end() ); // shuffle the positions
				}

			}
			if ( battle.IsProxy() )
			{
				if ( ( startpostype == IBattle::ST_Random ) || ( startpostype == IBattle::ST_Fixed ) )
				{
					tdf.Append( _T("startpostype"), IBattle::ST_Pick );
				}
				else tdf.Append( _T("startpostype"), startpostype );
			}
			else tdf.Append( _T("startpostype"), startpostype );

			tdf.EnterSection( _T("mapoptions") );
				OptionsWrapper::wxStringTripleVec optlistMap = battle.CustomBattleOptions().getOptions( OptionsWrapper::MapOption );
				for (OptionsWrapper::wxStringTripleVec::const_iterator it = optlistMap.begin(); it != optlistMap.end(); ++it)
				{
						tdf.Append(it->first,it->second.second);
				}
			tdf.LeaveSection();


			tdf.EnterSection(_T("modoptions"));
				OptionsWrapper::wxStringTripleVec optlistMod = battle.CustomBattleOptions().getOptions( OptionsWrapper::ModOption );
				for (OptionsWrapper::wxStringTripleVec::const_iterator it = optlistMod.begin(); it != optlistMod.end(); ++it)
				{
						tdf.Append(it->first,it->second.second);
				}
			tdf.LeaveSection();

			std::map<wxString,int> units = battle.RestrictedUnits();
			tdf.Append( _T("NumRestrictions"), units.size());
			tdf.EnterSection( _T("RESTRICT") );
				int restrictcount = 0;
				for ( std::map<wxString, int>::iterator itor = units.begin(); itor != units.end(); itor++ )
				{
						tdf.Append(_T("Unit") + TowxString( restrictcount ), itor->first );
						tdf.Append(_T("Limit") + TowxString( restrictcount ), itor->second );
						restrictcount++;
				}
			tdf.LeaveSection();


			tdf.AppendLineBreak();

			if ( battle.IsProxy() )
			{
				tdf.Append( _T("NumPlayers"), battle.GetNumPlayers() -1 );
				tdf.Append( _T("NumUsers"), battle.GetNumUsers() -1 );
			}
			else
			{
				tdf.Append( _T("NumPlayers"), battle.GetNumPlayers() );
				tdf.Append( _T("NumUsers"), battle.GetNumUsers() );
			}

			tdf.AppendLineBreak();

			unsigned int NumUsers = battle.GetNumUsers();

			typedef std::map<int, int> ProgressiveTeamsVec;
			typedef ProgressiveTeamsVec::iterator ProgressiveTeamsVecIter;
			ProgressiveTeamsVec teams_to_sorted_teams; // original team -> progressive team
			int free_team = 0;
			std::map<User*, int> player_to_number; // player -> ordernumber
			srand ( time(NULL) );
			for ( unsigned int i = 0; i < NumUsers; i++ )
			{
					User& user = battle.GetUser( i );
					UserBattleStatus& status = user.BattleStatus();
					if ( !status.spectator )
					{
						ProgressiveTeamsVecIter itor = teams_to_sorted_teams.find ( status.team );
						if ( itor == teams_to_sorted_teams.end() )
						{
							teams_to_sorted_teams[status.team] = free_team;
							free_team++;
						}
					}
				  if ( battle.IsProxy() && ( user.GetNick() == battle.GetFounder().GetNick() ) ) continue;
					if ( status.IsBot() ) continue;
					tdf.EnterSection( _T("PLAYER") + TowxString( i ) );
							tdf.Append( _T("Name"), user.GetNick() );
							tdf.Append( _T("CountryCode"), user.GetCountry().Lower());
							tdf.Append( _T("Spectator"), status.spectator );
							tdf.Append( _T("Rank"), (int)user.GetRank() );
							tdf.Append( _T("IsFromDemo"), int(status.isfromdemo) );
							if ( !status.spectator )
							{
								tdf.Append( _T("Team"), teams_to_sorted_teams[status.team] );
							}
							else
							{
								int speccteam = 0;
								if ( teams_to_sorted_teams.size() != 0 ) speccteam = rand() % teams_to_sorted_teams.size();
								tdf.Append( _T("Team"), speccteam );
							}
					tdf.LeaveSection();
					player_to_number[&user] = i;
			}
			if ( usync().VersionSupports( IUnitSync::USYNC_GetSkirmishAI ) )
			{
				for ( unsigned int i = 0; i < NumUsers; i++ )
				{
						User& user = battle.GetUser( i );
						UserBattleStatus& status = user.BattleStatus();
						if ( !status.IsBot() ) continue;
						tdf.EnterSection( _T("AI") + TowxString( i ) );
								tdf.Append( _T("Name"), user.GetNick() ); // AI's nick;
								tdf.Append( _T("ShortName"), status.aishortname ); // AI libtype
								tdf.Append( _T("Version"), status.aiversion ); // AI libtype version
								tdf.Append( _T("Team"), teams_to_sorted_teams[status.team] );
								tdf.Append( _T("IsFromDemo"), int(status.isfromdemo) );
								tdf.Append( _T("Host"), player_to_number[&battle.GetUser( status.owner )] );
								tdf.EnterSection( _T("Options") );
									int optionmapindex = battle.CustomBattleOptions().GetAIOptionIndex( user.GetNick() );
									if ( optionmapindex > 0 )
									{
										OptionsWrapper::wxStringTripleVec optlistMod_ = battle.CustomBattleOptions().getOptions( (OptionsWrapper::GameOption)optionmapindex );
										for (OptionsWrapper::wxStringTripleVec::const_iterator it = optlistMod_.begin(); it != optlistMod_.end(); ++it)
										{
												tdf.Append(it->first,it->second.second);
										}
									}
								tdf.LeaveSection();
						tdf.LeaveSection();
						player_to_number[&user] = i;
				}
			}

			tdf.AppendLineBreak();

			std::set<int> parsedteams;
			wxArrayString sides = usync().GetSides( battle.GetHostModName() );
			for ( unsigned int i = 0; i < NumUsers; i++ )
			{
					User& usr = battle.GetUser( i );
					UserBattleStatus& status = usr.BattleStatus();
					if ( status.spectator ) continue;
					if ( parsedteams.find( status.team ) != parsedteams.end() ) continue; // skip duplicates
					parsedteams.insert( status.team );

					tdf.EnterSection( _T("TEAM") + TowxString( teams_to_sorted_teams[status.team] ) );
						if ( !usync().VersionSupports( IUnitSync::USYNC_GetSkirmishAI ) && status.IsBot() )
						{
								tdf.Append( _T("AIDLL"), status.aishortname );
								tdf.Append( _T("TeamLeader"), player_to_number[&battle.GetUser( status.owner )] ); // bot owner is the team leader
						}
						else
						{
								if ( status.IsBot() )
								{
										tdf.Append( _T("TeamLeader"), player_to_number[&battle.GetUser( status.owner )] );
								}
								else
								{
										tdf.Append( _T("TeamLeader"), player_to_number[&usr] );
								}
						}
						if ( battle.IsProxy() )
						{
							if ( startpostype == IBattle::ST_Pick )
							{
									tdf.Append(_T("StartPosX"), status.pos.x );
									tdf.Append(_T("StartPosZ"), status.pos.y );
							}
							else if ( ( startpostype == IBattle::ST_Fixed ) || ( startpostype == IBattle::ST_Random ) )
							{
									int teamnumber = teams_to_sorted_teams[status.team];
									if ( teamnumber < int(remap_positions.size()) ) // don't overflow
									{
										StartPos position = remap_positions[teamnumber];
										tdf.Append(_T("StartPosX"), position.x );
										tdf.Append(_T("StartPosZ"), position.y );
									}
							}
						}
						else
						{
							if ( startpostype == IBattle::ST_Pick )
							{
									tdf.Append(_T("StartPosX"), status.pos.x );
									tdf.Append(_T("StartPosZ"), status.pos.y );
							}
						}

						tdf.Append( _T("AllyTeam"),status.ally );

						wxString colourstring =
								TowxString( status.colour.Red()/255.0 ) + _T(' ') +
								TowxString( status.colour.Green()/255.0 ) + _T(' ') +
								TowxString( status.colour.Blue()/255.0 );
						tdf.Append( _T("RGBColor"), colourstring);

						unsigned int side = status.side;
						if ( side < sides.GetCount() ) tdf.Append( _T("Side"), sides[side] );
						tdf.Append( _T("Handicap"), status.handicap );
					tdf.LeaveSection();
			}

			tdf.AppendLineBreak();


			unsigned int maxiter = std::max( NumUsers, battle.GetLastRectIdx() + 1 );
			std::set<int> parsedallys;
			for ( unsigned int i = 0; i < maxiter; i++ )
			{

					User& usr = battle.GetUser( i );
					UserBattleStatus& status = usr.BattleStatus();
					BattleStartRect sr = battle.GetStartRect( i );
					if ( status.spectator && !sr.IsOk() ) continue;
					int ally = status.ally;
					if ( status.spectator ) ally = i;
					if ( parsedallys.find( ally ) != parsedallys.end() ) continue; // skip duplicates
					sr = battle.GetStartRect( ally );
					parsedallys.insert( ally );

					tdf.EnterSection( _T("ALLYTEAM") + TowxString( ally ) );
						tdf.Append( _T("NumAllies"), 0 );
						if ( sr.IsOk() )
						{
								const char* old_locale = std::setlocale(LC_NUMERIC, "C");

								tdf.Append( _T("StartRectLeft"), wxString::Format( _T("%.3f"), sr.left / 200.0 ) );
								tdf.Append( _T("StartRectTop"), wxString::Format( _T("%.3f"), sr.top / 200.0 ) );
								tdf.Append( _T("StartRectRight"), wxString::Format( _T("%.3f"), sr.right / 200.0 ) );
								tdf.Append( _T("StartRectBottom"), wxString::Format( _T("%.3f"), sr.bottom / 200.0 ) );

								std::setlocale(LC_NUMERIC, old_locale);
						}
					tdf.LeaveSection();
			}

    tdf.LeaveSection();

    return ret;

}
Example #2
0
wxString Spring::WriteScriptTxt( IBattle& battle ) const
{
	wxLogMessage(_T("0 WriteScriptTxt called "));

	std::stringstream ret;

	LSL::TDF::TDFWriter tdf(ret);

	// Start generating the script.
	tdf.EnterSection("GAME");

	if ( battle.IsFounderMe() ) {
		tdf.Append("HostIP", ""); //Listen on all addresses for connections when hosting
		if ( battle.GetNatType() == NAT_Hole_punching ) tdf.Append("HostPort", battle.GetMyInternalUdpSourcePort() );
		else tdf.Append("HostPort", battle.GetHostPort() );
	} else {
		tdf.Append("HostIP", battle.GetHostIp() );
		tdf.Append("HostPort", battle.GetHostPort() );
		if ( battle.GetNatType() == NAT_Hole_punching ) {
			tdf.Append("SourcePort", battle.GetMyInternalUdpSourcePort() );
		} else if ( sett().GetClientPort() != 0) {
			tdf.Append("SourcePort", sett().GetClientPort() ); /// this allows to play with broken router by setting SourcePort to some forwarded port.
		}
	}
	tdf.Append("IsHost", battle.IsFounderMe() );

	User& me = battle.GetMe();
	tdf.Append("MyPlayerName", me.GetNick());

	if ( !me.BattleStatus().scriptPassword.empty() ) {
		tdf.Append("MyPasswd", me.BattleStatus().scriptPassword);
	}

	if ( !battle.IsFounderMe() ) {
		tdf.LeaveSection();
		return TowxString(ret.str());
	}

	/**********************************************************************************
																Host-only section
	**********************************************************************************/

	tdf.AppendLineBreak();

	tdf.Append("ModHash", battle.LoadMod().hash);
	tdf.Append("MapHash", battle.LoadMap().hash);

	tdf.Append("Mapname", battle.GetHostMapName());
	tdf.Append("GameType", battle.GetHostModName());

	tdf.AppendLineBreak();

	switch ( battle.GetBattleType() ) {
	case BT_Played:
		break;
	case BT_Replay: {
		wxString path = TowxString(battle.GetPlayBackFilePath());
		if ( path.Find(_T("/")) != wxNOT_FOUND ) path.BeforeLast(_T('/'));
		tdf.Append("DemoFile", STD_STRING(path));
		tdf.AppendLineBreak();
		break;
	}
	case BT_Savegame: {
		wxString path = TowxString(battle.GetPlayBackFilePath());
		if ( path.Find(_T("/")) != wxNOT_FOUND ) path.BeforeLast(_T('/'));
		tdf.Append("Savefile", STD_STRING(path));
		tdf.AppendLineBreak();
		break;
	}
	default:
		slLogDebugFunc("");
		break;
	}

	const long startpostype = LSL::Util::FromString<long>(
					  battle.CustomBattleOptions().getSingleValue("startpostype", LSL::Enum::EngineOption ));

	std::vector<LSL::StartPos> remap_positions;
	if ( battle.IsProxy() && ( startpostype != IBattle::ST_Pick ) && ( startpostype != IBattle::ST_Choose ) ) {
		std::set<int> parsedteams;
		unsigned int NumUsers = battle.GetNumUsers();
		unsigned int NumTeams = 0;
		for ( unsigned int i = 0; i < NumUsers; i++ ) {
			User& usr = battle.GetUser( i );
			UserBattleStatus& status = usr.BattleStatus();
			if ( status.spectator ) continue;
			if ( parsedteams.find( status.team ) != parsedteams.end() ) continue; // skip duplicates
			parsedteams.insert( status.team );
			NumTeams++;
		}

		LSL::MapInfo infos = battle.LoadMap().info;
		unsigned int nummapstartpositions = infos.positions.size();
		unsigned int copysize = std::min( nummapstartpositions, NumTeams );
		remap_positions = std::vector<LSL::StartPos> ( infos.positions.begin(), infos.positions.begin() + copysize ); // only add the first x positions

		if ( startpostype == IBattle::ST_Random ) {
			std::random_shuffle( remap_positions.begin(), remap_positions.end() ); // shuffle the positions
		}

	}
	if ( battle.IsProxy() ) {
		if ( ( startpostype == IBattle::ST_Random ) || ( startpostype == IBattle::ST_Fixed ) ) {
			tdf.Append("startpostype", IBattle::ST_Pick );
		} else tdf.Append("startpostype", startpostype );
	} else tdf.Append("startpostype", startpostype );

	tdf.EnterSection("mapoptions");
	for (const auto& it : battle.CustomBattleOptions().getOptions( LSL::Enum::MapOption )) {
		tdf.Append(it.first, it.second.second);
	}
	tdf.LeaveSection();


	tdf.EnterSection("modoptions");
	tdf.Append("relayhoststartpostype", startpostype ); // also save the original wanted setting
	for (const auto& it : battle.CustomBattleOptions().getOptions( LSL::Enum::ModOption )) {
		tdf.Append(it.first, it.second.second);
	}
	tdf.LeaveSection();

	std::map<std::string,int> units = battle.RestrictedUnits();
	tdf.Append("NumRestrictions", units.size());
	tdf.EnterSection("RESTRICT");
	int restrictcount = 0;
	for ( std::map<std::string, int>::const_iterator itor = units.begin(); itor != units.end(); ++itor ) {
		tdf.Append(stdprintf("Unit%d", restrictcount), itor->first );
		tdf.Append(stdprintf("Limit%d", restrictcount), itor->second );
		restrictcount++;
	}
	tdf.LeaveSection();


	tdf.AppendLineBreak();

	if ( battle.IsProxy() ) {
		tdf.Append("NumPlayers", battle.GetNumPlayers() -1 );
		tdf.Append("NumUsers", battle.GetNumUsers() -1 );
	} else {
		tdf.Append("NumPlayers", battle.GetNumPlayers() );
		tdf.Append("NumUsers", battle.GetNumUsers() );
	}

	tdf.AppendLineBreak();

	unsigned int NumUsers = battle.GetNumUsers();

	typedef std::map<int, int> ProgressiveTeamsVec;
	typedef ProgressiveTeamsVec::iterator ProgressiveTeamsVecIter;
	ProgressiveTeamsVec teams_to_sorted_teams; // original team -> progressive team
	int free_team = 0;
	std::map<User*, int> player_to_number; // player -> ordernumber
	srand ( time(NULL) );
	for ( unsigned int i = 0; i < NumUsers; i++ ) {
		User& user = battle.GetUser( i );
		UserBattleStatus& status = user.BattleStatus();
		if ( !status.spectator ) {
			ProgressiveTeamsVecIter itor = teams_to_sorted_teams.find ( status.team );
			if ( itor == teams_to_sorted_teams.end() ) {
				teams_to_sorted_teams[status.team] = free_team;
				free_team++;
			}
		}
		if ( battle.IsProxy() && ( user.GetNick() == battle.GetFounder().GetNick() ) ) continue;
		if ( status.IsBot() ) continue;
		tdf.EnterSection(stdprintf("PLAYER%d", i));
		tdf.Append("Name", user.GetNick() );
		tdf.Append("CountryCode", STD_STRING(TowxString(user.GetCountry()).Lower()));
		tdf.Append("Spectator", status.spectator );
		tdf.Append("Rank", (int)user.GetRank() );
		tdf.Append("IsFromDemo", int(status.isfromdemo) );
		if ( !status.scriptPassword.empty() ) {
			tdf.Append("Password", status.scriptPassword );
		}
		if ( !status.spectator ) {
			tdf.Append("Team", teams_to_sorted_teams[status.team] );
		} else {
			int speccteam = 0;
			if ( !teams_to_sorted_teams.empty() ) speccteam = rand() % teams_to_sorted_teams.size();
			tdf.Append("Team", speccteam );
		}
		tdf.LeaveSection();
		player_to_number[&user] = i;
	}
	for ( unsigned int i = 0; i < NumUsers; i++ ) {
		User& user = battle.GetUser( i );
		UserBattleStatus& status = user.BattleStatus();
		if ( !status.IsBot() ) continue;
		tdf.EnterSection(stdprintf("AI%d", i));
		tdf.Append("Name", user.GetNick()); // AI's nick;
		tdf.Append("ShortName", status.aishortname ); // AI libtype
		tdf.Append("Version", status.aiversion ); // AI libtype version
		tdf.Append("Team", teams_to_sorted_teams[status.team] );
		tdf.Append("IsFromDemo", int(status.isfromdemo) );
		tdf.Append("Host", player_to_number[&battle.GetUser( status.owner )] );
		tdf.EnterSection("Options");
		int optionmapindex = battle.CustomBattleOptions().GetAIOptionIndex(user.GetNick());
		if ( optionmapindex > 0 ) {
			for (const auto& it : battle.CustomBattleOptions().getOptions((LSL::Enum::GameOption)optionmapindex )) {
				tdf.Append(it.first, it.second.second);
			}
		}
		tdf.LeaveSection();
		tdf.LeaveSection();
		player_to_number[&user] = i;
	}

	tdf.AppendLineBreak();

	std::set<int> parsedteams;
	const auto sides = LSL::usync().GetSides(battle.GetHostModName());
	for ( unsigned int i = 0; i < NumUsers; i++ ) {
		User& usr = battle.GetUser( i );
		UserBattleStatus& status = usr.BattleStatus();
		if ( status.spectator ) continue;
		if ( parsedteams.find( status.team ) != parsedteams.end() ) continue; // skip duplicates
		parsedteams.insert( status.team );

		tdf.EnterSection(stdprintf("TEAM%d", teams_to_sorted_teams[status.team]));
		if ( status.IsBot() ) {
			tdf.Append("TeamLeader", player_to_number[&battle.GetUser( status.owner )] );
		} else {
			tdf.Append("TeamLeader", player_to_number[&usr] );
		}
		if ( battle.IsProxy() ) {
			if ( startpostype == IBattle::ST_Pick ) {
				tdf.Append("StartPosX", status.pos.x );
				tdf.Append("StartPosZ", status.pos.y );
			} else if ( ( startpostype == IBattle::ST_Fixed ) || ( startpostype == IBattle::ST_Random ) ) {
				int teamnumber = teams_to_sorted_teams[status.team];
				if ( teamnumber < int(remap_positions.size()) ) { // don't overflow
					LSL::StartPos position = remap_positions[teamnumber];
					tdf.Append("StartPosX", position.x );
					tdf.Append("StartPosZ", position.y );
				}
			}
		} else {
			if ( startpostype == IBattle::ST_Pick ) {
				tdf.Append("StartPosX", status.pos.x );
				tdf.Append("StartPosZ", status.pos.y );
			}
		}

		tdf.Append("AllyTeam",status.ally );

		wxString colourstring =
			TowxString( status.colour.Red()/255.0 ) + _T(' ') +
			TowxString( status.colour.Green()/255.0 ) + _T(' ') +
			TowxString( status.colour.Blue()/255.0 );
		tdf.Append("RGBColor", STD_STRING(colourstring));

		unsigned int side = status.side;
		if ( side < sides.size() ) tdf.Append("Side", sides[side] );
		tdf.Append("Handicap", status.handicap );
		tdf.LeaveSection();
	}

	tdf.AppendLineBreak();

	unsigned int maxiter = std::max( NumUsers, battle.GetLastRectIdx() + 1 );
	std::set<int> parsedallys;
	for ( unsigned int i = 0; i < maxiter; i++ ) {

		User& usr = battle.GetUser( i );
		UserBattleStatus& status = usr.BattleStatus();
		BattleStartRect sr = battle.GetStartRect( i );
		if ( status.spectator && !sr.IsOk() )
			continue;
		int ally = status.ally;
		if ( status.spectator )
			ally = i;
		if ( parsedallys.find( ally ) != parsedallys.end() )
			continue; // skip duplicates
		sr = battle.GetStartRect( ally );
		parsedallys.insert( ally );

		tdf.EnterSection( stdprintf("ALLYTEAM%d", ally));
		tdf.Append("NumAllies", 0 );
		if ( startpostype == IBattle::ST_Choose ) {
			if ( sr.IsOk() ) {
				const char* old_locale = std::setlocale(LC_NUMERIC, "C");

				tdf.Append("StartRectLeft", wxFormat( _T("%.3f") ) % ( sr.left / 200.0 ) );
				tdf.Append("StartRectTop", wxFormat( _T("%.3f") ) % ( sr.top / 200.0 ) );
				tdf.Append("StartRectRight", wxFormat( _T("%.3f") ) % ( sr.right / 200.0 ) );
				tdf.Append("StartRectBottom", wxFormat( _T("%.3f") ) % ( sr.bottom / 200.0 ) );

				std::setlocale(LC_NUMERIC, old_locale);
			}
		}
		tdf.LeaveSection();
	}

	tdf.LeaveSection();
	return TowxString(ret.str());
}