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