3266fda819
FailType for a given Player can be overriden with respect to preferences, but Lua has no way to check it. Expose a function to allow doing that, in order to not accidentally submit invalid scores to GrooveStats.
3532 lines
100 KiB
C++
3532 lines
100 KiB
C++
#include "global.h"
|
|
#include "GameState.h"
|
|
#include "Actor.h"
|
|
#include "ActorUtil.h"
|
|
#include "AdjustSync.h"
|
|
#include "AnnouncerManager.h"
|
|
#include "Bookkeeper.h"
|
|
#include "Character.h"
|
|
#include "CharacterManager.h"
|
|
#include "CommonMetrics.h"
|
|
#include "Course.h"
|
|
#include "CryptManager.h"
|
|
#include "Game.h"
|
|
#include "GameCommand.h"
|
|
#include "GameConstantsAndTypes.h"
|
|
#include "GameManager.h"
|
|
#include "GamePreferences.h"
|
|
#include "HighScore.h"
|
|
#include "LightsManager.h"
|
|
#include "LuaReference.h"
|
|
#include "MessageManager.h"
|
|
#include "MemoryCardManager.h"
|
|
#include "NoteData.h"
|
|
#include "NoteSkinManager.h"
|
|
#include "PlayerState.h"
|
|
#include "PrefsManager.h"
|
|
#include "Profile.h"
|
|
#include "ProfileManager.h"
|
|
#include "RageFile.h"
|
|
#include "RageFileManager.h"
|
|
#include "RageLog.h"
|
|
#include "RageUtil.h"
|
|
#include "Song.h"
|
|
#include "SongManager.h"
|
|
#include "SongUtil.h"
|
|
#include "StatsManager.h"
|
|
#include "StepMania.h"
|
|
#include "Steps.h"
|
|
#include "Style.h"
|
|
#include "ThemeManager.h"
|
|
#include "UnlockManager.h"
|
|
#include "ScreenManager.h"
|
|
#include "Screen.h"
|
|
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <ctime>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
|
|
GameState* GAMESTATE = nullptr; // global and accessible from anywhere in our program
|
|
|
|
#define NAME_BLACKLIST_FILE "/Data/NamesBlacklist.txt"
|
|
|
|
class GameStateMessageHandler: public MessageSubscriber
|
|
{
|
|
void HandleMessage( const Message &msg )
|
|
{
|
|
if( msg.GetName() == "RefreshCreditText" )
|
|
{
|
|
RString sJoined;
|
|
FOREACH_HumanPlayer( pn )
|
|
{
|
|
if( sJoined != "" )
|
|
sJoined += ", ";
|
|
sJoined += ssprintf( "P%i", pn+1 );
|
|
}
|
|
|
|
if( sJoined == "" )
|
|
sJoined = "none";
|
|
|
|
LOG->MapLog( "JOINED", "Players joined: %s", sJoined.c_str() );
|
|
}
|
|
}
|
|
};
|
|
|
|
struct GameStateImpl
|
|
{
|
|
GameStateMessageHandler m_Subscriber;
|
|
GameStateImpl()
|
|
{
|
|
m_Subscriber.SubscribeToMessage( "RefreshCreditText" );
|
|
}
|
|
};
|
|
static GameStateImpl *g_pImpl = nullptr;
|
|
|
|
ThemeMetric<bool> ALLOW_LATE_JOIN("GameState","AllowLateJoin");
|
|
ThemeMetric<bool> USE_NAME_BLACKLIST("GameState","UseNameBlacklist");
|
|
|
|
ThemeMetric<RString> DEFAULT_SORT ("GameState","DefaultSort");
|
|
SortOrder GetDefaultSort()
|
|
{
|
|
return StringToSortOrder( DEFAULT_SORT );
|
|
}
|
|
ThemeMetric<RString> DEFAULT_SONG ("GameState","DefaultSong");
|
|
Song* GameState::GetDefaultSong() const
|
|
{
|
|
SongID sid;
|
|
sid.FromString( DEFAULT_SONG );
|
|
return sid.ToSong();
|
|
}
|
|
|
|
static const ThemeMetric<bool> EDIT_ALLOWED_FOR_EXTRA ("GameState","EditAllowedForExtra");
|
|
static const ThemeMetric<Difficulty> MIN_DIFFICULTY_FOR_EXTRA ("GameState","MinDifficultyForExtra");
|
|
static const ThemeMetric<Grade> GRADE_TIER_FOR_EXTRA_1 ("GameState","GradeTierForExtra1");
|
|
static const ThemeMetric<bool> ALLOW_EXTRA_2 ("GameState","AllowExtra2");
|
|
static const ThemeMetric<Grade> GRADE_TIER_FOR_EXTRA_2 ("GameState","GradeTierForExtra2");
|
|
|
|
static ThemeMetric<bool> ARE_STAGE_PLAYER_MODS_FORCED ("GameState","AreStagePlayerModsForced");
|
|
static ThemeMetric<bool> ARE_STAGE_SONG_MODS_FORCED ("GameState","AreStageSongModsForced");
|
|
|
|
static Preference<Premium> g_Premium( "Premium", Premium_DoubleFor1Credit );
|
|
Preference<bool> GameState::m_bAutoJoin( "AutoJoin", false );
|
|
|
|
GameState::GameState() :
|
|
processedTiming(nullptr),
|
|
m_pCurGame( Message_CurrentGameChanged ),
|
|
m_pCurStyle( Message_CurrentStyleChanged ),
|
|
m_PlayMode( Message_PlayModeChanged ),
|
|
m_iCoins( Message_CoinsChanged ),
|
|
m_sPreferredSongGroup( Message_PreferredSongGroupChanged ),
|
|
m_sPreferredCourseGroup( Message_PreferredCourseGroupChanged ),
|
|
m_PreferredStepsType( Message_PreferredStepsTypeChanged ),
|
|
m_PreferredDifficulty( Message_PreferredDifficultyP1Changed ),
|
|
m_PreferredCourseDifficulty( Message_PreferredCourseDifficultyP1Changed ),
|
|
m_SortOrder( Message_SortOrderChanged ),
|
|
m_pCurSong( Message_CurrentSongChanged ),
|
|
m_pCurSteps( Message_CurrentStepsP1Changed ),
|
|
m_pCurCourse( Message_CurrentCourseChanged ),
|
|
m_pCurTrail( Message_CurrentTrailP1Changed ),
|
|
m_bGameplayLeadIn( Message_GameplayLeadInChanged ),
|
|
m_bDidModeChangeNoteSkin( false ),
|
|
m_bIsUsingStepTiming( true ),
|
|
m_bInStepEditor( false ),
|
|
m_stEdit( Message_EditStepsTypeChanged ),
|
|
m_cdEdit( Message_EditCourseDifficultyChanged ),
|
|
m_pEditSourceSteps( Message_EditSourceStepsChanged ),
|
|
m_stEditSource( Message_EditSourceStepsTypeChanged ),
|
|
m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ),
|
|
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged )
|
|
{
|
|
g_pImpl = new GameStateImpl;
|
|
|
|
m_pCurStyle.Set(nullptr);
|
|
FOREACH_PlayerNumber(rpn)
|
|
{
|
|
m_SeparatedStyles[rpn]= nullptr;
|
|
}
|
|
|
|
m_pCurGame.Set(nullptr);
|
|
m_iCoins.Set( 0 );
|
|
m_timeGameStarted.SetZero();
|
|
m_bDemonstrationOrJukebox = false;
|
|
|
|
m_iNumTimesThroughAttract = -1; // initial screen will bump this up to 0
|
|
m_iStageSeed = m_iGameSeed = 0;
|
|
|
|
m_PlayMode.Set( PlayMode_Invalid ); // used by IsPlayerEnabled before the first screen
|
|
FOREACH_PlayerNumber( p )
|
|
m_bSideIsJoined[p] = false; // used by GetNumSidesJoined before the first screen
|
|
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
m_pPlayerState[p] = new PlayerState;
|
|
m_pPlayerState[p]->SetPlayerNumber(p);
|
|
}
|
|
FOREACH_MultiPlayer( p )
|
|
{
|
|
m_pMultiPlayerState[p] = new PlayerState;
|
|
m_pMultiPlayerState[p]->SetPlayerNumber(PLAYER_1);
|
|
m_pMultiPlayerState[p]->m_mp = p;
|
|
}
|
|
|
|
m_Environment = new LuaTable;
|
|
|
|
m_bDopefish = false;
|
|
sLastOpenSection = "";
|
|
sExpandedSectionName = "";
|
|
|
|
// Don't reset yet; let the first screen do it, so we can use PREFSMAN and THEME.
|
|
//Reset();
|
|
|
|
// Register with Lua.
|
|
{
|
|
Lua *L = LUA->Get();
|
|
lua_pushstring( L, "GAMESTATE" );
|
|
this->PushSelf( L );
|
|
lua_settable( L, LUA_GLOBALSINDEX );
|
|
LUA->Release( L );
|
|
}
|
|
}
|
|
|
|
GameState::~GameState()
|
|
{
|
|
// Unregister with Lua.
|
|
LUA->UnsetGlobal( "GAMESTATE" );
|
|
|
|
FOREACH_PlayerNumber( p )
|
|
RageUtil::SafeDelete( m_pPlayerState[p] );
|
|
FOREACH_MultiPlayer( p )
|
|
RageUtil::SafeDelete( m_pMultiPlayerState[p] );
|
|
|
|
RageUtil::SafeDelete( m_Environment );
|
|
RageUtil::SafeDelete( g_pImpl );
|
|
RageUtil::SafeDelete( processedTiming );
|
|
}
|
|
|
|
PlayerNumber GameState::GetMasterPlayerNumber() const
|
|
{
|
|
return this->masterPlayerNumber;
|
|
}
|
|
|
|
void GameState::SetMasterPlayerNumber(const PlayerNumber p)
|
|
{
|
|
this->masterPlayerNumber = p;
|
|
}
|
|
|
|
TimingData * GameState::GetProcessedTimingData() const
|
|
{
|
|
return this->processedTiming;
|
|
}
|
|
|
|
void GameState::SetProcessedTimingData(TimingData * t)
|
|
{
|
|
this->processedTiming = t;
|
|
}
|
|
|
|
void GameState::ApplyGameCommand( const RString &sCommand, PlayerNumber pn )
|
|
{
|
|
GameCommand m;
|
|
m.Load( 0, ParseCommands(sCommand) );
|
|
|
|
RString sWhy;
|
|
if( !m.IsPlayable(&sWhy) )
|
|
{
|
|
LuaHelpers::ReportScriptErrorFmt("Can't apply GameCommand \"%s\": %s", sCommand.c_str(), sWhy.c_str());
|
|
return;
|
|
}
|
|
|
|
if( pn == PLAYER_INVALID )
|
|
m.ApplyToAllPlayers();
|
|
else
|
|
m.Apply( pn );
|
|
}
|
|
|
|
void GameState::ApplyCmdline()
|
|
{
|
|
// We need to join players before we can set the style.
|
|
RString sPlayer;
|
|
for( int i = 0; GetCommandlineArgument( "player", &sPlayer, i ); ++i )
|
|
{
|
|
int pn = StringToInt( sPlayer )-1;
|
|
if( !IsAnInt( sPlayer ) || pn < 0 || pn >= NUM_PLAYERS )
|
|
RageException::Throw( "Invalid argument \"--player=%s\".", sPlayer.c_str() );
|
|
|
|
JoinPlayer( (PlayerNumber) pn );
|
|
}
|
|
|
|
RString sMode;
|
|
for( int i = 0; GetCommandlineArgument( "mode", &sMode, i ); ++i )
|
|
{
|
|
ApplyGameCommand( sMode );
|
|
}
|
|
}
|
|
|
|
void GameState::ResetPlayer( PlayerNumber pn )
|
|
{
|
|
m_PreferredStepsType.Set( StepsType_Invalid );
|
|
m_PreferredDifficulty[pn].Set( Difficulty_Invalid );
|
|
m_PreferredCourseDifficulty[pn].Set( Difficulty_Medium );
|
|
m_iPlayerStageTokens[pn] = 0;
|
|
m_iAwardedExtraStages[pn] = 0;
|
|
m_pCurSteps[pn].Set(nullptr);
|
|
m_pCurTrail[pn].Set(nullptr);
|
|
m_pPlayerState[pn]->Reset();
|
|
PROFILEMAN->UnloadProfile( pn );
|
|
ResetPlayerOptions(pn);
|
|
}
|
|
|
|
void GameState::ResetPlayerOptions( PlayerNumber pn )
|
|
{
|
|
PlayerOptions po;
|
|
GetDefaultPlayerOptions( po );
|
|
m_pPlayerState[pn]->m_PlayerOptions.Assign( ModsLevel_Preferred, po );
|
|
}
|
|
|
|
void GameState::Reset()
|
|
{
|
|
this->SetMasterPlayerNumber(PLAYER_INVALID); // must initialize for UnjoinPlayer
|
|
|
|
FOREACH_PlayerNumber( pn )
|
|
UnjoinPlayer( pn );
|
|
|
|
ASSERT( THEME != nullptr );
|
|
|
|
m_timeGameStarted.SetZero();
|
|
SetCurrentStyle( nullptr, PLAYER_INVALID );
|
|
FOREACH_MultiPlayer( p )
|
|
m_MultiPlayerStatus[p] = MultiPlayerStatus_NotJoined;
|
|
FOREACH_PlayerNumber( pn )
|
|
MEMCARDMAN->UnlockCard( pn );
|
|
m_bMultiplayer = false;
|
|
m_iNumMultiplayerNoteFields = 1;
|
|
*m_Environment = LuaTable();
|
|
m_sPreferredSongGroup.Set( GROUP_ALL );
|
|
m_sPreferredCourseGroup.Set( GROUP_ALL );
|
|
m_bFailTypeWasExplicitlySet = false;
|
|
m_SortOrder.Set( SortOrder_Invalid );
|
|
m_PreferredSortOrder = GetDefaultSort();
|
|
m_PlayMode.Set( PlayMode_Invalid );
|
|
m_EditMode = EditMode_Invalid;
|
|
m_bDemonstrationOrJukebox = false;
|
|
m_bJukeboxUsesModifiers = false;
|
|
m_iCurrentStageIndex = 0;
|
|
|
|
m_bGameplayLeadIn.Set( false );
|
|
m_iNumStagesOfThisSong = 0;
|
|
m_bLoadingNextSong = false;
|
|
|
|
NOTESKIN->RefreshNoteSkinData( m_pCurGame );
|
|
|
|
m_iGameSeed = rand();
|
|
m_iStageSeed = rand();
|
|
|
|
m_AdjustTokensBySongCostForFinalStageCheck= true;
|
|
|
|
m_pCurSong.Set( GetDefaultSong() );
|
|
m_pPreferredSong = nullptr;
|
|
m_pCurCourse.Set(nullptr);
|
|
m_pPreferredCourse = nullptr;
|
|
|
|
FOREACH_MultiPlayer( p )
|
|
m_pMultiPlayerState[p]->Reset();
|
|
|
|
m_SongOptions.Init();
|
|
|
|
m_paused= false;
|
|
ResetMusicStatistics();
|
|
ResetStageStatistics();
|
|
AdjustSync::ResetOriginalSyncData();
|
|
|
|
SONGMAN->UpdatePopular();
|
|
SONGMAN->UpdateShuffled();
|
|
|
|
/* We may have cached trails from before everything was loaded (eg. from
|
|
* before SongManager::UpdatePopular could be called). Erase the cache. */
|
|
SONGMAN->RegenerateNonFixedCourses();
|
|
|
|
STATSMAN->Reset();
|
|
|
|
FOREACH_PlayerNumber(p)
|
|
{
|
|
if( PREFSMAN->m_ShowDancingCharacters == SDC_Random )
|
|
m_pCurCharacters[p] = CHARMAN->GetRandomCharacter();
|
|
else
|
|
m_pCurCharacters[p] = CHARMAN->GetDefaultCharacter();
|
|
ASSERT( m_pCurCharacters[p] != nullptr );
|
|
}
|
|
|
|
m_bTemporaryEventMode = false;
|
|
|
|
LIGHTSMAN->SetLightsMode( LIGHTSMODE_ATTRACT );
|
|
|
|
m_stEdit.Set( StepsType_Invalid );
|
|
m_pEditSourceSteps.Set(nullptr);
|
|
m_stEditSource.Set( StepsType_Invalid );
|
|
m_iEditCourseEntryIndex.Set( -1 );
|
|
m_sEditLocalProfileID.Set( "" );
|
|
|
|
m_bBackedOutOfFinalStage = false;
|
|
m_bEarnedExtraStage = false;
|
|
sExpandedSectionName = "";
|
|
|
|
ApplyCmdline();
|
|
}
|
|
|
|
void GameState::JoinPlayer( PlayerNumber pn )
|
|
{
|
|
// Make sure the join will be successful before doing it. -Kyz
|
|
{
|
|
int players_joined= 0;
|
|
for(int i= 0; i < NUM_PLAYERS; ++i)
|
|
{
|
|
players_joined+= m_bSideIsJoined[i];
|
|
}
|
|
if(players_joined > 0)
|
|
{
|
|
const Style* cur_style= GetCurrentStyle(PLAYER_INVALID);
|
|
if(cur_style)
|
|
{
|
|
const Style* new_style= GAMEMAN->GetFirstCompatibleStyle(m_pCurGame,
|
|
players_joined + 1, cur_style->m_StepsType);
|
|
if(new_style == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* If joint premium and we're not taking away a credit for the 2nd join,
|
|
* give the new player the same number of stage tokens that the old player
|
|
* has. */
|
|
if( GetCoinMode() == CoinMode_Pay && GetPremium() == Premium_2PlayersFor1Credit && GetNumSidesJoined() == 1 )
|
|
m_iPlayerStageTokens[pn] = m_iPlayerStageTokens[this->GetMasterPlayerNumber()];
|
|
else
|
|
m_iPlayerStageTokens[pn] = PREFSMAN->m_iSongsPerPlay;
|
|
|
|
m_bSideIsJoined[pn] = true;
|
|
|
|
if( this->GetMasterPlayerNumber() == PLAYER_INVALID )
|
|
this->SetMasterPlayerNumber(pn);
|
|
|
|
// if first player to join, set start time
|
|
if( GetNumSidesJoined() == 1 )
|
|
BeginGame();
|
|
|
|
// Count each player join as a play.
|
|
{
|
|
Profile* pMachineProfile = PROFILEMAN->GetMachineProfile();
|
|
pMachineProfile->m_iTotalSessions++;
|
|
}
|
|
|
|
// Set the current style to something appropriate for the new number of joined players.
|
|
// beat gametype's versus styles use a different stepstype from its single
|
|
// styles, so when GameCommand tries to join both players for a versus
|
|
// style, it hits the assert when joining the first player. So if the first
|
|
// player is being joined and the current styletype is for two players,
|
|
// assume that the second player will be joined immediately afterwards and
|
|
// don't try to change the style. -Kyz
|
|
const Style* cur_style= GetCurrentStyle(PLAYER_INVALID);
|
|
if(cur_style != nullptr && !(pn == PLAYER_1 &&
|
|
(cur_style->m_StyleType == StyleType_TwoPlayersTwoSides ||
|
|
cur_style->m_StyleType == StyleType_TwoPlayersSharedSides)))
|
|
{
|
|
const Style *pStyle;
|
|
// Only use one player for StyleType_OnePlayerTwoSides and StepsTypes
|
|
// that can only be played by one player (e.g. dance-solo,
|
|
// dance-threepanel, popn-nine). -aj
|
|
// XXX?: still shows joined player as "Insert Card". May not be an issue? -aj
|
|
if( cur_style->m_StyleType == StyleType_OnePlayerTwoSides ||
|
|
cur_style->m_StepsType == StepsType_dance_solo ||
|
|
cur_style->m_StepsType == StepsType_dance_threepanel ||
|
|
cur_style->m_StepsType == StepsType_popn_nine )
|
|
pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, 1, cur_style->m_StepsType );
|
|
else
|
|
pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, GetNumSidesJoined(), cur_style->m_StepsType );
|
|
|
|
// use SetCurrentStyle in case of StyleType_OnePlayerTwoSides
|
|
SetCurrentStyle( pStyle, pn );
|
|
}
|
|
|
|
Message msg( MessageIDToString(Message_PlayerJoined) );
|
|
msg.SetParam( "Player", pn );
|
|
MESSAGEMAN->Broadcast( msg );
|
|
}
|
|
|
|
void GameState::UnjoinPlayer( PlayerNumber pn )
|
|
{
|
|
/* Unjoin STATSMAN first, so steps used by this player are released
|
|
* and can be released by PROFILEMAN. */
|
|
STATSMAN->UnjoinPlayer( pn );
|
|
m_bSideIsJoined[pn] = false;
|
|
m_iPlayerStageTokens[pn] = 0;
|
|
|
|
ResetPlayer( pn );
|
|
|
|
if( this->GetMasterPlayerNumber() == pn )
|
|
{
|
|
// We can't use GetFirstHumanPlayer() because if both players were joined, GetFirstHumanPlayer() will always return PLAYER_1, even when PLAYER_1 is the player we're unjoining.
|
|
FOREACH_HumanPlayer( hp )
|
|
{
|
|
if( pn != hp )
|
|
{
|
|
this->SetMasterPlayerNumber(hp);
|
|
}
|
|
}
|
|
if( this->GetMasterPlayerNumber() == pn )
|
|
{
|
|
this->SetMasterPlayerNumber(PLAYER_INVALID);
|
|
}
|
|
}
|
|
|
|
Message msg( MessageIDToString(Message_PlayerUnjoined) );
|
|
msg.SetParam( "Player", pn );
|
|
MESSAGEMAN->Broadcast( msg );
|
|
|
|
// If there are no players left, reset some non-player-specific stuff, too.
|
|
if( this->GetMasterPlayerNumber() == PLAYER_INVALID )
|
|
{
|
|
SongOptions so;
|
|
GetDefaultSongOptions( so );
|
|
m_SongOptions.Assign( ModsLevel_Preferred, so );
|
|
m_bDidModeChangeNoteSkin = false;
|
|
}
|
|
}
|
|
|
|
/* xxx: handle multiplayer join? -aj */
|
|
|
|
namespace
|
|
{
|
|
bool JoinInputInternal( PlayerNumber pn )
|
|
{
|
|
if( !GAMESTATE->PlayersCanJoin() )
|
|
return false;
|
|
|
|
// If this side is already in, don't re-join.
|
|
if( GAMESTATE->m_bSideIsJoined[pn] )
|
|
return false;
|
|
|
|
// subtract coins
|
|
int iCoinsNeededToJoin = GAMESTATE->GetCoinsNeededToJoin();
|
|
|
|
if( GAMESTATE->m_iCoins < iCoinsNeededToJoin )
|
|
return false; // not enough coins
|
|
|
|
GAMESTATE->m_iCoins.Set( GAMESTATE->m_iCoins - iCoinsNeededToJoin );
|
|
|
|
GAMESTATE->JoinPlayer( pn );
|
|
|
|
// On Join, make sure to update Coins File
|
|
BOOKKEEPER->WriteCoinsFile(GAMESTATE->m_iCoins.Get());
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// Handle an input that can join a player. Return true if the player joined.
|
|
bool GameState::JoinInput( PlayerNumber pn )
|
|
{
|
|
// When AutoJoin is enabled, join all players on a single start press.
|
|
if( GAMESTATE->m_bAutoJoin.Get() )
|
|
return JoinPlayers();
|
|
else
|
|
return JoinInputInternal( pn );
|
|
}
|
|
|
|
// Attempt to join all players, as if each player pressed Start.
|
|
bool GameState::JoinPlayers()
|
|
{
|
|
bool bJoined = false;
|
|
FOREACH_PlayerNumber( pn )
|
|
{
|
|
if( JoinInputInternal(pn) )
|
|
bJoined = true;
|
|
}
|
|
return bJoined;
|
|
}
|
|
|
|
int GameState::GetCoinsNeededToJoin() const
|
|
{
|
|
int iCoinsToCharge = 0;
|
|
|
|
if( GetCoinMode() == CoinMode_Pay )
|
|
iCoinsToCharge = PREFSMAN->m_iCoinsPerCredit;
|
|
|
|
// If joint premium, don't take away a credit for the second join.
|
|
if( GetPremium() == Premium_2PlayersFor1Credit &&
|
|
GetNumSidesJoined() == 1 )
|
|
iCoinsToCharge = 0;
|
|
|
|
return iCoinsToCharge;
|
|
}
|
|
|
|
/* Game flow:
|
|
*
|
|
* BeginGame() - the first player has joined; the game is starting.
|
|
*
|
|
* PlayersFinalized() - player memory cards are loaded; later joins won't have memory cards this stage
|
|
*
|
|
* BeginStage() - gameplay is beginning
|
|
*
|
|
* optional: CancelStage() - gameplay aborted (Back pressed), undo BeginStage and back up
|
|
*
|
|
* CommitStageStats() - gameplay is finished
|
|
* Saves STATSMAN->m_CurStageStats to the profiles, so profile information
|
|
* is up-to-date for Evaluation.
|
|
*
|
|
* FinishStage() - gameplay and evaluation is finished
|
|
* Clears data which was stored by CommitStageStats. */
|
|
void GameState::BeginGame()
|
|
{
|
|
m_timeGameStarted.Touch();
|
|
|
|
m_vpsNamesThatWereFilled.clear();
|
|
m_sPlayersThatWereFilled.clear();
|
|
|
|
// Play attract on the ending screen, then on the ranking screen
|
|
// even if attract sounds are set to off.
|
|
m_iNumTimesThroughAttract = -1;
|
|
|
|
FOREACH_PlayerNumber( pn )
|
|
MEMCARDMAN->UnlockCard( pn );
|
|
}
|
|
|
|
void GameState::LoadProfiles( bool bLoadEdits )
|
|
{
|
|
// Unlock any cards that we might want to load.
|
|
FOREACH_HumanPlayer( pn )
|
|
if( !PROFILEMAN->IsPersistentProfile(pn) )
|
|
MEMCARDMAN->UnlockCard( pn );
|
|
|
|
MEMCARDMAN->WaitForCheckingToComplete();
|
|
|
|
FOREACH_HumanPlayer( pn )
|
|
{
|
|
// If a profile is already loaded, this was already called.
|
|
if( PROFILEMAN->IsPersistentProfile(pn) )
|
|
continue;
|
|
|
|
MEMCARDMAN->MountCard( pn );
|
|
bool bSuccess = PROFILEMAN->LoadFirstAvailableProfile( pn, bLoadEdits ); // load full profile
|
|
MEMCARDMAN->UnmountCard( pn );
|
|
|
|
if( !bSuccess )
|
|
continue;
|
|
|
|
// Lock the card on successful load, so we won't allow it to be changed.
|
|
MEMCARDMAN->LockCard( pn );
|
|
|
|
LoadCurrentSettingsFromProfile( pn );
|
|
|
|
Profile* pPlayerProfile = PROFILEMAN->GetProfile( pn );
|
|
if( pPlayerProfile )
|
|
pPlayerProfile->m_iTotalSessions++;
|
|
}
|
|
}
|
|
|
|
void GameState::SavePlayerProfiles()
|
|
{
|
|
FOREACH_HumanPlayer( pn )
|
|
SavePlayerProfile( pn );
|
|
}
|
|
|
|
void GameState::SavePlayerProfile( PlayerNumber pn )
|
|
{
|
|
if( !PROFILEMAN->IsPersistentProfile(pn) )
|
|
return;
|
|
|
|
// AutoplayCPU should not save scores. -aj
|
|
// xxx: this MAY cause issues with Multiplayer. However, without a working
|
|
// Multiplayer build, we'll never know. -aj
|
|
if( m_pPlayerState[pn]->m_PlayerController != PC_HUMAN )
|
|
return;
|
|
|
|
bool bWasMemoryCard = PROFILEMAN->ProfileWasLoadedFromMemoryCard(pn);
|
|
if( bWasMemoryCard )
|
|
MEMCARDMAN->MountCard( pn );
|
|
PROFILEMAN->SaveProfile( pn );
|
|
if( bWasMemoryCard )
|
|
MEMCARDMAN->UnmountCard( pn );
|
|
}
|
|
|
|
bool GameState::HaveProfileToLoad()
|
|
{
|
|
FOREACH_HumanPlayer( pn )
|
|
{
|
|
// We won't load this profile if it's already loaded.
|
|
if( PROFILEMAN->IsPersistentProfile(pn) )
|
|
continue;
|
|
|
|
// If a memory card is inserted, we'l try to load it.
|
|
if( MEMCARDMAN->CardInserted(pn) )
|
|
return true;
|
|
if( !PROFILEMAN->m_sDefaultLocalProfileID[pn].Get().empty() )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameState::HaveProfileToSave()
|
|
{
|
|
FOREACH_HumanPlayer( pn )
|
|
if( PROFILEMAN->IsPersistentProfile(pn) )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void GameState::SaveLocalData()
|
|
{
|
|
BOOKKEEPER->WriteToDisk();
|
|
PROFILEMAN->SaveMachineProfile();
|
|
}
|
|
|
|
int GameState::GetNumStagesMultiplierForSong( const Song* pSong )
|
|
{
|
|
int iNumStages = 1;
|
|
|
|
ASSERT( pSong != nullptr );
|
|
if( pSong->IsMarathon() )
|
|
iNumStages *= 3;
|
|
if( pSong->IsLong() )
|
|
iNumStages *= 2;
|
|
|
|
return iNumStages;
|
|
}
|
|
|
|
int GameState::GetNumStagesForCurrentSongAndStepsOrCourse() const
|
|
{
|
|
int iNumStagesOfThisSong = 1;
|
|
if( m_pCurSong )
|
|
{
|
|
/* Extra stages need to only count as one stage in case a multi-stage
|
|
* song is chosen. */
|
|
if( IsAnExtraStage() )
|
|
iNumStagesOfThisSong = 1;
|
|
else
|
|
iNumStagesOfThisSong = GameState::GetNumStagesMultiplierForSong( m_pCurSong );
|
|
}
|
|
else if( m_pCurCourse )
|
|
iNumStagesOfThisSong = PREFSMAN->m_iSongsPerPlay;
|
|
else
|
|
return -1;
|
|
|
|
iNumStagesOfThisSong = std::max( iNumStagesOfThisSong, 1 );
|
|
|
|
return iNumStagesOfThisSong;
|
|
}
|
|
|
|
// Called by ScreenGameplay. Set the length of the current song.
|
|
void GameState::BeginStage()
|
|
{
|
|
if( m_bDemonstrationOrJukebox )
|
|
return;
|
|
|
|
// This should only be called once per stage.
|
|
if( m_iNumStagesOfThisSong != 0 )
|
|
LOG->Warn( "XXX: m_iNumStagesOfThisSong == %i?", m_iNumStagesOfThisSong );
|
|
|
|
ResetStageStatistics();
|
|
AdjustSync::ResetOriginalSyncData();
|
|
|
|
if( !ARE_STAGE_PLAYER_MODS_FORCED )
|
|
{
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
ModsGroup<PlayerOptions> &po = m_pPlayerState[p]->m_PlayerOptions;
|
|
po.Assign(ModsLevel_Stage,
|
|
m_pPlayerState[p]->m_PlayerOptions.GetPreferred());
|
|
}
|
|
}
|
|
if( !ARE_STAGE_SONG_MODS_FORCED )
|
|
m_SongOptions.Assign( ModsLevel_Stage, m_SongOptions.GetPreferred() );
|
|
|
|
STATSMAN->m_CurStageStats.m_fMusicRate = m_SongOptions.GetSong().m_fMusicRate;
|
|
m_iNumStagesOfThisSong = GetNumStagesForCurrentSongAndStepsOrCourse();
|
|
ASSERT( m_iNumStagesOfThisSong != -1 );
|
|
FOREACH_EnabledPlayer( p )
|
|
{
|
|
// only do this check with human players, assume CPU players (Rave)
|
|
// always have tokens. -aj (this could probably be moved below, even.)
|
|
if( !IsEventMode() && !IsCpuPlayer(p) )
|
|
{
|
|
if(m_iPlayerStageTokens[p] < m_iNumStagesOfThisSong)
|
|
{
|
|
LuaHelpers::ReportScriptErrorFmt("Player %d only has %d stage tokens, but needs %d.", p, m_iPlayerStageTokens[p], m_iNumStagesOfThisSong);
|
|
}
|
|
}
|
|
m_iPlayerStageTokens[p] -= m_iNumStagesOfThisSong;
|
|
}
|
|
FOREACH_HumanPlayer( pn )
|
|
if( CurrentOptionsDisqualifyPlayer(pn) )
|
|
STATSMAN->m_CurStageStats.m_player[pn].m_bDisqualified = true;
|
|
m_bEarnedExtraStage = false;
|
|
m_sStageGUID = CryptManager::GenerateRandomUUID();
|
|
}
|
|
|
|
void GameState::CancelStage()
|
|
{
|
|
FOREACH_CpuPlayer( p )
|
|
{
|
|
switch( m_PlayMode )
|
|
{
|
|
case PLAY_MODE_BATTLE:
|
|
case PLAY_MODE_RAVE:
|
|
m_iPlayerStageTokens[p] = PREFSMAN->m_iSongsPerPlay;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
FOREACH_EnabledPlayer( p )
|
|
m_iPlayerStageTokens[p] += m_iNumStagesOfThisSong;
|
|
m_iNumStagesOfThisSong = 0;
|
|
ResetStageStatistics();
|
|
}
|
|
|
|
void GameState::CommitStageStats()
|
|
{
|
|
if( m_bDemonstrationOrJukebox )
|
|
return;
|
|
|
|
STATSMAN->CommitStatsToProfiles( &STATSMAN->m_CurStageStats );
|
|
|
|
// Update TotalPlaySeconds.
|
|
int iPlaySeconds = std::max( 0, (int) m_timeGameStarted.GetDeltaTime() );
|
|
|
|
Profile* pMachineProfile = PROFILEMAN->GetMachineProfile();
|
|
pMachineProfile->m_iTotalSessionSeconds += iPlaySeconds;
|
|
|
|
FOREACH_HumanPlayer( p )
|
|
{
|
|
Profile* pPlayerProfile = PROFILEMAN->GetProfile( p );
|
|
if( pPlayerProfile )
|
|
pPlayerProfile->m_iTotalSessionSeconds += iPlaySeconds;
|
|
}
|
|
}
|
|
|
|
/* Called by ScreenSelectMusic (etc). Increment the stage counter if we just
|
|
* played a song. Might be called more than once. */
|
|
void GameState::FinishStage()
|
|
{
|
|
// Increment the stage counter.
|
|
const int iOldStageIndex = m_iCurrentStageIndex;
|
|
++m_iCurrentStageIndex;
|
|
|
|
m_iNumStagesOfThisSong = 0;
|
|
|
|
EarnedExtraStage e = CalculateEarnedExtraStage();
|
|
STATSMAN->m_CurStageStats.m_EarnedExtraStage = e;
|
|
if( e != EarnedExtraStage_No )
|
|
{
|
|
LOG->Trace( "awarded extra stage" );
|
|
FOREACH_HumanPlayer( p )
|
|
{
|
|
// todo: unhardcode the extra stage limit? -aj
|
|
if( m_iAwardedExtraStages[p] < 2 )
|
|
{
|
|
++m_iAwardedExtraStages[p];
|
|
++m_iPlayerStageTokens[p];
|
|
m_bEarnedExtraStage = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the current combo to the profiles so it can be used for ComboContinuesBetweenSongs.
|
|
FOREACH_HumanPlayer( p )
|
|
{
|
|
Profile* pProfile = PROFILEMAN->GetProfile(p);
|
|
pProfile->m_iCurrentCombo = STATSMAN->m_CurStageStats.m_player[p].m_iCurCombo;
|
|
//If the sort order is Recent, move the profile to the top of the list.
|
|
if (PREFSMAN->m_ProfileSortOrder == ProfileSortOrder_Recent && PROFILEMAN->IsPersistentProfile(p))
|
|
{
|
|
int numLocalProfiles = PROFILEMAN->GetNumLocalProfiles();
|
|
for (int i = 0; i < numLocalProfiles; i++)
|
|
{
|
|
Profile *profile = PROFILEMAN->GetLocalProfileFromIndex(i);
|
|
if (profile->m_sGuid == pProfile->m_sGuid)
|
|
{
|
|
PROFILEMAN->MoveProfileTopBottom(i, PREFSMAN->m_bProfileSortOrderAscending);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_bDemonstrationOrJukebox )
|
|
return;
|
|
|
|
// todo: simplify. profile saving is accomplished in ScreenProfileSave
|
|
// now; all this code does differently is save machine profile as well. -aj
|
|
if( IsEventMode() )
|
|
{
|
|
const int iSaveProfileEvery = 3;
|
|
if( iOldStageIndex/iSaveProfileEvery < m_iCurrentStageIndex/iSaveProfileEvery )
|
|
{
|
|
LOG->Trace( "Played %i stages; saving profiles ...", iSaveProfileEvery );
|
|
PROFILEMAN->SaveMachineProfile();
|
|
this->SavePlayerProfiles();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameState::LoadCurrentSettingsFromProfile( PlayerNumber pn )
|
|
{
|
|
if( !PROFILEMAN->IsPersistentProfile(pn) )
|
|
return;
|
|
|
|
const Profile *pProfile = PROFILEMAN->GetProfile(pn);
|
|
|
|
// apply saved default modifiers if any
|
|
RString sModifiers;
|
|
if( pProfile->GetDefaultModifiers( m_pCurGame, sModifiers ) )
|
|
{
|
|
/* We don't save negative preferences (eg. "no reverse"). If the theme
|
|
* sets a default of "reverse", and the player turns it off, we should
|
|
* set it off. However, don't reset modifiers that aren't saved by the
|
|
* profile, so we don't ignore unsaved modifiers when a profile is in use. */
|
|
PO_GROUP_CALL( m_pPlayerState[pn]->m_PlayerOptions, ModsLevel_Preferred, ResetSavedPrefs );
|
|
ApplyPreferredModifiers( pn, sModifiers );
|
|
}
|
|
// Only set the sort order if it wasn't already set by a GameCommand (or by an earlier profile)
|
|
if( m_PreferredSortOrder == SortOrder_Invalid && pProfile->m_SortOrder != SortOrder_Invalid )
|
|
m_PreferredSortOrder = pProfile->m_SortOrder;
|
|
if( pProfile->m_LastDifficulty != Difficulty_Invalid )
|
|
m_PreferredDifficulty[pn].Set( pProfile->m_LastDifficulty );
|
|
if( pProfile->m_LastCourseDifficulty != Difficulty_Invalid )
|
|
m_PreferredCourseDifficulty[pn].Set( pProfile->m_LastCourseDifficulty );
|
|
// Only set the PreferredStepsType if it wasn't already set by a GameCommand (or by an earlier profile)
|
|
if( m_PreferredStepsType == StepsType_Invalid && pProfile->m_LastStepsType != StepsType_Invalid )
|
|
m_PreferredStepsType.Set( pProfile->m_LastStepsType );
|
|
if( m_pPreferredSong == nullptr )
|
|
m_pPreferredSong = pProfile->m_lastSong.ToSong();
|
|
if( m_pPreferredCourse == nullptr )
|
|
m_pPreferredCourse = pProfile->m_lastCourse.ToCourse();
|
|
}
|
|
|
|
void GameState::SaveCurrentSettingsToProfile( PlayerNumber pn )
|
|
{
|
|
if( !PROFILEMAN->IsPersistentProfile(pn) )
|
|
return;
|
|
if( m_bDemonstrationOrJukebox )
|
|
return;
|
|
|
|
Profile* pProfile = PROFILEMAN->GetProfile(pn);
|
|
|
|
pProfile->SetDefaultModifiers( m_pCurGame, m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().GetSavedPrefsString() );
|
|
if( IsSongSort(m_PreferredSortOrder) )
|
|
pProfile->m_SortOrder = m_PreferredSortOrder;
|
|
if( m_PreferredDifficulty[pn] != Difficulty_Invalid )
|
|
pProfile->m_LastDifficulty = m_PreferredDifficulty[pn];
|
|
if( m_PreferredCourseDifficulty[pn] != Difficulty_Invalid )
|
|
pProfile->m_LastCourseDifficulty = m_PreferredCourseDifficulty[pn];
|
|
if( m_PreferredStepsType != StepsType_Invalid )
|
|
pProfile->m_LastStepsType = m_PreferredStepsType;
|
|
if( m_pPreferredSong )
|
|
pProfile->m_lastSong.FromSong( m_pPreferredSong );
|
|
if( m_pPreferredCourse )
|
|
pProfile->m_lastCourse.FromCourse( m_pPreferredCourse );
|
|
}
|
|
|
|
bool GameState::CanSafelyEnterGameplay(RString& reason)
|
|
{
|
|
if(!IsCourseMode())
|
|
{
|
|
Song const* song= m_pCurSong;
|
|
if(song == nullptr)
|
|
{
|
|
reason= "Current song is null.";
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Course const* song= m_pCurCourse;
|
|
if(song == nullptr)
|
|
{
|
|
reason= "Current course is null.";
|
|
return false;
|
|
}
|
|
}
|
|
FOREACH_EnabledPlayer(pn)
|
|
{
|
|
Style const* style= GetCurrentStyle(pn);
|
|
if(style == nullptr)
|
|
{
|
|
reason= ssprintf("Style for player %d is null.", pn+1);
|
|
return false;
|
|
}
|
|
if(!IsCourseMode())
|
|
{
|
|
Steps const* steps= m_pCurSteps[pn];
|
|
if(steps == nullptr)
|
|
{
|
|
reason= ssprintf("Steps for player %d is null.", pn+1);
|
|
return false;
|
|
}
|
|
if(steps->m_StepsType != style->m_StepsType)
|
|
{
|
|
reason= ssprintf("Player %d StepsType %s for steps does not equal "
|
|
"StepsType %s for style.", pn+1,
|
|
GAMEMAN->GetStepsTypeInfo(steps->m_StepsType).szName,
|
|
GAMEMAN->GetStepsTypeInfo(style->m_StepsType).szName);
|
|
return false;
|
|
}
|
|
if(steps->m_pSong != m_pCurSong)
|
|
{
|
|
reason= ssprintf("Steps for player %d are not for the current song.",
|
|
pn+1);
|
|
return false;
|
|
}
|
|
NoteData ndtemp;
|
|
steps->GetNoteData(ndtemp);
|
|
if(ndtemp.GetNumTracks() != style->m_iColsPerPlayer)
|
|
{
|
|
reason= ssprintf("Steps for player %d have %d columns, style has %d "
|
|
"columns.", pn+1, ndtemp.GetNumTracks(), style->m_iColsPerPlayer);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Trail const* steps= m_pCurTrail[pn];
|
|
if(steps == nullptr)
|
|
{
|
|
reason= ssprintf("Steps for player %d is null.", pn+1);
|
|
return false;
|
|
}
|
|
if(steps->m_StepsType != style->m_StepsType)
|
|
{
|
|
reason= ssprintf("Player %d StepsType %s for steps does not equal "
|
|
"StepsType %s for style.", pn+1,
|
|
GAMEMAN->GetStepsTypeInfo(steps->m_StepsType).szName,
|
|
GAMEMAN->GetStepsTypeInfo(style->m_StepsType).szName);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GameState::SetCompatibleStylesForPlayers()
|
|
{
|
|
bool style_set= false;
|
|
if(IsCourseMode())
|
|
{
|
|
if(m_pCurCourse != nullptr)
|
|
{
|
|
const Style* style= m_pCurCourse->GetCourseStyle(m_pCurGame, GetNumSidesJoined());
|
|
if(style != nullptr)
|
|
{
|
|
style_set= true;
|
|
SetCurrentStyle(style, PLAYER_INVALID);
|
|
}
|
|
}
|
|
else if(GetCurrentStyle(PLAYER_INVALID) == nullptr)
|
|
{
|
|
std::vector<StepsType> vst;
|
|
GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst);
|
|
const Style *style = GAMEMAN->GetFirstCompatibleStyle(
|
|
m_pCurGame, GetNumSidesJoined(), vst[0]);
|
|
SetCurrentStyle(style, PLAYER_INVALID);
|
|
}
|
|
}
|
|
if(!style_set)
|
|
{
|
|
FOREACH_EnabledPlayer(pn)
|
|
{
|
|
StepsType st= StepsType_Invalid;
|
|
if(m_pCurSteps[pn] != nullptr)
|
|
{
|
|
st= m_pCurSteps[pn]->m_StepsType;
|
|
}
|
|
else if(m_pCurTrail[pn] != nullptr)
|
|
{
|
|
st= m_pCurTrail[pn]->m_StepsType;
|
|
}
|
|
else
|
|
{
|
|
std::vector<StepsType> vst;
|
|
GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst);
|
|
st= vst[0];
|
|
}
|
|
const Style *style = GAMEMAN->GetFirstCompatibleStyle(
|
|
m_pCurGame, GetNumSidesJoined(), st);
|
|
SetCurrentStyle(style, pn);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameState::ForceSharedSidesMatch()
|
|
{
|
|
PlayerNumber pn_with_shared= PLAYER_INVALID;
|
|
const Style* shared_style= nullptr;
|
|
FOREACH_EnabledPlayer(pn)
|
|
{
|
|
const Style* style= GetCurrentStyle(pn);
|
|
ASSERT_M(style != nullptr, "Style being null should not be possible.");
|
|
if(style->m_StyleType == StyleType_TwoPlayersSharedSides)
|
|
{
|
|
pn_with_shared= pn;
|
|
shared_style= style;
|
|
}
|
|
}
|
|
if(pn_with_shared != PLAYER_INVALID)
|
|
{
|
|
ASSERT_M(GetNumPlayersEnabled() == 2, "2 players must be enabled for shared sides.");
|
|
PlayerNumber other_pn= OPPOSITE_PLAYER[pn_with_shared];
|
|
const Style* other_style= GetCurrentStyle(other_pn);
|
|
ASSERT_M(other_style != nullptr, "Other player's style being null should not be possible.");
|
|
if(other_style->m_StyleType != StyleType_TwoPlayersSharedSides)
|
|
{
|
|
SetCurrentStyle(shared_style, other_pn);
|
|
if(IsCourseMode())
|
|
{
|
|
m_pCurTrail[other_pn].Set(m_pCurTrail[pn_with_shared]);
|
|
}
|
|
else
|
|
{
|
|
m_pCurSteps[other_pn].Set(m_pCurSteps[pn_with_shared]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameState::ForceOtherPlayersToCompatibleSteps(PlayerNumber main)
|
|
{
|
|
if(IsCourseMode())
|
|
{
|
|
Trail* steps_to_match= m_pCurTrail[main].Get();
|
|
if(steps_to_match == nullptr) { return; }
|
|
int num_players= GAMESTATE->GetNumPlayersEnabled();
|
|
StyleType styletype_to_match= GAMEMAN->GetFirstCompatibleStyle(
|
|
GAMESTATE->GetCurrentGame(), num_players, steps_to_match->m_StepsType)
|
|
->m_StyleType;
|
|
FOREACH_EnabledPlayer(pn)
|
|
{
|
|
Trail* pn_steps= m_pCurTrail[pn].Get();
|
|
bool match_failed= pn_steps == nullptr;
|
|
if(steps_to_match != pn_steps && pn_steps != nullptr)
|
|
{
|
|
StyleType pn_styletype= GAMEMAN->GetFirstCompatibleStyle(
|
|
GAMESTATE->GetCurrentGame(), num_players, pn_steps->m_StepsType)
|
|
->m_StyleType;
|
|
if(styletype_to_match == StyleType_TwoPlayersSharedSides ||
|
|
pn_styletype == StyleType_TwoPlayersSharedSides)
|
|
{
|
|
match_failed= true;
|
|
}
|
|
}
|
|
if(match_failed)
|
|
{
|
|
m_pCurTrail[pn].Set(steps_to_match);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Steps* steps_to_match= m_pCurSteps[main].Get();
|
|
if(steps_to_match == nullptr) { return; }
|
|
int num_players= GAMESTATE->GetNumPlayersEnabled();
|
|
StyleType styletype_to_match= GAMEMAN->GetFirstCompatibleStyle(
|
|
GAMESTATE->GetCurrentGame(), num_players, steps_to_match->m_StepsType)
|
|
->m_StyleType;
|
|
RString music_to_match= steps_to_match->GetMusicFile();
|
|
FOREACH_EnabledPlayer(pn)
|
|
{
|
|
Steps* pn_steps= m_pCurSteps[pn].Get();
|
|
bool match_failed= pn_steps == nullptr;
|
|
if(steps_to_match != pn_steps && pn_steps != nullptr)
|
|
{
|
|
StyleType pn_styletype= GAMEMAN->GetFirstCompatibleStyle(
|
|
GAMESTATE->GetCurrentGame(), num_players, pn_steps->m_StepsType)
|
|
->m_StyleType;
|
|
if(styletype_to_match == StyleType_TwoPlayersSharedSides ||
|
|
pn_styletype == StyleType_TwoPlayersSharedSides)
|
|
{
|
|
match_failed= true;
|
|
}
|
|
if(music_to_match != pn_steps->GetMusicFile())
|
|
{
|
|
match_failed= true;
|
|
}
|
|
}
|
|
if(match_failed)
|
|
{
|
|
m_pCurSteps[pn].Set(steps_to_match);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameState::Update( float fDelta )
|
|
{
|
|
m_SongOptions.Update( fDelta );
|
|
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
m_pPlayerState[p]->Update( fDelta );
|
|
|
|
if( !m_bGoalComplete[p] && IsGoalComplete(p) )
|
|
{
|
|
m_bGoalComplete[p] = true;
|
|
MESSAGEMAN->Broadcast( (MessageID)(Message_GoalCompleteP1+Enum::to_integral(p)) );
|
|
}
|
|
}
|
|
|
|
if( GAMESTATE->m_pCurCourse )
|
|
{
|
|
if( GAMESTATE->m_pCurCourse->m_fGoalSeconds > 0 && !m_bWorkoutGoalComplete )
|
|
{
|
|
const StageStats &ssCurrent = STATSMAN->m_CurStageStats;
|
|
bool bGoalComplete = ssCurrent.m_fGameplaySeconds > GAMESTATE->m_pCurCourse->m_fGoalSeconds;
|
|
if( bGoalComplete )
|
|
{
|
|
MESSAGEMAN->Broadcast( "WorkoutGoalComplete" );
|
|
m_bWorkoutGoalComplete = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameState::SetCurGame( const Game *pGame )
|
|
{
|
|
m_pCurGame.Set( pGame );
|
|
RString sGame = pGame ? RString(pGame->m_szName) : RString();
|
|
PREFSMAN->SetCurrentGame( sGame );
|
|
}
|
|
|
|
const float GameState::MUSIC_SECONDS_INVALID = -5000.0f;
|
|
|
|
void GameState::ResetMusicStatistics()
|
|
{
|
|
m_Position.Reset();
|
|
m_LastPositionTimer.Touch();
|
|
m_LastPositionSeconds = 0.0f;
|
|
|
|
Actor::SetBGMTime( 0, 0, 0, 0 );
|
|
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
m_pPlayerState[p]->m_Position.Reset();
|
|
}
|
|
}
|
|
|
|
void GameState::ResetStageStatistics()
|
|
{
|
|
StageStats OldStats = STATSMAN->m_CurStageStats;
|
|
STATSMAN->m_CurStageStats = StageStats();
|
|
if( PREFSMAN->m_bComboContinuesBetweenSongs )
|
|
{
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
bool FirstSong = m_iCurrentStageIndex == 0;
|
|
if( FirstSong )
|
|
{
|
|
Profile* pProfile = PROFILEMAN->GetProfile(p);
|
|
STATSMAN->m_CurStageStats.m_player[p].m_iCurCombo = pProfile->m_iCurrentCombo;
|
|
}
|
|
else
|
|
{
|
|
STATSMAN->m_CurStageStats.m_player[p].m_iCurCombo = OldStats.m_player[p].m_iCurCombo;
|
|
}
|
|
}
|
|
}
|
|
|
|
RemoveAllActiveAttacks();
|
|
FOREACH_PlayerNumber( p )
|
|
m_pPlayerState[p]->RemoveAllInventory();
|
|
m_fOpponentHealthPercent = 1;
|
|
m_fHasteRate = 0;
|
|
m_fLastHasteUpdateMusicSeconds = 0;
|
|
m_fAccumulatedHasteSeconds = 0;
|
|
m_fTugLifePercentP1 = 0.5f;
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
m_pPlayerState[p]->m_fSuperMeter = 0;
|
|
m_pPlayerState[p]->m_HealthState = HealthState_Alive;
|
|
|
|
m_pPlayerState[p]->m_iLastPositiveSumOfAttackLevels = 0;
|
|
m_pPlayerState[p]->m_fSecondsUntilAttacksPhasedOut = 0; // PlayerAI not affected
|
|
|
|
m_bGoalComplete[p] = false;
|
|
}
|
|
m_bWorkoutGoalComplete = false;
|
|
|
|
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
m_vLastStageAwards[p].clear();
|
|
m_vLastPeakComboAwards[p].clear();
|
|
}
|
|
|
|
// Reset the round seed. Do this here and not in FinishStage so that players
|
|
// get new shuffle patterns if they Back out of gameplay and play again.
|
|
m_iStageSeed = rand();
|
|
}
|
|
|
|
void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp )
|
|
{
|
|
/* It's not uncommon to get a lot of duplicated positions from the sound
|
|
* driver, like so: 13.120953,13.130975,13.130975,13.130975,13.140998,...
|
|
* This causes visual stuttering of the arrows. To compensate, keep a
|
|
* RageTimer since the last change. */
|
|
if(fPositionSeconds == m_LastPositionSeconds && !m_paused)
|
|
fPositionSeconds += m_LastPositionTimer.Ago();
|
|
else
|
|
{
|
|
//LOG->Info("Time difference: %+f",
|
|
// m_LastPositionTimer.Ago() - (fPositionSeconds - m_LastPositionSeconds)
|
|
//);
|
|
m_LastPositionTimer.Touch();
|
|
m_LastPositionSeconds = fPositionSeconds;
|
|
}
|
|
|
|
m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp );
|
|
|
|
FOREACH_EnabledPlayer( pn )
|
|
{
|
|
if( m_pCurSteps[pn] )
|
|
{
|
|
float fAdditionalVisualDelay = m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_fVisualDelay;
|
|
m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, *m_pCurSteps[pn]->GetTimingData(), timestamp, fAdditionalVisualDelay );
|
|
Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset );
|
|
}
|
|
}
|
|
Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset );
|
|
// LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze );
|
|
}
|
|
|
|
/*
|
|
update player position code goes here
|
|
*/
|
|
|
|
float GameState::GetSongPercent( float beat ) const
|
|
{
|
|
// 0 = first step; 1 = last step
|
|
float curTime = this->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat(beat);
|
|
return (curTime - m_pCurSong->GetFirstSecond()) / m_pCurSong->GetLastSecond();
|
|
}
|
|
|
|
int GameState::GetNumStagesLeft( PlayerNumber pn ) const
|
|
{
|
|
return m_iPlayerStageTokens[pn];
|
|
}
|
|
|
|
int GameState::GetSmallestNumStagesLeftForAnyHumanPlayer() const
|
|
{
|
|
if( IsEventMode() )
|
|
return 999;
|
|
int iSmallest = INT_MAX;
|
|
FOREACH_HumanPlayer( p )
|
|
iSmallest = std::min( iSmallest, m_iPlayerStageTokens[p] );
|
|
return iSmallest;
|
|
}
|
|
|
|
bool GameState::IsFinalStageForAnyHumanPlayer() const
|
|
{
|
|
return GetSmallestNumStagesLeftForAnyHumanPlayer() == 1;
|
|
}
|
|
|
|
bool GameState::IsFinalStageForEveryHumanPlayer() const
|
|
{
|
|
int song_cost= 1;
|
|
if(m_pCurSong != nullptr)
|
|
{
|
|
if(m_pCurSong->IsLong())
|
|
{
|
|
song_cost= 2;
|
|
}
|
|
else if(m_pCurSong->IsMarathon())
|
|
{
|
|
song_cost= 3;
|
|
}
|
|
}
|
|
// If we're on gameplay or evaluation, they set this to false because those
|
|
// screens have already had the stage tokens subtracted.
|
|
song_cost*= m_AdjustTokensBySongCostForFinalStageCheck;
|
|
int num_on_final= 0;
|
|
int num_humans= 0;
|
|
FOREACH_HumanPlayer(p)
|
|
{
|
|
if(m_iPlayerStageTokens[p] - song_cost <= 0)
|
|
{
|
|
++num_on_final;
|
|
}
|
|
++num_humans;
|
|
}
|
|
return num_on_final >= num_humans;
|
|
}
|
|
|
|
bool GameState::IsAnExtraStage() const
|
|
{
|
|
if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid )
|
|
return false;
|
|
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] > 0;
|
|
}
|
|
|
|
static ThemeMetric<bool> LOCK_EXTRA_STAGE_SELECTION("GameState","LockExtraStageSelection");
|
|
bool GameState::IsAnExtraStageAndSelectionLocked() const
|
|
{
|
|
return IsAnExtraStage() && LOCK_EXTRA_STAGE_SELECTION;
|
|
}
|
|
|
|
bool GameState::IsExtraStage() const
|
|
{
|
|
if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid )
|
|
return false;
|
|
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 1;
|
|
}
|
|
|
|
bool GameState::IsExtraStage2() const
|
|
{
|
|
if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid )
|
|
return false;
|
|
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 2;
|
|
}
|
|
|
|
Stage GameState::GetCurrentStage() const
|
|
{
|
|
if( m_bDemonstrationOrJukebox ) return Stage_Demo;
|
|
// "event" has precedence
|
|
else if( IsEventMode() ) return Stage_Event;
|
|
else if( m_PlayMode == PLAY_MODE_ONI ) return Stage_Oni;
|
|
else if( m_PlayMode == PLAY_MODE_NONSTOP ) return Stage_Nonstop;
|
|
else if( m_PlayMode == PLAY_MODE_ENDLESS ) return Stage_Endless;
|
|
else if( IsExtraStage() ) return Stage_Extra1;
|
|
else if( IsExtraStage2() ) return Stage_Extra2;
|
|
// Previous logic did not factor in current song length, or the fact that
|
|
// players aren't allowed to start a song with 0 tokens. This new
|
|
// function also has logic for handling the Gameplay and Evaluation cases
|
|
// which used to require workarounds on the theme side. -Kyz
|
|
else if(IsFinalStageForEveryHumanPlayer()) return Stage_Final;
|
|
else
|
|
{
|
|
switch( this->m_iCurrentStageIndex )
|
|
{
|
|
case 0: return Stage_1st;
|
|
case 1: return Stage_2nd;
|
|
case 2: return Stage_3rd;
|
|
case 3: return Stage_4th;
|
|
case 4: return Stage_5th;
|
|
case 5: return Stage_6th;
|
|
default: return Stage_Next;
|
|
}
|
|
}
|
|
}
|
|
|
|
int GameState::GetCourseSongIndex() const
|
|
{
|
|
// iSongsPlayed includes the current song, so it's 1-based; subtract one.
|
|
if( GAMESTATE->m_bMultiplayer )
|
|
{
|
|
FOREACH_EnabledMultiPlayer(mp)
|
|
return STATSMAN->m_CurStageStats.m_multiPlayer[mp].m_iSongsPlayed-1;
|
|
FAIL_M("At least one MultiPlayer must be joined.");
|
|
}
|
|
else
|
|
{
|
|
return STATSMAN->m_CurStageStats.m_player[this->GetMasterPlayerNumber()].m_iSongsPlayed-1;
|
|
}
|
|
}
|
|
|
|
/* Hack: when we're loading a new course song, we want to display the new song
|
|
* number, even though we haven't started that song yet. */
|
|
int GameState::GetLoadingCourseSongIndex() const
|
|
{
|
|
int iIndex = GetCourseSongIndex();
|
|
if( m_bLoadingNextSong )
|
|
++iIndex;
|
|
return iIndex;
|
|
}
|
|
|
|
static char const* prepare_song_failures[]= {
|
|
"success",
|
|
"no_current_song",
|
|
"card_mount_failed",
|
|
"load_interrupted",
|
|
};
|
|
|
|
int GameState::prepare_song_for_gameplay()
|
|
{
|
|
Song* curr= m_pCurSong;
|
|
if(curr == nullptr)
|
|
{
|
|
return 1;
|
|
}
|
|
if(curr->m_LoadedFromProfile == ProfileSlot_Invalid)
|
|
{
|
|
return 0;
|
|
}
|
|
ProfileSlot prof_slot= curr->m_LoadedFromProfile;
|
|
PlayerNumber slot_as_pn= PlayerNumber(prof_slot);
|
|
if(!PROFILEMAN->ProfileWasLoadedFromMemoryCard(slot_as_pn))
|
|
{
|
|
return 0;
|
|
}
|
|
if(!MEMCARDMAN->MountCard(slot_as_pn))
|
|
{
|
|
return 2;
|
|
}
|
|
RString prof_dir= PROFILEMAN->GetProfileDir(prof_slot);
|
|
// Song loading changes its paths to point to the cache area. -Kyz
|
|
RString to_dir= curr->GetSongDir();
|
|
RString from_dir= curr->GetPreCustomifyDir();
|
|
// The problem of what files to copy is complicated by steps being able to
|
|
// specify their own music file, and the variety of step file formats.
|
|
// Complex logic to figure out what files the song actually uses would be
|
|
// bug prone. Just copy all audio files and step files. -Kyz
|
|
std::vector<RString> copy_exts= ActorUtil::GetTypeExtensionList(FT_Sound);
|
|
copy_exts.push_back("sm");
|
|
copy_exts.push_back("ssc");
|
|
copy_exts.push_back("lrc");
|
|
std::vector<RString> files_in_dir;
|
|
FILEMAN->GetDirListingWithMultipleExtensions(from_dir, copy_exts, files_in_dir);
|
|
for(size_t i= 0; i < files_in_dir.size(); ++i)
|
|
{
|
|
RString& fname= files_in_dir[i];
|
|
if(!FileCopy(from_dir + fname, to_dir + fname))
|
|
{
|
|
return 3;
|
|
}
|
|
}
|
|
MEMCARDMAN->UnmountCard(slot_as_pn);
|
|
return 0;
|
|
}
|
|
|
|
static LocalizedString PLAYER1 ("GameState","Player 1");
|
|
static LocalizedString PLAYER2 ("GameState","Player 2");
|
|
static LocalizedString CPU ("GameState","CPU");
|
|
RString GameState::GetPlayerDisplayName( PlayerNumber pn ) const
|
|
{
|
|
ASSERT( IsPlayerEnabled(pn) );
|
|
const LocalizedString *pDefaultNames[] = { &PLAYER1, &PLAYER2 };
|
|
if( IsHumanPlayer(pn) )
|
|
{
|
|
if( !PROFILEMAN->GetPlayerName(pn).empty() )
|
|
return PROFILEMAN->GetPlayerName(pn);
|
|
else
|
|
return pDefaultNames[pn]->GetValue();
|
|
}
|
|
else
|
|
{
|
|
return CPU.GetValue();
|
|
}
|
|
}
|
|
|
|
bool GameState::PlayersCanJoin() const
|
|
{
|
|
if(GetNumSidesJoined() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
// If we check the style and it comes up nullptr, either the style has not been
|
|
// chosen, or we're on ScreenSelectMusic with AutoSetStyle.
|
|
// If the style does not come up nullptr, we might be on a screen in a custom
|
|
// theme that wants to allow joining after the style is set anyway.
|
|
// Either way, we can't use the existence of a style to decide.
|
|
// -Kyz
|
|
if( ALLOW_LATE_JOIN.IsLoaded() && ALLOW_LATE_JOIN )
|
|
{
|
|
Screen *pScreen = SCREENMAN->GetTopScreen();
|
|
if(pScreen)
|
|
{
|
|
if(!pScreen->AllowLateJoin())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// We can't use FOREACH_EnabledPlayer because that uses PlayersCanJoin
|
|
// in part of its logic chain. -Kyz
|
|
FOREACH_PlayerNumber(pn)
|
|
{
|
|
const Style* style= GetCurrentStyle(pn);
|
|
if(style)
|
|
{
|
|
const Style* compat_style= GAMEMAN->GetFirstCompatibleStyle(
|
|
m_pCurGame, 2, style->m_StepsType);
|
|
if(compat_style == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int GameState::GetNumSidesJoined() const
|
|
{
|
|
int iNumSidesJoined = 0;
|
|
FOREACH_PlayerNumber( p )
|
|
if( m_bSideIsJoined[p] )
|
|
iNumSidesJoined++; // left side, and right side
|
|
return iNumSidesJoined;
|
|
}
|
|
|
|
const Game* GameState::GetCurrentGame() const
|
|
{
|
|
ASSERT( m_pCurGame != nullptr ); // the game must be set before calling this
|
|
return m_pCurGame;
|
|
}
|
|
|
|
const Style* GameState::GetCurrentStyle(PlayerNumber pn) const
|
|
{
|
|
if(GetCurrentGame() == nullptr) { return nullptr; }
|
|
if(!GetCurrentGame()->m_PlayersHaveSeparateStyles)
|
|
{
|
|
return m_pCurStyle;
|
|
}
|
|
else
|
|
{
|
|
if(pn >= NUM_PLAYERS)
|
|
{
|
|
return m_SeparatedStyles[PLAYER_1] == nullptr ? m_SeparatedStyles[PLAYER_2]
|
|
: m_SeparatedStyles[PLAYER_1];
|
|
}
|
|
return m_SeparatedStyles[pn];
|
|
}
|
|
}
|
|
|
|
void GameState::SetCurrentStyle(const Style *style, PlayerNumber pn)
|
|
{
|
|
if(!GetCurrentGame()->m_PlayersHaveSeparateStyles)
|
|
{
|
|
m_pCurStyle.Set(style);
|
|
}
|
|
else
|
|
{
|
|
if(pn == PLAYER_INVALID)
|
|
{
|
|
FOREACH_PlayerNumber(rpn)
|
|
{
|
|
m_SeparatedStyles[rpn]= style;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_SeparatedStyles[pn]= style;
|
|
}
|
|
}
|
|
if(INPUTMAPPER)
|
|
{
|
|
if(GetCurrentStyle(pn) && GetCurrentStyle(pn)->m_StyleType == StyleType_OnePlayerTwoSides)
|
|
{
|
|
// If the other player is joined, unjoin them because this style only
|
|
// allows one player.
|
|
PlayerNumber other_pn= OPPOSITE_PLAYER[this->GetMasterPlayerNumber()];
|
|
if(GetNumSidesJoined() > 1)
|
|
{
|
|
UnjoinPlayer(other_pn);
|
|
}
|
|
INPUTMAPPER->SetJoinControllers(this->GetMasterPlayerNumber());
|
|
}
|
|
else
|
|
INPUTMAPPER->SetJoinControllers(PLAYER_INVALID);
|
|
}
|
|
}
|
|
|
|
bool GameState::SetCompatibleStyle(StepsType stype, PlayerNumber pn)
|
|
{
|
|
bool style_incompatible= false;
|
|
if(!GetCurrentStyle(pn))
|
|
{
|
|
style_incompatible= true;
|
|
}
|
|
else
|
|
{
|
|
style_incompatible= stype != GetCurrentStyle(pn)->m_StepsType;
|
|
}
|
|
if(CommonMetrics::AUTO_SET_STYLE && style_incompatible)
|
|
{
|
|
const Style* compatible_style= GAMEMAN->GetFirstCompatibleStyle(
|
|
m_pCurGame, GetNumSidesJoined(), stype);
|
|
if(!compatible_style)
|
|
{
|
|
return false;
|
|
}
|
|
SetCurrentStyle(compatible_style, pn);
|
|
}
|
|
return stype == GetCurrentStyle(pn)->m_StepsType;
|
|
}
|
|
|
|
bool GameState::IsPlayerEnabled( PlayerNumber pn ) const
|
|
{
|
|
// In rave, all players are present. Non-human players are CPU controlled.
|
|
switch( m_PlayMode )
|
|
{
|
|
case PLAY_MODE_BATTLE:
|
|
case PLAY_MODE_RAVE:
|
|
return true;
|
|
default:
|
|
return IsHumanPlayer(pn);
|
|
}
|
|
}
|
|
|
|
bool GameState::IsMultiPlayerEnabled( MultiPlayer mp ) const
|
|
{
|
|
return m_MultiPlayerStatus[ mp ] == MultiPlayerStatus_Joined;
|
|
}
|
|
|
|
bool GameState::IsPlayerEnabled( const PlayerState* pPlayerState ) const
|
|
{
|
|
if( pPlayerState->m_mp != MultiPlayer_Invalid )
|
|
return IsMultiPlayerEnabled( pPlayerState->m_mp );
|
|
if( pPlayerState->m_PlayerNumber != PLAYER_INVALID )
|
|
return IsPlayerEnabled( pPlayerState->m_PlayerNumber );
|
|
return false;
|
|
}
|
|
|
|
int GameState::GetNumPlayersEnabled() const
|
|
{
|
|
int count = 0;
|
|
FOREACH_EnabledPlayer( pn )
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
bool GameState::IsHumanPlayer( PlayerNumber pn ) const
|
|
{
|
|
if( pn == PLAYER_INVALID )
|
|
return false;
|
|
|
|
if(GetCurrentGame()->m_PlayersHaveSeparateStyles)
|
|
{
|
|
if( GetCurrentStyle(pn) == nullptr ) // no style chosen
|
|
{
|
|
return m_bSideIsJoined[pn];
|
|
}
|
|
else
|
|
{
|
|
StyleType type = GetCurrentStyle(pn)->m_StyleType;
|
|
switch( type )
|
|
{
|
|
case StyleType_TwoPlayersTwoSides:
|
|
case StyleType_TwoPlayersSharedSides:
|
|
return true;
|
|
case StyleType_OnePlayerOneSide:
|
|
case StyleType_OnePlayerTwoSides:
|
|
return pn == this->GetMasterPlayerNumber();
|
|
default:
|
|
FAIL_M(ssprintf("Invalid style type: %i", type));
|
|
}
|
|
}
|
|
}
|
|
if( GetCurrentStyle(pn) == nullptr ) // no style chosen
|
|
{
|
|
return m_bSideIsJoined[pn]; // only allow input from sides that have already joined
|
|
}
|
|
|
|
StyleType type = GetCurrentStyle(pn)->m_StyleType;
|
|
switch( type )
|
|
{
|
|
case StyleType_TwoPlayersTwoSides:
|
|
case StyleType_TwoPlayersSharedSides:
|
|
return true;
|
|
case StyleType_OnePlayerOneSide:
|
|
case StyleType_OnePlayerTwoSides:
|
|
return pn == this->GetMasterPlayerNumber();
|
|
default:
|
|
FAIL_M(ssprintf("Invalid style type: %i", type));
|
|
}
|
|
}
|
|
|
|
int GameState::GetNumHumanPlayers() const
|
|
{
|
|
int count = 0;
|
|
FOREACH_HumanPlayer( pn )
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
PlayerNumber GameState::GetFirstHumanPlayer() const
|
|
{
|
|
FOREACH_HumanPlayer( pn )
|
|
return pn;
|
|
return PLAYER_INVALID;
|
|
}
|
|
|
|
PlayerNumber GameState::GetFirstDisabledPlayer() const
|
|
{
|
|
FOREACH_PlayerNumber( pn )
|
|
if( !IsPlayerEnabled(pn) )
|
|
return pn;
|
|
return PLAYER_INVALID;
|
|
}
|
|
|
|
bool GameState::IsCpuPlayer( PlayerNumber pn ) const
|
|
{
|
|
return IsPlayerEnabled(pn) && !IsHumanPlayer(pn);
|
|
}
|
|
|
|
bool GameState::AnyPlayersAreCpu() const
|
|
{
|
|
FOREACH_CpuPlayer( pn )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
bool GameState::IsCourseMode() const
|
|
{
|
|
switch(m_PlayMode)
|
|
{
|
|
case PLAY_MODE_ONI:
|
|
case PLAY_MODE_NONSTOP:
|
|
case PLAY_MODE_ENDLESS:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GameState::IsBattleMode() const
|
|
{
|
|
switch( m_PlayMode )
|
|
{
|
|
case PLAY_MODE_BATTLE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
EarnedExtraStage GameState::CalculateEarnedExtraStage() const
|
|
{
|
|
if( IsEventMode() )
|
|
return EarnedExtraStage_No;
|
|
|
|
if( !PREFSMAN->m_bAllowExtraStage )
|
|
return EarnedExtraStage_No;
|
|
|
|
if( m_PlayMode != PLAY_MODE_REGULAR )
|
|
return EarnedExtraStage_No;
|
|
|
|
if( m_bBackedOutOfFinalStage )
|
|
return EarnedExtraStage_No;
|
|
|
|
if( GetSmallestNumStagesLeftForAnyHumanPlayer() > 0 )
|
|
return EarnedExtraStage_No;
|
|
|
|
if( m_iAwardedExtraStages[this->GetMasterPlayerNumber()] >= 2 )
|
|
return EarnedExtraStage_No;
|
|
|
|
FOREACH_EnabledPlayer( pn )
|
|
{
|
|
Difficulty dc = m_pCurSteps[pn]->GetDifficulty();
|
|
switch( dc )
|
|
{
|
|
case Difficulty_Edit:
|
|
if( !EDIT_ALLOWED_FOR_EXTRA )
|
|
continue; // can't use edit steps
|
|
break;
|
|
default:
|
|
if( dc < MIN_DIFFICULTY_FOR_EXTRA )
|
|
continue; // not hard enough!
|
|
break;
|
|
}
|
|
|
|
if( IsExtraStage() )
|
|
{
|
|
if( ALLOW_EXTRA_2 && STATSMAN->m_CurStageStats.m_player[pn].GetGrade() <= GRADE_TIER_FOR_EXTRA_2 )
|
|
return EarnedExtraStage_Extra2;
|
|
}
|
|
else if( STATSMAN->m_CurStageStats.m_player[pn].GetGrade() <= GRADE_TIER_FOR_EXTRA_1 )
|
|
{
|
|
return EarnedExtraStage_Extra1;
|
|
}
|
|
}
|
|
|
|
return EarnedExtraStage_No;
|
|
}
|
|
|
|
PlayerNumber GameState::GetBestPlayer() const
|
|
{
|
|
FOREACH_PlayerNumber( pn )
|
|
if( GetStageResult(pn) == RESULT_WIN )
|
|
return pn;
|
|
return PLAYER_INVALID; // draw
|
|
}
|
|
|
|
StageResult GameState::GetStageResult( PlayerNumber pn ) const
|
|
{
|
|
switch( m_PlayMode )
|
|
{
|
|
case PLAY_MODE_BATTLE:
|
|
case PLAY_MODE_RAVE:
|
|
if( std::abs(m_fTugLifePercentP1 - 0.5f) < 0.0001f )
|
|
return RESULT_DRAW;
|
|
switch( pn )
|
|
{
|
|
case PLAYER_1: return (m_fTugLifePercentP1>=0.5f)?RESULT_WIN:RESULT_LOSE;
|
|
case PLAYER_2: return (m_fTugLifePercentP1<0.5f)?RESULT_WIN:RESULT_LOSE;
|
|
default: FAIL_M("Invalid player for battle! Aborting...");
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
StageResult win = RESULT_WIN;
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
if( p == pn )
|
|
continue;
|
|
|
|
// If anyone did just as well, at best it's a draw.
|
|
if( STATSMAN->m_CurStageStats.m_player[p].m_iActualDancePoints == STATSMAN->m_CurStageStats.m_player[pn].m_iActualDancePoints )
|
|
win = RESULT_DRAW;
|
|
|
|
// If anyone did better, we lost.
|
|
if( STATSMAN->m_CurStageStats.m_player[p].m_iActualDancePoints > STATSMAN->m_CurStageStats.m_player[pn].m_iActualDancePoints )
|
|
return RESULT_LOSE;
|
|
}
|
|
return win;
|
|
}
|
|
|
|
void GameState::GetDefaultPlayerOptions( PlayerOptions &po )
|
|
{
|
|
po.Init();
|
|
po.FromString( PREFSMAN->m_sDefaultModifiers );
|
|
po.FromString( CommonMetrics::DEFAULT_MODIFIERS );
|
|
if( po.m_sNoteSkin.empty() )
|
|
po.m_sNoteSkin = CommonMetrics::DEFAULT_NOTESKIN_NAME;
|
|
}
|
|
|
|
void GameState::GetDefaultSongOptions( SongOptions &so )
|
|
{
|
|
so.Init();
|
|
so.FromString( PREFSMAN->m_sDefaultModifiers );
|
|
so.FromString( CommonMetrics::DEFAULT_MODIFIERS );
|
|
}
|
|
|
|
void GameState::ResetToDefaultSongOptions( ModsLevel l )
|
|
{
|
|
SongOptions so;
|
|
GetDefaultSongOptions( so );
|
|
m_SongOptions.Assign( l, so );
|
|
}
|
|
|
|
void GameState::ApplyPreferredModifiers( PlayerNumber pn, RString sModifiers )
|
|
{
|
|
m_pPlayerState[pn]->m_PlayerOptions.FromString( ModsLevel_Preferred, sModifiers );
|
|
m_SongOptions.FromString( ModsLevel_Preferred, sModifiers );
|
|
}
|
|
|
|
void GameState::ApplyStageModifiers( PlayerNumber pn, RString sModifiers )
|
|
{
|
|
m_pPlayerState[pn]->m_PlayerOptions.FromString( ModsLevel_Stage, sModifiers );
|
|
m_SongOptions.FromString( ModsLevel_Stage, sModifiers );
|
|
}
|
|
|
|
void GameState::ClearStageModifiersIllegalForCourse()
|
|
{
|
|
FOREACH_EnabledPlayer( pn )
|
|
PO_GROUP_CALL( m_pPlayerState[pn]->m_PlayerOptions, ModsLevel_Stage, ResetSavedPrefsInvalidForCourse );
|
|
}
|
|
|
|
bool GameState::CurrentOptionsDisqualifyPlayer( PlayerNumber pn )
|
|
{
|
|
if( !PREFSMAN->m_bDisqualification )
|
|
return false;
|
|
|
|
if( !IsHumanPlayer(pn) )
|
|
return false;
|
|
|
|
const PlayerOptions &po = m_pPlayerState[pn]->m_PlayerOptions.GetPreferred();
|
|
const SongOptions &so = m_SongOptions.GetPreferred();
|
|
|
|
// Playing a song/course at a slower music rate should disqualify score. -x0rbl
|
|
if (so.m_fMusicRate < 1.0) {
|
|
return true;
|
|
}
|
|
|
|
// Check the stored player options for disqualify. Don't disqualify because
|
|
// of mods that were forced.
|
|
if( IsCourseMode() )
|
|
return po.IsEasierForCourseAndTrail( m_pCurCourse, m_pCurTrail[pn] );
|
|
else
|
|
return po.IsEasierForSongAndSteps( m_pCurSong, m_pCurSteps[pn], pn);
|
|
}
|
|
|
|
/* reset noteskins (?)
|
|
* GameState::ResetNoteSkins()
|
|
* GameState::ResetNoteSkinsForPlayer( PlayerNumber pn )
|
|
*
|
|
*/
|
|
|
|
void GameState::GetAllUsedNoteSkins( std::vector<RString> &out ) const
|
|
{
|
|
FOREACH_EnabledPlayer( pn )
|
|
{
|
|
out.push_back( m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin );
|
|
|
|
// Add noteskins that are used in courses.
|
|
if( IsCourseMode() )
|
|
{
|
|
const Trail *pTrail = m_pCurTrail[pn];
|
|
ASSERT( pTrail != nullptr );
|
|
|
|
for (TrailEntry const &e : pTrail->m_vEntries)
|
|
{
|
|
PlayerOptions po;
|
|
po.FromString( e.Modifiers );
|
|
if( !po.m_sNoteSkin.empty() )
|
|
out.push_back( po.m_sNoteSkin );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove duplicates.
|
|
sort( out.begin(), out.end() );
|
|
out.erase( unique( out.begin(), out.end() ), out.end() );
|
|
}
|
|
|
|
void GameState::RemoveAllActiveAttacks() // called on end of song
|
|
{
|
|
FOREACH_PlayerNumber( p )
|
|
m_pPlayerState[p]->RemoveActiveAttacks();
|
|
}
|
|
|
|
void GameState::AddStageToPlayer( PlayerNumber pn )
|
|
{
|
|
// Add one stage more to player (bonus) -cerbo
|
|
++m_iPlayerStageTokens[pn];
|
|
}
|
|
|
|
template<class T>
|
|
void setmin( T &a, const T &b )
|
|
{
|
|
a = std::min(a, b);
|
|
}
|
|
|
|
template<class T>
|
|
void setmax( T &a, const T &b )
|
|
{
|
|
a = std::max(a, b);
|
|
}
|
|
|
|
FailType GameState::GetPlayerFailType( const PlayerState *pPlayerState ) const
|
|
{
|
|
PlayerNumber pn = pPlayerState->m_PlayerNumber;
|
|
FailType ft = pPlayerState->m_PlayerOptions.GetCurrent().m_FailType;
|
|
|
|
// If the player changed the fail mode explicitly, leave it alone.
|
|
if( m_bFailTypeWasExplicitlySet )
|
|
return ft;
|
|
|
|
if( IsCourseMode() )
|
|
{
|
|
if( PREFSMAN->m_bMinimum1FullSongInCourses && GetCourseSongIndex()==0 )
|
|
ft = std::max( ft, FailType_ImmediateContinue ); // take the least harsh of the two FailTypes
|
|
}
|
|
else
|
|
{
|
|
Difficulty dc = Difficulty_Invalid;
|
|
if( m_pCurSteps[pn] )
|
|
dc = m_pCurSteps[pn]->GetDifficulty();
|
|
|
|
bool bFirstStage = false;
|
|
if( !IsEventMode() )
|
|
bFirstStage |= m_iPlayerStageTokens[pPlayerState->m_PlayerNumber] == PREFSMAN->m_iSongsPerPlay-1; // HACK; -1 because this is called during gameplay
|
|
|
|
// Easy and beginner are never harder than FAIL_IMMEDIATE_CONTINUE.
|
|
if( dc <= Difficulty_Easy )
|
|
setmax( ft, FailType_ImmediateContinue );
|
|
|
|
if( dc <= Difficulty_Easy && bFirstStage && PREFSMAN->m_bFailOffForFirstStageEasy )
|
|
setmax( ft, FailType_Off );
|
|
|
|
/* If beginner's steps were chosen, and this is the first stage,
|
|
* turn off failure completely. */
|
|
if( dc == Difficulty_Beginner && bFirstStage )
|
|
setmax( ft, FailType_Off );
|
|
|
|
if( dc == Difficulty_Beginner && PREFSMAN->m_bFailOffInBeginner )
|
|
setmax( ft, FailType_Off );
|
|
}
|
|
|
|
return ft;
|
|
}
|
|
|
|
bool GameState::ShowW1() const
|
|
{
|
|
AllowW1 pref = PREFSMAN->m_AllowW1;
|
|
switch( pref )
|
|
{
|
|
case ALLOW_W1_NEVER: return false;
|
|
case ALLOW_W1_COURSES_ONLY: return IsCourseMode();
|
|
case ALLOW_W1_EVERYWHERE: return true;
|
|
default:
|
|
FAIL_M(ssprintf("Invalid AllowW1 preference: %i", pref));
|
|
}
|
|
}
|
|
|
|
|
|
static ThemeMetric<bool> PROFILE_RECORD_FEATS("GameState","ProfileRecordFeats");
|
|
static ThemeMetric<bool> CATEGORY_RECORD_FEATS("GameState","CategoryRecordFeats");
|
|
void GameState::GetRankingFeats( PlayerNumber pn, std::vector<RankingFeat> &asFeatsOut ) const
|
|
{
|
|
if( !IsHumanPlayer(pn) )
|
|
return;
|
|
|
|
Profile *pProf = PROFILEMAN->GetProfile(pn);
|
|
|
|
// Check for feats even if the PlayMode is rave or battle because the player
|
|
// may have made high scores then switched modes.
|
|
PlayMode mode = m_PlayMode.Get();
|
|
char const *modeStr = PlayModeToString(mode).c_str();
|
|
|
|
CHECKPOINT_M( ssprintf("Getting the feats for %s", modeStr));
|
|
switch( mode )
|
|
{
|
|
case PLAY_MODE_REGULAR:
|
|
case PLAY_MODE_BATTLE:
|
|
case PLAY_MODE_RAVE:
|
|
{
|
|
StepsType st = GetCurrentStyle(pn)->m_StepsType;
|
|
|
|
// Find unique Song and Steps combinations that were played.
|
|
// We must keep only the unique combination or else we'll double-count
|
|
// high score markers.
|
|
std::vector<SongAndSteps> vSongAndSteps;
|
|
|
|
for( unsigned i=0; i<STATSMAN->m_vPlayedStageStats.size(); i++ )
|
|
{
|
|
CHECKPOINT_M( ssprintf("%u/%i", i, (int)STATSMAN->m_vPlayedStageStats.size() ) );
|
|
SongAndSteps sas;
|
|
ASSERT( !STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs.empty() );
|
|
sas.pSong = STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs[0];
|
|
ASSERT( sas.pSong != nullptr );
|
|
if( STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps.empty() )
|
|
continue;
|
|
sas.pSteps = STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps[0];
|
|
ASSERT( sas.pSteps != nullptr );
|
|
vSongAndSteps.push_back( sas );
|
|
}
|
|
CHECKPOINT_M( ssprintf("All songs/steps from %s gathered", modeStr));
|
|
|
|
sort( vSongAndSteps.begin(), vSongAndSteps.end() );
|
|
|
|
std::vector<SongAndSteps>::iterator toDelete = unique( vSongAndSteps.begin(), vSongAndSteps.end() );
|
|
vSongAndSteps.erase(toDelete, vSongAndSteps.end());
|
|
|
|
CHECKPOINT_M( "About to find records from the gathered.");
|
|
for( unsigned i=0; i<vSongAndSteps.size(); i++ )
|
|
{
|
|
Song* pSong = vSongAndSteps[i].pSong;
|
|
Steps* pSteps = vSongAndSteps[i].pSteps;
|
|
|
|
// Find Machine Records
|
|
{
|
|
HighScoreList &hsl = PROFILEMAN->GetMachineProfile()->GetStepsHighScoreList(pSong,pSteps);
|
|
for( unsigned j=0; j<hsl.vHighScores.size(); j++ )
|
|
{
|
|
HighScore &hs = hsl.vHighScores[j];
|
|
|
|
if( hs.GetName() != RANKING_TO_FILL_IN_MARKER[pn] )
|
|
continue;
|
|
|
|
RankingFeat feat;
|
|
feat.Type = RankingFeat::SONG;
|
|
feat.pSong = pSong;
|
|
feat.pSteps = pSteps;
|
|
feat.Feat = ssprintf("MR #%d in %s %s", j+1, pSong->GetTranslitMainTitle().c_str(), DifficultyToString(pSteps->GetDifficulty()).c_str() );
|
|
feat.pStringToFill = hs.GetNameMutable();
|
|
feat.grade = hs.GetGrade();
|
|
feat.fPercentDP = hs.GetPercentDP();
|
|
feat.iScore = hs.GetScore();
|
|
|
|
if( pSong->HasBanner() )
|
|
feat.Banner = pSong->GetBannerPath();
|
|
|
|
asFeatsOut.push_back( feat );
|
|
}
|
|
}
|
|
|
|
// Find Personal Records
|
|
if( pProf && PROFILE_RECORD_FEATS )
|
|
{
|
|
HighScoreList &hsl = pProf->GetStepsHighScoreList(pSong,pSteps);
|
|
for( unsigned j=0; j<hsl.vHighScores.size(); j++ )
|
|
{
|
|
HighScore &hs = hsl.vHighScores[j];
|
|
|
|
if( hs.GetName() != RANKING_TO_FILL_IN_MARKER[pn] )
|
|
continue;
|
|
|
|
RankingFeat feat;
|
|
feat.pSong = pSong;
|
|
feat.pSteps = pSteps;
|
|
feat.Type = RankingFeat::SONG;
|
|
feat.Feat = ssprintf("PR #%d in %s %s", j+1, pSong->GetTranslitMainTitle().c_str(), DifficultyToString(pSteps->GetDifficulty()).c_str() );
|
|
feat.pStringToFill = hs.GetNameMutable();
|
|
feat.grade = hs.GetGrade();
|
|
feat.fPercentDP = hs.GetPercentDP();
|
|
feat.iScore = hs.GetScore();
|
|
|
|
// XXX: temporary hack
|
|
// Why is this here? -aj
|
|
if( pSong->HasBackground() )
|
|
feat.Banner = pSong->GetBackgroundPath();
|
|
// if( pSong->HasBanner() )
|
|
// feat.Banner = pSong->GetBannerPath();
|
|
|
|
asFeatsOut.push_back( feat );
|
|
}
|
|
}
|
|
}
|
|
|
|
CHECKPOINT_M("Getting the final evaluation stage stats.");
|
|
StageStats stats;
|
|
STATSMAN->GetFinalEvalStageStats( stats );
|
|
|
|
|
|
// Find Machine Category Records
|
|
FOREACH_ENUM( RankingCategory, rc )
|
|
{
|
|
if( !CATEGORY_RECORD_FEATS )
|
|
continue;
|
|
HighScoreList &hsl = PROFILEMAN->GetMachineProfile()->GetCategoryHighScoreList( st, rc );
|
|
for( unsigned j=0; j<hsl.vHighScores.size(); j++ )
|
|
{
|
|
HighScore &hs = hsl.vHighScores[j];
|
|
if( hs.GetName() != RANKING_TO_FILL_IN_MARKER[pn] )
|
|
continue;
|
|
|
|
RankingFeat feat;
|
|
feat.Type = RankingFeat::CATEGORY;
|
|
feat.Feat = ssprintf("MR #%d in Type %c (%d)", j+1, 'A'+rc, stats.GetAverageMeter(pn) );
|
|
feat.pStringToFill = hs.GetNameMutable();
|
|
feat.grade = Grade_NoData;
|
|
feat.iScore = hs.GetScore();
|
|
feat.fPercentDP = hs.GetPercentDP();
|
|
asFeatsOut.push_back( feat );
|
|
}
|
|
}
|
|
|
|
// Find Personal Category Records
|
|
FOREACH_ENUM( RankingCategory, rc )
|
|
{
|
|
if( !CATEGORY_RECORD_FEATS )
|
|
continue;
|
|
|
|
if( pProf && PROFILE_RECORD_FEATS )
|
|
{
|
|
HighScoreList &hsl = pProf->GetCategoryHighScoreList( st, rc );
|
|
for( unsigned j=0; j<hsl.vHighScores.size(); j++ )
|
|
{
|
|
HighScore &hs = hsl.vHighScores[j];
|
|
if( hs.GetName() != RANKING_TO_FILL_IN_MARKER[pn] )
|
|
continue;
|
|
|
|
RankingFeat feat;
|
|
feat.Type = RankingFeat::CATEGORY;
|
|
feat.Feat = ssprintf("PR #%d in Type %c (%d)", j+1, 'A'+rc, stats.GetAverageMeter(pn) );
|
|
feat.pStringToFill = hs.GetNameMutable();
|
|
feat.grade = Grade_NoData;
|
|
feat.iScore = hs.GetScore();
|
|
feat.fPercentDP = hs.GetPercentDP();
|
|
asFeatsOut.push_back( feat );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PLAY_MODE_NONSTOP:
|
|
case PLAY_MODE_ONI:
|
|
case PLAY_MODE_ENDLESS:
|
|
{
|
|
Course* pCourse = m_pCurCourse;
|
|
ASSERT( pCourse != nullptr );
|
|
Trail *pTrail = m_pCurTrail[pn];
|
|
ASSERT( pTrail != nullptr );
|
|
CourseDifficulty cd = pTrail->m_CourseDifficulty;
|
|
|
|
// Find Machine Records
|
|
{
|
|
Profile* pProfile = PROFILEMAN->GetMachineProfile();
|
|
HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, pTrail );
|
|
for( unsigned i=0; i<hsl.vHighScores.size(); i++ )
|
|
{
|
|
HighScore &hs = hsl.vHighScores[i];
|
|
if( hs.GetName() != RANKING_TO_FILL_IN_MARKER[pn] )
|
|
continue;
|
|
|
|
RankingFeat feat;
|
|
feat.Type = RankingFeat::COURSE;
|
|
feat.pCourse = pCourse;
|
|
feat.Feat = ssprintf("MR #%d in %s", i+1, pCourse->GetDisplayFullTitle().c_str() );
|
|
if( cd != Difficulty_Medium )
|
|
feat.Feat += " " + CourseDifficultyToLocalizedString(cd);
|
|
feat.pStringToFill = hs.GetNameMutable();
|
|
feat.grade = Grade_NoData;
|
|
feat.iScore = hs.GetScore();
|
|
feat.fPercentDP = hs.GetPercentDP();
|
|
if( pCourse->HasBanner() )
|
|
feat.Banner = pCourse->GetBannerPath();
|
|
asFeatsOut.push_back( feat );
|
|
}
|
|
}
|
|
|
|
// Find Personal Records
|
|
if( PROFILE_RECORD_FEATS && PROFILEMAN->IsPersistentProfile(pn) )
|
|
{
|
|
HighScoreList &hsl = pProf->GetCourseHighScoreList( pCourse, pTrail );
|
|
for( unsigned i=0; i<hsl.vHighScores.size(); i++ )
|
|
{
|
|
HighScore& hs = hsl.vHighScores[i];
|
|
if( hs.GetName() != RANKING_TO_FILL_IN_MARKER[pn] )
|
|
continue;
|
|
|
|
RankingFeat feat;
|
|
feat.Type = RankingFeat::COURSE;
|
|
feat.pCourse = pCourse;
|
|
feat.Feat = ssprintf("PR #%d in %s", i+1, pCourse->GetDisplayFullTitle().c_str() );
|
|
feat.pStringToFill = hs.GetNameMutable();
|
|
feat.grade = Grade_NoData;
|
|
feat.iScore = hs.GetScore();
|
|
feat.fPercentDP = hs.GetPercentDP();
|
|
if( pCourse->HasBanner() )
|
|
feat.Banner = pCourse->GetBannerPath();
|
|
asFeatsOut.push_back( feat );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
FAIL_M(ssprintf("Invalid play mode: %i", int(m_PlayMode)));
|
|
}
|
|
}
|
|
|
|
bool GameState::AnyPlayerHasRankingFeats() const
|
|
{
|
|
std::vector<RankingFeat> vFeats;
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
GetRankingFeats( p, vFeats );
|
|
if( !vFeats.empty() )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void GameState::StoreRankingName( PlayerNumber pn, RString sName )
|
|
{
|
|
// The theme can upper it if desired. -Kyz
|
|
// sName.MakeUpper();
|
|
|
|
if( USE_NAME_BLACKLIST )
|
|
{
|
|
RageFile file;
|
|
if( file.Open(NAME_BLACKLIST_FILE) )
|
|
{
|
|
RString sLine;
|
|
|
|
while( !file.AtEOF() )
|
|
{
|
|
if( file.GetLine(sLine) == -1 )
|
|
{
|
|
LuaHelpers::ReportScriptErrorFmt( "Error reading \"%s\": %s", NAME_BLACKLIST_FILE, file.GetError().c_str() );
|
|
break;
|
|
}
|
|
|
|
sLine.MakeUpper();
|
|
if( !sLine.empty() && sName.find(sLine) != std::string::npos ) // name contains a bad word
|
|
{
|
|
LOG->Trace( "entered '%s' matches blacklisted item '%s'", sName.c_str(), sLine.c_str() );
|
|
sName = "";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<RankingFeat> aFeats;
|
|
GetRankingFeats( pn, aFeats );
|
|
|
|
for( unsigned i=0; i<aFeats.size(); i++ )
|
|
{
|
|
*aFeats[i].pStringToFill = sName;
|
|
|
|
// save name pointers as we fill them
|
|
m_vpsNamesThatWereFilled.push_back( aFeats[i].pStringToFill );
|
|
}
|
|
|
|
m_sPlayersThatWereFilled.insert(pn);
|
|
|
|
// Only attempt to remove/clamp scores after the last enabled player has saved their scores.
|
|
if (GAMESTATE->GetNumPlayersEnabled() <= static_cast<int>(m_sPlayersThatWereFilled.size())) {
|
|
StepsType st = GetCurrentStyle(pn)->m_StepsType;
|
|
PlayMode mode = m_PlayMode.Get();
|
|
Profile *pProfile = PROFILEMAN->GetMachineProfile();
|
|
switch (mode)
|
|
{
|
|
case PLAY_MODE_REGULAR:
|
|
case PLAY_MODE_BATTLE:
|
|
case PLAY_MODE_RAVE:
|
|
{
|
|
// Find unique Song and Steps combinations that were played.
|
|
// Code is taken from implementation in GetRankingFeats()
|
|
std::vector<SongAndSteps> vSongAndSteps;
|
|
|
|
for( unsigned i=0; i<STATSMAN->m_vPlayedStageStats.size(); i++ )
|
|
{
|
|
CHECKPOINT_M( ssprintf("%u/%i", i, (int)STATSMAN->m_vPlayedStageStats.size() ) );
|
|
SongAndSteps sas;
|
|
ASSERT( !STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs.empty() );
|
|
sas.pSong = STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs[0];
|
|
ASSERT( sas.pSong != nullptr );
|
|
|
|
if ( STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps.empty() )
|
|
continue;
|
|
sas.pSteps = STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps[0];
|
|
ASSERT( sas.pSteps != nullptr );
|
|
vSongAndSteps.push_back( sas );
|
|
}
|
|
|
|
std::vector<SongAndSteps>::iterator toDelete = std::unique( vSongAndSteps.begin(), vSongAndSteps.end() );
|
|
vSongAndSteps.erase(toDelete, vSongAndSteps.end());
|
|
|
|
for (unsigned i = 0; i < vSongAndSteps.size(); ++i)
|
|
{
|
|
HighScoreList &hsl = pProfile->GetStepsHighScoreList(vSongAndSteps[i].pSong,vSongAndSteps[i].pSteps);
|
|
if (!PREFSMAN->m_bAllowMultipleHighScoreWithSameName)
|
|
{
|
|
// erase all but the highest score for each name
|
|
hsl.RemoveAllButOneOfEachName();
|
|
}
|
|
hsl.ClampSize(true);
|
|
}
|
|
}
|
|
break;
|
|
case PLAY_MODE_NONSTOP:
|
|
case PLAY_MODE_ONI:
|
|
case PLAY_MODE_ENDLESS:
|
|
{
|
|
// Code is taken from implementation in GetRankingFeats()
|
|
Course* pCourse = m_pCurCourse;
|
|
ASSERT( pCourse != nullptr );
|
|
Trail *pTrail = m_pCurTrail[pn];
|
|
ASSERT( pTrail != nullptr );
|
|
CourseDifficulty cd = pTrail->m_CourseDifficulty;
|
|
HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, pTrail );
|
|
if (!PREFSMAN->m_bAllowMultipleHighScoreWithSameName)
|
|
{
|
|
// erase all but the highest score for each name
|
|
hsl.RemoveAllButOneOfEachName();
|
|
}
|
|
hsl.ClampSize(true);
|
|
}
|
|
break;
|
|
default:
|
|
FAIL_M(ssprintf("Invalid play mode: %i", int(m_PlayMode)));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool GameState::AllAreInDangerOrWorse() const
|
|
{
|
|
FOREACH_EnabledPlayer( p )
|
|
if( m_pPlayerState[p]->m_HealthState < HealthState_Danger )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool GameState::OneIsHot() const
|
|
{
|
|
FOREACH_EnabledPlayer( p )
|
|
if( m_pPlayerState[p]->m_HealthState == HealthState_Hot )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool GameState::IsTimeToPlayAttractSounds() const
|
|
{
|
|
// m_iNumTimesThroughAttract will be -1 from the first attract screen after
|
|
// the end of a game until the next time FIRST_ATTRACT_SCREEN is reached.
|
|
// Play attract sounds for this sort span of time regardless of
|
|
// m_AttractSoundFrequency because it's awkward to have the machine go
|
|
// silent immediately after the end of a game.
|
|
if( m_iNumTimesThroughAttract == -1 )
|
|
return true;
|
|
|
|
if( PREFSMAN->m_AttractSoundFrequency == ASF_NEVER )
|
|
return false;
|
|
|
|
// play attract sounds once every m_iAttractSoundFrequency times through
|
|
if( (m_iNumTimesThroughAttract % PREFSMAN->m_AttractSoundFrequency)==0 )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void GameState::VisitAttractScreen( const RString sScreenName )
|
|
{
|
|
if( sScreenName == CommonMetrics::FIRST_ATTRACT_SCREEN.GetValue() )
|
|
m_iNumTimesThroughAttract++;
|
|
}
|
|
|
|
bool GameState::DifficultiesLocked() const
|
|
{
|
|
if( m_PlayMode == PLAY_MODE_RAVE )
|
|
return true;
|
|
if( IsCourseMode() )
|
|
return PREFSMAN->m_bLockCourseDifficulties;
|
|
if( GetCurrentStyle(PLAYER_INVALID)->m_bLockDifficulties )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool GameState::ChangePreferredDifficultyAndStepsType( PlayerNumber pn, Difficulty dc, StepsType st )
|
|
{
|
|
m_PreferredDifficulty[pn].Set( dc );
|
|
m_PreferredStepsType.Set( st );
|
|
if( DifficultiesLocked() )
|
|
FOREACH_PlayerNumber( p )
|
|
if( p != pn )
|
|
m_PreferredDifficulty[p].Set( m_PreferredDifficulty[pn] );
|
|
|
|
return true;
|
|
}
|
|
|
|
/* When only displaying difficulties in DIFFICULTIES_TO_SHOW, use GetClosestShownDifficulty
|
|
* to find which difficulty to show, and ChangePreferredDifficulty(pn, dir) to change
|
|
* difficulty. */
|
|
bool GameState::ChangePreferredDifficulty( PlayerNumber pn, int dir )
|
|
{
|
|
const std::vector<Difficulty> &v = CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue();
|
|
|
|
Difficulty d = GetClosestShownDifficulty(pn);
|
|
for(;;)
|
|
{
|
|
d = enum_add2( d, dir );
|
|
if( d < 0 || d >= NUM_Difficulty )
|
|
{
|
|
return false;
|
|
}
|
|
if( find(v.begin(), v.end(), d) != v.end() )
|
|
{
|
|
break; // found
|
|
}
|
|
}
|
|
m_PreferredDifficulty[pn].Set( d );
|
|
return true;
|
|
}
|
|
|
|
/* The user may be set to prefer a difficulty that isn't always shown; typically,
|
|
* Difficulty_Edit. Return the closest shown difficulty <= m_PreferredDifficulty. */
|
|
Difficulty GameState::GetClosestShownDifficulty( PlayerNumber pn ) const
|
|
{
|
|
const std::vector<Difficulty> &v = CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue();
|
|
|
|
Difficulty iClosest = (Difficulty) 0;
|
|
int iClosestDist = -1;
|
|
for (Difficulty const &dc : v)
|
|
{
|
|
int iDist = m_PreferredDifficulty[pn] - dc;
|
|
if( iDist < 0 )
|
|
continue;
|
|
if( iClosestDist != -1 && iDist > iClosestDist )
|
|
continue;
|
|
iClosestDist = iDist;
|
|
iClosest = dc;
|
|
}
|
|
|
|
return iClosest;
|
|
}
|
|
|
|
bool GameState::ChangePreferredCourseDifficultyAndStepsType( PlayerNumber pn, CourseDifficulty cd, StepsType st )
|
|
{
|
|
m_PreferredCourseDifficulty[pn].Set( cd );
|
|
m_PreferredStepsType.Set( st );
|
|
if( PREFSMAN->m_bLockCourseDifficulties )
|
|
FOREACH_PlayerNumber( p )
|
|
if( p != pn )
|
|
m_PreferredCourseDifficulty[p].Set( m_PreferredCourseDifficulty[pn] );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GameState::ChangePreferredCourseDifficulty( PlayerNumber pn, int dir )
|
|
{
|
|
/* If we have a course selected, only choose among difficulties available in the course. */
|
|
const Course *pCourse = m_pCurCourse;
|
|
|
|
const std::vector<CourseDifficulty> &v = CommonMetrics::COURSE_DIFFICULTIES_TO_SHOW.GetValue();
|
|
|
|
CourseDifficulty cd = m_PreferredCourseDifficulty[pn];
|
|
for(;;)
|
|
{
|
|
cd = enum_add2( cd, dir );
|
|
if( cd < 0 || cd >= NUM_Difficulty )
|
|
{
|
|
return false;
|
|
}
|
|
if( find(v.begin(),v.end(),cd) == v.end() )
|
|
{
|
|
continue; /* not available */
|
|
}
|
|
if( !pCourse || pCourse->GetTrail( GetCurrentStyle(pn)->m_StepsType, cd ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return ChangePreferredCourseDifficulty( pn, cd );
|
|
}
|
|
|
|
bool GameState::IsCourseDifficultyShown( CourseDifficulty cd )
|
|
{
|
|
const std::vector<CourseDifficulty> &v = CommonMetrics::COURSE_DIFFICULTIES_TO_SHOW.GetValue();
|
|
return find(v.begin(), v.end(), cd) != v.end();
|
|
}
|
|
|
|
Difficulty GameState::GetEasiestStepsDifficulty() const
|
|
{
|
|
Difficulty dc = Difficulty_Invalid;
|
|
FOREACH_HumanPlayer( p )
|
|
{
|
|
if( m_pCurSteps[p] == nullptr )
|
|
{
|
|
LuaHelpers::ReportScriptErrorFmt( "GetEasiestStepsDifficulty called but p%i hasn't chosen notes", p+1 );
|
|
continue;
|
|
}
|
|
dc = std::min( dc, m_pCurSteps[p]->GetDifficulty() );
|
|
}
|
|
return dc;
|
|
}
|
|
|
|
Difficulty GameState::GetHardestStepsDifficulty() const
|
|
{
|
|
Difficulty dc = Difficulty_Beginner;
|
|
FOREACH_HumanPlayer( p )
|
|
{
|
|
if( m_pCurSteps[p] == nullptr )
|
|
{
|
|
LuaHelpers::ReportScriptErrorFmt( "GetHardestStepsDifficulty called but p%i hasn't chosen notes", p+1 );
|
|
continue;
|
|
}
|
|
dc = std::max( dc, m_pCurSteps[p]->GetDifficulty() );
|
|
}
|
|
return dc;
|
|
}
|
|
|
|
void GameState::SetNewStageSeed()
|
|
{
|
|
m_iStageSeed= rand();
|
|
}
|
|
|
|
bool GameState::IsEventMode() const
|
|
{
|
|
return m_bTemporaryEventMode || PREFSMAN->m_bEventMode;
|
|
}
|
|
|
|
CoinMode GameState::GetCoinMode() const
|
|
{
|
|
// XXX: Event mode shouldn't be valid if CoinMode_Pay
|
|
if( IsEventMode() && GamePreferences::m_CoinMode == CoinMode_Pay )
|
|
return CoinMode_Free;
|
|
else
|
|
return GamePreferences::m_CoinMode;
|
|
}
|
|
|
|
ThemeMetric<bool> DISABLE_PREMIUM_IN_EVENT_MODE("GameState","DisablePremiumInEventMode");
|
|
Premium GameState::GetPremium() const
|
|
{
|
|
return DISABLE_PREMIUM_IN_EVENT_MODE ? Premium_Off : g_Premium;
|
|
}
|
|
|
|
float GameState::GetGoalPercentComplete( PlayerNumber pn )
|
|
{
|
|
const Profile *pProfile = PROFILEMAN->GetProfile(pn);
|
|
const StageStats &ssCurrent = STATSMAN->m_CurStageStats;
|
|
const PlayerStageStats &pssCurrent = ssCurrent.m_player[pn];
|
|
|
|
float fActual = 0;
|
|
float fGoal = 0;
|
|
switch( pProfile->m_GoalType )
|
|
{
|
|
case GoalType_Calories:
|
|
fActual = pssCurrent.m_fCaloriesBurned;
|
|
fGoal = (float)pProfile->m_iGoalCalories;
|
|
break;
|
|
case GoalType_Time:
|
|
fActual = ssCurrent.m_fGameplaySeconds;
|
|
fGoal = (float)pProfile->m_iGoalSeconds;
|
|
break;
|
|
case GoalType_None:
|
|
return 0; // never complete
|
|
default:
|
|
FAIL_M(ssprintf("Invalid GoalType: %i", pProfile->m_GoalType));
|
|
}
|
|
if( fGoal == 0 )
|
|
return 0;
|
|
else
|
|
return fActual / fGoal;
|
|
}
|
|
|
|
bool GameState::PlayerIsUsingModifier( PlayerNumber pn, const RString &sModifier )
|
|
{
|
|
PlayerOptions po = m_pPlayerState[pn]->m_PlayerOptions.GetCurrent();
|
|
SongOptions so = m_SongOptions.GetCurrent();
|
|
po.FromString( sModifier );
|
|
so.FromString( sModifier );
|
|
|
|
return po == m_pPlayerState[pn]->m_PlayerOptions.GetCurrent() && so == m_SongOptions.GetCurrent();
|
|
}
|
|
|
|
Profile* GameState::GetEditLocalProfile()
|
|
{
|
|
if( m_sEditLocalProfileID.Get().empty() )
|
|
return nullptr;
|
|
return PROFILEMAN->GetLocalProfile( m_sEditLocalProfileID );
|
|
}
|
|
|
|
|
|
PlayerNumber GetNextHumanPlayer( PlayerNumber pn )
|
|
{
|
|
for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) )
|
|
if( GAMESTATE->IsHumanPlayer(pn) )
|
|
return pn;
|
|
return PLAYER_INVALID;
|
|
}
|
|
|
|
PlayerNumber GetNextEnabledPlayer( PlayerNumber pn )
|
|
{
|
|
for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) )
|
|
if( GAMESTATE->IsPlayerEnabled(pn) )
|
|
return pn;
|
|
return PLAYER_INVALID;
|
|
}
|
|
|
|
PlayerNumber GetNextCpuPlayer( PlayerNumber pn )
|
|
{
|
|
for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) )
|
|
if( GAMESTATE->IsCpuPlayer(pn) )
|
|
return pn;
|
|
return PLAYER_INVALID;
|
|
}
|
|
|
|
PlayerNumber GetNextPotentialCpuPlayer( PlayerNumber pn )
|
|
{
|
|
for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) )
|
|
if( !GAMESTATE->IsHumanPlayer(pn) )
|
|
return pn;
|
|
return PLAYER_INVALID;
|
|
}
|
|
|
|
MultiPlayer GetNextEnabledMultiPlayer( MultiPlayer mp )
|
|
{
|
|
for( enum_add(mp, 1); mp < NUM_MultiPlayer; enum_add(mp, 1) )
|
|
if( GAMESTATE->IsMultiPlayerEnabled(mp) )
|
|
return mp;
|
|
return MultiPlayer_Invalid;
|
|
}
|
|
|
|
|
|
|
|
// lua start
|
|
#include "LuaBinding.h"
|
|
#include "Game.h"
|
|
|
|
/** @brief Allow Lua to have access to the GameState. */
|
|
class LunaGameState: public Luna<GameState>
|
|
{
|
|
public:
|
|
DEFINE_METHOD( IsPlayerEnabled, IsPlayerEnabled(Enum::Check<PlayerNumber>(L, 1)) )
|
|
DEFINE_METHOD( IsHumanPlayer, IsHumanPlayer(Enum::Check<PlayerNumber>(L, 1)) )
|
|
DEFINE_METHOD( GetPlayerDisplayName, GetPlayerDisplayName(Enum::Check<PlayerNumber>(L, 1)) )
|
|
DEFINE_METHOD( GetMasterPlayerNumber, GetMasterPlayerNumber() )
|
|
DEFINE_METHOD( GetMultiplayer, m_bMultiplayer )
|
|
static int SetMultiplayer( T* p, lua_State *L )
|
|
{
|
|
p->m_bMultiplayer = BArg(1);
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
DEFINE_METHOD( InStepEditor, m_bInStepEditor );
|
|
DEFINE_METHOD( GetNumMultiplayerNoteFields, m_iNumMultiplayerNoteFields )
|
|
DEFINE_METHOD( ShowW1, ShowW1() )
|
|
|
|
static int SetNumMultiplayerNoteFields( T* p, lua_State *L )
|
|
{
|
|
p->m_iNumMultiplayerNoteFields = IArg(1);
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetPlayerState( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
p->m_pPlayerState[pn]->PushSelf(L);
|
|
return 1;
|
|
}
|
|
static int GetMultiPlayerState( T* p, lua_State *L )
|
|
{
|
|
MultiPlayer mp = Enum::Check<MultiPlayer>(L, 1);
|
|
p->m_pMultiPlayerState[mp]->PushSelf(L);
|
|
return 1;
|
|
}
|
|
static int ApplyGameCommand( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = PLAYER_INVALID;
|
|
if( lua_gettop(L) >= 2 && !lua_isnil(L,2) ) {
|
|
// Legacy behavior: if an old-style numerical argument
|
|
// is given, decrement it before trying to parse
|
|
if( lua_isnumber(L,2) ) {
|
|
int arg = (int) lua_tonumber( L, 2 );
|
|
arg--;
|
|
LuaHelpers::Push( L, arg );
|
|
lua_replace( L, -2 );
|
|
}
|
|
pn = Enum::Check<PlayerNumber>(L, 2);
|
|
}
|
|
p->ApplyGameCommand(SArg(1),pn);
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetCurrentSong( T* p, lua_State *L ) { if(p->m_pCurSong) p->m_pCurSong->PushSelf(L); else lua_pushnil(L); return 1; }
|
|
static int SetCurrentSong( T* p, lua_State *L )
|
|
{
|
|
if( lua_isnil(L,1) ) { p->m_pCurSong.Set(nullptr); }
|
|
else { Song *pS = Luna<Song>::check( L, 1, true ); p->m_pCurSong.Set( pS ); }
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int CanSafelyEnterGameplay(T* p, lua_State* L)
|
|
{
|
|
RString reason;
|
|
bool can= p->CanSafelyEnterGameplay(reason);
|
|
lua_pushboolean(L, can);
|
|
LuaHelpers::Push(L, reason);
|
|
return 2;
|
|
}
|
|
static void SetCompatibleStyleOrError(T* p, lua_State* L, StepsType stype, PlayerNumber pn)
|
|
{
|
|
if(!p->SetCompatibleStyle(stype, pn))
|
|
{
|
|
luaL_error(L, "No compatible style for steps/trail.");
|
|
}
|
|
if(!p->GetCurrentStyle(pn))
|
|
{
|
|
luaL_error(L, "No style set and AutoSetStyle is false, cannot set steps/trail.");
|
|
}
|
|
}
|
|
static int GetCurrentSteps( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
Steps *pSteps = p->m_pCurSteps[pn];
|
|
if( pSteps ) { pSteps->PushSelf(L); }
|
|
else { lua_pushnil(L); }
|
|
return 1;
|
|
}
|
|
static int SetCurrentSteps( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
if(lua_isnil(L,2))
|
|
{
|
|
p->m_pCurSteps[pn].Set(nullptr);
|
|
}
|
|
else
|
|
{
|
|
Steps *pS = Luna<Steps>::check(L,2);
|
|
SetCompatibleStyleOrError(p, L, pS->m_StepsType, pn);
|
|
p->m_pCurSteps[pn].Set(pS);
|
|
p->ForceOtherPlayersToCompatibleSteps(pn);
|
|
}
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetCurrentCourse( T* p, lua_State *L ) { if(p->m_pCurCourse) p->m_pCurCourse->PushSelf(L); else lua_pushnil(L); return 1; }
|
|
static int SetCurrentCourse( T* p, lua_State *L )
|
|
{
|
|
if( lua_isnil(L,1) ) { p->m_pCurCourse.Set(nullptr); }
|
|
else { Course *pC = Luna<Course>::check(L,1); p->m_pCurCourse.Set( pC ); }
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetCurrentTrail( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
Trail *pTrail = p->m_pCurTrail[pn];
|
|
if( pTrail ) { pTrail->PushSelf(L); }
|
|
else { lua_pushnil(L); }
|
|
return 1;
|
|
}
|
|
static int SetCurrentTrail( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
if(lua_isnil(L,2))
|
|
{
|
|
p->m_pCurTrail[pn].Set(nullptr);
|
|
}
|
|
else
|
|
{
|
|
Trail *pS = Luna<Trail>::check(L,2);
|
|
SetCompatibleStyleOrError(p, L, pS->m_StepsType, pn);
|
|
p->m_pCurTrail[pn].Set(pS);
|
|
p->ForceOtherPlayersToCompatibleSteps(pn);
|
|
}
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetPreferredSong( T* p, lua_State *L ) { if(p->m_pPreferredSong) p->m_pPreferredSong->PushSelf(L); else lua_pushnil(L); return 1; }
|
|
static int SetPreferredSong( T* p, lua_State *L )
|
|
{
|
|
if( lua_isnil(L,1) ) { p->m_pPreferredSong = nullptr; }
|
|
else { Song *pS = Luna<Song>::check(L,1); p->m_pPreferredSong = pS; }
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int SetTemporaryEventMode( T* p, lua_State *L ) { p->m_bTemporaryEventMode = BArg(1); COMMON_RETURN_SELF; }
|
|
static int Env( T* p, lua_State *L ) { p->m_Environment->PushSelf(L); return 1; }
|
|
static int GetEditSourceSteps( T* p, lua_State *L )
|
|
{
|
|
Steps *pSteps = p->m_pEditSourceSteps;
|
|
if( pSteps ) { pSteps->PushSelf(L); }
|
|
else { lua_pushnil(L); }
|
|
return 1;
|
|
}
|
|
static int SetPreferredDifficulty( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>( L, 1 );
|
|
Difficulty dc = Enum::Check<Difficulty>( L, 2 );
|
|
p->m_PreferredDifficulty[pn].Set( dc );
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
DEFINE_METHOD( GetPreferredDifficulty, m_PreferredDifficulty[Enum::Check<PlayerNumber>(L, 1)] )
|
|
DEFINE_METHOD( AnyPlayerHasRankingFeats, AnyPlayerHasRankingFeats() )
|
|
DEFINE_METHOD( IsCourseMode, IsCourseMode() )
|
|
DEFINE_METHOD( IsBattleMode, IsBattleMode() )
|
|
DEFINE_METHOD( IsDemonstration, m_bDemonstrationOrJukebox )
|
|
DEFINE_METHOD( GetPlayMode, m_PlayMode )
|
|
DEFINE_METHOD( GetSortOrder, m_SortOrder )
|
|
DEFINE_METHOD( GetCurrentStageIndex, m_iCurrentStageIndex )
|
|
DEFINE_METHOD( IsGoalComplete, IsGoalComplete(Enum::Check<PlayerNumber>(L, 1)) )
|
|
DEFINE_METHOD( PlayerIsUsingModifier, PlayerIsUsingModifier(Enum::Check<PlayerNumber>(L, 1), SArg(2)) )
|
|
DEFINE_METHOD( GetCourseSongIndex, GetCourseSongIndex() )
|
|
DEFINE_METHOD( GetLoadingCourseSongIndex, GetLoadingCourseSongIndex() )
|
|
DEFINE_METHOD( GetSmallestNumStagesLeftForAnyHumanPlayer, GetSmallestNumStagesLeftForAnyHumanPlayer() )
|
|
DEFINE_METHOD( IsAnExtraStage, IsAnExtraStage() )
|
|
DEFINE_METHOD( IsExtraStage, IsExtraStage() )
|
|
DEFINE_METHOD( IsExtraStage2, IsExtraStage2() )
|
|
DEFINE_METHOD( GetCurrentStage, GetCurrentStage() )
|
|
DEFINE_METHOD( HasEarnedExtraStage, HasEarnedExtraStage() )
|
|
DEFINE_METHOD( GetEarnedExtraStage, GetEarnedExtraStage() )
|
|
DEFINE_METHOD( GetEasiestStepsDifficulty, GetEasiestStepsDifficulty() )
|
|
DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() )
|
|
DEFINE_METHOD( IsEventMode, IsEventMode() )
|
|
DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() )
|
|
/*DEFINE_METHOD( GetSongBeat, m_Position.m_fSongBeat )
|
|
DEFINE_METHOD( GetSongBeatVisible, m_Position.m_fSongBeatVisible )
|
|
DEFINE_METHOD( GetSongBPS, m_Position.m_fCurBPS )
|
|
DEFINE_METHOD( GetSongFreeze, m_Position.m_bFreeze )
|
|
DEFINE_METHOD( GetSongDelay, m_Position.m_bDelay )*/
|
|
static int GetSongPosition( T* p, lua_State *L )
|
|
{
|
|
p->m_Position.PushSelf(L);
|
|
return 1;
|
|
}
|
|
DEFINE_METHOD( GetLastGameplayDuration, m_DanceDuration )
|
|
DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn )
|
|
DEFINE_METHOD( GetCoins, m_iCoins )
|
|
DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check<PlayerNumber>(L, 1)] )
|
|
DEFINE_METHOD( GetCoinsNeededToJoin, GetCoinsNeededToJoin() )
|
|
DEFINE_METHOD( EnoughCreditsToJoin, EnoughCreditsToJoin() )
|
|
DEFINE_METHOD( PlayersCanJoin, PlayersCanJoin() )
|
|
DEFINE_METHOD( GetNumSidesJoined, GetNumSidesJoined() )
|
|
DEFINE_METHOD( GetCoinMode, GetCoinMode() )
|
|
DEFINE_METHOD( GetPremium, GetPremium() )
|
|
DEFINE_METHOD( GetSongOptionsString, m_SongOptions.GetCurrent().GetString() )
|
|
static int GetSongOptions( T* p, lua_State *L )
|
|
{
|
|
ModsLevel m = Enum::Check<ModsLevel>( L, 1 );
|
|
RString s = p->m_SongOptions.Get(m).GetString();
|
|
LuaHelpers::Push( L, s );
|
|
return 1;
|
|
}
|
|
static int GetSongOptionsObject( T* p, lua_State *L )
|
|
{
|
|
ModsLevel m = Enum::Check<ModsLevel>( L, 1 );
|
|
p->m_SongOptions.Get(m).PushSelf(L);
|
|
return 1;
|
|
}
|
|
static int GetDefaultSongOptions( T* p, lua_State *L )
|
|
{
|
|
SongOptions so;
|
|
p->GetDefaultSongOptions( so );
|
|
lua_pushstring(L, so.GetString());
|
|
return 1;
|
|
}
|
|
static int ApplyPreferredSongOptionsToOtherLevels(T* p, lua_State* L)
|
|
{
|
|
p->m_SongOptions.Assign(ModsLevel_Preferred,
|
|
p->m_SongOptions.Get(ModsLevel_Preferred));
|
|
return 0;
|
|
}
|
|
static int ApplyStageModifiers( T* p, lua_State *L )
|
|
{
|
|
p->ApplyStageModifiers( Enum::Check<PlayerNumber>(L, 1), SArg(2) );
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int ApplyPreferredModifiers( T* p, lua_State *L )
|
|
{
|
|
p->ApplyPreferredModifiers( Enum::Check<PlayerNumber>(L, 1), SArg(2) );
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int ClearStageModifiersIllegalForCourse( T* p, lua_State *L )
|
|
{
|
|
p->ClearStageModifiersIllegalForCourse();
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int SetSongOptions( T* p, lua_State *L )
|
|
{
|
|
ModsLevel m = Enum::Check<ModsLevel>( L, 1 );
|
|
|
|
SongOptions so;
|
|
|
|
so.FromString( SArg(2) );
|
|
p->m_SongOptions.Assign( m, so );
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetStageResult( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
LuaHelpers::Push( L, p->GetStageResult(pn) );
|
|
return 1;
|
|
}
|
|
static int IsWinner( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
lua_pushboolean(L, p->GetStageResult(pn)==RESULT_WIN); return 1;
|
|
}
|
|
static int IsDraw( T* p, lua_State *L )
|
|
{
|
|
lua_pushboolean(L, p->GetStageResult(PLAYER_1)==RESULT_DRAW); return 1;
|
|
}
|
|
static int GetCurrentGame( T* p, lua_State *L ) { const_cast<Game*>(p->GetCurrentGame())->PushSelf( L ); return 1; }
|
|
DEFINE_METHOD( GetEditCourseEntryIndex, m_iEditCourseEntryIndex )
|
|
DEFINE_METHOD( GetEditLocalProfileID, m_sEditLocalProfileID.Get() )
|
|
static int GetEditLocalProfile( T* p, lua_State *L )
|
|
{
|
|
Profile *pProfile = p->GetEditLocalProfile();
|
|
if( pProfile )
|
|
pProfile->PushSelf(L);
|
|
else
|
|
lua_pushnil( L );
|
|
return 1;
|
|
}
|
|
|
|
static int GetCurrentStepsCredits( T* t, lua_State *L )
|
|
{
|
|
const Song* pSong = t->m_pCurSong;
|
|
if( pSong == nullptr )
|
|
return 0;
|
|
|
|
// use a vector and not a set so that ordering is maintained
|
|
std::vector<const Steps*> vpStepsToShow;
|
|
FOREACH_HumanPlayer( p )
|
|
{
|
|
const Steps* pSteps = GAMESTATE->m_pCurSteps[p];
|
|
if( pSteps == nullptr )
|
|
return 0;
|
|
bool bAlreadyAdded = find( vpStepsToShow.begin(), vpStepsToShow.end(), pSteps ) != vpStepsToShow.end();
|
|
if( !bAlreadyAdded )
|
|
vpStepsToShow.push_back( pSteps );
|
|
}
|
|
|
|
for( unsigned i=0; i<vpStepsToShow.size(); i++ )
|
|
{
|
|
const Steps* pSteps = vpStepsToShow[i];
|
|
RString sDifficulty = CustomDifficultyToLocalizedString( GetCustomDifficulty( pSteps->m_StepsType, pSteps->GetDifficulty(), CourseType_Invalid ) );
|
|
|
|
lua_pushstring( L, sDifficulty );
|
|
lua_pushstring( L, pSteps->GetDescription() );
|
|
}
|
|
|
|
return vpStepsToShow.size()*2;
|
|
}
|
|
|
|
static int SetPreferredSongGroup( T* p, lua_State *L ) { p->m_sPreferredSongGroup.Set( SArg(1) ); COMMON_RETURN_SELF; }
|
|
DEFINE_METHOD( GetPreferredSongGroup, m_sPreferredSongGroup.Get() );
|
|
static int GetHumanPlayers( T* p, lua_State *L )
|
|
{
|
|
std::vector<PlayerNumber> vHP;
|
|
FOREACH_HumanPlayer( pn )
|
|
vHP.push_back( pn );
|
|
LuaHelpers::CreateTableFromArray( vHP, L );
|
|
return 1;
|
|
}
|
|
static int GetEnabledPlayers(T* , lua_State *L )
|
|
{
|
|
std::vector<PlayerNumber> vEP;
|
|
FOREACH_EnabledPlayer( pn )
|
|
vEP.push_back( pn );
|
|
LuaHelpers::CreateTableFromArray( vEP, L );
|
|
return 1;
|
|
}
|
|
static int GetCurrentStyle( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn= Enum::Check<PlayerNumber>(L, 1, true, true);
|
|
Style *pStyle = const_cast<Style *> (p->GetCurrentStyle(pn));
|
|
LuaHelpers::Push( L, pStyle );
|
|
return 1;
|
|
}
|
|
static int IsAnyHumanPlayerUsingMemoryCard( T* , lua_State *L )
|
|
{
|
|
bool bUsingMemoryCard = false;
|
|
FOREACH_HumanPlayer( pn )
|
|
{
|
|
if( MEMCARDMAN->GetCardState(pn) == MemoryCardState_Ready )
|
|
bUsingMemoryCard = true;
|
|
}
|
|
lua_pushboolean(L, bUsingMemoryCard );
|
|
return 1;
|
|
}
|
|
static int GetNumStagesForCurrentSongAndStepsOrCourse( T* , lua_State *L )
|
|
{
|
|
lua_pushnumber(L, GAMESTATE->GetNumStagesForCurrentSongAndStepsOrCourse() );
|
|
return 1;
|
|
}
|
|
static int GetNumStagesLeft( T* p, lua_State *L )
|
|
{
|
|
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
|
|
lua_pushnumber(L, p->GetNumStagesLeft(pn));
|
|
return 1;
|
|
}
|
|
static int GetGameSeed( T* p, lua_State *L ) { LuaHelpers::Push( L, p->m_iGameSeed ); return 1; }
|
|
static int GetStageSeed( T* p, lua_State *L ) { LuaHelpers::Push( L, p->m_iStageSeed ); return 1; }
|
|
static int SaveLocalData( T* p, lua_State *L ) { p->SaveLocalData(); COMMON_RETURN_SELF; }
|
|
|
|
static int SetJukeboxUsesModifiers( T* p, lua_State *L )
|
|
{
|
|
p->m_bJukeboxUsesModifiers = BArg(1); COMMON_RETURN_SELF;
|
|
}
|
|
static int Reset( T* p, lua_State *L ) { p->Reset(); COMMON_RETURN_SELF; }
|
|
static int JoinPlayer( T* p, lua_State *L ) { p->JoinPlayer(Enum::Check<PlayerNumber>(L, 1)); COMMON_RETURN_SELF; }
|
|
static int UnjoinPlayer( T* p, lua_State *L ) { p->UnjoinPlayer(Enum::Check<PlayerNumber>(L, 1)); COMMON_RETURN_SELF; }
|
|
static int JoinInput( T* p, lua_State *L )
|
|
{
|
|
lua_pushboolean(L, p->JoinInput(Enum::Check<PlayerNumber>(L, 1)));
|
|
return 1;
|
|
}
|
|
static int GetSongPercent( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongPercent(FArg(1))); return 1; }
|
|
DEFINE_METHOD( GetCurMusicSeconds, m_Position.m_fMusicSeconds )
|
|
|
|
DEFINE_METHOD( GetWorkoutGoalComplete, m_bWorkoutGoalComplete )
|
|
static int GetCharacter( T* p, lua_State *L ) { p->m_pCurCharacters[Enum::Check<PlayerNumber>(L, 1)]->PushSelf(L); return 1; }
|
|
static int SetCharacter( T* p, lua_State *L ){
|
|
Character* c = CHARMAN->GetCharacterFromID(SArg(2));
|
|
if (c)
|
|
p->m_pCurCharacters[Enum::Check<PlayerNumber>(L, 1)] = c;
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int GetExpandedSectionName( T* p, lua_State *L ) { lua_pushstring(L, p->sExpandedSectionName); return 1; }
|
|
static int AddStageToPlayer( T* p, lua_State *L ) { p->AddStageToPlayer(Enum::Check<PlayerNumber>(L, 1)); COMMON_RETURN_SELF; }
|
|
static int InsertCoin( T* p, lua_State *L )
|
|
{
|
|
int numCoins = IArg(1);
|
|
if (GAMESTATE->m_iCoins + numCoins >= 0)
|
|
{
|
|
StepMania::InsertCoin(numCoins, false);
|
|
} else {
|
|
// Warn themers if they attempt to set credits to a negative value.
|
|
luaL_error( L, "Credits may not be negative." );
|
|
}
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int InsertCredit( T* p, lua_State *L )
|
|
{
|
|
StepMania::InsertCredit();
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
static int CurrentOptionsDisqualifyPlayer( T* p, lua_State *L ) { lua_pushboolean(L, p->CurrentOptionsDisqualifyPlayer(Enum::Check<PlayerNumber>(L, 1))); return 1; }
|
|
|
|
static int ResetPlayerOptions( T* p, lua_State *L )
|
|
{
|
|
p->ResetPlayerOptions(Enum::Check<PlayerNumber>(L, 1));
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int RefreshNoteSkinData( T* p, lua_State *L )
|
|
{
|
|
NOTESKIN->RefreshNoteSkinData(p->m_pCurGame);
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int Dopefish( T* p, lua_State *L )
|
|
{
|
|
lua_pushboolean(L, p->m_bDopefish);
|
|
return 1;
|
|
}
|
|
|
|
static int LoadProfiles( T* p, lua_State *L )
|
|
{
|
|
bool LoadEdits = true;
|
|
if(lua_isboolean(L, 1))
|
|
{
|
|
LoadEdits = BArg(1);
|
|
}
|
|
p->LoadProfiles( LoadEdits );
|
|
SCREENMAN->ZeroNextUpdate();
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int SaveProfiles( T* p, lua_State *L )
|
|
{
|
|
p->SavePlayerProfiles();
|
|
SCREENMAN->ZeroNextUpdate();
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int SetFailTypeExplicitlySet(T* p, lua_State* L)
|
|
{
|
|
p->m_bFailTypeWasExplicitlySet= true;
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int StoreRankingName( T* p, lua_State *L )
|
|
{
|
|
p->StoreRankingName(Enum::Check<PlayerNumber>(L, 1), SArg(2));
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
DEFINE_METHOD( HaveProfileToLoad, HaveProfileToLoad() )
|
|
DEFINE_METHOD( HaveProfileToSave, HaveProfileToSave() )
|
|
|
|
static bool AreStyleAndPlayModeCompatible( T* p, lua_State* L, const Style *style, PlayMode pm )
|
|
{
|
|
if( pm != PLAY_MODE_BATTLE && pm != PLAY_MODE_RAVE )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Do not allow styles with StepsTypes with shared sides or that are one player only with Battle or Rave.
|
|
if( style->m_StyleType != StyleType_TwoPlayersSharedSides )
|
|
{
|
|
std::vector<const Style*> vpStyles;
|
|
GAMEMAN->GetCompatibleStyles( p->m_pCurGame, 2, vpStyles );
|
|
for (const Style *s : vpStyles)
|
|
{
|
|
if( s->m_StepsType == style->m_StepsType )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
luaL_error( L, "Style %s is incompatible with PlayMode %s",
|
|
style->m_szName, PlayModeToString( pm ).c_str() );
|
|
return false;
|
|
}
|
|
|
|
static void ClearIncompatibleStepsAndTrails( T *p, lua_State* L )
|
|
{
|
|
FOREACH_HumanPlayer( pn )
|
|
{
|
|
const Style *style = p->GetCurrentStyle(pn);
|
|
if( p->m_pCurSteps[pn] && ( !style || style->m_StepsType != p->m_pCurSteps[pn]->m_StepsType ) )
|
|
{
|
|
p->m_pCurSteps[pn].Set( nullptr );
|
|
}
|
|
if( p->m_pCurTrail[pn] && ( !style || style->m_StepsType != p->m_pCurTrail[pn]->m_StepsType ) )
|
|
{
|
|
p->m_pCurTrail[pn].Set( nullptr );
|
|
}
|
|
}
|
|
}
|
|
|
|
static int SetCurrentStyle( T* p, lua_State *L )
|
|
{
|
|
const Style* pStyle = nullptr;
|
|
if( lua_isstring(L,1) )
|
|
{
|
|
RString style = SArg(1);
|
|
pStyle = GAMEMAN->GameAndStringToStyle( GAMESTATE->m_pCurGame, style );
|
|
if( !pStyle )
|
|
{
|
|
luaL_error( L, "SetCurrentStyle: %s is not a valid style.", style.c_str() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pStyle = Luna<Style>::check(L,1);
|
|
}
|
|
|
|
StyleType st = pStyle->m_StyleType;
|
|
if( p->GetNumSidesJoined() == 2 &&
|
|
( st == StyleType_OnePlayerOneSide || st == StyleType_OnePlayerTwoSides ) )
|
|
{
|
|
luaL_error( L, "Too many sides joined for style %s", pStyle->m_szName );
|
|
}
|
|
else if( p->GetNumSidesJoined() == 1 &&
|
|
( st == StyleType_TwoPlayersTwoSides || st == StyleType_TwoPlayersSharedSides ) )
|
|
{
|
|
luaL_error( L, "Too few sides joined for style %s", pStyle->m_szName );
|
|
}
|
|
|
|
if( !AreStyleAndPlayModeCompatible( p, L, pStyle, p->m_PlayMode ) )
|
|
{
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
PlayerNumber pn= Enum::Check<PlayerNumber>(L, 2, true, true);
|
|
|
|
p->SetCurrentStyle(pStyle, pn);
|
|
ClearIncompatibleStepsAndTrails( p, L );
|
|
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int SetCurrentPlayMode( T* p, lua_State *L )
|
|
{
|
|
PlayMode pm = Enum::Check<PlayMode>( L, 1 );
|
|
if( AreStyleAndPlayModeCompatible( p, L, p->GetCurrentStyle(PLAYER_INVALID), pm ) )
|
|
{
|
|
p->m_PlayMode.Set( pm );
|
|
}
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int SetStepsForEditMode(T* p, lua_State *L)
|
|
{
|
|
// Arg forms:
|
|
// 1. Edit existing steps:
|
|
// song, steps
|
|
// 2. Create new steps to edit:
|
|
// song, nil, stepstype, difficulty
|
|
// 3. Copy steps to new difficulty to edit:
|
|
// song, steps, stepstype, difficulty
|
|
Song* song= Luna<Song>::check(L, 1);
|
|
Steps* steps= nullptr;
|
|
if(!lua_isnil(L, 2))
|
|
{
|
|
steps= Luna<Steps>::check(L, 2);
|
|
}
|
|
// Form 1.
|
|
if(steps != nullptr && lua_gettop(L) == 2)
|
|
{
|
|
p->m_pCurSong.Set(song);
|
|
p->m_pCurSteps[PLAYER_1].Set(steps);
|
|
p->SetCurrentStyle(GAMEMAN->GetEditorStyleForStepsType(
|
|
steps->m_StepsType), PLAYER_INVALID);
|
|
p->m_pCurCourse.Set(nullptr);
|
|
return 0;
|
|
}
|
|
StepsType stype= Enum::Check<StepsType>(L, 3);
|
|
Difficulty diff= Enum::Check<Difficulty>(L, 4);
|
|
Steps* new_steps= song->CreateSteps();
|
|
RString edit_name;
|
|
// Form 2.
|
|
if(steps == nullptr)
|
|
{
|
|
new_steps->CreateBlank(stype);
|
|
new_steps->SetMeter(1);
|
|
edit_name= "";
|
|
}
|
|
// Form 3.
|
|
else
|
|
{
|
|
new_steps->CopyFrom(steps, stype, song->m_fMusicLengthSeconds);
|
|
edit_name= steps->GetDescription();
|
|
}
|
|
SongUtil::MakeUniqueEditDescription(song, stype, edit_name);
|
|
steps->SetDescription(edit_name);
|
|
song->AddSteps(new_steps);
|
|
p->m_pCurSong.Set(song);
|
|
p->m_pCurSteps[PLAYER_1].Set(steps);
|
|
p->SetCurrentStyle(GAMEMAN->GetEditorStyleForStepsType(
|
|
steps->m_StepsType), PLAYER_INVALID);
|
|
p->m_pCurCourse.Set(nullptr);
|
|
return 0;
|
|
}
|
|
|
|
static int GetAutoGenFarg(T* p, lua_State *L)
|
|
{
|
|
int i= IArg(1) - 1;
|
|
if(i < 0) { lua_pushnil(L); return 1; }
|
|
size_t si= static_cast<size_t>(i);
|
|
if(si >= p->m_autogen_fargs.size()) { lua_pushnil(L); return 1; }
|
|
lua_pushnumber(L, p->GetAutoGenFarg(si));
|
|
return 1;
|
|
}
|
|
static int SetAutoGenFarg(T* p, lua_State* L)
|
|
{
|
|
int i= IArg(1) - 1;
|
|
if(i < 0)
|
|
{
|
|
luaL_error(L, "%i is not a valid autogen arg index.", i);
|
|
}
|
|
float v= FArg(2);
|
|
size_t si= static_cast<size_t>(i);
|
|
while(si >= p->m_autogen_fargs.size())
|
|
{
|
|
p->m_autogen_fargs.push_back(0.0f);
|
|
}
|
|
p->m_autogen_fargs[si]= v;
|
|
COMMON_RETURN_SELF;
|
|
}
|
|
|
|
static int GetPlayerFailType(T* p, lua_State* L)
|
|
{
|
|
Enum::Push(L, p->GetPlayerFailType(Enum::Check<PlayerNumber>(L, 1)));
|
|
return 1;
|
|
}
|
|
|
|
static int prepare_song_for_gameplay(T* p, lua_State* L)
|
|
{
|
|
int result= p->prepare_song_for_gameplay();
|
|
lua_pushstring(L, prepare_song_failures[result]);
|
|
return 1;
|
|
}
|
|
|
|
LunaGameState()
|
|
{
|
|
ADD_METHOD( IsPlayerEnabled );
|
|
ADD_METHOD( IsHumanPlayer );
|
|
ADD_METHOD( GetPlayerDisplayName );
|
|
ADD_METHOD( GetMasterPlayerNumber );
|
|
ADD_METHOD( GetMultiplayer );
|
|
ADD_METHOD( SetMultiplayer );
|
|
ADD_METHOD( InStepEditor );
|
|
ADD_METHOD( GetNumMultiplayerNoteFields );
|
|
ADD_METHOD( SetNumMultiplayerNoteFields );
|
|
ADD_METHOD( ShowW1 );
|
|
ADD_METHOD( GetPlayerState );
|
|
ADD_METHOD( GetMultiPlayerState );
|
|
ADD_METHOD( ApplyGameCommand );
|
|
ADD_METHOD( CanSafelyEnterGameplay );
|
|
ADD_METHOD( GetCurrentSong );
|
|
ADD_METHOD( SetCurrentSong );
|
|
ADD_METHOD( GetCurrentSteps );
|
|
ADD_METHOD( SetCurrentSteps );
|
|
ADD_METHOD( GetCurrentCourse );
|
|
ADD_METHOD( SetCurrentCourse );
|
|
ADD_METHOD( GetCurrentTrail );
|
|
ADD_METHOD( SetCurrentTrail );
|
|
ADD_METHOD( SetPreferredSong );
|
|
ADD_METHOD( GetPreferredSong );
|
|
ADD_METHOD( SetTemporaryEventMode );
|
|
ADD_METHOD( Env );
|
|
ADD_METHOD( GetEditSourceSteps );
|
|
ADD_METHOD( SetPreferredDifficulty );
|
|
ADD_METHOD( GetPreferredDifficulty );
|
|
ADD_METHOD( AnyPlayerHasRankingFeats );
|
|
ADD_METHOD( IsCourseMode );
|
|
ADD_METHOD( IsBattleMode );
|
|
ADD_METHOD( IsDemonstration );
|
|
ADD_METHOD( GetPlayMode );
|
|
ADD_METHOD( GetSortOrder );
|
|
ADD_METHOD( GetCurrentStageIndex );
|
|
ADD_METHOD( IsGoalComplete );
|
|
ADD_METHOD( PlayerIsUsingModifier );
|
|
ADD_METHOD( GetCourseSongIndex );
|
|
ADD_METHOD( GetLoadingCourseSongIndex );
|
|
ADD_METHOD( GetSmallestNumStagesLeftForAnyHumanPlayer );
|
|
ADD_METHOD( IsAnExtraStage );
|
|
ADD_METHOD( IsExtraStage );
|
|
ADD_METHOD( IsExtraStage2 );
|
|
ADD_METHOD( GetCurrentStage );
|
|
ADD_METHOD( HasEarnedExtraStage );
|
|
ADD_METHOD( GetEarnedExtraStage );
|
|
ADD_METHOD( GetEasiestStepsDifficulty );
|
|
ADD_METHOD( GetHardestStepsDifficulty );
|
|
ADD_METHOD( IsEventMode );
|
|
ADD_METHOD( GetNumPlayersEnabled );
|
|
/*ADD_METHOD( GetSongBeat );
|
|
ADD_METHOD( GetSongBeatVisible );
|
|
ADD_METHOD( GetSongBPS );
|
|
ADD_METHOD( GetSongFreeze );
|
|
ADD_METHOD( GetSongDelay );*/
|
|
ADD_METHOD( GetSongPosition );
|
|
ADD_METHOD( GetLastGameplayDuration );
|
|
ADD_METHOD( GetGameplayLeadIn );
|
|
ADD_METHOD( GetCoins );
|
|
ADD_METHOD( IsSideJoined );
|
|
ADD_METHOD( GetCoinsNeededToJoin );
|
|
ADD_METHOD( EnoughCreditsToJoin );
|
|
ADD_METHOD( PlayersCanJoin );
|
|
ADD_METHOD( GetNumSidesJoined );
|
|
ADD_METHOD( GetCoinMode );
|
|
ADD_METHOD( GetPremium );
|
|
ADD_METHOD( GetSongOptionsString );
|
|
ADD_METHOD( GetSongOptions );
|
|
ADD_METHOD( GetSongOptionsObject );
|
|
ADD_METHOD( GetDefaultSongOptions );
|
|
ADD_METHOD( ApplyPreferredSongOptionsToOtherLevels );
|
|
ADD_METHOD( ApplyPreferredModifiers );
|
|
ADD_METHOD( ApplyStageModifiers );
|
|
ADD_METHOD( ClearStageModifiersIllegalForCourse );
|
|
ADD_METHOD( SetSongOptions );
|
|
ADD_METHOD( GetStageResult );
|
|
ADD_METHOD( IsWinner );
|
|
ADD_METHOD( IsDraw );
|
|
ADD_METHOD( GetCurrentGame );
|
|
ADD_METHOD( GetEditCourseEntryIndex );
|
|
ADD_METHOD( GetEditLocalProfileID );
|
|
ADD_METHOD( GetEditLocalProfile );
|
|
ADD_METHOD( GetCurrentStepsCredits );
|
|
ADD_METHOD( SetPreferredSongGroup );
|
|
ADD_METHOD( GetPreferredSongGroup );
|
|
ADD_METHOD( GetHumanPlayers );
|
|
ADD_METHOD( GetEnabledPlayers );
|
|
ADD_METHOD( GetCurrentStyle );
|
|
ADD_METHOD( IsAnyHumanPlayerUsingMemoryCard );
|
|
ADD_METHOD( GetNumStagesForCurrentSongAndStepsOrCourse );
|
|
ADD_METHOD( GetNumStagesLeft );
|
|
ADD_METHOD( GetGameSeed );
|
|
ADD_METHOD( GetStageSeed );
|
|
ADD_METHOD( SaveLocalData );
|
|
ADD_METHOD( SetJukeboxUsesModifiers );
|
|
ADD_METHOD( GetWorkoutGoalComplete );
|
|
ADD_METHOD( Reset );
|
|
ADD_METHOD( JoinPlayer );
|
|
ADD_METHOD( UnjoinPlayer );
|
|
ADD_METHOD( JoinInput );
|
|
ADD_METHOD( GetSongPercent );
|
|
ADD_METHOD( GetCurMusicSeconds );
|
|
ADD_METHOD( GetCharacter );
|
|
ADD_METHOD( SetCharacter );
|
|
ADD_METHOD( GetExpandedSectionName );
|
|
ADD_METHOD( AddStageToPlayer );
|
|
ADD_METHOD( InsertCoin );
|
|
ADD_METHOD( InsertCredit );
|
|
ADD_METHOD( CurrentOptionsDisqualifyPlayer );
|
|
ADD_METHOD( ResetPlayerOptions );
|
|
ADD_METHOD( RefreshNoteSkinData );
|
|
ADD_METHOD( Dopefish );
|
|
ADD_METHOD( LoadProfiles );
|
|
ADD_METHOD( SaveProfiles );
|
|
ADD_METHOD( HaveProfileToLoad );
|
|
ADD_METHOD( HaveProfileToSave );
|
|
ADD_METHOD( SetFailTypeExplicitlySet );
|
|
ADD_METHOD( StoreRankingName );
|
|
ADD_METHOD( SetCurrentStyle );
|
|
ADD_METHOD( SetCurrentPlayMode );
|
|
ADD_METHOD( SetStepsForEditMode );
|
|
ADD_METHOD( GetAutoGenFarg );
|
|
ADD_METHOD( SetAutoGenFarg );
|
|
ADD_METHOD( GetPlayerFailType );
|
|
ADD_METHOD(prepare_song_for_gameplay);
|
|
}
|
|
};
|
|
|
|
LUA_REGISTER_CLASS( GameState )
|
|
// lua end
|
|
|
|
/*
|
|
* (c) 2001-2004 Chris Danford, Glenn Maynard, Chris Gomez
|
|
* 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.
|
|
*/
|