add experimental screen ScreenGameplayMultiplayer for up to 16 players playing using the same screen.

This commit is contained in:
Chris Danford
2004-12-22 12:58:41 +00:00
parent 798b55b41e
commit 0e2ceed54c
5 changed files with 816 additions and 4 deletions
+679
View File
@@ -0,0 +1,679 @@
#include "global.h"
#include "ScreenGameplayMultiplayer.h"
#include "ThemeMetric.h"
#include "GameSoundManager.h"
#include "GameState.h"
#include "Foreach.h"
#include "Course.h"
#include "StageStats.h"
#include "ScoreKeeperMAX2.h"
#include "ScoreKeeperRave.h"
#include "ScreenDimensions.h"
#include "ScreenManager.h"
#include "ScoreDisplayPercentage.h"
#include "ScoreDisplayNormal.h"
#include "ScoreDisplayOni.h"
#include "Steps.h"
#include "NoteDataUtil.h"
#include "RageLog.h"
#include "Style.h"
#include "PlayerState.h"
#include "InputMapper.h"
//
// Defines
//
#define PREV_SCREEN THEME->GetMetric (m_sName,"PrevScreen")
#define NEXT_SCREEN THEME->GetMetric (m_sName,"NextScreen")
// received while STATE_DANCING
const ScreenMessage SM_NotesEnded = ScreenMessage(SM_User+10);
const ScreenMessage SM_Ready = ScreenMessage(SM_User+12);
const ScreenMessage SM_GoToStateAfterCleared= ScreenMessage(SM_User+22);
const ScreenMessage SM_GoToScreenAfterBack = ScreenMessage(SM_User+23);
REGISTER_SCREEN_CLASS( ScreenGameplayMultiplayer );
ScreenGameplayMultiplayer::ScreenGameplayMultiplayer( CString sName, bool bDemonstration ) : Screen(sName)
{
Init(); // work around horrible gcc bug 3187
}
void ScreenGameplayMultiplayer::Init()
{
m_pSoundMusic = NULL;
/* We do this ourself. */
SOUND->HandleSongTimer( false );
//need to initialize these before checking for demonstration mode
//otherwise destructor will try to delete possibly invalid pointers
FOREACH_MultiPlayer(p)
{
m_pPrimaryScoreDisplay[p] = NULL;
m_pPrimaryScoreKeeper[p] = NULL;
m_PlayerState[p].m_PlayerNumber = GAMESTATE->m_MasterPlayerNumber;
m_PlayerState[p].m_PlayerController = PC_HUMAN;
}
if( GAMESTATE->m_pCurSong == NULL && GAMESTATE->m_pCurCourse == NULL )
return; // ScreenDemonstration will move us to the next scren. We just need to survive for one update without crashing.
/* This is usually done already, but we might have come here without going through
* ScreenSelectMusic or the options menus at all. */
GAMESTATE->AdjustFailType();
/* Save selected options before we change them. */
GAMESTATE->StoreSelectedOptions();
/* Save settings to the profile now. Don't do this on extra stages, since the
* user doesn't have full control; saving would force profiles to DIFFICULTY_HARD
* and save over their default modifiers every time someone got an extra stage.
* Do this before course modifiers are set up. */
if( !GAMESTATE->IsExtraStage() && !GAMESTATE->IsExtraStage2() )
{
FOREACH_HumanPlayer( pn )
GAMESTATE->SaveCurrentSettingsToProfile(pn);
}
GAMESTATE->ResetStageStatistics();
// fill in difficulty of CPU players with that of the first human player
FOREACH_PotentialCpuPlayer(p)
GAMESTATE->m_pCurSteps[p] = GAMESTATE->m_pCurSteps[ GAMESTATE->GetFirstHumanPlayer() ];
//
// fill in m_vpSongsQueue, m_vpStepsQueue, m_asModifiersQueue
//
if( GAMESTATE->IsCourseMode() )
{
Course* pCourse = GAMESTATE->m_pCurCourse;
ASSERT( pCourse );
m_vpSongsQueue.clear();
PlayerNumber pnMaster = GAMESTATE->m_MasterPlayerNumber;
Trail *pTrail = GAMESTATE->m_pCurTrail[pnMaster];
FOREACH_CONST( TrailEntry, pTrail->m_vEntries, e )
{
m_vpSongsQueue.push_back( e->pSong );
}
m_vpStepsQueue.clear();
FOREACH_CONST( TrailEntry, pTrail->m_vEntries, e )
{
m_vpStepsQueue.push_back( e->pSteps );
m_vModifiersQueue.push_back( AttackArray() );
}
}
else
{
m_vpSongsQueue.push_back( GAMESTATE->m_pCurSong );
{
m_vpStepsQueue.push_back( GAMESTATE->m_pCurSteps[GAMESTATE->m_MasterPlayerNumber] );
m_vModifiersQueue.push_back( AttackArray() );
}
}
/* Called once per stage (single song or single course). */
GAMESTATE->BeginStage();
g_CurStageStats.playMode = GAMESTATE->m_PlayMode;
g_CurStageStats.pStyle = GAMESTATE->m_pCurStyle;
{
ASSERT( !m_vpStepsQueue.empty() );
}
if( GAMESTATE->IsExtraStage() )
g_CurStageStats.StageType = StageStats::STAGE_EXTRA;
else if( GAMESTATE->IsExtraStage2() )
g_CurStageStats.StageType = StageStats::STAGE_EXTRA2;
else
g_CurStageStats.StageType = StageStats::STAGE_NORMAL;
//
// Init ScoreKeepers
//
FOREACH_MultiPlayer( p )
{
switch( PREFSMAN->m_iScoringType )
{
case PrefsManager::SCORING_MAX2:
case PrefsManager::SCORING_5TH:
m_pPrimaryScoreKeeper[p] = new ScoreKeeperMAX2(
m_vpSongsQueue,
m_vpStepsQueue,
m_vModifiersQueue,
&m_PlayerState[p],
&m_PlayerStageStats[p] );
break;
default: ASSERT(0);
}
}
m_Background.SetDrawOrder( DRAW_ORDER_BEFORE_EVERYTHING );
this->AddChild( &m_Background );
m_Foreground.SetDrawOrder( DRAW_ORDER_AFTER_EVERYTHING ); // on top of everything else, including transitions
this->AddChild( &m_Foreground );
{
float fPlayerX = SCREEN_CENTER_X;
/* Perhaps this should be handled better by defining a new
* StyleType for ONE_PLAYER_ONE_CREDIT_AND_ONE_COMPUTER,
* but for now just ignore SoloSingles when it's Battle or Rave
* Mode. This doesn't begin to address two-player solo (6 arrows) */
if( PREFSMAN->m_bSoloSingle &&
GAMESTATE->m_PlayMode != PLAY_MODE_BATTLE &&
GAMESTATE->m_PlayMode != PLAY_MODE_RAVE &&
GAMESTATE->GetCurrentStyle()->m_StyleType == ONE_PLAYER_ONE_SIDE )
fPlayerX = SCREEN_CENTER_X;
m_AutoPlayer.SetName( "Player" );
m_AutoPlayer.SetX( fPlayerX );
m_AutoPlayer.SetY( SCREEN_CENTER_Y );
this->AddChild( &m_AutoPlayer );
}
FOREACH_MultiPlayer( p )
{
this->AddChild( &m_HumanPlayer[p] );
}
FOREACH_MultiPlayer( p )
{
//
// primary score display
//
switch( GAMESTATE->m_PlayMode )
{
case PLAY_MODE_REGULAR:
case PLAY_MODE_NONSTOP:
case PLAY_MODE_BATTLE:
case PLAY_MODE_RAVE:
if( PREFSMAN->m_bPercentageScoring )
m_pPrimaryScoreDisplay[p] = new ScoreDisplayPercentage;
else
m_pPrimaryScoreDisplay[p] = new ScoreDisplayNormal;
break;
case PLAY_MODE_ONI:
case PLAY_MODE_ENDLESS:
m_pPrimaryScoreDisplay[p] = new ScoreDisplayOni;
break;
default:
ASSERT(0);
}
m_pPrimaryScoreDisplay[p]->Init( &m_PlayerState[p] );
m_pPrimaryScoreDisplay[p]->SetName( ssprintf("ScoreP%d",p+1) );
SET_XY( *m_pPrimaryScoreDisplay[p] );
this->AddChild( m_pPrimaryScoreDisplay[p] );
}
m_In.Load( THEME->GetPathB(m_sName,"in") );
m_In.SetDrawOrder( DRAW_ORDER_TRANSITIONS );
this->AddChild( &m_In );
m_Out.Load( THEME->GetPathB(m_sName,"out") );
m_Out.SetDrawOrder( DRAW_ORDER_TRANSITIONS );
this->AddChild( &m_Out );
m_Back.Load( THEME->GetPathB("Common","back") );
m_Back.SetDrawOrder( DRAW_ORDER_TRANSITIONS ); // on top of everything else
this->AddChild( &m_Back );
/* LoadNextSong first, since that positions some elements which need to be
* positioned before we TweenOnScreen. */
LoadNextSong();
TweenOnScreen();
this->SortByDrawOrder();
// Get the transitions rolling on the first update.
// We can't do this in the constructor because ScreenGameplayMultiplayer is constructed
// in the middle of ScreenStage.
}
ScreenGameplayMultiplayer::~ScreenGameplayMultiplayer()
{
if( this->IsFirstUpdate() )
{
/* We never received any updates. That means we were deleted without being
* used, and never actually played. (This can happen when backing out of
* ScreenStage.) Cancel the stage. */
GAMESTATE->CancelStage();
}
FOREACH_MultiPlayer( p )
{
SAFE_DELETE( m_pPrimaryScoreDisplay[p] );
SAFE_DELETE( m_pPrimaryScoreKeeper[p] );
}
if( m_pSoundMusic )
m_pSoundMusic->StopPlaying();
}
bool ScreenGameplayMultiplayer::IsLastSong()
{
if( GAMESTATE->m_pCurCourse && GAMESTATE->m_pCurCourse->m_bRepeat )
return false;
return GAMESTATE->GetCourseSongIndex()+1 == (int)m_vpSongsQueue.size(); // GetCourseSongIndex() is 0-based but size() is not
}
void ScreenGameplayMultiplayer::SetupSong( MultiPlayer p, int iSongIndex )
{
/* This is the first beat that can be changed without it being visible. Until
* we draw for the first time, any beat can be changed. */
GAMESTATE->m_pPlayerState[GAMESTATE->m_MasterPlayerNumber]->m_fLastDrawnBeat = -100;
GAMESTATE->m_pCurSteps[GAMESTATE->m_MasterPlayerNumber] = m_vpStepsQueue[iSongIndex];
/* Load new NoteData into Player. Do this before
* RebuildPlayerOptionsFromActiveAttacks or else transform mods will get
* propogated to GAMESTATE->m_AutoPlayerOptions too early and be double-applied
* to the NoteData:
* once in Player::Load, then again in Player::ApplyActiveAttacks. This
* is very bad for transforms like AddMines.
*/
NoteData originalNoteData;
GAMESTATE->m_pCurSteps[GAMESTATE->m_MasterPlayerNumber]->GetNoteData( originalNoteData );
const Style* pStyle = GAMESTATE->GetCurrentStyle();
NoteData ndTransformed;
pStyle->GetTransformedNoteDataForStyle( GAMESTATE->m_MasterPlayerNumber, originalNoteData, ndTransformed );
// load player
{
NoteData nd = ndTransformed;
NoteDataUtil::RemoveAllTapsOfType( nd, TapNote::autoKeysound );
m_AutoPlayer.Load(
GAMESTATE->m_pPlayerState[ GAMESTATE->m_MasterPlayerNumber ],
nd,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL );
m_HumanPlayer[p].Load(
&m_PlayerState[p],
nd,
&m_PlayerStageStats[p],
NULL,
NULL,
m_pPrimaryScoreDisplay[p],
NULL,
NULL,
m_pPrimaryScoreKeeper[p],
NULL,
NULL );
}
// load auto keysounds
{
NoteData nd = ndTransformed;
NoteDataUtil::RemoveAllTapsExceptForType( nd, TapNote::autoKeysound );
m_AutoKeysounds.Load( GAMESTATE->m_MasterPlayerNumber, nd );
}
}
void ScreenGameplayMultiplayer::LoadNextSong()
{
GAMESTATE->ResetMusicStatistics();
int iPlaySongIndex = GAMESTATE->GetCourseSongIndex();
iPlaySongIndex %= m_vpSongsQueue.size();
GAMESTATE->m_pCurSong = m_vpSongsQueue[iPlaySongIndex];
g_CurStageStats.vpSongs.push_back( GAMESTATE->m_pCurSong );
// No need to do this here. We do it in SongFinished().
//GAMESTATE->RemoveAllActiveAttacks();
// Restore the player's originally selected options.
GAMESTATE->RestoreSelectedOptions();
/* If we're in battery mode, force FailImmediate. We assume in PlayerMinus::Step that
* failed players can't step. */
if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY )
GAMESTATE->m_SongOptions.m_FailType = SongOptions::FAIL_IMMEDIATE;
Steps* pSteps = GAMESTATE->m_pCurSteps[GAMESTATE->m_MasterPlayerNumber];
g_CurStageStats.m_player[GAMESTATE->m_MasterPlayerNumber].vpSteps.push_back( pSteps );
FOREACH_MultiPlayer( p )
{
SetupSong( p, iPlaySongIndex );
ASSERT( GAMESTATE->m_pCurSteps[GAMESTATE->m_MasterPlayerNumber] );
/* The actual note data for scoring is the base class of Player. This includes
* transforms, like Wide. Otherwise, the scoring will operate on the wrong data. */
m_pPrimaryScoreKeeper[p]->OnNextSong(
GAMESTATE->GetCourseSongIndex(),
GAMESTATE->m_pCurSteps[GAMESTATE->m_MasterPlayerNumber],
&m_HumanPlayer[p].m_NoteData );
}
GAMESTATE->m_pPlayerState[GAMESTATE->m_MasterPlayerNumber]->m_PlayerController = PC_AUTOPLAY;
m_AutoKeysounds.FinishLoading();
m_pSoundMusic = m_AutoKeysounds.GetSound();
{
/* BeginnerHelper disabled/failed to load. */
m_Background.LoadFromSong( GAMESTATE->m_pCurSong );
/* This will fade from a preset brightness to the actual brightness (based
* on prefs and "cover"). The preset brightness may be 0 (to fade from
* black), or it might be 1, if the stage screen has the song BG and we're
* coming from it (like Pump). This used to be done in SM_PlayReady, but
* that means it's impossible to snap to the new brightness immediately. */
m_Background.FadeToActualBrightness();
}
m_Foreground.LoadFromSong( GAMESTATE->m_pCurSong );
}
float ScreenGameplayMultiplayer::StartPlayingSong(float MinTimeToNotes, float MinTimeToMusic)
{
ASSERT(MinTimeToNotes >= 0);
ASSERT(MinTimeToMusic >= 0);
/* XXX: We want the first beat *in use*, so we don't delay needlessly. */
const float fFirstBeat = GAMESTATE->m_pCurSong->m_fFirstBeat;
const float fFirstSecond = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( fFirstBeat );
float fStartSecond = fFirstSecond - MinTimeToNotes;
fStartSecond = min(fStartSecond, -MinTimeToMusic);
RageSoundParams p;
p.AccurateSync = true;
p.SetPlaybackRate( GAMESTATE->m_SongOptions.m_fMusicRate );
p.StopMode = RageSoundParams::M_CONTINUE;
p.m_StartSecond = fStartSecond;
m_pSoundMusic->Play( &p );
/* Make sure GAMESTATE->m_fMusicSeconds is set up. */
GAMESTATE->m_fMusicSeconds = -5000;
UpdateSongPosition(0);
ASSERT( GAMESTATE->m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */
/* Return the amount of time until the first beat. */
return fFirstSecond - fStartSecond;
}
void ScreenGameplayMultiplayer::UpdateSongPosition( float fDeltaTime )
{
if( !m_pSoundMusic->IsPlaying() )
return;
RageTimer tm;
const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm );
const float fAdjust = SOUND->GetFrameTimingAdjustment( fDeltaTime );
GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_Timing, tm+fAdjust );
}
void ScreenGameplayMultiplayer::Update( float fDeltaTime )
{
if( GAMESTATE->m_pCurSong == NULL )
{
/* ScreenDemonstration will move us to the next screen. We just need to
* survive for one update without crashing. We need to call Screen::Update
* to make sure we receive the next-screen message. */
Screen::Update( fDeltaTime );
return;
}
if( m_bFirstUpdate )
{
SOUND->PlayOnceFromAnnouncer( "gameplay intro" ); // crowd cheer
//
// Get the transitions rolling
//
StartPlayingSong( 0, 0 ); // *kick* (no transitions)
}
UpdateSongPosition( fDeltaTime );
Screen::Update( fDeltaTime );
/* This happens if ScreenDemonstration::HandleScreenMessage sets a new screen when
* PREFSMAN->m_bDelayedScreenLoad. */
if( GAMESTATE->m_pCurSong == NULL )
return;
/* This can happen if ScreenDemonstration::HandleScreenMessage sets a new screen when
* !PREFSMAN->m_bDelayedScreenLoad. (The new screen was loaded when we called Screen::Update,
* and the ctor might set a new GAMESTATE->m_pCurSong, so the above check can fail.) */
if( SCREENMAN->GetTopScreen() != this )
return;
m_AutoKeysounds.Update(fDeltaTime);
//
// Check for end of song
//
float fSecondsToStop = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
/* Make sure we keep going long enough to register a miss for the last note. */
fSecondsToStop += PlayerMinus::GetMaxStepDistanceSeconds();
if( GAMESTATE->m_fMusicSeconds > fSecondsToStop && !m_Out.IsTransitioning() )
this->HandleScreenMessage( SM_NotesEnded );
}
void ScreenGameplayMultiplayer::DrawPrimitives()
{
Screen::DrawPrimitives();
}
void ScreenGameplayMultiplayer::Input( const DeviceInput& DeviceI, const InputEventType type, const GameInput &GameI, const MenuInput &MenuI, const StyleInput &StyleI )
{
//LOG->Trace( "ScreenGameplayMultiplayer::Input()" );
if( type == IET_LEVEL_CHANGED )
return;
if( MenuI.IsValid() &&
!m_Back.IsTransitioning() )
{
if( MenuI.button == MENU_BUTTON_BACK &&
((!PREFSMAN->m_bDelayedBack && type==IET_FIRST_PRESS) ||
(DeviceI.device==DEVICE_KEYBOARD && (type==IET_SLOW_REPEAT||type==IET_FAST_REPEAT)) ||
(DeviceI.device!=DEVICE_KEYBOARD && type==IET_FAST_REPEAT)) )
{
/* I had battle mode back out on me mysteriously once. -glenn */
LOG->Trace("Player %i went back", MenuI.player+1);
SCREENMAN->PlayBackSound();
/* Hmm. There are a bunch of subtly different ways we can
* tween out:
* 1. Keep rendering the song, and keep it moving. This might
* cause problems if the cancel and the end of the song overlap.
* 2. Stop the song completely, so all song motion under the tween
* ceases.
* 3. Stop the song, but keep effects (eg. Drunk) running.
* 4. Don't display the song at all.
*
* We're doing #3. I'm not sure which is best.
*/
m_pSoundMusic->StopPlaying();
this->ClearMessageQueue();
m_Back.StartTransitioning( SM_GoToScreenAfterBack );
return;
}
}
/* Nothing below cares about releases. */
if(type == IET_RELEASE) return;
// Handle special keys to adjust the offset
if( DeviceI.device == DEVICE_KEYBOARD )
{
switch( DeviceI.button )
{
case KEY_F5:
this->HandleScreenMessage( SM_NotesEnded );
break;
}
}
// Translate input and sent to the appropriate player. Assume that all
// joystick devices are mapped the same as the master player.
if( DeviceI.device >= DEVICE_JOY1 &&
DeviceI.device < DEVICE_JOY1 + NUM_JOYSTICKS )
{
DeviceInput _DeviceI = DeviceI;
_DeviceI.device = DEVICE_JOY1;
GameInput _GameI;
INPUTMAPPER->DeviceToGame( _DeviceI, _GameI );
if( GameI.IsValid() )
{
StyleInput _StyleI;
INPUTMAPPER->GameToStyle( _GameI, _StyleI );
MultiPlayer mp = (MultiPlayer)(DeviceI.device - DEVICE_JOY1);
ASSERT( mp>=0 && mp<NUM_MULTI_PLAYERS );
m_HumanPlayer[mp].Step( _StyleI.col, DeviceI.ts );
}
}
}
void ScreenGameplayMultiplayer::StageFinished( bool bBackedOut )
{
if( GAMESTATE->IsCourseMode() && GAMESTATE->m_PlayMode != PLAY_MODE_ENDLESS )
{
LOG->Trace("Stage finished at index %i/%i", GAMESTATE->GetCourseSongIndex(), (int) m_vpSongsQueue.size() );
/* +1 to skip the current song; that's done already. */
for( unsigned iPlaySongIndex = GAMESTATE->GetCourseSongIndex()+1;
iPlaySongIndex < m_vpSongsQueue.size(); ++iPlaySongIndex )
{
LOG->Trace("Running stats for %i", iPlaySongIndex );
FOREACH_MultiPlayer(p)
{
SetupSong( p, iPlaySongIndex );
}
}
}
// save current stage stats
if( !bBackedOut )
g_vPlayedStageStats.push_back( g_CurStageStats );
/* Reset options. */
GAMESTATE->RestoreSelectedOptions();
}
void ScreenGameplayMultiplayer::HandleScreenMessage( const ScreenMessage SM )
{
CHECKPOINT_M( ssprintf("HandleScreenMessage(%i)", SM) );
switch( SM )
{
case SM_Ready:
GAMESTATE->m_bPastHereWeGo = true;
break;
case SM_NotesEnded:
m_Out.StartTransitioning( SM_GoToStateAfterCleared );
break;
case SM_GoToScreenAfterBack:
StageFinished( true );
GAMESTATE->CancelStage();
SCREENMAN->DeletePreparedScreens();
SCREENMAN->SetNewScreen( PREV_SCREEN );
break;
case SM_GoToStateAfterCleared:
StageFinished( false );
SCREENMAN->SetNewScreen( NEXT_SCREEN );
break;
}
}
void ScreenGameplayMultiplayer::TweenOnScreen()
{
FOREACH_MultiPlayer(p)
{
if( m_pPrimaryScoreDisplay[p] )
ON_COMMAND( *m_pPrimaryScoreDisplay[p] );
}
m_In.StartTransitioning( SM_Ready );
}
void ScreenGameplayMultiplayer::TweenOffScreen()
{
FOREACH_MultiPlayer(p)
{
if( m_pPrimaryScoreDisplay[p] )
OFF_COMMAND( *m_pPrimaryScoreDisplay[p] );
}
}
/*
* (c) 2001-2004 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/