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