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