1 /* Copyright (C) 2007 The SpringLobby Team. All rights reserved. */
2 //
3 // Class: Battle
4 //
5 #include "battle.h"
6 #include "ui.h"
7 #include "server.h"
8 #include "user.h"
9 #include "utils/misc.h"
10 #include "utils/debug.h"
11 #include "utils/conversion.h"
12 #include <lslutils/misc.h>
13 #include "utils/uievents.h"
14 #include "utils/battleevents.h"
15 #include "uiutils.h"
16 #include "settings.h"
17 #include "useractions.h"
18 #include "utils/customdialogs.h"
19 #include <lslutils/conversion.h>
20 #include "iconimagelist.h"
21 #include "spring.h"
22 
23 #include <wx/timer.h>
24 #include <wx/image.h>
25 #include <wx/string.h>
26 #include <wx/log.h>
27 #include <wx/filename.h>
28 
29 const unsigned int TIMER_INTERVAL         = 1000;
30 const unsigned int TIMER_ID               = 101;
31 
BEGIN_EVENT_TABLE(Battle,wxEvtHandler)32 BEGIN_EVENT_TABLE(Battle, wxEvtHandler)
33     EVT_TIMER(TIMER_ID, Battle::OnTimer)
34 END_EVENT_TABLE()
35 
36 Battle::Battle( Server& serv, int id ) :
37 		IBattle(),
38         m_serv(serv),
39         m_ah(*this),
40         m_autolock_on_start(false),
41         m_id( id )
42 
43 {
44 	ConnectGlobalEvent(this, GlobalEvent::OnUnitsyncReloaded, wxObjectEventFunction(&Battle::OnUnitsyncReloaded));
45     m_opts.battleid =  m_id;
46 }
47 
48 
~Battle()49 Battle::~Battle()
50 {
51 }
52 
53 
SendHostInfo(HostInfo update)54 void Battle::SendHostInfo( HostInfo update )
55 {
56     m_serv.SendHostInfo( update );
57 }
58 
59 
SendHostInfo(const wxString & Tag)60 void Battle::SendHostInfo( const wxString& Tag )
61 {
62     m_serv.SendHostInfo( Tag );
63 }
64 
65 
Update()66 void Battle::Update()
67 {
68 	BattleEvents::GetBattleEventSender( BattleEvents::BattleInfoUpdate ).SendEvent( std::make_pair(this,wxString()) );
69 }
70 
71 
Update(const wxString & Tag)72 void Battle::Update( const wxString& Tag )
73 {
74 	BattleEvents::GetBattleEventSender( BattleEvents::BattleInfoUpdate ).SendEvent( std::make_pair(this,Tag) );
75 }
76 
77 
Join(const wxString & password)78 void Battle::Join( const wxString& password )
79 {
80     m_serv.JoinBattle( m_opts.battleid, password );
81     m_is_self_in = true;
82 }
83 
84 
Leave()85 void Battle::Leave()
86 {
87     m_serv.LeaveBattle( m_opts.battleid );
88 }
89 
90 
OnRequestBattleStatus()91 void Battle::OnRequestBattleStatus()
92 {
93     UserBattleStatus& bs = m_serv.GetMe().BattleStatus();
94     bs.team = GetFreeTeam( true );
95     bs.ally = GetFreeAlly( true );
96     bs.spectator = false;
97     bs.colour = sett().GetBattleLastColour();
98     bs.side = sett().GetBattleLastSideSel( GetHostModName() );
99     // theres some highly annoying bug with color changes on player join/leave.
100     if ( !bs.colour.IsOk() ) bs.colour = GetFreeColour( GetMe() );
101 
102     SendMyBattleStatus();
103 }
104 
105 
SendMyBattleStatus()106 void Battle::SendMyBattleStatus()
107 {
108     UserBattleStatus& bs = m_serv.GetMe().BattleStatus();
109     if ( IsSynced() ) bs.sync = SYNC_SYNCED;
110     else bs.sync = SYNC_UNSYNCED;
111     m_serv.SendMyBattleStatus( bs );
112 }
113 
114 
SetImReady(bool ready)115 void Battle::SetImReady( bool ready )
116 {
117     UserBattleStatus& bs = m_serv.GetMe().BattleStatus();
118 
119     bs.ready = ready;
120 
121     //m_serv.GetMe().SetBattleStatus( bs );
122     SendMyBattleStatus();
123 }
124 
125 
126 
127 
128 
129 /*bool Battle::HasMod()
130 {
131   return LSL::usync().ModExists( m_opts.modname );
132 }*/
133 
134 
Say(const wxString & msg)135 void Battle::Say( const wxString& msg )
136 {
137     m_serv.SayBattle( m_opts.battleid, msg );
138 }
139 
140 
DoAction(const wxString & msg)141 void Battle::DoAction( const wxString& msg )
142 {
143     m_serv.DoActionBattle( m_opts.battleid, msg );
144 }
145 
SetLocalMap(const LSL::UnitsyncMap & map)146 void Battle::SetLocalMap( const LSL::UnitsyncMap& map )
147 {
148 	IBattle::SetLocalMap( map );
149     if ( IsFounderMe() )  LoadMapDefaults( TowxString(map.name) );
150 }
151 
GetMe()152 User& Battle::GetMe()
153 {
154     return m_serv.GetMe();
155 }
156 
GetMe() const157 const User& Battle::GetMe() const
158 {
159     return m_serv.GetMe();
160 }
161 
SaveMapDefaults()162 void Battle::SaveMapDefaults()
163 {
164     // save map preset
165         const wxString mapname = TowxString(LoadMap().name);
166         const std::string startpostype = CustomBattleOptions().getSingleValue( "startpostype", LSL::OptionsWrapper::EngineOption );
167         sett().SetMapLastStartPosType( mapname, TowxString(startpostype));
168 		std::vector<Settings::SettStartBox> rects;
169 		for( unsigned int i = 0; i <= GetLastRectIdx(); ++i )
170 		{
171 			 BattleStartRect rect = GetStartRect( i );
172 			 if ( rect.IsOk() )
173 			 {
174 				 Settings::SettStartBox box;
175 				 box.ally = rect.ally;
176 				 box.topx = rect.left;
177 				 box.topy = rect.top;
178 				 box.bottomx = rect.right;
179 				 box.bottomy = rect.bottom;
180 				 rects.push_back( box );
181 			 }
182 		}
183 		sett().SetMapLastRectPreset( mapname, rects );
184 }
185 
LoadMapDefaults(const wxString & mapname)186 void Battle::LoadMapDefaults( const wxString& mapname )
187 {
188     CustomBattleOptions().setSingleOption( "startpostype",
189                                            STD_STRING(sett().GetMapLastStartPosType(mapname)),
190                                            LSL::OptionsWrapper::EngineOption );
191 	SendHostInfo( wxFormat( _T("%d_startpostype") ) % LSL::OptionsWrapper::EngineOption );
192 
193 	for( unsigned int i = 0; i <= GetLastRectIdx(); ++i ) if ( GetStartRect( i ).IsOk() ) RemoveStartRect(i); // remove all rects
194 	SendHostInfo( IBattle::HI_StartRects );
195 
196 	std::vector<Settings::SettStartBox> savedrects = sett().GetMapLastRectPreset( mapname );
197 	for ( std::vector<Settings::SettStartBox>::const_iterator itor = savedrects.begin(); itor != savedrects.end(); ++itor )
198 	{
199 		AddStartRect( itor->ally, itor->topx, itor->topy, itor->bottomx, itor->bottomy );
200 	}
201 	SendHostInfo( IBattle::HI_StartRects );
202 }
203 
OnUserAdded(User & user)204 User& Battle::OnUserAdded( User& user )
205 {
206 		user = IBattle::OnUserAdded( user );
207 		if ( &user == &GetMe() && (m_timer == NULL) )
208 		{
209 			 m_timer = new wxTimer(this,TIMER_ID);
210 			 m_timer->Start( TIMER_INTERVAL );
211 		}
212     user.SetBattle( this );
213     user.BattleStatus().isfromdemo = false;
214 
215     if ( IsFounderMe() )
216     {
217         if ( CheckBan( user ) ) return user;
218 
219 		if ( ( &user != &GetMe() ) && !user.BattleStatus().IsBot() && ( m_opts.rankneeded != UserStatus::RANK_1 ) && !user.BattleStatus().spectator )
220         {
221 			 if ( m_opts.rankneeded > UserStatus::RANK_1 && user.GetStatus().rank < m_opts.rankneeded )
222 			 {
223 				DoAction( _T("Rank limit autospec: ") + user.GetNick() );
224 				ForceSpectator( user, true );
225 			 }
226 			 else if ( m_opts.rankneeded < UserStatus::RANK_1 && user.GetStatus().rank > ( -m_opts.rankneeded - 1 ) )
227 			 {
228 				 DoAction( _T("Rank limit autospec: ") + user.GetNick() );
229 				 ForceSpectator( user, true );
230 			 }
231         }
232 
233         m_ah.OnUserAdded( user );
234         if ( !user.BattleStatus().IsBot() && sett().GetBattleLastAutoAnnounceDescription() ) DoAction( m_opts.description );
235     }
236     // any code here may be skipped if the user was autokicked
237     return user;
238 }
239 
OnUserBattleStatusUpdated(User & user,UserBattleStatus status)240 void Battle::OnUserBattleStatusUpdated( User &user, UserBattleStatus status )
241 {
242     if ( IsFounderMe() )
243     {
244 			if ( ( &user != &GetMe() ) && !status.IsBot() && ( m_opts.rankneeded != UserStatus::RANK_1 ) && !status.spectator )
245 			{
246 				if ( m_opts.rankneeded > UserStatus::RANK_1 && user.GetStatus().rank < m_opts.rankneeded )
247 				{
248 					DoAction( _T("Rank limit autospec: ") + user.GetNick() );
249 					ForceSpectator( user, true );
250 				}
251 				else if ( m_opts.rankneeded < UserStatus::RANK_1 && user.GetStatus().rank > ( -m_opts.rankneeded - 1 ) )
252 				{
253 					DoAction( _T("Rank limit autospec: ") + user.GetNick() );
254 					ForceSpectator( user, true );
255 				}
256 			}
257 			UserBattleStatus previousstatus = user.BattleStatus();
258 			if ( m_opts.lockexternalbalancechanges )
259 			{
260 				if ( previousstatus.team != status.team )
261 				{
262 					 ForceTeam( user, previousstatus.team );
263 					 status.team = previousstatus.team;
264 				}
265 				if ( previousstatus.ally != status.ally )
266 				{
267 					ForceAlly( user, previousstatus.ally );
268 					status.ally = previousstatus.ally;
269 				}
270 			}
271     }
272 		IBattle::OnUserBattleStatusUpdated( user, status );
273     if ( status.handicap != 0 )
274     {
275 		UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
276 				UiEvents::OnBattleActionData( wxString(_T(" ")) , ( _T("Warning: user ") + user.GetNick() + _T(" got bonus ") ) << status.handicap )
277 			);
278     }
279 		if ( IsFounderMe() )
280 		{
281 			if ( ShouldAutoStart() )
282 			{
283 				if ( sett().GetBattleLastAutoStartState() )
284 				{
285 					if ( !spring().IsRunning() ) StartHostedBattle();
286 				}
287 			}
288 		}
289 	ShouldAutoUnspec();
290 	ui().OnUserBattleStatus( *this, user );
291 }
292 
293 
OnUserRemoved(User & user)294 void Battle::OnUserRemoved( User& user )
295 {
296     m_ah.OnUserRemoved(user);
297     IBattle::OnUserRemoved( user );
298 	ShouldAutoUnspec();
299 }
300 
301 
RingNotReadyPlayers()302 void Battle::RingNotReadyPlayers()
303 {
304     for (user_map_t::size_type i = 0; i < GetNumUsers(); i++)
305     {
306         User& u = GetUser(i);
307         UserBattleStatus& bs = u.BattleStatus();
308         if ( bs.IsBot() ) continue;
309         if ( !bs.ready && !bs.spectator ) m_serv.Ring( u.GetNick() );
310     }
311 }
312 
RingNotSyncedPlayers()313 void Battle::RingNotSyncedPlayers()
314 {
315     for (user_map_t::size_type i = 0; i < GetNumUsers(); i++)
316     {
317         User& u = GetUser(i);
318         UserBattleStatus& bs = u.BattleStatus();
319         if ( bs.IsBot() ) continue;
320         if ( !bs.sync && !bs.spectator ) m_serv.Ring( u.GetNick() );
321     }
322 }
323 
RingNotSyncedAndNotReadyPlayers()324 void Battle::RingNotSyncedAndNotReadyPlayers()
325 {
326     for (user_map_t::size_type i = 0; i < GetNumUsers(); i++)
327     {
328         User& u = GetUser(i);
329         UserBattleStatus& bs = u.BattleStatus();
330         if ( bs.IsBot() ) continue;
331         if ( ( !bs.sync || !bs.ready ) && !bs.spectator ) m_serv.Ring( u.GetNick() );
332     }
333 }
334 
RingPlayer(const User & u)335 void Battle::RingPlayer( const User& u )
336 {
337 	if ( u.BattleStatus().IsBot() ) return;
338 	m_serv.Ring( u.GetNick() );
339 }
340 
ExecuteSayCommand(const wxString & cmd)341 bool Battle::ExecuteSayCommand( const wxString& cmd )
342 {
343     wxString cmd_name=cmd.BeforeFirst(' ').Lower();
344     if ( cmd_name == _T("/me") )
345     {
346         m_serv.DoActionBattle( m_opts.battleid, cmd.AfterFirst(' ') );
347         return true;
348     }
349 		if ( cmd_name == _T("/replacehostip") )
350 		{
351 				wxString ip = cmd.AfterFirst(' ');
352 				if ( ip.IsEmpty() ) return false;
353 				m_opts.ip = ip;
354 				return true;
355 		}
356     //< quick hotfix for bans
357     if (IsFounderMe())
358     {
359         if ( cmd_name == _T("/ban") )
360         {
361             wxString nick=cmd.AfterFirst(' ');
362             m_banned_users.insert(nick);
363             try
364 						{
365 							User& user = GetUser( nick );
366 							m_serv.BattleKickPlayer( m_opts.battleid, user );
367 						}
368 						catch( assert_exception ) {}
369 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
370 					UiEvents::OnBattleActionData( wxString(_T(" ")) , nick+_T(" banned") )
371 				);
372 
373 			//m_serv.DoActionBattle( m_opts.battleid, cmd.AfterFirst(' ') );
374             return true;
375         }
376         if ( cmd_name == _T("/unban") )
377         {
378             wxString nick=cmd.AfterFirst(' ');
379             m_banned_users.erase(nick);
380 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
381 					UiEvents::OnBattleActionData( wxString(_T(" ")) , nick+_T(" unbanned") )
382 				);
383             //m_serv.DoActionBattle( m_opts.battleid, cmd.AfterFirst(' ') );
384             return true;
385         }
386         if ( cmd_name == _T("/banlist") )
387         {
388 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
389 					UiEvents::OnBattleActionData( wxString(_T(" ")) , _T("banlist:") )
390 				);
391 
392 			for (std::set<wxString>::const_iterator i=m_banned_users.begin();i!=m_banned_users.end();++i)
393             {
394 				UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
395 						UiEvents::OnBattleActionData( wxString(_T(" ")) , *i )
396 					);
397             }
398             for (std::set<wxString>::iterator i=m_banned_ips.begin();i!=m_banned_ips.end();++i)
399             {
400 				UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
401 						UiEvents::OnBattleActionData( wxString(_T(" ")) , *i )
402 					);
403 
404             }
405             return true;
406         }
407         if ( cmd_name == _T("/unban") )
408         {
409             wxString nick=cmd.AfterFirst(' ');
410             m_banned_users.erase(nick);
411             m_banned_ips.erase(nick);
412 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
413 					UiEvents::OnBattleActionData( wxString(_T(" ")) , nick+_T(" unbanned") )
414 				);
415 
416             //m_serv.DoActionBattle( m_opts.battleid, cmd.AfterFirst(' ') );
417             return true;
418         }
419         if ( cmd_name == _T("/ipban") )
420         {
421             wxString nick=cmd.AfterFirst(' ');
422             m_banned_users.insert(nick);
423 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
424 					UiEvents::OnBattleActionData( wxString(_T(" ")) , nick+_T(" banned") )
425 				);
426 
427             if (UserExists(nick))
428             {
429                 User &user=GetUser(nick);
430                 if (!user.BattleStatus().ip.empty())
431                 {
432                     m_banned_ips.insert(user.BattleStatus().ip);
433 					UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
434 							UiEvents::OnBattleActionData( wxString(_T(" ")) , user.BattleStatus().ip+_T(" banned") )
435 						);
436                 }
437                 m_serv.BattleKickPlayer( m_opts.battleid, user );
438             }
439             //m_banned_ips.erase(nick);
440 
441             //m_serv.DoActionBattle( m_opts.battleid, cmd.AfterFirst(' ') );
442             return true;
443         }
444     }
445     //>
446     return false;
447 }
448 ///< quick hotfix for bans
449 /// returns true if user is banned (and hence has been kicked)
CheckBan(User & user)450 bool Battle::CheckBan(User &user)
451 {
452     if (IsFounderMe())
453     {
454         if (m_banned_users.count(user.GetNick())>0
455                 || useractions().DoActionOnUser(UserActions::ActAutokick, user.GetNick() ) )
456         {
457             KickPlayer(user);
458 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
459 					UiEvents::OnBattleActionData( wxString(_T(" ")) , user.GetNick()+_T(" is banned, kicking") )
460 				);
461             return true;
462         }
463         else if (m_banned_ips.count(user.BattleStatus().ip)>0)
464         {
465 			UiEvents::GetUiEventSender( UiEvents::OnBattleActionEvent ).SendEvent(
466 					UiEvents::OnBattleActionData( wxString(_T(" ")) , user.BattleStatus().ip+_T(" is banned, kicking") )
467 				);
468             KickPlayer(user);
469             return true;
470         }
471     }
472     return false;
473 }
474 ///>
475 
SetAutoLockOnStart(bool value)476 void Battle::SetAutoLockOnStart( bool value )
477 {
478     m_autolock_on_start = value;
479 }
480 
GetAutoLockOnStart()481 bool Battle::GetAutoLockOnStart()
482 {
483     return m_autolock_on_start;
484 }
485 
SetLockExternalBalanceChanges(bool value)486 void Battle::SetLockExternalBalanceChanges( bool value )
487 {
488     if ( value ) DoAction( _T("has locked player balance changes") );
489     else DoAction( _T("has unlocked player balance changes") );
490     m_opts.lockexternalbalancechanges = value;
491 }
492 
GetLockExternalBalanceChanges()493 bool Battle::GetLockExternalBalanceChanges()
494 {
495     return m_opts.lockexternalbalancechanges;
496 }
497 
498 
AddBot(const wxString & nick,UserBattleStatus status)499 void Battle::AddBot( const wxString& nick, UserBattleStatus status )
500 {
501     m_serv.AddBot( m_opts.battleid, nick, status );
502 }
503 
504 
505 
ForceSide(User & user,int side)506 void Battle::ForceSide( User& user, int side )
507 {
508 		m_serv.ForceSide( m_opts.battleid, user, side );
509 }
510 
511 
ForceTeam(User & user,int team)512 void Battle::ForceTeam( User& user, int team )
513 {
514 	IBattle::ForceTeam( user, team );
515   m_serv.ForceTeam( m_opts.battleid, user, team );
516 }
517 
518 
ForceAlly(User & user,int ally)519 void Battle::ForceAlly( User& user, int ally )
520 {
521 	IBattle::ForceAlly( user, ally );
522   m_serv.ForceAlly( m_opts.battleid, user, ally );
523 }
524 
525 
ForceColour(User & user,const wxColour & col)526 void Battle::ForceColour( User& user, const wxColour& col )
527 {
528 		IBattle::ForceColour( user, col );
529     m_serv.ForceColour( m_opts.battleid, user, col );
530 }
531 
532 
ForceSpectator(User & user,bool spectator)533 void Battle::ForceSpectator( User& user, bool spectator )
534 {
535 		m_serv.ForceSpectator( m_opts.battleid, user, spectator );
536 }
537 
538 
KickPlayer(User & user)539 void Battle::KickPlayer( User& user )
540 {
541     m_serv.BattleKickPlayer( m_opts.battleid, user );
542 }
543 
SetHandicap(User & user,int handicap)544 void Battle::SetHandicap( User& user, int handicap)
545 {
546     m_serv.SetHandicap ( m_opts.battleid, user, handicap );
547 }
548 
549 
550 
ForceUnsyncedToSpectate()551 void Battle::ForceUnsyncedToSpectate()
552 {
553     size_t numusers = GetNumUsers();
554     for ( size_t i = 0; i < numusers; ++i )
555     {
556         User &user = GetUser(i);
557         UserBattleStatus& bs = user.BattleStatus();
558         if ( bs.IsBot() ) continue;
559         if ( !bs.spectator && !bs.sync ) ForceSpectator( user, true );
560     }
561 }
562 
ForceUnReadyToSpectate()563 void Battle::ForceUnReadyToSpectate()
564 {
565     size_t numusers = GetNumUsers();
566     for ( size_t i = 0; i < numusers; ++i )
567     {
568         User &user = GetUser(i);
569         UserBattleStatus& bs = user.BattleStatus();
570         if ( bs.IsBot() ) continue;
571         if ( !bs.spectator && !bs.ready ) ForceSpectator( user, true );
572     }
573 }
574 
ForceUnsyncedAndUnreadyToSpectate()575 void Battle::ForceUnsyncedAndUnreadyToSpectate()
576 {
577     size_t numusers = GetNumUsers();
578     for ( size_t i = 0; i < numusers; ++i )
579     {
580         User &user = GetUser(i);
581         UserBattleStatus& bs = user.BattleStatus();
582         if ( bs.IsBot() ) continue;
583 				if ( !bs.spectator && ( !bs.sync || !bs.ready ) ) ForceSpectator( user, true );
584     }
585 }
586 
587 
UserPositionChanged(const User & user)588 void Battle::UserPositionChanged( const User& user )
589 {
590 	  m_serv.SendUserPosition( user );
591 }
592 
593 
SendScriptToClients()594 void Battle::SendScriptToClients()
595 {
596 	m_serv.SendScriptToClients( GetScript() );
597 }
598 
599 
StartHostedBattle()600 void Battle::StartHostedBattle()
601 {
602 	if ( UserExists( GetMe().GetNick() ) )
603 	{
604 		if ( IsFounderMe() )
605 		{
606 			if ( sett().GetBattleLastAutoControlState() )
607 			{
608 				FixTeamIDs( (IBattle::BalanceType)sett().GetFixIDMethod(), sett().GetFixIDClans(), sett().GetFixIDStrongClans(), sett().GetFixIDGrouping() );
609 				Autobalance( (IBattle::BalanceType)sett().GetBalanceMethod(), sett().GetBalanceClans(), sett().GetBalanceStrongClans(), sett().GetBalanceGrouping() );
610 				FixColours();
611 			}
612 			if ( IsProxy() )
613 			{
614 				if ( UserExists( GetProxy() ) && !GetUser(GetProxy()).Status().in_game )
615 				{
616 					// DON'T set m_generating_script here, it will trick the script generating code to think we're the host
617 					wxString hostscript = spring().WriteScriptTxt( *this );
618 					try
619 					{
620 						wxString path = sett().GetCurrentUsedDataDir() + wxFileName::GetPathSeparator() + _T("relayhost_script.txt");
621 						if ( !wxFile::Access( path, wxFile::write ) ) {
622 								wxLogError( _T("Access denied to script.txt.") );
623 						}
624 
625 						wxFile f( path, wxFile::write );
626 						f.Write( hostscript );
627 						f.Close();
628 
629 					} catch (...) {}
630 					m_serv.SendScriptToProxy( hostscript );
631 				}
632 			}
633 			if( GetAutoLockOnStart() )
634 			{
635 				SetIsLocked( true );
636 				SendHostInfo( IBattle::HI_Locked );
637 			}
638 			sett().SetLastHostMap( GetServer().GetCurrentBattle()->GetHostMapName() );
639 			sett().SaveSettings();
640 			if ( !IsProxy() ) GetServer().StartHostedBattle();
641 			else if ( UserExists( GetProxy() ) && GetUser(GetProxy()).Status().in_game ) // relayhost is already ingame, let's try to join it
642 			{
643 				StartSpring();
644 			}
645 		}
646 	}
647 }
648 
StartSpring()649 void Battle::StartSpring()
650 {
651 	if ( UserExists( GetMe().GetNick() ) && !GetMe().Status().in_game )
652 	{
653 		GetMe().BattleStatus().ready = false;
654 		SendMyBattleStatus();
655 		// set m_generating_script, this will make the script.txt writer realize we're just clients even if using a relayhost
656 		m_generating_script = true;
657 		GetMe().Status().in_game = spring().Run( *this );
658 		m_generating_script = false;
659 		GetMe().SendMyUserStatus();
660 	}
661 	ui().OnBattleStarted( *this );
662 }
663 
OnTimer(wxTimerEvent &)664 void Battle::OnTimer( wxTimerEvent&  )
665 {
666 	if ( !IsFounderMe() ) return;
667 	if ( m_ingame ) return;
668 	int autospect_trigger_time = sett().GetBattleLastAutoSpectTime();
669 	if ( autospect_trigger_time == 0 ) return;
670 	time_t now = time(0);
671 	for ( unsigned int i = 0; i < GetNumUsers(); i++)
672 	{
673 		User& usr = GetUser( i );
674 		UserBattleStatus& status = usr.BattleStatus();
675 		if ( status.IsBot() || status.spectator ) continue;
676 		if ( status.sync && status.ready ) continue;
677 		if ( &usr == &GetMe() ) continue;
678 		std::map<wxString, time_t>::const_iterator itor = m_ready_up_map.find( usr.GetNick() );
679 		if ( itor != m_ready_up_map.end() )
680 		{
681 			if ( ( now - itor->second ) > autospect_trigger_time )
682 			{
683 				ForceSpectator( usr, true );
684 			}
685 		}
686 	}
687 }
688 
SetInGame(bool value)689 void Battle::SetInGame( bool value )
690 {
691 	time_t now = time(0);
692 	if ( m_ingame && !value )
693 	{
694 		for ( int i = 0; i < long(GetNumUsers()); i++ )
695 		{
696 			User& user = GetUser( i );
697 			UserBattleStatus& status = user.BattleStatus();
698 			if ( status.IsBot() || status.spectator ) continue;
699 			if ( status.ready && status.sync ) continue;
700 			m_ready_up_map[user.GetNick()] = now;
701 		}
702 	}
703 	IBattle::SetInGame( value );
704 }
705 
706 
707 
FixColours()708 void Battle::FixColours()
709 {
710     if ( !IsFounderMe() )return;
711     std::vector<wxColour> &palette = GetFixColoursPalette( m_teams_sizes.size() + 1 );
712     std::vector<int> palette_use( palette.size(), 0 );
713 
714     wxColour my_col = GetMe().BattleStatus().colour; // Never changes color of founder (me) :-)
715     int my_diff = 0;
716     int my_col_i = GetClosestFixColour( my_col, palette_use,my_diff );
717     palette_use[my_col_i]++;
718     std::set<int> parsed_teams;
719 
720     for ( user_map_t::size_type i = 0; i < GetNumUsers(); i++ )
721     {
722         User &user=GetUser(i);
723         if ( &user == &GetMe() ) continue; // skip founder ( yourself )
724         UserBattleStatus& status = user.BattleStatus();
725         if ( status.spectator ) continue;
726         if ( parsed_teams.find( status.team ) != parsed_teams.end() ) continue; // skip duplicates
727         parsed_teams.insert( status.team );
728 
729         wxColour &user_col=status.colour;
730         int user_col_i=GetClosestFixColour(user_col,palette_use, 60);
731         palette_use[user_col_i]++;
732 				for ( user_map_t::size_type j = 0; j < GetNumUsers(); j++ )
733 				{
734 					User &usr=GetUser(j);
735 					if ( usr.BattleStatus().team == status.team )
736 					{
737 						 ForceColour( usr, palette[user_col_i]);
738 					}
739 				}
740     }
741 }
742 
743 
PlayerRankCompareFunction(User * a,User * b)744 bool PlayerRankCompareFunction( User *a, User *b ) // should never operate on nulls. Hence, ASSERT_LOGIC is appropriate here.
745 {
746     ASSERT_LOGIC( a, _T("fail in Autobalance, NULL player") );
747     ASSERT_LOGIC( b, _T("fail in Autobalance, NULL player") );
748     return ( a->GetBalanceRank() > b->GetBalanceRank() );
749 }
750 
PlayerTeamCompareFunction(User * a,User * b)751 bool PlayerTeamCompareFunction( User *a, User *b ) // should never operate on nulls. Hence, ASSERT_LOGIC is appropriate here.
752 {
753     ASSERT_LOGIC( a, _T("fail in Autobalance, NULL player") );
754     ASSERT_LOGIC( b, _T("fail in Autobalance, NULL player") );
755     return ( a->BattleStatus().team > b->BattleStatus().team );
756 }
757 
758 struct Alliance
759 {
760     std::vector<User *>players;
761     float ranksum;
762     int allynum;
AllianceAlliance763     Alliance(): ranksum(0), allynum(-1) {}
AllianceAlliance764     Alliance(int i): ranksum(0), allynum(i) {}
AddPlayerAlliance765     void AddPlayer( User *player )
766     {
767         if ( player )
768         {
769             players.push_back( player );
770             ranksum += player->GetBalanceRank();
771         }
772     }
AddAllianceAlliance773     void AddAlliance( Alliance &other )
774     {
775 		for ( std::vector<User *>::const_iterator i = other.players.begin(); i != other.players.end(); ++i ) AddPlayer( *i );
776     }
operator <Alliance777     bool operator < ( const Alliance &other ) const
778     {
779         return ranksum < other.ranksum;
780     }
781 };
782 
783 struct ControlTeam
784 {
785     std::vector<User*> players;
786     float ranksum;
787     int teamnum;
ControlTeamControlTeam788     ControlTeam(): ranksum(0), teamnum(-1) {}
ControlTeamControlTeam789     ControlTeam( int i ): ranksum(0), teamnum(i) {}
AddPlayerControlTeam790     void AddPlayer( User *player )
791     {
792         if ( player )
793         {
794             players.push_back( player );
795             ranksum += player->GetBalanceRank();
796         }
797     }
AddTeamControlTeam798     void AddTeam( ControlTeam &other )
799     {
800 		for ( std::vector<User*>::const_iterator i = other.players.begin(); i != other.players.end(); ++i ) AddPlayer( *i );
801     }
operator <ControlTeam802     bool operator < (const ControlTeam &other) const
803     {
804         return ranksum < other.ranksum;
805     }
806 };
807 
my_random(int range)808 int my_random( int range )
809 {
810     return rand() % range;
811 }
812 
shuffle(std::vector<User * > & players)813 void shuffle(std::vector<User *> &players) // proper shuffle.
814 {
815     for ( size_t i=0; i < players.size(); ++i ) // the players below i are shuffled, the players above arent
816     {
817         int rn = i + my_random( players.size() - i ); // the top of shuffled part becomes random card from unshuffled part
818         User *tmp = players[i];
819         players[i] = players[rn];
820         players[rn] = tmp;
821     }
822 }
823 
824 /*
825 bool ClanRemovalFunction(const std::map<wxString, Alliance>::value_type &v){
826   return v.second.players.size()<2;
827 }
828 */
829 /*
830 struct ClannersRemovalPredicate{
831   std::map<wxString, Alliance> &clans;
832   PlayerRemovalPredicate(std::map<wxString, Alliance> &clans_):clans(clans_)
833   {
834   }
835   bool operator()(User *u) const{
836     return clans.find(u->GetClan());
837   }
838 }*/
839 
Autobalance(BalanceType balance_type,bool support_clans,bool strong_clans,int numallyteams)840 void Battle::Autobalance( BalanceType balance_type, bool support_clans, bool strong_clans, int numallyteams )
841 {
842     wxLogMessage(_T("Autobalancing alliances, type=%d, clans=%d, strong_clans=%d, numallyteams=%d"),balance_type, support_clans,strong_clans, numallyteams);
843     //size_t i;
844     //int num_alliances;
845     std::vector<Alliance>alliances;
846 	if ( numallyteams == 0 || numallyteams == -1 ) // 0 or 1 -> use num start rects
847     {
848         int ally = 0;
849         for ( unsigned int i = 0; i < GetNumRects(); ++i )
850         {
851             BattleStartRect sr = GetStartRect(i);
852             if ( sr.IsOk() )
853             {
854                 ally=i;
855                 alliances.push_back( Alliance( ally ) );
856                 ally++;
857             }
858         }
859         // make at least two alliances
860         while ( alliances.size() < 2 )
861         {
862             alliances.push_back( Alliance( ally ) );
863             ally++;
864         }
865     }
866     else
867     {
868         for ( int i = 0; i < numallyteams; i++ ) alliances.push_back( Alliance( i ) );
869     }
870 
871     //for(i=0;i<alliances.size();++i)alliances[i].allynum=i;
872 
873     wxLogMessage( _T("number of alliances: %u"), alliances.size() );
874 
875     std::vector<User*> players_sorted;
876     players_sorted.reserve( GetNumUsers() );
877 
878     for ( size_t i = 0; i < GetNumUsers(); ++i )
879     {
880         User& usr = GetUser( i );
881         if ( !usr.BattleStatus().spectator )
882         {
883             players_sorted.push_back( &usr );
884         }
885     }
886 
887     // remove players in the same team so only one remains
888     std::map< int, User*> dedupe_teams;
889 	for ( std::vector<User*>::const_iterator it = players_sorted.begin(); it != players_sorted.end(); ++it )
890     {
891         dedupe_teams[(*it)->BattleStatus().team] = *it;
892     }
893     players_sorted.clear();
894     players_sorted.reserve( dedupe_teams.size() );
895 	for ( std::map<int, User*>::const_iterator it = dedupe_teams.begin(); it != dedupe_teams.end(); ++it )
896     {
897         players_sorted.push_back( it->second );
898     }
899 
900     shuffle( players_sorted );
901 
902     std::map<wxString, Alliance> clan_alliances;
903     if ( support_clans )
904     {
905         for ( size_t i=0; i < players_sorted.size(); ++i )
906         {
907             wxString clan = players_sorted[i]->GetClan();
908             if ( !clan.empty() )
909             {
910                 clan_alliances[clan].AddPlayer( players_sorted[i] );
911             }
912         }
913     };
914 
915     if ( balance_type != balance_random ) std::sort( players_sorted.begin(), players_sorted.end(), PlayerRankCompareFunction );
916 
917     if ( support_clans )
918     {
919 		std::map<wxString, Alliance>::iterator clan_it = clan_alliances.begin();
920         while ( clan_it != clan_alliances.end() )
921         {
922             Alliance &clan = (*clan_it).second;
923             // if clan is too small (only 1 clan member in battle) or too big, dont count it as clan
924             if ( ( clan.players.size() < 2 ) || ( !strong_clans && ( clan.players.size() > ( ( players_sorted.size() + alliances.size() -1 ) / alliances.size() ) ) ) )
925             {
926                 wxLogMessage( _T("removing clan %s"), (*clan_it).first.c_str() );
927                 std::map<wxString, Alliance>::iterator next = clan_it;
928                 ++next;
929                 clan_alliances.erase( clan_it );
930                 clan_it = next;
931                 continue;
932             }
933             wxLogMessage( _T("Inserting clan %s"), (*clan_it).first.c_str() );
934             std::sort( alliances.begin(), alliances.end() );
935             float lowestrank = alliances[0].ranksum;
936             int rnd_k = 1;// number of alliances with rank equal to lowestrank
937             while ( size_t( rnd_k ) < alliances.size() )
938             {
939                 if ( fabs( alliances[rnd_k].ranksum - lowestrank ) > 0.01 ) break;
940                 rnd_k++;
941             }
942             wxLogMessage( _T("number of lowestrank alliances with same rank=%d"), rnd_k );
943             alliances[my_random( rnd_k )].AddAlliance( clan );
944             ++clan_it;
945         }
946     }
947 
948     for ( size_t i = 0; i < players_sorted.size(); ++i )
949     {
950         // skip clanners, those have been added already.
951         if ( clan_alliances.count( players_sorted[i]->GetClan() ) > 0 )
952         {
953             wxLogMessage( _T("clanner already added, nick=%s"), players_sorted[i]->GetNick().c_str() );
954             continue;
955         }
956 
957         // find alliances with lowest ranksum
958         // insert current user into random one out of them
959         // since performance doesnt matter here, i simply sort alliances,
960         // then find how many alliances in beginning have lowest ranksum
961         // note that balance player ranks range from 1 to 1.1 now
962         // i.e. them are quasi equal
963         // so we're essentially adding to alliance with smallest number of players,
964         // the one with smallest ranksum.
965 
966         std::sort( alliances.begin(), alliances.end() );
967         float lowestrank = alliances[0].ranksum;
968         int rnd_k = 1;// number of alliances with rank equal to lowestrank
969         while ( size_t( rnd_k ) < alliances.size() )
970         {
971             if ( fabs( alliances[rnd_k].ranksum - lowestrank ) > 0.01 ) break;
972             rnd_k++;
973         }
974         wxLogMessage( _T("number of lowestrank alliances with same rank=%d"), rnd_k );
975         alliances[my_random( rnd_k )].AddPlayer( players_sorted[i] );
976     }
977 
978     UserList::user_map_t::size_type totalplayers = GetNumUsers();
979     for ( size_t i = 0; i < alliances.size(); ++i )
980     {
981         for ( size_t j = 0; j < alliances[i].players.size(); ++j )
982         {
983             ASSERT_LOGIC( alliances[i].players[j], _T("fail in Autobalance, NULL player") );
984             int balanceteam = alliances[i].players[j]->BattleStatus().team;
985             wxLogMessage( _T("setting team %d to alliance %d"), balanceteam, i );
986             for ( size_t h = 0; h < totalplayers; h++ ) // change ally num of all players in the team
987             {
988               User& usr = GetUser( h );
989               if ( usr.BattleStatus().team == balanceteam ) ForceAlly( usr, alliances[i].allynum );
990             }
991         }
992     }
993 }
994 
FixTeamIDs(BalanceType balance_type,bool support_clans,bool strong_clans,int numcontrolteams)995 void Battle::FixTeamIDs( BalanceType balance_type, bool support_clans, bool strong_clans, int numcontrolteams )
996 {
997     wxLogMessage(_T("Autobalancing teams, type=%d, clans=%d, strong_clans=%d, numcontrolteams=%d"),balance_type, support_clans, strong_clans, numcontrolteams);
998     //size_t i;
999     //int num_alliances;
1000     std::vector<ControlTeam> control_teams;
1001 
1002 	if ( numcontrolteams == 0 || numcontrolteams == -1 ) numcontrolteams = GetNumUsers() - GetSpectators(); // 0 or -1 -> use num players, will use comshare only if no available team slots
1003     IBattle::StartType position_type = (IBattle::StartType)
1004             LSL::Util::FromString<long>(CustomBattleOptions().getSingleValue("startpostype", LSL::OptionsWrapper::EngineOption));
1005     if ( ( position_type == ST_Fixed ) || ( position_type == ST_Random ) ) // if fixed start pos type or random, use max teams = start pos count
1006     {
1007       try
1008       {
1009       	int mapposcount = LoadMap().info.positions.size();
1010         numcontrolteams = std::min( numcontrolteams, mapposcount );
1011       }
1012       catch( assert_exception ) {}
1013     }
1014 
1015     if ( numcontrolteams >= (int)( GetNumUsers() - GetSpectators() ) ) // autobalance behaves weird when trying to put one player per team and i CBA to fix it, so i'll reuse the old code :P
1016     {
1017       // apparently tasserver doesnt like when i fix/force ids of everyone.
1018       std::set<int> allteams;
1019       size_t numusers = GetNumUsers();
1020       for( size_t i = 0; i < numusers; ++i )
1021       {
1022         User &user = GetUser(i);
1023         if( !user.BattleStatus().spectator ) allteams.insert( user.BattleStatus().team );
1024       }
1025       std::set<int> teams;
1026       int t = 0;
1027       for( size_t i = 0; i < GetNumUsers(); ++i )
1028       {
1029         User &user = GetUser(i);
1030         if( !user.BattleStatus().spectator )
1031         {
1032           if( teams.count( user.BattleStatus().team ) )
1033           {
1034             while( allteams.count(t) || teams.count( t ) ) t++;
1035             ForceTeam( GetUser(i), t );
1036             teams.insert( t );
1037           }
1038           else
1039           {
1040             teams.insert( user.BattleStatus().team );
1041           }
1042         }
1043       }
1044       return;
1045     }
1046     for ( int i = 0; i < numcontrolteams; i++ ) control_teams.push_back( ControlTeam( i ) );
1047 
1048     wxLogMessage(_T("number of teams: %u"), control_teams.size() );
1049 
1050     std::vector<User*> players_sorted;
1051     players_sorted.reserve( GetNumUsers() );
1052 
1053     int player_team_counter = 0;
1054 
1055     for ( size_t i = 0; i < GetNumUsers(); ++i ) // don't count spectators
1056     {
1057         if ( !GetUser(i).BattleStatus().spectator )
1058         {
1059             players_sorted.push_back( &GetUser(i) );
1060             // -- server fail? it doesnt work right.
1061             //ForceTeam(GetUser(i),player_team_counter);
1062             player_team_counter++;
1063         }
1064     }
1065 
1066     shuffle( players_sorted );
1067 
1068     std::map<wxString, ControlTeam> clan_teams;
1069     if ( support_clans )
1070     {
1071         for ( size_t i = 0; i < players_sorted.size(); ++i )
1072         {
1073             wxString clan = players_sorted[i]->GetClan();
1074             if ( !clan.empty() )
1075             {
1076                 clan_teams[clan].AddPlayer( players_sorted[i] );
1077             }
1078         }
1079     };
1080 
1081     if ( balance_type != balance_random ) std::sort( players_sorted.begin(), players_sorted.end(), PlayerRankCompareFunction );
1082 
1083     if ( support_clans )
1084     {
1085         std::map<wxString, ControlTeam>::iterator clan_it = clan_teams.begin();
1086         while ( clan_it != clan_teams.end() )
1087         {
1088             ControlTeam &clan = (*clan_it).second;
1089             // if clan is too small (only 1 clan member in battle) or too big, dont count it as clan
1090             if ( ( clan.players.size() < 2 ) || ( !strong_clans && ( clan.players.size() >  ( ( players_sorted.size() + control_teams.size() -1 ) / control_teams.size() ) ) ) )
1091             {
1092                 wxLogMessage(_T("removing clan %s"),(*clan_it).first.c_str());
1093                 std::map<wxString, ControlTeam>::iterator next = clan_it;
1094                 ++next;
1095                 clan_teams.erase( clan_it );
1096                 clan_it = next;
1097                 continue;
1098             }
1099             wxLogMessage( _T("Inserting clan %s"), (*clan_it).first.c_str() );
1100             std::sort( control_teams.begin(), control_teams.end() );
1101             float lowestrank = control_teams[0].ranksum;
1102             int rnd_k = 1; // number of alliances with rank equal to lowestrank
1103             while ( size_t( rnd_k ) < control_teams.size() )
1104             {
1105                 if ( fabs( control_teams[rnd_k].ranksum - lowestrank ) > 0.01 ) break;
1106                 rnd_k++;
1107             }
1108             wxLogMessage(_T("number of lowestrank teams with same rank=%d"), rnd_k );
1109             control_teams[my_random( rnd_k )].AddTeam( clan );
1110             ++clan_it;
1111         }
1112     }
1113 
1114     for (size_t i = 0; i < players_sorted.size(); ++i )
1115     {
1116         // skip clanners, those have been added already.
1117         if ( clan_teams.count( players_sorted[i]->GetClan() ) > 0 )
1118         {
1119             wxLogMessage( _T("clanner already added, nick=%s"),players_sorted[i]->GetNick().c_str() );
1120             continue;
1121         }
1122 
1123         // find teams with lowest ranksum
1124         // insert current user into random one out of them
1125         // since performance doesnt matter here, i simply sort teams,
1126         // then find how many teams in beginning have lowest ranksum
1127         // note that balance player ranks range from 1 to 1.1 now
1128         // i.e. them are quasi equal
1129         // so we're essentially adding to teams with smallest number of players,
1130         // the one with smallest ranksum.
1131 
1132         std::sort( control_teams.begin(), control_teams.end() );
1133         float lowestrank = control_teams[0].ranksum;
1134         int rnd_k = 1; // number of alliances with rank equal to lowestrank
1135         while ( size_t( rnd_k ) < control_teams.size() )
1136         {
1137             if ( fabs ( control_teams[rnd_k].ranksum - lowestrank ) > 0.01 ) break;
1138             rnd_k++;
1139         }
1140         wxLogMessage( _T("number of lowestrank teams with same rank=%d"), rnd_k );
1141         control_teams[my_random( rnd_k )].AddPlayer( players_sorted[i] );
1142     }
1143 
1144 
1145     for ( size_t i=0; i < control_teams.size(); ++i )
1146     {
1147         for ( size_t j = 0; j < control_teams[i].players.size(); ++j )
1148         {
1149             ASSERT_LOGIC( control_teams[i].players[j], _T("fail in Autobalance teams, NULL player") );
1150 			wxString msg = wxFormat( _T("setting player %s to team and ally %d") ) % control_teams[i].players[j]->GetNick() % i;
1151             wxLogMessage( _T("%s"), msg.c_str() );
1152             ForceTeam( *control_teams[i].players[j], control_teams[i].teamnum );
1153             ForceAlly( *control_teams[i].players[j], control_teams[i].teamnum );
1154         }
1155     }
1156 }
1157 
OnUnitsyncReloaded(wxEvent & data)1158 void Battle::OnUnitsyncReloaded( wxEvent& data )
1159 {
1160 	IBattle::OnUnitsyncReloaded( data );
1161 	if ( m_is_self_in ) SendMyBattleStatus();
1162 }
1163 
ShouldAutoUnspec()1164 void Battle::ShouldAutoUnspec()
1165 {
1166 	if ( m_auto_unspec && !IsLocked() && GetMe().BattleStatus().spectator )
1167 	{
1168 		unsigned int numplayers = 0;
1169 		std::map<int, int> allysizes = GetAllySizes();
1170 		for ( std::map<int, int>::const_iterator itor = allysizes.begin(); itor != allysizes.end(); ++itor )
1171 		{
1172 			numplayers += itor->second;
1173 		}
1174 		if ( numplayers < m_auto_unspec_num_players )
1175 		{
1176 			ForceSpectator(GetMe(),false);
1177 		}
1178 	}
1179 }
1180 
SetAutoUnspec(bool value)1181 void Battle::SetAutoUnspec(bool value)
1182 {
1183 	m_auto_unspec = value;
1184 	m_auto_unspec_num_players = GetNumActivePlayers();
1185 	ShouldAutoUnspec();
1186 }
1187 
1188