00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00046 #include "SenseHandler.h"
00047 #include "ActHandler.h"
00048 #include "Parse.h"
00049
00050 #include <signal.h>
00051 #include <string.h>
00052 #include <stdio.h>
00053 #include <iostream.h>
00054
00055
00056
00057
00058
00064 void* sense_callback( void *v )
00065 {
00066 Log.log( 1, "Starting to listen for server messages" );
00067 SenseHandler* s = (SenseHandler*)v;
00068 s->handleMessagesFromServer( );
00069 return NULL;
00070 }
00071
00072
00079 SenseHandler::SenseHandler( Connection *c, WorldModel *wm, ServerSettings *ss,
00080 PlayerSettings *ps )
00081 {
00082 connection = c;
00083 SS = ss;
00084 PS = ps;
00085 WM = wm;
00086 iSimStep = SS->getSimulatorStep()*1000;
00087 iTimeSignal = (int)(iSimStep*0.85);
00088
00089 struct sigaction sigact;
00090
00091 sigact.sa_flags = SA_RESTART;
00092 sigact.sa_handler = (void (*)(int))sigalarmHandler;
00093 sigaction( SIGALRM, &sigact, NULL );
00094
00095
00096
00097 itv.it_interval.tv_sec = 0;
00098 itv.it_interval.tv_usec = 0;
00099 itv.it_value.tv_sec = 0;
00100 itv.it_value.tv_usec = 0;
00101 }
00102
00105 void SenseHandler::handleMessagesFromServer( )
00106 {
00107 char strBuf[MAX_MSG];
00108 int i=0;
00109
00110 while( 1 )
00111 {
00112 strBuf[0]='\0';
00113 if( i != -1 )
00114 i = connection->receiveMessage( strBuf, MAX_MSG );
00115 if( strBuf[0] != '\0' )
00116 analyzeMessage( strBuf );
00117 }
00118 }
00119
00120
00129 void SenseHandler::setTimeSignal( )
00130 {
00131 if( WM->getAgentViewFrequency() == 1.0 )
00132 {
00133 if( iTriCounter % 3 == 0 )
00134 {
00135 iTimeSignal = (int)(iSimStep * PS->getFractionWaitSeeBegin() );
00136 iTriCounter = 0;
00137 }
00138 else if( iTriCounter % 3 == 1 )
00139 {
00140 iTimeSignal = (int)(iSimStep * PS->getFractionWaitSeeEnd() );
00141 }
00142 else
00143 iTimeSignal = (int)(iSimStep * PS->getFractionWaitNoSee( ) );
00144 }
00145 else if( WM->getAgentViewFrequency() == 2.0 )
00146 {
00147 if( iTriCounter % 3 == 0 )
00148 {
00149 iTimeSignal = (int)(iSimStep * PS->getFractionWaitSeeEnd() );
00150 iTriCounter = 0;
00151 }
00152 else
00153 iTimeSignal = (int)(iSimStep * PS->getFractionWaitNoSee() );
00154 }
00155 else
00156 iTimeSignal = (int)(iSimStep * PS->getFractionWaitSeeEnd() );
00157
00158 iTriCounter++;
00159 itv.it_value.tv_usec = iTimeSignal;
00160 setitimer( ITIMER_REAL, &itv, NULL );
00161 }
00162
00167 bool SenseHandler::analyzeMessage( char *strMsg )
00168 {
00169 switch( strMsg[1] )
00170 {
00171 case 'c':
00172 return analyzeChangePlayerTypeMessage( strMsg );
00173 case 'o':
00174 if( strlen(strMsg) > 14 && strMsg[4] == 'c' && strMsg[10] == 'b' )
00175 analyzeCheckBall( strMsg );
00176 return true;
00177 case 's':
00178 {
00179 switch( strMsg[3] )
00180 {
00181 case 'e':
00182 if( strMsg[5] == 'g' )
00183 return analyzeSeeGlobalMessage ( strMsg );
00184 else
00185 return analyzeSeeMessage ( strMsg );
00186 case 'n': return analyzeSenseMessage ( strMsg );
00187 case 'r': return analyzeServerParamMessage( strMsg );
00188 default : break;
00189 }
00190 }
00191 case 'i': return analyzeInitMessage ( strMsg );
00192 case 'h': return analyzeHearMessage ( strMsg );
00193 case 'p': return ( strMsg[8] == 't')
00194 ? analyzePlayerTypeMessage ( strMsg )
00195 : analyzePlayerParamMessage( strMsg );
00196 case 'e': printf("%s", strMsg);
00197 default: cerr << "(" << WM->getCurrentTime() << ", " <<
00198 WM->getPlayerNumber()
00199 << ") (SenseHandler::analyzeMessage) " <<
00200 "ignored message: " << strMsg << endl;
00201 return false;
00202 }
00203 return false;
00204 }
00205
00206
00211 bool SenseHandler::analyzeSeeMessage( char *strMsg )
00212 {
00213 strcpy( WM->strLastSeeMessage, strMsg );
00214
00215 Log.logWithTime( 2, " incoming see message" );
00216 Log.logWithTime( 2, " %s",strMsg );
00217
00218 if( WM->getRelativeDistance( OBJECT_BALL ) < SS->getVisibleDistance() )
00219 Log.logWithTime( 560, "%s", WM->strLastSeeMessage );
00220
00221 Time time = WM->getTimeLastSenseMessage();
00222 int iTime = Parse::parseFirstInt( &strMsg );
00223 if( time.getTime() != iTime )
00224 {
00225 cerr << "(SenseHandler:analyzeSeeMessage) see with different time as sense:"
00226 << time.getTime() << " vs. " << iTime << endl;
00227 return false;
00228 }
00229
00230
00231 if( WM->getTimeLastSeeMessage() == time )
00232 m_iSeeCounter++;
00233 else
00234 m_iSeeCounter = 1;
00235
00236
00237 if( m_iSeeCounter >= 2 )
00238 {
00239 Log.logWithTime( 4, "second see message in cycle; do nothing " );
00240 return true;
00241 }
00242
00243
00244 if( WM->getAgentViewFrequency() == 1.0 &&
00245 time.getTimeDifference( WM->getTimeLastSeeMessage() )== 2 )
00246 iTriCounter = 1;
00247 else if( WM->getAgentViewFrequency() == 2.0 &&
00248 time.getTimeDifference( WM->getTimeLastSeeMessage() ) == 3 )
00249 iTriCounter = 1;
00250
00251 WM->setTimeLastSeeMessage( time );
00252 return true;
00253 }
00254
00260 bool SenseHandler::analyzeSeeGlobalMessage( char *strMsg )
00261 {
00262 Log.logWithTime( 2, " incoming see global message" );
00263 strcpy( WM->strLastSeeMessage, strMsg );
00264
00265 ObjectT o;
00266 bool isGoalie;
00267 double dX, dY, dVelX, dVelY;
00268 int iTime;
00269 AngDeg angBody, angNeck;
00270 Time time = WM->getCurrentTime();
00271
00272 iTime = Parse::parseFirstInt( &strMsg );
00273 time.updateTime( iTime );
00274
00275 while( *strMsg != ')' )
00276 {
00277 dX = dY = dVelX = dVelY = UnknownDoubleValue;
00278 angBody = angNeck = UnknownAngleValue;
00279 strMsg += 2;
00280
00281
00282 o = SoccerTypes::getObjectFromStr( &strMsg, &isGoalie, WM->getTeamName() );
00283 if( o == OBJECT_ILLEGAL )
00284 {
00285 Log.log( 4, "Illegal object" );
00286 Log.log( 4, "total messages: %s", WM->strLastSeeMessage );
00287 Log.log( 4, "rest of message: %s", strMsg );
00288 }
00289
00290 dX = Parse::parseFirstDouble( &strMsg );
00291 dY = Parse::parseFirstDouble( &strMsg );
00292 if ( *strMsg != ')' )
00293 {
00294 dVelX = Parse::parseFirstDouble( &strMsg );
00295 dVelY = Parse::parseFirstDouble( &strMsg );
00296 if( *strMsg != ')' )
00297 {
00298 angBody = Parse::parseFirstDouble( &strMsg );
00299 angNeck = Parse::parseFirstDouble( &strMsg );
00300 }
00301 }
00302
00303 strMsg++;
00304
00305
00306 WM->processSeeGlobalInfo( o, time, VecPosition(dX,dY),
00307 VecPosition(dVelX,dVelY), angBody, angNeck );
00308 }
00309 WM->setTimeLastSeeGlobalMessage( time );
00310 return true;
00311 }
00312
00320 bool SenseHandler::analyzeSenseMessage( char *strMsg )
00321 {
00322 setTimeSignal();
00323
00324 strcpy( WM->strLastSenseMessage, strMsg );
00325
00326 if( WM->getRelativeDistance( OBJECT_BALL ) < SS->getVisibleDistance() )
00327 Log.logWithTime( 560, "%s", WM->strLastSenseMessage );
00328
00329 int iTime = Parse::parseFirstInt( &strMsg );
00330 Time timeOld = WM->getCurrentTime();
00331 Time timeNew = timeOld;
00332 timeNew.updateTime( iTime );
00333
00334 if( timeNew.getTimeDifference( timeOld ) > 1 )
00335 Log.log( 1, "Missed a sense!!" );
00336
00337 Log.logWithTime ( 2, "\n\nSENSE (%d, %d)", timeNew.getTime(),
00338 timeNew.getTimeStopped() );
00339 Log.restartTimer( );
00340 Log.logWithTime ( 2, " alarm after %d", iTimeSignal );
00341
00342 strMsg++;
00343
00344 Parse::gotoFirstOccurenceOf( ' ', &strMsg );
00345 strMsg++;
00346
00347 ViewQualityT vq = SoccerTypes::getViewQualityFromStr( strMsg );
00348 Parse::gotoFirstOccurenceOf( ' ', &strMsg );
00349 strMsg++;
00350 ViewAngleT va = SoccerTypes::getViewAngleFromStr( strMsg );
00351 double dStamina = Parse::parseFirstDouble( &strMsg );
00352 double dEffort = Parse::parseFirstDouble( &strMsg );
00353
00354 double dSpeed = Parse::parseFirstDouble( &strMsg );
00355 AngDeg angSpeed = Parse::parseFirstDouble( &strMsg );
00356
00357
00358 int iHeadAngle = - Parse::parseFirstInt( &strMsg );
00359
00360 WM->processNewAgentInfo( vq, va, dStamina, dEffort, dSpeed,
00361 (AngDeg) angSpeed, (AngDeg)iHeadAngle );
00362
00363
00364 WM->setNrOfCommands( CMD_KICK , Parse::parseFirstInt( &strMsg ) );
00365 WM->setNrOfCommands( CMD_DASH , Parse::parseFirstInt( &strMsg ) );
00366 WM->setNrOfCommands( CMD_TURN , Parse::parseFirstInt( &strMsg ) );
00367 WM->setNrOfCommands( CMD_SAY , Parse::parseFirstInt( &strMsg ) );
00368 WM->setNrOfCommands( CMD_TURNNECK , Parse::parseFirstInt( &strMsg ) );
00369 WM->setNrOfCommands( CMD_CATCH , Parse::parseFirstInt( &strMsg ) );
00370 WM->setNrOfCommands( CMD_MOVE , Parse::parseFirstInt( &strMsg ) );
00371 WM->setNrOfCommands( CMD_CHANGEVIEW , Parse::parseFirstInt( &strMsg ) );
00372
00373 WM->setTimeLastSenseMessage( timeNew );
00374
00375 Log.logWithTime( 2, " end analyzing sense" );
00376 return true;
00377 }
00378
00384 bool SenseHandler::analyzeInitMessage( char *strMsg )
00385 {
00386 strMsg += 6;
00387 WM->setSide( SoccerTypes::getSideFromStr( strMsg ) );
00388 int nr = Parse::parseFirstInt( &strMsg );
00389 if( nr == 0 )
00390 {
00391 WM->setPlayerNumber( nr );
00392 return true;
00393 }
00394 WM->setAgentObjectType( SoccerTypes::getTeammateObjectFromIndex( nr - 1 ) );
00395 WM->setPlayerNumber( nr );
00396 strMsg++;
00397 WM->setPlayMode( SoccerTypes::getPlayModeFromStr( strMsg ) );
00398 return true;
00399 }
00400
00408 bool SenseHandler::analyzeHearMessage( char *strMsg )
00409 {
00410 RefereeMessageT rm;
00411 PlayModeT pm;
00412 strcpy( WM->strLastHearMessage, strMsg);
00413
00414 Parse::parseFirstInt( &strMsg );
00415 Time time = WM->getCurrentTime();
00416
00417 switch( Parse::gotoFirstNonSpace( &strMsg ) )
00418 {
00419 case 'r':
00420 WM->setTimeLastRefereeMessage( time );
00421 Parse::gotoFirstOccurenceOf( ' ', &strMsg );
00422 Parse::gotoFirstNonSpace ( &strMsg );
00423 rm = SoccerTypes::getRefereeMessageFromStr( strMsg );
00424 Log.logWithTime( 2, " referee message: %s %s",
00425 SoccerTypes::getRefereeMessageStr(rm), WM->strLastHearMessage);
00426 pm = SoccerTypes::getPlayModeFromRefereeMessage( rm );
00427 if( pm != PM_ILLEGAL )
00428 WM->setPlayMode( pm );
00429
00430 if( rm == REFC_GOAL_LEFT )
00431 {
00432 if( WM->getSide() == SIDE_LEFT )
00433 WM->addOneToGoalDiff();
00434 else
00435 WM->subtractOneFromGoalDiff();
00436 WM->processSeeGlobalInfo( OBJECT_BALL, time, VecPosition( 0, 0 ),
00437 VecPosition( 0, 0 ), 0, 0 );
00438 }
00439 else if( rm == REFC_GOAL_RIGHT )
00440 {
00441 if( WM->getSide() == SIDE_RIGHT )
00442 WM->addOneToGoalDiff();
00443 else
00444 WM->subtractOneFromGoalDiff();
00445 WM->processSeeGlobalInfo( OBJECT_BALL, time, VecPosition( 0, 0 ),
00446 VecPosition( 0, 0 ), 0, 0 );
00447 }
00448 else if( rm == REFC_GOALIE_CATCH_BALL_LEFT ||
00449 rm == REFC_GOALIE_CATCH_BALL_RIGHT )
00450 WM->processCatchedBall( rm, time );
00451 break;
00452 case 's':
00453 break;
00454 default:
00455 analyzePlayerMessage( strMsg );
00456 break;
00457 }
00458
00459 return true;
00460 }
00461
00467 bool SenseHandler::analyzePlayerMessage( char *strMsg )
00468 {
00469 Parse::gotoFirstNonSpace( &strMsg );
00470
00471 if( WM->getPlayerNumber() == 0 )
00472 return false;
00473 if( strlen( strMsg ) < 2 || strMsg[0] == 'o' )
00474 return false;
00475
00476 Parse::parseFirstInt( &strMsg );
00477 Parse::gotoFirstNonSpace( &strMsg );
00478 if( strlen( strMsg ) < 2 || strMsg[1] == 'p' )
00479 return false;
00480
00481 int iPlayer = Parse::parseFirstInt( &strMsg );
00482 Parse::gotoFirstNonSpace( &strMsg );
00483 strMsg++;
00484
00485 if( strlen( strMsg ) < 4 )
00486 return false;
00487
00488 WM->storePlayerMessage( iPlayer, strMsg, WM->getCurrentCycle() );
00489 return true;
00490 }
00491
00492
00499 bool SenseHandler::analyzeCheckBall( char *strMsg )
00500 {
00501 WM->setTimeCheckBall( Parse::parseFirstInt( &strMsg ) );
00502 strMsg++;
00503 WM->setCheckBallStatus( SoccerTypes::getBallStatusFromStr( strMsg ) );
00504 return true;
00505 }
00506
00513 bool SenseHandler::analyzeChangePlayerTypeMessage( char *strMsg )
00514 {
00515 int iPlayer = Parse::parseFirstInt( &strMsg );
00516 if( *strMsg != ')' && WM->getPlayerNumber() == iPlayer )
00517 {
00518 int iPlayerType = Parse::parseFirstInt( &strMsg );
00519 return WM->updateSSToHeteroPlayerType( iPlayerType );
00520 }
00521 return false;
00522 }
00523
00530 bool SenseHandler::analyzeServerParamMessage( char *strMsg )
00531 {
00532
00533 readServerParam( "goal_width", strMsg );
00534 readServerParam( "player_size", strMsg );
00535 readServerParam( "player_decay", strMsg );
00536 readServerParam( "player_rand", strMsg );
00537 readServerParam( "player_weight", strMsg );
00538 readServerParam( "player_speed_max", strMsg );
00539 readServerParam( "player_accel_max", strMsg );
00540 readServerParam( "stamina_max", strMsg );
00541 readServerParam( "stamina_inc_max", strMsg );
00542 readServerParam( "recover_dec_thr", strMsg );
00543 readServerParam( "recover_min", strMsg );
00544 readServerParam( "recover_dec", strMsg );
00545 readServerParam( "effort_dec_thr", strMsg );
00546 readServerParam( "effort_min", strMsg );
00547 readServerParam( "effort_dec", strMsg );
00548 readServerParam( "effort_inc_thr", strMsg );
00549 readServerParam( "effort_inc", strMsg );
00550 readServerParam( "kick_rand", strMsg );
00551 readServerParam( "ball_size", strMsg );
00552 readServerParam( "ball_decay", strMsg );
00553 readServerParam( "ball_rand", strMsg );
00554 readServerParam( "ball_weight", strMsg );
00555 readServerParam( "ball_speed_max", strMsg );
00556 readServerParam( "ball_accel_max", strMsg );
00557 readServerParam( "dash_power_rate", strMsg );
00558 readServerParam( "kick_power_rate", strMsg );
00559 readServerParam( "kickable_margin", strMsg );
00560 readServerParam( "catch_probability", strMsg );
00561 readServerParam( "catchable_area_l", strMsg );
00562 readServerParam( "catchable_area_w", strMsg );
00563 readServerParam( "goalie_max_moves", strMsg );
00564 readServerParam( "maxpower", strMsg );
00565 readServerParam( "minpower", strMsg );
00566 readServerParam( "maxmoment", strMsg );
00567 readServerParam( "minmoment", strMsg );
00568 readServerParam( "maxneckmoment", strMsg );
00569 readServerParam( "minneckmoment", strMsg );
00570 readServerParam( "maxneckang", strMsg );
00571 readServerParam( "minneckang", strMsg );
00572 readServerParam( "visible_angle", strMsg );
00573 readServerParam( "visible_distance", strMsg );
00574 readServerParam( "audio_cut_dist", strMsg );
00575 readServerParam( "quantize_step", strMsg );
00576 readServerParam( "quantize_step_l", strMsg );
00577 readServerParam( "ckick_margin", strMsg );
00578 readServerParam( "wind_dir", strMsg );
00579 readServerParam( "wind_force", strMsg );
00580 readServerParam( "wind_rand", strMsg );
00581 readServerParam( "wind_random", strMsg );
00582 readServerParam( "inertia_moment", strMsg );
00583 readServerParam( "half_time", strMsg );
00584 readServerParam( "drop_ball_time", strMsg );
00585 readServerParam( "port", strMsg );
00586 readServerParam( "coach_port", strMsg );
00587 readServerParam( "olcoach_port", strMsg );
00588 readServerParam( "say_coach_cnt_max", strMsg );
00589 readServerParam( "say_coach_msg_size", strMsg );
00590 readServerParam( "simulator_step", strMsg );
00591 readServerParam( "send_step", strMsg );
00592 readServerParam( "recv_step", strMsg );
00593 readServerParam( "sense_body_step", strMsg );
00594 readServerParam( "say_msg_size", strMsg );
00595 readServerParam( "clang_win_size", strMsg );
00596 readServerParam( "clang_define_win", strMsg );
00597 readServerParam( "clang_meta_win", strMsg );
00598 readServerParam( "clang_advice_win", strMsg );
00599 readServerParam( "clang_info_win", strMsg );
00600 readServerParam( "clang_mess_delay", strMsg );
00601 readServerParam( "clang_mess_per_cycle", strMsg );
00602 readServerParam( "hear_max", strMsg );
00603 readServerParam( "hear_inc", strMsg );
00604 readServerParam( "hear_decay", strMsg );
00605 readServerParam( "catch_ban_cycle", strMsg );
00606 readServerParam( "send_vi_step", strMsg );
00607 readServerParam( "use_offside", strMsg );
00608 readServerParam( "offside_active_area_size", strMsg );
00609 readServerParam( "forbid_kick_off_offside", strMsg );
00610 readServerParam( "verbose", strMsg );
00611 readServerParam( "offside_kick_margin", strMsg );
00612 readServerParam( "slow_down_factor", strMsg );
00613
00614 SS->setMaximalKickDist ( SS->getKickableMargin() +
00615 SS->getPlayerSize() +
00616 SS->getBallSize() );
00617 return true;
00618 }
00619
00628 bool SenseHandler::readServerParam( char *strParam, char *strMsg )
00629 {
00630 char strFormat[128];
00631 char strValue[128] = "";
00632 sprintf( strValue, "none" );
00633
00634 sprintf( strFormat, "%s ", strParam );
00635 char *str = strstr( strMsg, strFormat );
00636 sprintf( strFormat, "%s %%[^)]", strParam );
00637
00638 if( str == NULL )
00639 {
00640 cerr << "(SenseHandler::readServerParam) error finding " << strParam <<endl;
00641 return false;
00642 }
00643 int ret = sscanf( str, strFormat, strValue );
00644
00645 if( ret == 1 )
00646 SS->setValue( strParam, strValue );
00647 else
00648 cerr << "(SenseHandler::readServerParam) error reading " << strParam <<endl;
00649 return (ret == 1 ) ? true : false ;
00650 }
00651
00652
00659 bool SenseHandler::analyzePlayerTypeMessage ( char *strMsg )
00660 {
00661
00662
00663
00664 int iIndex = Parse::parseFirstInt( &strMsg );
00665 double dPlayerSpeedMax = Parse::parseFirstDouble( &strMsg );
00666 double dStaminaIncMax = Parse::parseFirstDouble( &strMsg );
00667 double dPlayerDecay = Parse::parseFirstDouble( &strMsg );
00668 double dInertiaMoment = Parse::parseFirstDouble( &strMsg );
00669 double dDashPowerRate = Parse::parseFirstDouble( &strMsg );
00670 double dPlayerSize = Parse::parseFirstDouble( &strMsg );
00671 double dKickableMargin = Parse::parseFirstDouble( &strMsg );
00672 double dKickRand = Parse::parseFirstDouble( &strMsg );
00673 double dExtraStamina = Parse::parseFirstDouble( &strMsg );
00674 double dEffortMax = Parse::parseFirstDouble( &strMsg );
00675 double dEffortMin = Parse::parseFirstDouble( &strMsg );
00676
00677 WM->processNewHeteroPlayer( iIndex, dPlayerSpeedMax, dStaminaIncMax,
00678 dPlayerDecay, dInertiaMoment, dDashPowerRate, dPlayerSize,
00679 dKickableMargin, dKickRand, dExtraStamina, dEffortMax,
00680 dEffortMin );
00681 return true;
00682 }
00683
00689 bool SenseHandler::analyzePlayerParamMessage( char *strMsg )
00690 {
00691
00692 readServerParam( "player_types", strMsg );
00693 readServerParam( "subs_max", strMsg );
00694 readServerParam( "player_speed_max_delta_min", strMsg );
00695 readServerParam( "player_speed_max_delta_max", strMsg );
00696 readServerParam( "stamina_inc_max_delta_factor", strMsg );
00697 readServerParam( "player_decay_delta_min", strMsg );
00698 readServerParam( "player_decay_delta_max", strMsg );
00699 readServerParam( "inertia_moment_delta_factor", strMsg );
00700 readServerParam( "dash_power_rate_delta_min", strMsg );
00701 readServerParam( "dash_power_rate_delta_max", strMsg );
00702 readServerParam( "player_size_delta_factor", strMsg );
00703 readServerParam( "kickable_margin_delta_min", strMsg );
00704 readServerParam( "kickable_margin_delta_max", strMsg );
00705 readServerParam( "kick_rand_delta_factor", strMsg );
00706 readServerParam( "extra_stamina_delta_min", strMsg );
00707 readServerParam( "extra_stamina_delta_max", strMsg );
00708 readServerParam( "effort_max_delta_factor", strMsg );
00709 readServerParam( "effort_min_delta_factor", strMsg );
00710 readServerParam( "new_dash_power_rate_delta_min", strMsg );
00711 readServerParam( "new_dash_power_rate_delta_max", strMsg );
00712 readServerParam( "new_stamina_inc_max_delta_factor", strMsg );
00713
00714 return true;
00715 }
00716
00717
00718
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735