Files
itgmania212121/src/ScreenEdit.cpp
T
Devin J. Pohly ce507b9800 use empty TimingData in Steps to signify fallback on Song timing.
This required using the new Steps::GetTimingData function and the allowEmpty
parameter to TimingData::TidyUpData when appropriate, as well as clearing the
TimingData to remove step timing rather than coping the song timing over it.

Fixes some odd editor behavior when changing song timing, and is overall a
slightly less hacky way of doing things.
2013-01-23 14:51:18 -05:00

5940 lines
213 KiB
C++

#include "global.h"
#include <utility>
#include <float.h>
#include "ScreenEdit.h"
#include "ActorUtil.h"
#include "AdjustSync.h"
#include "BackgroundUtil.h"
#include "CommonMetrics.h"
#include "Foreach.h"
#include "GameManager.h"
#include "GamePreferences.h"
#include "GameSoundManager.h"
#include "GameState.h"
#include "InputEventPlus.h"
#include "InputMapper.h"
#include "LocalizedString.h"
#include "NoteDataUtil.h"
#include "NoteSkinManager.h"
#include "NoteTypes.h"
#include "NotesWriterSM.h"
#include "PrefsManager.h"
#include "RageSoundManager.h"
#include "RageInput.h"
#include "RageLog.h"
#include "ScreenDimensions.h"
#include "ScreenManager.h"
#include "ScreenMiniMenu.h"
#include "ScreenPrompt.h"
#include "ScreenSaveSync.h"
#include "ScreenTextEntry.h"
#include "SongManager.h"
#include "SongUtil.h"
#include "StepsUtil.h"
#include "Style.h"
#include "ThemeManager.h"
#include "ThemeMetric.h"
#include "TimingData.h"
#include "Game.h"
#include "RageSoundReader.h"
static Preference<float> g_iDefaultRecordLength( "DefaultRecordLength", 4 );
static Preference<bool> g_bEditorShowBGChangesPlay( "EditorShowBGChangesPlay", true );
/** @brief How long must the button be held to generate a hold in record mode? */
const float RECORD_HOLD_SECONDS = 0.3f;
#define PLAYER_X (SCREEN_CENTER_X)
#define PLAYER_Y (SCREEN_CENTER_Y)
#define PLAYER_HEIGHT (360)
#define PLAYER_Y_STANDARD (PLAYER_Y-PLAYER_HEIGHT/2)
#define EDIT_X (SCREEN_CENTER_X)
#define EDIT_Y (PLAYER_Y)
#define RECORD_X (SCREEN_CENTER_X)
#define RECORD_Y (SCREEN_CENTER_Y)
#define PLAY_RECORD_HELP_TEXT THEME->GetString(m_sName,"PlayRecordHelpText")
#define EDIT_HELP_TEXT THEME->GetString(m_sName,"EditHelpText")
AutoScreenMessage( SM_UpdateTextInfo );
AutoScreenMessage( SM_BackFromMainMenu );
AutoScreenMessage( SM_BackFromAreaMenu );
AutoScreenMessage( SM_BackFromAlterMenu );
AutoScreenMessage( SM_BackFromStepsInformation );
AutoScreenMessage( SM_BackFromStepsData );
AutoScreenMessage( SM_BackFromOptions );
AutoScreenMessage( SM_BackFromSongInformation );
AutoScreenMessage( SM_BackFromBGChange );
AutoScreenMessage( SM_BackFromInsertTapAttack );
AutoScreenMessage( SM_BackFromInsertTapAttackPlayerOptions );
AutoScreenMessage( SM_BackFromAttackAtTime );
AutoScreenMessage( SM_BackFromInsertStepAttack );
AutoScreenMessage( SM_BackFromAddingModToExistingAttack );
AutoScreenMessage( SM_BackFromEditingModToExistingAttack );
AutoScreenMessage( SM_BackFromEditingAttackStart );
AutoScreenMessage( SM_BackFromEditingAttackLength );
AutoScreenMessage( SM_BackFromAddingAttackToChart );
AutoScreenMessage( SM_BackFromInsertStepAttackPlayerOptions );
AutoScreenMessage( SM_BackFromInsertCourseAttack );
AutoScreenMessage( SM_BackFromInsertCourseAttackPlayerOptions );
AutoScreenMessage( SM_BackFromCourseModeMenu );
AutoScreenMessage( SM_BackFromKeysoundTrack );
AutoScreenMessage( SM_BackFromNewKeysound );
AutoScreenMessage( SM_DoRevertToLastSave );
AutoScreenMessage( SM_DoRevertFromDisk );
AutoScreenMessage( SM_BackFromTimingDataInformation );
AutoScreenMessage( SM_BackFromDifficultyMeterChange );
AutoScreenMessage( SM_BackFromBeat0Change );
AutoScreenMessage( SM_BackFromBPMChange );
AutoScreenMessage( SM_BackFromStopChange );
AutoScreenMessage( SM_BackFromDelayChange );
AutoScreenMessage( SM_BackFromTimeSignatureChange );
AutoScreenMessage( SM_BackFromTickcountChange );
AutoScreenMessage( SM_BackFromComboChange );
AutoScreenMessage( SM_BackFromLabelChange );
AutoScreenMessage( SM_BackFromWarpChange );
AutoScreenMessage( SM_BackFromSpeedPercentChange );
AutoScreenMessage( SM_BackFromSpeedWaitChange );
AutoScreenMessage( SM_BackFromSpeedModeChange );
AutoScreenMessage( SM_BackFromScrollChange );
AutoScreenMessage( SM_BackFromFakeChange );
AutoScreenMessage( SM_DoEraseStepTiming );
AutoScreenMessage( SM_DoSaveAndExit );
AutoScreenMessage( SM_DoExit );
AutoScreenMessage( SM_SaveSuccessful );
AutoScreenMessage( SM_SaveFailed );
static const char *EditStateNames[] = {
"Edit",
"Record",
"RecordPaused",
"Playing"
};
XToString( EditState );
LuaXType( EditState );
void ScreenEdit::InitEditMappings()
{
m_EditMappingsDeviceInput.Clear();
// Common mappings:
switch( EDIT_MODE.GetValue() )
{
case EditMode_Practice:
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_PREV_MEASURE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_UP);
m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_PREV_MEASURE][0] = GAME_BUTTON_UP;
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_NEXT_MEASURE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DOWN);
m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_NEXT_MEASURE][0] = GAME_BUTTON_DOWN;
break;
default:
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_LINE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_UP);
m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_UP_LINE][0] = GAME_BUTTON_UP;
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_LINE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DOWN);
m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_DOWN_LINE][0] = GAME_BUTTON_DOWN;
break;
}
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_PAGE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGUP);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_PAGE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SEMICOLON);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_PAGE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGDN);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_PAGE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SQUOTE);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_UP_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_UP_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGUP);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SEMICOLON);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_DOWN_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_DOWN_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGDN);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SQUOTE);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_HOME][0] = DeviceInput(DEVICE_KEYBOARD, KEY_HOME);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_END][0] = DeviceInput(DEVICE_KEYBOARD, KEY_END);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PERIOD);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_COMMA);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SEGMENT_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PERIOD);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_NEXT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SEGMENT_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_COMMA);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_PREV][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_LAY_SELECT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_SPACE);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_FROM_START][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_START][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_START][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_FROM_CURSOR][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_CURSOR][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_CURSOR][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
// EditMode-specific mappings
switch( EDIT_MODE.GetValue() )
{
case EditMode_Practice:
// Left = Zoom out (1x, 2x, 4x)
// Right = Zoom in
m_EditMappingsDeviceInput.button [EDIT_BUTTON_SCROLL_SPEED_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LEFT);
m_EditMappingsDeviceInput.button [EDIT_BUTTON_SCROLL_SPEED_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RIGHT);
// F1 = Show help popup
m_EditMappingsDeviceInput.button [EDIT_BUTTON_OPEN_INPUT_HELP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F1);
// Esc = Show Edit Menu
m_EditMappingsDeviceInput.button [EDIT_BUTTON_OPEN_EDIT_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_EditMappingsMenuButton.button [EDIT_BUTTON_OPEN_EDIT_MENU][0] = GAME_BUTTON_START;
m_EditMappingsMenuButton.button [EDIT_BUTTON_OPEN_EDIT_MENU][1] = GAME_BUTTON_BACK;
// Escape, Enter = exit play/record
m_PlayMappingsDeviceInput.button [EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
m_PlayMappingsDeviceInput.button [EDIT_BUTTON_RETURN_TO_EDIT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_PlayMappingsMenuButton.button [EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_BACK;
return;
case EditMode_CourseMods:
// Left/Right = Snap to Next/Prev
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LEFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RIGHT);
// v = course playback menu
// m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_COURSE_ATTACK_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cv);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADD_COURSE_MODS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Co);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_COURSE_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cv);
// F1 = Show help popup
m_EditMappingsDeviceInput.button [EDIT_BUTTON_OPEN_INPUT_HELP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F1);
// Esc = Show Edit Menu
m_EditMappingsDeviceInput.button [EDIT_BUTTON_OPEN_EDIT_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_EditMappingsMenuButton.button [EDIT_BUTTON_OPEN_EDIT_MENU][0] = GAME_BUTTON_START;
m_EditMappingsMenuButton.button [EDIT_BUTTON_OPEN_EDIT_MENU][1] = GAME_BUTTON_BACK;
// Escape, Enter = exit play/record
m_PlayMappingsDeviceInput.button [EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
m_PlayMappingsDeviceInput.button [EDIT_BUTTON_RETURN_TO_EDIT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_PlayMappingsMenuButton.button [EDIT_BUTTON_RETURN_TO_EDIT][0] = GAME_BUTTON_START;
return;
case EditMode_Full:
// F4 = Show timing menu
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_TIMING_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F4);
/* Don't allow F5/F6 in home mode. It breaks the "delay creation until first save" logic. */
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_PREV_STEPS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F5);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_NEXT_STEPS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F6);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_BPM_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F7);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_BPM_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F8);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_STOP_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F9);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_STOP_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F10);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELAY_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F9);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_DOWN][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELAY_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F10);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_UP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OFFSET_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F11);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OFFSET_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F12);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_START_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RBRACKET);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_START_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LBRACKET);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_LENGTH_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RBRACKET);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_UP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_LENGTH_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LBRACKET);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_DOWN][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_SAMPLE_MUSIC][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cl);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
// m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADD_STEP_MODS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cc);
// m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_STEP_ATTACK_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cv);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_INSERT_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_INSERT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_INSERT_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_INSERT_SHIFT_PAUSES][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELETE_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DEL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELETE_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELETE_SHIFT_PAUSES][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
break;
default: break;
}
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_0][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C1);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_1][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C2);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_2][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C3);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_3][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C4);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_4][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C5);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_5][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C6);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_6][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C7);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_7][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C8);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_8][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C9);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_9][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C0);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_RIGHT_SIDE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_RIGHT_SIDE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_TAP_LEFT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cn);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_TAP_RIGHT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cm);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_SEGMENT_LEFT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cn);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_LEFT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_LEFT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_SEGMENT_RIGHT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cm);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_RIGHT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_RIGHT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button [EDIT_BUTTON_SCROLL_SPEED_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_UP);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_UP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button [EDIT_BUTTON_SCROLL_SPEED_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DOWN);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_DOWN][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LEFT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RIGHT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_EDIT_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_EditMappingsMenuButton.button[EDIT_BUTTON_OPEN_EDIT_MENU][0] = GAME_BUTTON_START;
m_EditMappingsMenuButton.button[EDIT_BUTTON_OPEN_EDIT_MENU][1] = GAME_BUTTON_BACK;
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_AREA_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_ALTER_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Ca);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_INPUT_HELP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F1);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cr);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_INSERT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_INSERT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_INSERT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_BACKSLASH);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELETE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DEL);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADJUST_FINE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADJUST_FINE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAVE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_Cs);
#if defined(MACOSX)
/* use cmd */
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LMETA);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RMETA);
#else
/* use ctrl */
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
#endif
m_EditMappingsDeviceInput.button[EDIT_BUTTON_UNDO][1] = DeviceInput(DEVICE_KEYBOARD, KEY_Cu);
// Switch players, if it makes sense to do so.
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SWITCH_PLAYERS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_SLASH);
// Allow song and step timing to be swapped.
m_EditMappingsDeviceInput.button[EDIT_BUTTON_SWITCH_TIMINGS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Ct);
m_PlayMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
m_PlayMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_PlayMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = GAME_BUTTON_BACK;
m_PlayMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_START;
m_RecordMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
m_RecordMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
m_RecordMappingsDeviceInput.button[EDIT_BUTTON_REMOVE_NOTE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
m_RecordMappingsDeviceInput.button[EDIT_BUTTON_REMOVE_NOTE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
m_RecordMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_RecordMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
m_RecordMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = GAME_BUTTON_BACK;
m_RecordMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_START;
m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_PLAY_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cr);
m_RecordPausedMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
m_RecordPausedMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_RECORD_FROM_CURSOR][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cr);
m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
m_RecordPausedMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_BACK;
m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_UNDO][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cu);
}
/* Given a DeviceInput that was just depressed, return an active edit function. */
EditButton ScreenEdit::DeviceToEdit( const DeviceInput &DeviceI ) const
{
ASSERT( DeviceI.IsValid() );
const MapEditToDI *pCurrentMap = GetCurrentDeviceInputMap();
/* First, search to see if a key that requires a modifier is pressed. */
FOREACH_EditButton( e )
{
for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
{
if( pCurrentMap->button[e][slot] == DeviceI && pCurrentMap->hold[e][0].IsValid() )
{
/* The button maps to this function, and has one or more shift modifiers attached. */
for( int holdslot = 0; holdslot < NUM_EDIT_TO_DEVICE_SLOTS; ++holdslot )
{
DeviceInput hDI = pCurrentMap->hold[e][holdslot];
if( hDI.IsValid() && INPUTFILTER->IsBeingPressed(hDI) )
return e;
}
}
}
}
/* No shifted keys matched. See if any unshifted inputs are bound to this key. */
FOREACH_EditButton(e)
{
for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
{
if( pCurrentMap->button[e][slot] == DeviceI && !pCurrentMap->hold[e][0].IsValid() )
{
/* The button maps to this function. */
return e;
}
}
}
return EditButton_Invalid;
}
/* Given a DeviceInput that was just depressed, return an active edit function. */
EditButton ScreenEdit::MenuButtonToEditButton( GameButton MenuI ) const
{
const MapEditButtonToMenuButton *pCurrentMap = GetCurrentMenuButtonMap();
FOREACH_EditButton(e)
{
for( int slot = 0; slot < NUM_EDIT_TO_MENU_SLOTS; ++slot )
{
if( pCurrentMap->button[e][slot] == MenuI )
{
/* The button maps to this function. */
return e;
}
}
}
return EditButton_Invalid;
}
/* If DeviceI was just pressed, return true if button is triggered. (More than one
* function may be mapped to a key.) */
bool ScreenEdit::EditPressed( EditButton button, const DeviceInput &DeviceI )
{
ASSERT( DeviceI.IsValid() );
const MapEditToDI *pCurrentMap = GetCurrentDeviceInputMap();
/* First, search to see if a key that requires a modifier is pressed. */
bool bPrimaryButtonPressed = false;
for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
{
if( pCurrentMap->button[button][slot] == DeviceI )
bPrimaryButtonPressed = true;
}
if( !bPrimaryButtonPressed )
return false;
/* The button maps to this function. Does the function has one or more shift modifiers attached? */
if( !pCurrentMap->hold[button][0].IsValid() )
return true;
for( int holdslot = 0; holdslot < NUM_EDIT_TO_DEVICE_SLOTS; ++holdslot )
{
DeviceInput hDI = pCurrentMap->hold[button][holdslot];
if( INPUTFILTER->IsBeingPressed(hDI) )
return true;
}
/* No shifted keys matched. */
return false;
}
bool ScreenEdit::EditToDevice( EditButton button, int iSlotNum, DeviceInput &DeviceI ) const
{
ASSERT( iSlotNum < NUM_EDIT_TO_DEVICE_SLOTS );
const MapEditToDI *pCurrentMap = GetCurrentDeviceInputMap();
if( pCurrentMap->button[button][iSlotNum].IsValid() )
DeviceI = pCurrentMap->button[button][iSlotNum];
else if( pCurrentMap->hold[button][iSlotNum].IsValid() )
DeviceI = pCurrentMap->hold[button][iSlotNum];
return DeviceI.IsValid();
}
bool ScreenEdit::EditIsBeingPressed( EditButton button ) const
{
for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
{
DeviceInput DeviceI;
if( EditToDevice( button, slot, DeviceI ) && INPUTFILTER->IsBeingPressed(DeviceI) )
return true;
}
return false;
}
const MapEditToDI *ScreenEdit::GetCurrentDeviceInputMap() const
{
switch( m_EditState )
{
DEFAULT_FAIL( m_EditState );
case STATE_EDITING: return &m_EditMappingsDeviceInput;
case STATE_PLAYING: return &m_PlayMappingsDeviceInput;
case STATE_RECORDING: return &m_RecordMappingsDeviceInput;
case STATE_RECORDING_PAUSED: return &m_RecordPausedMappingsDeviceInput;
}
}
const MapEditButtonToMenuButton *ScreenEdit::GetCurrentMenuButtonMap() const
{
switch( m_EditState )
{
DEFAULT_FAIL( m_EditState );
case STATE_EDITING: return &m_EditMappingsMenuButton;
case STATE_PLAYING: return &m_PlayMappingsMenuButton;
case STATE_RECORDING: return &m_RecordMappingsMenuButton;
case STATE_RECORDING_PAUSED: return &m_RecordPausedMappingsMenuButton;
}
}
static MenuDef g_EditHelp(
"ScreenMiniMenuEditHelp"
// fill this in dynamically
);
static MenuDef g_AttackAtTimeMenu(
"ScreenMiniMenuAttackAtTimeMenu"
// fill this in dynamically
);
static MenuDef g_IndividualAttack(
"ScreenMiniMenuIndividualAttack"
// fill this in dynamically
);
static MenuDef g_KeysoundTrack(
"ScreenMiniMenuKeysoundTrack"
); // fill this in dynamically
static MenuDef g_MainMenu(
"ScreenMiniMenuMainMenu",
MenuRowDef(ScreenEdit::play_whole_song,
"Play whole song",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::play_current_beat_to_end,
"Play current beat to end",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::save,
"Save",
true, EditMode_Home, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::play_selection,
"Play selection",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::set_selection_start,
"Set selection start",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::set_selection_end,
"Set selection end",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::revert_to_last_save,
"Revert to last save",
true, EditMode_Home, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::revert_from_disk,
"Revert from disk",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::options,
"Editor options",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::edit_song_info,
"Edit song info",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::edit_steps_information,
"Edit steps information",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::edit_timing_data,
"Edit Timing Data",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::view_steps_data,
"View steps data",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::play_preview_music,
"Play preview music",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::exit,
"Exit Edit Mode",
true, EditMode_Practice, true, true, 0, NULL )
);
static MenuDef g_AlterMenu(
"ScreenMiniMenuAlterMenu",
MenuRowDef(ScreenEdit::cut,
"Cut",
true,
EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::copy,
"Copy",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::clear, "Clear area", true,
EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::quantize, "Quantize", true,
EditMode_Practice, true, true, 0,
"4th","8th","12th","16th","24th","32nd","48th","64th","192nd"),
MenuRowDef(ScreenEdit::turn, "Turn", true,
EditMode_Practice, true, true, 0, "Left","Right","Mirror","Backwards","Shuffle","SuperShuffle" ),
MenuRowDef(ScreenEdit::transform, "Transform", true,
EditMode_Practice, true, true, 0, "NoHolds","NoMines","Little","Wide",
"Big","Quick","Skippy","Mines","Echo","Stomp","Planted","Floored",
"Twister","NoJumps","NoHands","NoQuads","NoStretch" ),
MenuRowDef(ScreenEdit::alter, "Alter", true,
EditMode_Practice, true, true, 0, "Autogen To Fill Width","Backwards","Swap Sides",
"Copy Left To Right","Copy Right To Left","Clear Left","Clear Right",
"Collapse To One","Collapse Left","Shift Left","Shift Right" ),
MenuRowDef(ScreenEdit::tempo, "Tempo", true,
EditMode_Full, true, true, 0, "Compress 2x","Compress 3->2",
"Compress 4->3","Expand 3->4","Expand 2->3","Expand 2x" ),
MenuRowDef(ScreenEdit::play,
"Play selection",
true,
EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::record,
"Record in selection",
true,
EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::preview_designation,
"Designate as Music Preview",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::convert_to_pause,
"Convert selection to pause",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::convert_to_delay,
"Convert selection to delay",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::convert_to_warp,
"Convert selection to warp",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::convert_to_fake,
"Convert selection to fake",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::convert_to_attack,
"Convert selection to attack",
true,
EditMode_Full, true, true, 0, NULL),
MenuRowDef(ScreenEdit::routine_invert_notes,
"Invert notes' player",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::routine_mirror_1_to_2,
"Mirror Player 1 to 2",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::routine_mirror_2_to_1,
"Mirror Player 2 to 1",
true,
EditMode_Full, true, true, 0, NULL )
);
static MenuDef g_AreaMenu(
"ScreenMiniMenuAreaMenu",
MenuRowDef(ScreenEdit::paste_at_current_beat,
"Paste at current beat",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::paste_at_begin_marker,
"Paste at begin marker",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::insert_and_shift,
"Insert beat and shift down",
true, EditMode_Practice, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
MenuRowDef(ScreenEdit::delete_and_shift,
"Delete beat and shift up",
true, EditMode_Practice, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
MenuRowDef(ScreenEdit::shift_pauses_forward,
"Shift all timing changes down",
true, EditMode_Full, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
MenuRowDef(ScreenEdit::shift_pauses_backward,
"Shift all timing changes up",
true, EditMode_Full, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
MenuRowDef(ScreenEdit::convert_pause_to_beat,
"Convert pause to beats",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::convert_delay_to_beat,
"Convert delay to beats",
true,
EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::last_second_at_beat,
"Designate last second at current beat",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::undo,
"Undo",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::clear_clipboard,
"Clear clipboard",
true,
EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::modify_attacks_at_row,
"Modify Attacks at current beat",
true, EditMode_CourseMods, true, true, 0, NULL),
MenuRowDef(ScreenEdit::modify_keysounds_at_row,
"Modify Keysounds at current beat",
true, EditMode_Full, true, true, 0, NULL)
);
static MenuDef g_StepsInformation(
"ScreenMiniMenuStepsInformation",
MenuRowDef(ScreenEdit::difficulty,
"Difficulty",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::meter,
"Meter",
true, EditMode_Practice, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::predict_meter,
"Predicted Meter",
false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::chartname,
"Chart Name",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::description,
"Description",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::chartstyle,
"Chart Style",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::step_credit,
"Step Author",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::step_display_bpm,
"Display BPM",
true, EditMode_Full, true, true, 0, "Actual", "Specified", "Random" ),
MenuRowDef(ScreenEdit::step_min_bpm,
"Min BPM",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::step_max_bpm,
"Max BPM",
true, EditMode_Full, true, true, 0, NULL )
);
static MenuDef g_StepsData(
"ScreenMiniMenuStepsData",
MenuRowDef( ScreenEdit::tap_notes, "Tap Steps", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::jumps, "Jumps", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::hands, "Hands", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::quads, "Quads", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::holds, "Holds", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::mines, "Mines", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::rolls, "Rolls", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::lifts, "Lifts", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::fakes, "Fakes", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::stream, "Stream", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::voltage, "Voltage", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::air, "Air", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::freeze, "Freeze", false, EditMode_Full, true, true, 0, NULL ),
MenuRowDef( ScreenEdit::chaos, "Chaos", false, EditMode_Full, true, true, 0, NULL )
);
static MenuDef g_SongInformation(
"ScreenMiniMenuSongInformation",
MenuRowDef(ScreenEdit::main_title,
"Main title",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::sub_title,
"Sub title",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::artist,
"Artist",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::genre,
"Genre",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::credit,
"Credit",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::main_title_transliteration,
"Main title transliteration",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::sub_title_transliteration,
"Sub title transliteration",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::artist_transliteration,
"Artist transliteration",
true, EditMode_Practice, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::last_second_hint,
"Last second hint",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::preview_start,
"Preview Start",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::preview_length,
"Preview Length",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::display_bpm,
"Display BPM",
true, EditMode_Full, true, true, 0, "Actual", "Specified", "Random" ),
MenuRowDef(ScreenEdit::min_bpm,
"Min BPM",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::max_bpm,
"Max BPM",
true, EditMode_Full, true, true, 0, NULL )
);
static MenuDef g_TimingDataInformation(
"ScreenMiniMenuTimingDataInformation",
MenuRowDef(ScreenEdit::beat_0_offset,
"Beat 0 Offset",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::bpm,
"Edit BPM change",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::stop,
"Edit stop",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::delay,
"Edit delay",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::time_signature,
"Edit time signature",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::label,
"Edit label",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::tickcount,
"Edit tickcount",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::combo,
"Edit combo",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::warp,
"Edit warp",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::speed_percent,
"Edit speed (percent)",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::speed_wait,
"Edit speed (wait)",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::speed_mode,
"Edit speed (mode)",
true, EditMode_Full, true, true, 0, "Beats", "Seconds" ),
MenuRowDef(ScreenEdit::scroll,
"Edit scrolling factor",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::fake,
"Edit fake",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::copy_full_timing,
"Copy timing data",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::paste_full_timing,
"Paste timing data",
true, EditMode_Full, true, true, 0, NULL ),
MenuRowDef(ScreenEdit::erase_step_timing,
"Erase step timing",
true, EditMode_Full, true, true, 0, NULL )
);
// XXX: What are these enums used for?
enum
{
song_bganimation,
song_movie,
song_bitmap,
global_bganimation,
global_movie,
global_movie_song_group,
global_movie_song_group_and_genre,
dynamic_random,
baked_random,
none
};
static bool EnabledIfSet1SongBGAnimation();
static bool EnabledIfSet1SongMovie();
static bool EnabledIfSet1SongBitmap();
static bool EnabledIfSet1GlobalBGAnimation();
static bool EnabledIfSet1GlobalMovie();
static bool EnabledIfSet1GlobalMovieSongGroup();
static bool EnabledIfSet1GlobalMovieSongGroupAndGenre();
static bool EnabledIfSet2SongBGAnimation();
static bool EnabledIfSet2SongMovie();
static bool EnabledIfSet2SongBitmap();
static bool EnabledIfSet2GlobalBGAnimation();
static bool EnabledIfSet2GlobalMovie();
static bool EnabledIfSet2GlobalMovieSongGroup();
static bool EnabledIfSet2GlobalMovieSongGroupAndGenre();
static MenuDef g_BackgroundChange(
"ScreenMiniMenuBackgroundChange",
MenuRowDef(ScreenEdit::layer,
"Layer",
false,
EditMode_Full, true, false, 0, "" ),
MenuRowDef(ScreenEdit::rate,
"Rate",
true,
EditMode_Full, true, false, 10, "0%","10%","20%","30%","40%","50%",
"60%","70%","80%","90%","100%","120%","140%","160%","180%","200%",
"220%","240%","260%","280%","300%","350%","400%" ),
MenuRowDef(ScreenEdit::transition,
"Force Transition",
true,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::effect,
"Force Effect",
true,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::color1,
"Force Color 1",
true,
EditMode_Full, true, false, 0, "-","#FFFFFF","#808080","#FFFFFF80",
"#000000","#FF0000","#00FF00","#0000FF","#FFFF00","#00FFFF","#FF00FF" ),
MenuRowDef(ScreenEdit::color2,
"Force Color 2",
true,
EditMode_Full, true, false, 0, "-","#FFFFFF","#808080","#FFFFFF80",
"#000000","#FF0000","#00FF00","#0000FF","#FFFF00","#00FFFF","#FF00FF" ),
MenuRowDef(ScreenEdit::file1_type,
"File1 Type",
true,
EditMode_Full, true, true, 0, "Song BGAnimation", "Song Movie",
"Song Bitmap", "Global BGAnimation", "Global Movie",
"Global Movie from Song Group", "Global Movie from Song Group and Genre",
"Dynamic Random", "Baked Random", "None" ),
MenuRowDef(ScreenEdit::file1_song_bganimation,
"File1 Song BGAnimation",
EnabledIfSet1SongBGAnimation,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file1_song_movie,
"File1 Song Movie",
EnabledIfSet1SongMovie,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file1_song_still,
"File1 Song Still",
EnabledIfSet1SongBitmap,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file1_global_bganimation,
"File1 Global BGAnimation",
EnabledIfSet1GlobalBGAnimation,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file1_global_movie,
"File1 Global Movie",
EnabledIfSet1GlobalMovie,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file1_global_movie_song_group,
"File1 Global Movie (Group)",
EnabledIfSet1GlobalMovieSongGroup,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file1_global_movie_song_group_and_genre,
"File1 Global Movie (Group + Genre)",
EnabledIfSet1GlobalMovieSongGroupAndGenre,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_type,
"File2 Type",
true,
EditMode_Full, true, true, 0, "Song BGAnimation", "Song Movie",
"Song Bitmap", "Global BGAnimation", "Global Movie",
"Global Movie from Song Group", "Global Movie from Song Group and Genre",
"Dynamic Random", "Baked Random", "None" ),
MenuRowDef(ScreenEdit::file2_song_bganimation,
"File2 Song BGAnimation",
EnabledIfSet2SongBGAnimation,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_song_movie,
"File2 Song Movie",
EnabledIfSet2SongMovie,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_song_still,
"File2 Song Still",
EnabledIfSet2SongBitmap,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_global_bganimation,
"File2 Global BGAnimation",
EnabledIfSet2GlobalBGAnimation,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_global_movie,
"File2 Global Movie",
EnabledIfSet2GlobalMovie,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_global_movie_song_group,
"File2 Global Movie (Group)",
EnabledIfSet2GlobalMovieSongGroup,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::file2_global_movie_song_group_and_genre,
"File2 Global Movie (Group + Genre)",
EnabledIfSet2GlobalMovieSongGroupAndGenre,
EditMode_Full, true, false, 0, NULL ),
MenuRowDef(ScreenEdit::delete_change,
"Remove Change",
true,
EditMode_Full, true, true, 0, NULL )
);
static bool EnabledIfSet1SongBGAnimation() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == song_bganimation && !g_BackgroundChange.rows[ScreenEdit::file1_song_bganimation].choices.empty(); }
static bool EnabledIfSet1SongMovie() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == song_movie && !g_BackgroundChange.rows[ScreenEdit::file1_song_movie].choices.empty(); }
static bool EnabledIfSet1SongBitmap() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == song_bitmap && !g_BackgroundChange.rows[ScreenEdit::file1_song_still].choices.empty(); }
static bool EnabledIfSet1GlobalBGAnimation() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_bganimation && !g_BackgroundChange.rows[ScreenEdit::file1_global_bganimation].choices.empty(); }
static bool EnabledIfSet1GlobalMovie() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_movie && !g_BackgroundChange.rows[ScreenEdit::file1_global_movie].choices.empty(); }
static bool EnabledIfSet1GlobalMovieSongGroup() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_movie_song_group && !g_BackgroundChange.rows[ScreenEdit::file1_global_movie_song_group].choices.empty(); }
static bool EnabledIfSet1GlobalMovieSongGroupAndGenre() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_movie_song_group_and_genre && !g_BackgroundChange.rows[ScreenEdit::file1_global_movie_song_group_and_genre].choices.empty(); }
static bool EnabledIfSet2SongBGAnimation() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == song_bganimation && !g_BackgroundChange.rows[ScreenEdit::file2_song_bganimation].choices.empty(); }
static bool EnabledIfSet2SongMovie() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == song_movie && !g_BackgroundChange.rows[ScreenEdit::file2_song_movie].choices.empty(); }
static bool EnabledIfSet2SongBitmap() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == song_bitmap && !g_BackgroundChange.rows[ScreenEdit::file2_song_still].choices.empty(); }
static bool EnabledIfSet2GlobalBGAnimation() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_bganimation && !g_BackgroundChange.rows[ScreenEdit::file2_global_bganimation].choices.empty(); }
static bool EnabledIfSet2GlobalMovie() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_movie && !g_BackgroundChange.rows[ScreenEdit::file2_global_movie].choices.empty(); }
static bool EnabledIfSet2GlobalMovieSongGroup() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_movie_song_group && !g_BackgroundChange.rows[ScreenEdit::file2_global_movie_song_group].choices.empty(); }
static bool EnabledIfSet2GlobalMovieSongGroupAndGenre() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_movie_song_group_and_genre && !g_BackgroundChange.rows[ScreenEdit::file2_global_movie_song_group_and_genre].choices.empty(); }
static RString GetOneBakedRandomFile( Song *pSong, bool bTryGenre = true )
{
vector<RString> vsPaths;
vector<RString> vsNames;
BackgroundUtil::GetGlobalRandomMovies(
pSong,
"",
vsPaths,
vsNames,
bTryGenre );
return vsNames.empty() ? RString() : vsNames[RandomInt(vsNames.size())];
}
static MenuDef g_InsertTapAttack(
"ScreenMiniMenuInsertTapAttack",
MenuRowDef(-1,
"Duration seconds",
true, EditMode_Practice, true, false, 3, "5","10","15","20","25","30","35","40","45" ), // TODO: Replace
MenuRowDef(-1,
"Set modifiers",
true, EditMode_Practice, true, true, 0, "Press Start" )
);
static MenuDef g_InsertCourseAttack(
"ScreenMiniMenuInsertCourseAttack",
MenuRowDef(ScreenEdit::duration,
"Duration seconds",
true, EditMode_Practice, true, false, 3, "5","10","15","20","25","30","35","40","45" ),
MenuRowDef(ScreenEdit::set_mods,
"Set modifiers",
true, EditMode_Practice, true, true, 0, "Press Start" ),
MenuRowDef(ScreenEdit::remove,
"Remove",
true, EditMode_Practice, true, true, 0, "Press Start" )
);
static MenuDef g_InsertStepAttack(
"ScreenMiniMenuInsertCourseAttack",
MenuRowDef(ScreenEdit::sa_duration,
"Duration seconds",
true, EditMode_Practice, true, false, 3, "5","10","15","20","25","30","35","40","45" ),
MenuRowDef(ScreenEdit::sa_set_mods,
"Set modifiers",
true, EditMode_Practice, true, true, 0, "Press Start" ),
MenuRowDef(ScreenEdit::sa_remove,
"Remove",
true, EditMode_Practice, true, true, 0, "Press Start" ));
static MenuDef g_CourseMode(
"ScreenMiniMenuCourseDisplay",
MenuRowDef( -1, "Play mods from course", true, EditMode_Practice, true, false, 0, NULL )
);
// HACK: need to remember the track we're inserting on so that we can lay the
// attack note after coming back from menus.
static int g_iLastInsertTapAttackTrack = -1;
static float g_fLastInsertAttackDurationSeconds = -1;
static float g_fLastInsertAttackPositionSeconds = -1;
static BackgroundLayer g_CurrentBGChangeLayer = BACKGROUND_LAYER_Invalid;
static void SetDefaultEditorNoteSkin( size_t num, RString &sNameOut, RString &defaultValueOut )
{
sNameOut = ssprintf( "EditorNoteSkinP%d", int(num + 1) );
// XXX: We need more supported noteskins.
defaultValueOut = "default";
}
static Preference1D<RString> EDITOR_NOTE_SKINS( SetDefaultEditorNoteSkin, NUM_PLAYERS );
static ThemeMetric<RString> EDIT_MODIFIERS ("ScreenEdit","EditModifiers");
static ThemeMetric<bool> LOOP_ON_CHART_END ("ScreenEdit","LoopOnChartEnd");
REGISTER_SCREEN_CLASS( ScreenEdit );
void ScreenEdit::Init()
{
m_pSoundMusic = NULL;
GAMESTATE->m_bIsUsingStepTiming = false;
GAMESTATE->m_bInStepEditor = true;
SubscribeToMessage( "Judgment" );
ASSERT( GAMESTATE->m_pCurSong != NULL );
ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != NULL );
EDIT_MODE.Load( m_sName, "EditMode" );
ScreenWithMenuElements::Init();
InitEditMappings();
currentCycleSegment = SEGMENT_LABEL;
// save the originals for reverting later
CopyToLastSave();
m_CurrentAction = MAIN_MENU_CHOICE_INVALID;
m_InputPlayerNumber = PLAYER_INVALID;
if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_TwoPlayersSharedSides )
m_InputPlayerNumber = PLAYER_1;
FOREACH_PlayerNumber( p )
GAMESTATE->m_bSideIsJoined[p] = false;
GAMESTATE->m_bSideIsJoined[PLAYER_1] = true;
m_pSong = GAMESTATE->m_pCurSong;
m_pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
/* The user will most likely switch into Step Timing after laying down
some initial notes. It also throws off many people at first glance.
*/
if( m_pSong->IsStepsUsingDifferentTiming(m_pSteps) )
GAMESTATE->m_bIsUsingStepTiming = true;
m_bReturnToRecordMenuAfterPlay = false;
m_fBeatToReturnTo = 0;
m_selectedTap = TAP_ORIGINAL_TAP;
GAMESTATE->m_bGameplayLeadIn.Set( true );
GAMESTATE->m_EditMode = EDIT_MODE.GetValue();
SetBeat(0);
m_fTrailingBeat = 0;
m_iShiftAnchor = -1;
m_iStartPlayingAt = -1;
m_iStopPlayingAt = -1;
attackInProcess = -1;
modInProcess = -1;
this->AddChild( &m_Background );
m_SnapDisplay.SetXY( EDIT_X, PLAYER_Y_STANDARD );
m_SnapDisplay.Load();
// xxx: hardcoded command -aj
m_SnapDisplay.SetZoom( 0.5f );
this->AddChild( &m_SnapDisplay );
// We keep track of this bit of state so that when the user is in Edit/Sync Songs and makes a change to the NoteSkins,
// that change is "sticky" across multiple ScreenEdits. That is the way the rest of the options work.
// TODO: It would be cleaner to do this by making it possible to set an option in metrics.ini.
if( !GAMESTATE->m_bDidModeChangeNoteSkin )
{
GAMESTATE->m_bDidModeChangeNoteSkin = true;
FOREACH_PlayerNumber( pn )
{
const RString &sNoteSkin = EDITOR_NOTE_SKINS[pn].Get();
if( NOTESKIN->DoesNoteSkinExist( sNoteSkin ) )
PO_GROUP_ASSIGN( GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions,
ModsLevel_Preferred, m_sNoteSkin, sNoteSkin );
}
}
m_PlayerStateEdit.m_PlayerNumber = PLAYER_1;
// If we always go with the GAMESTATE NoteSkin, we will have fun effects
// like Vivid or Flat in the editor notefield. This is not conducive to
// productive editing.
// todo: We should allow certain noteskins (note-colored/rhythm) to be
// displayed. (Perhaps this should be a noteskin metric.) -aj
if( NOTESKIN->DoesNoteSkinExist( EDITOR_NOTE_SKINS[PLAYER_1].Get() ) )
{
PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, EDITOR_NOTE_SKINS[PLAYER_1].Get() );
}
else
{
PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetStage().m_sNoteSkin );
}
m_PlayerStateEdit.m_PlayerOptions.FromString( ModsLevel_Stage, EDIT_MODIFIERS );
this->originalPlayerOptions.FromString(ModsLevel_Stage, EDIT_MODIFIERS);
m_pSteps->GetNoteData( m_NoteDataEdit );
m_NoteFieldEdit.SetXY( EDIT_X, EDIT_Y );
m_NoteFieldEdit.SetZoom( 0.5f );
m_NoteFieldEdit.Init( &m_PlayerStateEdit, PLAYER_HEIGHT*2 );
m_NoteFieldEdit.Load( &m_NoteDataEdit, -240, 850 );
this->AddChild( &m_NoteFieldEdit );
m_NoteDataRecord.SetNumTracks( m_NoteDataEdit.GetNumTracks() );
m_NoteFieldRecord.SetXY( RECORD_X, RECORD_Y );
m_NoteFieldRecord.Init( GAMESTATE->m_pPlayerState[PLAYER_1], PLAYER_HEIGHT );
m_NoteFieldRecord.Load( &m_NoteDataRecord, -120, 425 );
this->AddChild( &m_NoteFieldRecord );
m_EditState = EditState_Invalid;
TransitionEditState( STATE_EDITING );
m_bRemoveNoteButtonDown = false;
m_Clipboard.SetNumTracks( m_NoteDataEdit.GetNumTracks() );
clipboardFullTiming = GAMESTATE->m_pCurSong->m_SongTiming; // always have a backup.
m_bHasUndo = false;
m_Undo.SetNumTracks( m_NoteDataEdit.GetNumTracks() );
m_bDirty = m_NoteDataEdit.IsEmpty(); // require the usage of saving if empty.
m_Player->Init( "Player", GAMESTATE->m_pPlayerState[PLAYER_1], NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
m_Player->CacheAllUsedNoteSkins();
GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = PC_HUMAN;
m_Player->SetXY( PLAYER_X, PLAYER_Y );
this->AddChild( m_Player );
this->AddChild( &m_Foreground );
m_textInputTips.SetName( "EditHelp" );
m_textInputTips.LoadFromFont( THEME->GetPathF("ScreenEdit","EditHelp") );
m_textInputTips.SetText( EDIT_HELP_TEXT );
LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textInputTips );
this->AddChild( &m_textInputTips );
m_textInfo.SetName( "Info" );
m_textInfo.LoadFromFont( THEME->GetPathF("ScreenEdit","Info") );
LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textInfo );
this->AddChild( &m_textInfo );
m_textPlayRecordHelp.SetName( "PlayRecordHelp" );
m_textPlayRecordHelp.LoadFromFont( THEME->GetPathF("ScreenEdit","PlayRecordHelp") );
m_textPlayRecordHelp.SetText( PLAY_RECORD_HELP_TEXT );
LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textPlayRecordHelp );
this->AddChild( &m_textPlayRecordHelp );
m_soundAddNote.Load( THEME->GetPathS("ScreenEdit","AddNote"), true );
m_soundRemoveNote.Load( THEME->GetPathS("ScreenEdit","RemoveNote"), true );
m_soundChangeLine.Load( THEME->GetPathS("ScreenEdit","line"), true );
m_soundChangeSnap.Load( THEME->GetPathS("ScreenEdit","snap"), true );
m_soundMarker.Load( THEME->GetPathS("ScreenEdit","marker"), true );
m_soundValueIncrease.Load( THEME->GetPathS("ScreenEdit","value increase"), true );
m_soundValueDecrease.Load( THEME->GetPathS("ScreenEdit","value decrease"), true );
m_soundSwitchPlayer.Load( THEME->GetPathS("ScreenEdit","switch player"), true );
m_soundSwitchTiming.Load( THEME->GetPathS("ScreenEdit","switch timing"), true );
m_soundSwitchSteps.Load( THEME->GetPathS("ScreenEdit","switch steps") );
m_soundSave.Load( THEME->GetPathS("ScreenEdit","save") );
m_GameplayAssist.Init();
m_AutoKeysounds.FinishLoading();
m_pSoundMusic = m_AutoKeysounds.GetSound();
this->HandleScreenMessage( SM_UpdateTextInfo );
m_bTextInfoNeedsUpdate = true;
SubscribeToMessage( Message_SongModified );
}
ScreenEdit::~ScreenEdit()
{
// UGLY: Don't delete the Song's steps.
m_SongLastSave.DetachSteps();
LOG->Trace( "ScreenEdit::~ScreenEdit()" );
m_pSoundMusic->StopPlaying();
// Go back to Step Timing on leave.
GAMESTATE->m_bIsUsingStepTiming = true;
// DEFINITELY reset the InStepEditor variable.
GAMESTATE->m_bInStepEditor = false;
}
void ScreenEdit::BeginScreen()
{
ScreenWithMenuElements::BeginScreen();
/* We do this ourself. */
SOUND->HandleSongTimer( false );
}
void ScreenEdit::EndScreen()
{
ScreenWithMenuElements::EndScreen();
SOUND->HandleSongTimer( true );
}
// play assist ticks
void ScreenEdit::PlayTicks()
{
if( m_EditState != STATE_PLAYING )
return;
m_GameplayAssist.PlayTicks( m_Player->GetNoteData(), m_Player->GetPlayerState() );
}
static ThemeMetric<float> FADE_IN_PREVIEW("ScreenEdit", "FadeInPreview");
static ThemeMetric<float> FADE_OUT_PREVIEW("ScreenEdit", "FadeOutPreview");
void ScreenEdit::PlayPreviewMusic()
{
SOUND->StopMusic();
SOUND->PlayMusic(
m_pSong->GetMusicPath(),
NULL,
false,
m_pSong->m_fMusicSampleStartSeconds,
m_pSong->m_fMusicSampleLengthSeconds,
FADE_IN_PREVIEW,
FADE_OUT_PREVIEW );
}
void ScreenEdit::MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu )
{
menu = *pDef;
menu.rows.clear();
vector<MenuRowDef> aRows;
FOREACH_CONST( MenuRowDef, pDef->rows, r )
{
// Don't add rows that aren't applicable to this edit mode.
if( EDIT_MODE >= r->emShowIn )
menu.rows.push_back( *r );
}
}
void ScreenEdit::EditMiniMenu( const MenuDef* pDef, ScreenMessage SM_SendOnOK, ScreenMessage SM_SendOnCancel )
{
// Reload options.
MenuDef menu("");
MakeFilteredMenuDef( pDef, menu );
ScreenMiniMenu::MiniMenu( &menu, SM_SendOnOK, SM_SendOnCancel );
}
void ScreenEdit::Update( float fDeltaTime )
{
m_PlayerStateEdit.Update( fDeltaTime );
if( m_pSoundMusic->IsPlaying() )
{
RageTimer tm;
const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm );
GAMESTATE->UpdateSongPosition( fSeconds, GAMESTATE->m_pCurSong->m_SongTiming, tm, true );
}
if( m_EditState == STATE_RECORDING )
{
// TODO: Find a way to prevent STATE_RECORDING when in Song Timing.
for( int t=0; t<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; t++ ) // for each track
{
GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( t, PLAYER_1 );
float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI );
fSecsHeld = min( fSecsHeld, m_RemoveNoteButtonLastChanged.Ago() );
if( fSecsHeld == 0 )
continue;
// TODO: Currently, this will ignore a hold note that's
// held past the end of selection. Ideally the hold
// note should be created, ending right at the end of
// selection (or appropriate quantization).
float fStartPlayingAtBeat = NoteRowToBeat(m_iStartPlayingAt);
float fStopPlayingAtBeat = NoteRowToBeat(m_iStopPlayingAt);
if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat <= fStartPlayingAtBeat ||
GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat >= fStopPlayingAtBeat )
continue;
float fStartedHoldingSeconds = m_pSoundMusic->GetPositionSeconds() - fSecsHeld;
float fStartBeat = max( fStartPlayingAtBeat, m_pSteps->GetTimingData()->GetBeatFromElapsedTime(fStartedHoldingSeconds) );
float fEndBeat = max( fStartBeat, GetBeat() );
fEndBeat = min( fEndBeat, fStopPlayingAtBeat );
// Round start and end to the nearest snap interval
fStartBeat = Quantize( fStartBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );
fEndBeat = Quantize( fEndBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );
if( m_bRemoveNoteButtonDown )
{
m_NoteDataRecord.ClearRangeForTrack( BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), t );
}
else if( fSecsHeld > RECORD_HOLD_SECONDS )
{
// create or extend a hold or roll note
TapNote tn = EditIsBeingPressed(EDIT_BUTTON_LAY_ROLL) ? TAP_ORIGINAL_ROLL_HEAD: TAP_ORIGINAL_HOLD_HEAD;
tn.pn = m_InputPlayerNumber;
m_NoteDataRecord.AddHoldNote( t, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), tn );
}
}
}
//
// check for end of playback/record
//
if( m_EditState == STATE_RECORDING || m_EditState == STATE_PLAYING )
{
/*
* If any arrow is being held, continue for up to half a second after
* the end marker. This makes it possible to start a hold note near
* the end of the range. We won't allow placing anything outside of the
* range.
*/
bool bButtonIsBeingPressed = false;
for( int t=0; t<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; t++ ) // for each track
{
GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( t, PLAYER_1 );
if( INPUTMAPPER->IsBeingPressed(GameI) )
bButtonIsBeingPressed = true;
}
float fLastBeat = NoteRowToBeat(m_iStopPlayingAt);
if( bButtonIsBeingPressed && m_EditState == STATE_RECORDING )
{
float fSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( fLastBeat );
fLastBeat = m_pSteps->GetTimingData()->GetBeatFromElapsedTime( fSeconds + 0.5f );
}
float fStopAtSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( NoteRowToBeat(m_iStopPlayingAt) ) + 1;
if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fMusicSeconds > fStopAtSeconds )
{
TransitionEditState( ( LOOP_ON_CHART_END ? STATE_PLAYING : STATE_EDITING ) );
}
}
//LOG->Trace( "ScreenEdit::Update(%f)", fDeltaTime );
ScreenWithMenuElements::Update( fDeltaTime );
// Update trailing beat
float fDelta = GetBeat() - m_fTrailingBeat;
if( fabsf(fDelta) < 10 )
fapproach( m_fTrailingBeat, GetBeat(),
fDeltaTime*40 / m_NoteFieldEdit.GetPlayerState()->m_PlayerOptions.GetCurrent().m_fScrollSpeed );
else
fapproach( m_fTrailingBeat, GetBeat(),
fabsf(fDelta) * fDeltaTime*5 );
PlayTicks();
}
static vector<int> FindAllAttacksAtTime(const AttackArray& attacks, float fStartTime)
{
vector<int> ret;
for (unsigned i = 0; i < attacks.size(); ++i)
{
if (fabs(attacks[i].fStartSecond - fStartTime) < 0.001f)
{
ret.push_back(i);
}
}
return ret;
}
static int FindAttackAtTime( const AttackArray& attacks, float fStartTime )
{
for( unsigned i = 0; i < attacks.size(); ++i )
{
if( fabs(attacks[i].fStartSecond - fStartTime) < 0.001f )
return i;
}
return -1;
}
// TODO: Fix this mess. -Colby
static LocalizedString CURRENT_BEAT("ScreenEdit", "Current beat");
static LocalizedString CURRENT_SECOND("ScreenEdit", "Current second");
static LocalizedString SNAP_TO("ScreenEdit", "Snap to");
static LocalizedString NOTES("ScreenEdit", "%s notes");
static LocalizedString SELECTION_BEAT("ScreenEdit", "Selection beat");
static LocalizedString DIFFICULTY("ScreenEdit", "Difficulty");
static LocalizedString ROUTINE_PLAYER("ScreenEdit", "Routine Player");
static LocalizedString CHART_NAME("ScreenEdit", "Chart Name");
static LocalizedString DESCRIPTION("ScreenEdit", "Description");
static LocalizedString CHART_STYLE("ScreenEdit", "Chart Style");
static LocalizedString STEP_AUTHOR("ScreenEdit", "Step Author");
static LocalizedString MAIN_TITLE("ScreenEdit", "Main title");
static LocalizedString SUBTITLE("ScreenEdit", "Subtitle");
static LocalizedString TAP_NOTE_TYPE("ScreenEdit", "Tap Note");
static LocalizedString SEGMENT_TYPE("ScreenEdit", "Segment");
static LocalizedString TAP_STEPS("ScreenEdit", "Tap Steps");
static LocalizedString JUMPS("ScreenEdit", "Jumps");
static LocalizedString HANDS("ScreenEdit", "Hands");
static LocalizedString HOLDS("ScreenEdit", "Holds");
static LocalizedString MINES("ScreenEdit", "Mines");
static LocalizedString ROLLS("ScreenEdit", "Rolls");
static LocalizedString LIFTS("ScreenEdit", "Lifts");
static LocalizedString FAKES("ScreenEdit", "Fakes");
static LocalizedString TIMING_MODE("ScreenEdit", "Timing Mode");
static LocalizedString STEP_TIMING("ScreenEdit", "Step Timing");
static LocalizedString SONG_TIMING("ScreenEdit", "Song Timing");
static LocalizedString BEAT_0_OFFSET("ScreenEdit", "Beat 0 offset");
static LocalizedString PREVIEW_START("ScreenEdit", "Preview Start");
static LocalizedString PREVIEW_LENGTH("ScreenEdit", "Preview Length");
static ThemeMetric<RString> CURRENT_BEAT_FORMAT("ScreenEdit", "CurrentBeatFormat");
static ThemeMetric<RString> CURRENT_SECOND_FORMAT("ScreenEdit", "CurrentSecondFormat");
static ThemeMetric<RString> SNAP_TO_FORMAT("ScreenEdit", "SnapToFormat");
static ThemeMetric<RString> SELECTION_BEAT_BEGIN_FORMAT("ScreenEdit", "SelectionBeatBeginFormat");
static ThemeMetric<RString> SELECTION_BEAT_UNFINISHED_FORMAT("ScreenEdit", "SelectionBeatUnfinishedFormat");
static ThemeMetric<RString> SELECTION_BEAT_END_FORMAT("ScreenEdit", "SelectionBeatEndFormat");
static ThemeMetric<RString> DIFFICULTY_FORMAT("ScreenEdit", "DifficultyFormat");
static ThemeMetric<RString> ROUTINE_PLAYER_FORMAT("ScreenEdit", "RoutinePlayerFormat");
static ThemeMetric<RString> CHART_NAME_FORMAT("ScreenEdit", "ChartNameFormat");
static ThemeMetric<RString> DESCRIPTION_FORMAT("ScreenEdit", "DescriptionFormat");
static ThemeMetric<RString> CHART_STYLE_FORMAT("ScreenEdit", "ChartStyleFormat");
static ThemeMetric<RString> STEP_AUTHOR_FORMAT("ScreenEdit", "StepAuthorFormat");
static ThemeMetric<RString> MAIN_TITLE_FORMAT("ScreenEdit", "MainTitleFormat");
static ThemeMetric<RString> SUBTITLE_FORMAT("ScreenEdit", "SubtitleFormat");
static ThemeMetric<RString> TAP_NOTE_TYPE_FORMAT("ScreenEdit", "TapNoteTypeFormat");
static ThemeMetric<RString> SEGMENT_TYPE_FORMAT("ScreenEdit", "SegmentTypeFormat");
static ThemeMetric<RString> NUM_STEPS_FORMAT("ScreenEdit", "NumStepsFormat");
static ThemeMetric<RString> NUM_JUMPS_FORMAT("ScreenEdit", "NumJumpsFormat");
static ThemeMetric<RString> NUM_HOLDS_FORMAT("ScreenEdit", "NumHoldsFormat");
static ThemeMetric<RString> NUM_MINES_FORMAT("ScreenEdit", "NumMinesFormat");
static ThemeMetric<RString> NUM_HANDS_FORMAT("ScreenEdit", "NumHandsFormat");
static ThemeMetric<RString> NUM_ROLLS_FORMAT("ScreenEdit", "NumRollsFormat");
static ThemeMetric<RString> NUM_LIFTS_FORMAT("ScreenEdit", "NumLiftsFormat");
static ThemeMetric<RString> NUM_FAKES_FORMAT("ScreenEdit", "NumFakesFormat");
static ThemeMetric<RString> NUM_STEPS_FORMAT_TWO_PLAYER("ScreenEdit", "NumStepsFormatTwoPlayer");
static ThemeMetric<RString> NUM_JUMPS_FORMAT_TWO_PLAYER("ScreenEdit", "NumJumpsFormatTwoPlayer");
static ThemeMetric<RString> NUM_HOLDS_FORMAT_TWO_PLAYER("ScreenEdit", "NumHoldsFormatTwoPlayer");
static ThemeMetric<RString> NUM_MINES_FORMAT_TWO_PLAYER("ScreenEdit", "NumMinesFormatTwoPlayer");
static ThemeMetric<RString> NUM_HANDS_FORMAT_TWO_PLAYER("ScreenEdit", "NumHandsFormatTwoPlayer");
static ThemeMetric<RString> NUM_ROLLS_FORMAT_TWO_PLAYER("ScreenEdit", "NumRollsFormatTwoPlayer");
static ThemeMetric<RString> NUM_LIFTS_FORMAT_TWO_PLAYER("ScreenEdit", "NumLiftsFormatTwoPlayer");
static ThemeMetric<RString> NUM_FAKES_FORMAT_TWO_PLAYER("ScreenEdit", "NumFakesFormatTwoPlayer");
static ThemeMetric<RString> TIMING_MODE_FORMAT("ScreenEdit", "TimingModeFormat");
static ThemeMetric<RString> BEAT_0_OFFSET_FORMAT("ScreenEdit", "Beat0OffsetFormat");
static ThemeMetric<RString> PREVIEW_START_FORMAT("ScreenEdit", "PreviewStartFormat");
static ThemeMetric<RString> PREVIEW_LENGTH_FORMAT("ScreenEdit", "PreviewLengthFormat");
void ScreenEdit::UpdateTextInfo()
{
if( m_pSteps == NULL )
return;
// Don't update the text during playback or record. It causes skips.
if( m_EditState != STATE_EDITING )
return;
if( !m_bTextInfoNeedsUpdate )
return;
m_bTextInfoNeedsUpdate = false;
RString sNoteType = ssprintf( NOTES.GetValue(), NoteTypeToLocalizedString(m_SnapDisplay.GetNoteType()).c_str() );
RString sText;
sText += ssprintf( CURRENT_BEAT_FORMAT.GetValue(), CURRENT_BEAT.GetValue().c_str(), GetBeat() );
sText += ssprintf( CURRENT_SECOND_FORMAT.GetValue(), CURRENT_SECOND.GetValue().c_str(), GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat()) );
switch( EDIT_MODE.GetValue() )
{
DEFAULT_FAIL( EDIT_MODE.GetValue() );
case EditMode_Practice:
break;
case EditMode_CourseMods:
case EditMode_Home:
case EditMode_Full:
sText += ssprintf( SNAP_TO_FORMAT.GetValue(), SNAP_TO.GetValue().c_str(), sNoteType.c_str() );
break;
}
if( m_NoteFieldEdit.m_iBeginMarker != -1 )
{
sText += ssprintf( SELECTION_BEAT_BEGIN_FORMAT.GetValue(), SELECTION_BEAT.GetValue().c_str(), NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
if( m_NoteFieldEdit.m_iEndMarker != -1 )
sText += ssprintf( SELECTION_BEAT_END_FORMAT.GetValue(), NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
else
sText += SELECTION_BEAT_UNFINISHED_FORMAT;
}
if (EDIT_MODE.GetValue() == EditMode_Full)
{
sText += ssprintf( DIFFICULTY_FORMAT.GetValue(), DIFFICULTY.GetValue().c_str(), DifficultyToString( m_pSteps->GetDifficulty() ).c_str() );
if ( m_InputPlayerNumber != PLAYER_INVALID )
sText += ssprintf( ROUTINE_PLAYER_FORMAT.GetValue(), ROUTINE_PLAYER.GetValue().c_str(), m_InputPlayerNumber + 1 );
//sText += ssprintf( DESCRIPTION_FORMAT.GetValue(), DESCRIPTION.GetValue().c_str(), m_pSteps->GetDescription().c_str() );
sText += ssprintf( CHART_NAME_FORMAT.GetValue(), CHART_NAME.GetValue().c_str(), m_pSteps->GetChartName().c_str() );
sText += ssprintf( STEP_AUTHOR_FORMAT.GetValue(), STEP_AUTHOR.GetValue().c_str(), m_pSteps->GetCredit().c_str() );
//sText += ssprintf( CHART_STYLE_FORMAT.GetValue(), CHART_STYLE.GetValue().c_str(), m_pSteps->GetChartStyle().c_str() );
sText += ssprintf( MAIN_TITLE_FORMAT.GetValue(), MAIN_TITLE.GetValue().c_str(), m_pSong->m_sMainTitle.c_str() );
if( m_pSong->m_sSubTitle.size() )
sText += ssprintf( SUBTITLE_FORMAT.GetValue(), SUBTITLE.GetValue().c_str(), m_pSong->m_sSubTitle.c_str() );
sText += ssprintf( SEGMENT_TYPE_FORMAT.GetValue(), SEGMENT_TYPE.GetValue().c_str(), TimingSegmentTypeToString(currentCycleSegment).c_str() );
const RString tapnoteType = TapNoteTypeToString( m_selectedTap.type );
sText += ssprintf( TAP_NOTE_TYPE_FORMAT.GetValue(), TAP_NOTE_TYPE.GetValue().c_str(), tapnoteType.c_str() );
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
float beat = GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat());
sText += ssprintf("Attack here?: %s\n", FindAttackAtTime(attacks, beat) > -1 ? "YES" : "NO");
}
GAMESTATE->SetProcessedTimingData(m_pSteps->GetTimingData());
const StepsTypeCategory &cat = GAMEMAN->GetStepsTypeInfo(m_pSteps->m_StepsType).m_StepsTypeCategory;
if (cat == StepsTypeCategory_Couple || cat == StepsTypeCategory_Routine)
{
pair<int, int> tmp = m_NoteDataEdit.GetNumTapNotesTwoPlayer();
sText += ssprintf(NUM_STEPS_FORMAT_TWO_PLAYER.GetValue(),
TAP_STEPS.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumJumpsTwoPlayer();
sText += ssprintf(NUM_JUMPS_FORMAT_TWO_PLAYER.GetValue(),
JUMPS.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumHandsTwoPlayer();
sText += ssprintf(NUM_HANDS_FORMAT_TWO_PLAYER.GetValue(),
HANDS.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumHoldNotesTwoPlayer();
sText += ssprintf(NUM_HOLDS_FORMAT_TWO_PLAYER.GetValue(),
HOLDS.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumMinesTwoPlayer();
sText += ssprintf(NUM_MINES_FORMAT_TWO_PLAYER.GetValue(),
MINES.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumRollsTwoPlayer();
sText += ssprintf(NUM_ROLLS_FORMAT_TWO_PLAYER.GetValue(),
ROLLS.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumLiftsTwoPlayer();
sText += ssprintf(NUM_LIFTS_FORMAT_TWO_PLAYER.GetValue(),
LIFTS.GetValue().c_str(),
tmp.first, tmp.second);
tmp = m_NoteDataEdit.GetNumFakesTwoPlayer();
sText += ssprintf(NUM_FAKES_FORMAT_TWO_PLAYER.GetValue(),
FAKES.GetValue().c_str(),
tmp.first, tmp.second);
}
else
{
sText += ssprintf( NUM_STEPS_FORMAT.GetValue(), TAP_STEPS.GetValue().c_str(), m_NoteDataEdit.GetNumTapNotes() );
sText += ssprintf( NUM_JUMPS_FORMAT.GetValue(), JUMPS.GetValue().c_str(), m_NoteDataEdit.GetNumJumps() );
sText += ssprintf( NUM_HANDS_FORMAT.GetValue(), HANDS.GetValue().c_str(), m_NoteDataEdit.GetNumHands() );
sText += ssprintf( NUM_HOLDS_FORMAT.GetValue(), HOLDS.GetValue().c_str(), m_NoteDataEdit.GetNumHoldNotes() );
sText += ssprintf( NUM_MINES_FORMAT.GetValue(), MINES.GetValue().c_str(), m_NoteDataEdit.GetNumMines() );
sText += ssprintf( NUM_ROLLS_FORMAT.GetValue(), ROLLS.GetValue().c_str(), m_NoteDataEdit.GetNumRolls() );
sText += ssprintf( NUM_LIFTS_FORMAT.GetValue(), LIFTS.GetValue().c_str(), m_NoteDataEdit.GetNumLifts() );
sText += ssprintf( NUM_FAKES_FORMAT.GetValue(), FAKES.GetValue().c_str(), m_NoteDataEdit.GetNumFakes() );
}
switch( EDIT_MODE.GetValue() )
{
DEFAULT_FAIL( EDIT_MODE.GetValue() );
case EditMode_Practice:
case EditMode_CourseMods:
case EditMode_Home:
break;
case EditMode_Full:
sText += ssprintf( TIMING_MODE_FORMAT.GetValue(),
TIMING_MODE.GetValue().c_str(),
( GAMESTATE->m_bIsUsingStepTiming ?
STEP_TIMING.GetValue().c_str() :
SONG_TIMING.GetValue().c_str() ) );
sText += ssprintf( BEAT_0_OFFSET_FORMAT.GetValue(),
BEAT_0_OFFSET.GetValue().c_str(),
GetAppropriateTiming().m_fBeat0OffsetInSeconds );
sText += ssprintf( PREVIEW_START_FORMAT.GetValue(), PREVIEW_START.GetValue().c_str(), m_pSong->m_fMusicSampleStartSeconds );
sText += ssprintf( PREVIEW_LENGTH_FORMAT.GetValue(), PREVIEW_LENGTH.GetValue().c_str(), m_pSong->m_fMusicSampleLengthSeconds );
break;
}
m_textInfo.SetText( sText );
GAMESTATE->SetProcessedTimingData(NULL);
}
void ScreenEdit::DrawPrimitives()
{
if( m_pSoundMusic->IsPlaying() )
{
ScreenWithMenuElements::DrawPrimitives();
return;
}
// HACK: Draw using the trailing beat
PlayerState *pPlayerState = const_cast<PlayerState *> (m_NoteFieldEdit.GetPlayerState());
float fPlayerSongBeat = pPlayerState->m_Position.m_fSongBeat; // save song beat
float fPlayerSongBeatNoOffset = pPlayerState->m_Position.m_fSongBeatNoOffset;
float fPlayerSongBeatVisible = pPlayerState->m_Position.m_fSongBeatVisible;
float fGameSongBeat = GAMESTATE->m_Position.m_fSongBeat; // save song beat
float fGameSongBeatNoOffset = GAMESTATE->m_Position.m_fSongBeatNoOffset;
float fGameSongBeatVisible = GAMESTATE->m_Position.m_fSongBeatVisible;
pPlayerState->m_Position.m_fSongBeat = m_fTrailingBeat; // put trailing beat in effect
pPlayerState->m_Position.m_fSongBeatNoOffset = m_fTrailingBeat; // put trailing beat in effect
pPlayerState->m_Position.m_fSongBeatVisible = m_fTrailingBeat; // put trailing beat in effect
GAMESTATE->m_Position.m_fSongBeat = m_fTrailingBeat; // put trailing beat in effect
GAMESTATE->m_Position.m_fSongBeatNoOffset = m_fTrailingBeat; // put trailing beat in effect
GAMESTATE->m_Position.m_fSongBeatVisible = m_fTrailingBeat; // put trailing beat in effect
ScreenWithMenuElements::DrawPrimitives();
pPlayerState->m_Position.m_fSongBeat = fPlayerSongBeat; // restore real song beat
pPlayerState->m_Position.m_fSongBeatNoOffset = fPlayerSongBeatNoOffset;
pPlayerState->m_Position.m_fSongBeatVisible = fPlayerSongBeatVisible;
GAMESTATE->m_Position.m_fSongBeat = fGameSongBeat; // restore real song beat
GAMESTATE->m_Position.m_fSongBeatNoOffset = fGameSongBeatNoOffset;
GAMESTATE->m_Position.m_fSongBeatVisible = fGameSongBeatVisible;
}
bool ScreenEdit::Input( const InputEventPlus &input )
{
// LOG->Trace( "ScreenEdit::Input()" );
// invalidate input if cmd/meta is being held.
if( INPUTFILTER->IsBeingPressed(DeviceInput(DEVICE_KEYBOARD, KEY_LMETA)) ||
INPUTFILTER->IsBeingPressed(DeviceInput(DEVICE_KEYBOARD, KEY_RMETA)) )
return false;
if( m_In.IsTransitioning() || m_Out.IsTransitioning() )
return false;
EditButton EditB = DeviceToEdit( input.DeviceI );
if( EditB == EditButton_Invalid )
EditB = MenuButtonToEditButton( input.MenuI );
if( EditB == EDIT_BUTTON_REMOVE_NOTE )
{
/* Ugly: we need to know when the button was pressed or released, so we
* can clamp operations to that time. Should InputFilter keep track of
* last release, too? */
m_bRemoveNoteButtonDown = (input.type != IET_RELEASE);
m_RemoveNoteButtonLastChanged.Touch();
}
switch( m_EditState )
{
DEFAULT_FAIL( m_EditState );
case STATE_EDITING:
m_bTextInfoNeedsUpdate = true;
return InputEdit( input, EditB );
case STATE_RECORDING:
return InputRecord( input, EditB );
case STATE_RECORDING_PAUSED:
return InputRecordPaused( input, EditB );
case STATE_PLAYING:
return InputPlay( input, EditB );
}
}
static void ShiftToRightSide( int &iCol, int iNumTracks )
{
switch( GAMESTATE->GetCurrentStyle()->m_StyleType )
{
DEFAULT_FAIL( GAMESTATE->GetCurrentStyle()->m_StyleType );
case StyleType_OnePlayerOneSide:
break;
case StyleType_TwoPlayersTwoSides:
case StyleType_OnePlayerTwoSides:
case StyleType_TwoPlayersSharedSides:
iCol += iNumTracks/2;
break;
}
}
static LocalizedString BG_CHANGE_STEP_TIMING ( "ScreenEdit", "You must be in Song Timing Mode to edit BG Changes." );
static LocalizedString ALTER_MENU_NO_SELECTION ( "ScreenEdit", "You must have an area selected to enter the Alter Menu." );
static LocalizedString SWITCHED_TO ( "ScreenEdit", "Switched to" );
static LocalizedString NO_BACKGROUNDS_AVAILABLE ( "ScreenEdit", "No backgrounds available" );
static ThemeMetric<bool> INVERT_SCROLL_BUTTONS ( "ScreenEdit", "InvertScrollSpeedButtons" );
bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB )
{
if( input.type == IET_RELEASE )
{
if( EditPressed( EDIT_BUTTON_SCROLL_SELECT, input.DeviceI ) )
m_iShiftAnchor = -1;
// XXX Key releases usually don't count as "handled," but what
// does it mean in this case?
return false;
}
const TimingData &sTiming = GetAppropriateTiming();
float playerBeat = GetAppropriatePosition().m_fSongBeat;
int beatsPerMeasure = sTiming.GetTimeSignatureSegmentAtBeat( playerBeat )->GetNum();
switch( EditB )
{
case EDIT_BUTTON_COLUMN_0:
case EDIT_BUTTON_COLUMN_1:
case EDIT_BUTTON_COLUMN_2:
case EDIT_BUTTON_COLUMN_3:
case EDIT_BUTTON_COLUMN_4:
case EDIT_BUTTON_COLUMN_5:
case EDIT_BUTTON_COLUMN_6:
case EDIT_BUTTON_COLUMN_7:
case EDIT_BUTTON_COLUMN_8:
case EDIT_BUTTON_COLUMN_9:
{
if( input.type != IET_FIRST_PRESS )
return false; // We only care about first presses
int iCol = EditB - EDIT_BUTTON_COLUMN_0;
// Alt + number = input to right half
if( EditIsBeingPressed(EDIT_BUTTON_RIGHT_SIDE) )
ShiftToRightSide( iCol, m_NoteDataEdit.GetNumTracks() );
if( iCol >= m_NoteDataEdit.GetNumTracks() )
return false; // this button is not in the range of columns for this Style
const float fSongBeat = GetBeat();
const int iSongIndex = BeatToNoteRow( fSongBeat );
// check for to see if the user intended to remove a HoldNote
int iHeadRow;
if( m_NoteDataEdit.IsHoldNoteAtRow( iCol, iSongIndex, &iHeadRow ) )
{
m_soundRemoveNote.Play();
SetDirty( true );
SaveUndo();
m_NoteDataEdit.SetTapNote( iCol, iHeadRow, TAP_EMPTY );
// Don't CheckNumberOfNotesAndUndo. We don't want to revert any change that removes notes.
}
else if( m_NoteDataEdit.GetTapNote(iCol, iSongIndex).type != TapNote::empty )
{
m_soundRemoveNote.Play();
SetDirty( true );
SaveUndo();
m_NoteDataEdit.SetTapNote( iCol, iSongIndex, TAP_EMPTY );
// Don't CheckNumberOfNotesAndUndo. We don't want to revert any change that removes notes.
}
else if( EditIsBeingPressed(EDIT_BUTTON_LAY_TAP_ATTACK) )
{
g_iLastInsertTapAttackTrack = iCol;
EditMiniMenu( &g_InsertTapAttack, SM_BackFromInsertTapAttack );
}
else
{
m_soundAddNote.Play();
SetDirty( true );
SaveUndo();
TapNote tn = m_selectedTap;
tn.pn = m_InputPlayerNumber;
m_NoteDataEdit.SetTapNote(iCol, iSongIndex, tn );
CheckNumberOfNotesAndUndo();
}
}
return true;
case EDIT_BUTTON_CYCLE_TAP_LEFT:
{
switch ( m_selectedTap.type )
{
case TapNote::tap: m_selectedTap = TAP_ORIGINAL_FAKE; break;
case TapNote::mine: m_selectedTap = TAP_ORIGINAL_TAP; break;
case TapNote::lift: m_selectedTap = TAP_ORIGINAL_MINE; break;
case TapNote::fake: m_selectedTap = TAP_ORIGINAL_LIFT; break;
DEFAULT_FAIL( m_selectedTap.type );
}
return true;
}
case EDIT_BUTTON_CYCLE_TAP_RIGHT:
{
switch ( m_selectedTap.type )
{
case TapNote::tap: m_selectedTap = TAP_ORIGINAL_MINE; break;
case TapNote::mine: m_selectedTap = TAP_ORIGINAL_LIFT; break;
case TapNote::lift: m_selectedTap = TAP_ORIGINAL_FAKE; break;
case TapNote::fake: m_selectedTap = TAP_ORIGINAL_TAP; break;
DEFAULT_FAIL( m_selectedTap.type );
}
return true;
}
case EDIT_BUTTON_CYCLE_SEGMENT_LEFT:
{
int tmp = enum_add2( this->currentCycleSegment, -1 );
wrap( *ConvertValue<int>(&tmp), NUM_TimingSegmentType );
currentCycleSegment = (TimingSegmentType)tmp;
return true;
}
case EDIT_BUTTON_CYCLE_SEGMENT_RIGHT:
{
int tmp = enum_add2( this->currentCycleSegment, +1 );
wrap( *ConvertValue<int>(&tmp), NUM_TimingSegmentType );
currentCycleSegment = (TimingSegmentType)tmp;
return true;
}
case EDIT_BUTTON_SCROLL_SPEED_UP:
case EDIT_BUTTON_SCROLL_SPEED_DOWN:
{
PlayerState *pPlayerState = const_cast<PlayerState *> (m_NoteFieldEdit.GetPlayerState());
float fScrollSpeed = pPlayerState->m_PlayerOptions.GetSong().m_fScrollSpeed;
const float fSpeeds[] = { 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f };
int iSpeed = 0;
for( unsigned i = 0; i < ARRAYLEN(fSpeeds); ++i )
{
if( fSpeeds[i] == fScrollSpeed )
{
iSpeed = i;
break;
}
}
switch( EditB )
{
DEFAULT_FAIL(EditB);
case EDIT_BUTTON_SCROLL_SPEED_DOWN:
INVERT_SCROLL_BUTTONS ? ++iSpeed : --iSpeed;
break;
case EDIT_BUTTON_SCROLL_SPEED_UP:
INVERT_SCROLL_BUTTONS ? --iSpeed : ++iSpeed;
break;
}
iSpeed = clamp( iSpeed, 0, (int) ARRAYLEN(fSpeeds)-1 );
if( fSpeeds[iSpeed] != fScrollSpeed )
{
m_soundMarker.Play();
fScrollSpeed = fSpeeds[iSpeed];
}
PO_GROUP_ASSIGN( pPlayerState->m_PlayerOptions, ModsLevel_Song, m_fScrollSpeed, fScrollSpeed );
}
return true;
case EDIT_BUTTON_SCROLL_HOME:
ScrollTo( 0 );
return true;
case EDIT_BUTTON_SCROLL_END:
ScrollTo( m_NoteDataEdit.GetLastBeat() );
return true;
case EDIT_BUTTON_SCROLL_UP_LINE:
case EDIT_BUTTON_SCROLL_UP_PAGE:
case EDIT_BUTTON_SCROLL_UP_TS:
case EDIT_BUTTON_SCROLL_DOWN_LINE:
case EDIT_BUTTON_SCROLL_DOWN_PAGE:
case EDIT_BUTTON_SCROLL_DOWN_TS:
{
float fBeatsToMove=0.f;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_SCROLL_UP_LINE:
case EDIT_BUTTON_SCROLL_DOWN_LINE:
fBeatsToMove = NoteTypeToBeat( m_SnapDisplay.GetNoteType() );
if( EditB == EDIT_BUTTON_SCROLL_UP_LINE )
fBeatsToMove *= -1;
break;
case EDIT_BUTTON_SCROLL_UP_PAGE:
case EDIT_BUTTON_SCROLL_DOWN_PAGE:
fBeatsToMove = 4;
if( EditB == EDIT_BUTTON_SCROLL_UP_PAGE )
fBeatsToMove *= -1;
break;
case EDIT_BUTTON_SCROLL_UP_TS:
case EDIT_BUTTON_SCROLL_DOWN_TS:
fBeatsToMove = beatsPerMeasure;
if( EditB == EDIT_BUTTON_SCROLL_UP_TS )
fBeatsToMove *= -1;
break;
}
if( m_PlayerStateEdit.m_PlayerOptions.GetSong().m_fScrolls[PlayerOptions::SCROLL_REVERSE] > 0.5 )
fBeatsToMove *= -1;
float fDestinationBeat = GetBeat() + fBeatsToMove;
fDestinationBeat = Quantize( fDestinationBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );
ScrollTo( fDestinationBeat );
}
return true;
case EDIT_BUTTON_SCROLL_NEXT_MEASURE:
{
float fDestinationBeat = GetBeat() + beatsPerMeasure;
fDestinationBeat = ftruncf( fDestinationBeat, (float)beatsPerMeasure );
ScrollTo( fDestinationBeat );
return true;
}
case EDIT_BUTTON_SCROLL_PREV_MEASURE:
{
float fDestinationBeat = QuantizeUp( GetBeat(), (float)beatsPerMeasure );
fDestinationBeat -= (float)beatsPerMeasure;
ScrollTo( fDestinationBeat );
return true;
}
case EDIT_BUTTON_SCROLL_NEXT:
{
int iRow = BeatToNoteRow( GetBeat() );
NoteDataUtil::GetNextEditorPosition( m_NoteDataEdit, iRow );
ScrollTo( NoteRowToBeat(iRow) );
}
return true;
case EDIT_BUTTON_SCROLL_PREV:
{
int iRow = BeatToNoteRow( GetBeat() );
NoteDataUtil::GetPrevEditorPosition( m_NoteDataEdit, iRow );
ScrollTo( NoteRowToBeat(iRow) );
}
return true;
case EDIT_BUTTON_SEGMENT_NEXT:
{
// TODO: Work around Stops and Delays. We MAY have to separate them.
const TimingData &timing = GetAppropriateTiming();
ScrollTo(timing.GetNextSegmentBeatAtBeat(this->currentCycleSegment,
GetBeat()));
}
return true;
case EDIT_BUTTON_SEGMENT_PREV:
{
// TODO: Work around Stops and Delays. We MAY have to separate them.
const TimingData &timing = GetAppropriateTiming();
ScrollTo(timing.GetPreviousSegmentBeatAtBeat(this->currentCycleSegment,
GetBeat()));
}
return true;
case EDIT_BUTTON_SNAP_NEXT:
if( m_SnapDisplay.PrevSnapMode() )
OnSnapModeChange();
return true;
case EDIT_BUTTON_SNAP_PREV:
if( m_SnapDisplay.NextSnapMode() )
OnSnapModeChange();
return true;
case EDIT_BUTTON_LAY_SELECT:
{
const int iCurrentRow = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
if( m_NoteFieldEdit.m_iBeginMarker==-1 && m_NoteFieldEdit.m_iEndMarker==-1 )
{
// lay begin marker
m_NoteFieldEdit.m_iBeginMarker = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
}
else if( m_NoteFieldEdit.m_iEndMarker==-1 ) // only begin marker is laid
{
if( iCurrentRow == m_NoteFieldEdit.m_iBeginMarker )
{
m_NoteFieldEdit.m_iBeginMarker = -1;
}
else
{
m_NoteFieldEdit.m_iEndMarker = max( m_NoteFieldEdit.m_iBeginMarker, iCurrentRow );
m_NoteFieldEdit.m_iBeginMarker = min( m_NoteFieldEdit.m_iBeginMarker, iCurrentRow );
}
}
else // both markers are laid
{
m_NoteFieldEdit.m_iBeginMarker = iCurrentRow;
m_NoteFieldEdit.m_iEndMarker = -1;
}
m_soundMarker.Play();
}
return true;
case EDIT_BUTTON_OPEN_AREA_MENU:
{
// TODO: Improve behavior if timing changes are shifted down on beat 0.
g_AreaMenu.rows[shift_pauses_forward].bEnabled = (GetBeat() != 0);
g_AreaMenu.rows[paste_at_current_beat].bEnabled = !m_Clipboard.IsEmpty();
g_AreaMenu.rows[paste_at_begin_marker].bEnabled = !m_Clipboard.IsEmpty() != 0 && m_NoteFieldEdit.m_iBeginMarker!=-1;
g_AreaMenu.rows[undo].bEnabled = m_bHasUndo;
EditMiniMenu( &g_AreaMenu, SM_BackFromAreaMenu );
}
return true;
case EDIT_BUTTON_OPEN_ALTER_MENU:
{
bool bAreaSelected = m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1;
if (!bAreaSelected)
{
SCREENMAN->SystemMessage( ALTER_MENU_NO_SELECTION );
SCREENMAN->PlayInvalidSound();
}
else
{
// TODO: Improve interaction when field is 0.
g_AlterMenu.rows[convert_to_delay].bEnabled = (m_NoteFieldEdit.m_iBeginMarker > 0);
g_AlterMenu.rows[convert_to_pause].bEnabled = (m_NoteFieldEdit.m_iBeginMarker > 0);
bool isRoutine = (m_InputPlayerNumber != PLAYER_INVALID);
g_AlterMenu.rows[routine_invert_notes].bEnabled = isRoutine;
g_AlterMenu.rows[routine_mirror_1_to_2].bEnabled = isRoutine;
g_AlterMenu.rows[routine_mirror_2_to_1].bEnabled = isRoutine;
EditMiniMenu(&g_AlterMenu, SM_BackFromAlterMenu);
}
return true;
}
case EDIT_BUTTON_OPEN_EDIT_MENU:
EditMiniMenu( &g_MainMenu, SM_BackFromMainMenu );
return true;
case EDIT_BUTTON_OPEN_INPUT_HELP:
DoHelp();
return true;
case EDIT_BUTTON_OPEN_TIMING_MENU:
{
DisplayTimingMenu();
return true;
}
case EDIT_BUTTON_OPEN_NEXT_STEPS:
case EDIT_BUTTON_OPEN_PREV_STEPS:
{
// don't keep undo when changing Steps
ClearUndo();
// get the second of the current step.
float curSecond = GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat());
// save current steps
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
ASSERT( pSteps != NULL );
pSteps->SetNoteData( m_NoteDataEdit );
// Get all Steps of this StepsType
const StepsType st = pSteps->m_StepsType;
vector<Steps*> vSteps;
SongUtil::GetSteps( GAMESTATE->m_pCurSong, vSteps, st );
// Sort them by difficulty.
StepsUtil::SortStepsByTypeAndDifficulty( vSteps );
// Find out what index the current Steps are
vector<Steps*>::iterator it = find( vSteps.begin(), vSteps.end(), pSteps );
ASSERT( it != vSteps.end() );
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_OPEN_PREV_STEPS:
if( it==vSteps.begin() )
{
SCREENMAN->PlayInvalidSound();
return true;
}
it--;
break;
case EDIT_BUTTON_OPEN_NEXT_STEPS:
it++;
if( it==vSteps.end() )
{
SCREENMAN->PlayInvalidSound();
return true;
}
break;
}
pSteps = *it;
GAMESTATE->m_pCurSteps[PLAYER_1].Set( pSteps );
m_pSteps = pSteps;
pSteps->GetNoteData( m_NoteDataEdit );
RString s = ssprintf(
SWITCHED_TO.GetValue() + " %s %s '%s' (%d of %d)",
GAMEMAN->GetStepsTypeInfo( st ).szName,
DifficultyToString( pSteps->GetDifficulty() ).c_str(),
pSteps->GetChartName().c_str(),
it - vSteps.begin() + 1,
int(vSteps.size()) );
SCREENMAN->SystemMessage( s );
m_soundSwitchSteps.Play();
ScrollTo( GetAppropriateTiming().GetBeatFromElapsedTime(curSecond) );
}
return true;
case EDIT_BUTTON_BPM_UP:
case EDIT_BUTTON_BPM_DOWN:
{
float fBPM = GetAppropriateTiming().GetBPMAtBeat( GetBeat() );
float fDelta;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_BPM_UP: fDelta = +0.020f; break;
case EDIT_BUTTON_BPM_DOWN: fDelta = -0.020f; break;
}
if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
{
fDelta /= 20; // .001 bpm
}
else if( input.type == IET_REPEAT )
{
if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
fDelta *= 10;
else
fDelta *= 40;
}
float fNewBPM = fBPM + fDelta;
GetAppropriateTimingForUpdate().SetBPMAtBeat( GetBeat(), fNewBPM );
(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
SetDirty( true );
}
return true;
case EDIT_BUTTON_STOP_UP:
case EDIT_BUTTON_STOP_DOWN:
{
float fDelta;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_STOP_UP: fDelta = +0.020f; break;
case EDIT_BUTTON_STOP_DOWN: fDelta = -0.020f; break;
}
if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
{
fDelta /= 20; // 1ms
}
else if( input.type == IET_REPEAT )
{
if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
fDelta *= 10;
else
fDelta *= 40;
}
// is there a StopSegment on the current row?
TimingData & timing = GetAppropriateTimingForUpdate();
StopSegment *seg = timing.GetStopSegmentAtRow( GetRow() );
int i = timing.GetSegmentIndexAtRow(SEGMENT_STOP, GetRow());
if (i == -1 || seg->GetRow() != GetRow()) // invalid
{
if( fDelta > 0 )
timing.AddSegment( StopSegment(GetRow(), fDelta) );
else
return false;
}
else
{
vector<TimingSegment *> &stops = timing.GetTimingSegments(SEGMENT_STOP);
seg->SetPause(seg->GetPause() + fDelta);
if( seg->GetPause() <= 0 )
stops.erase( stops.begin()+i, stops.begin()+i+1);
}
(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
SetDirty( true );
}
return true;
// TODO: Combine the stop and delay call somehow?
case EDIT_BUTTON_DELAY_UP:
case EDIT_BUTTON_DELAY_DOWN:
{
float fDelta;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_DELAY_UP: fDelta = +0.020f; break;
case EDIT_BUTTON_DELAY_DOWN: fDelta = -0.020f; break;
}
if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
{
fDelta /= 20; // 1ms
}
else if( input.type == IET_REPEAT )
{
if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
fDelta *= 10;
else
fDelta *= 40;
}
// is there a StopSegment on the current row?
TimingData & timing = GetAppropriateTimingForUpdate();
DelaySegment *seg = timing.GetDelaySegmentAtRow( GetRow() );
int i = timing.GetSegmentIndexAtRow(SEGMENT_DELAY, GetRow());
if (i == -1 || seg->GetRow() != GetRow()) // invalid
{
if( fDelta > 0 )
timing.AddSegment( DelaySegment(GetRow(), fDelta) );
else
return false;
}
else
{
vector<TimingSegment *> &stops = timing.GetTimingSegments(SEGMENT_DELAY);
seg->SetPause(seg->GetPause() + fDelta);
if( seg->GetPause() <= 0 )
stops.erase( stops.begin()+i, stops.begin()+i+1);
}
(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
SetDirty( true );
}
return true;
case EDIT_BUTTON_OFFSET_UP:
case EDIT_BUTTON_OFFSET_DOWN:
{
float fDelta;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_OFFSET_DOWN: fDelta = -0.02f; break;
case EDIT_BUTTON_OFFSET_UP: fDelta = +0.02f; break;
}
if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
{
fDelta /= 20; /* 1ms */
}
else if( input.type == IET_REPEAT )
{
if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
fDelta *= 10;
else
fDelta *= 40;
}
GetAppropriateTimingForUpdate().m_fBeat0OffsetInSeconds += fDelta;
(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
if (GAMESTATE->m_bIsUsingStepTiming)
{
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.UpdateStartTimes(fDelta);
}
else
{
GAMESTATE->m_pCurSong->m_Attacks.UpdateStartTimes(fDelta);
GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += fDelta;
}
SetDirty( true );
}
return true;
case EDIT_BUTTON_SAMPLE_START_UP:
case EDIT_BUTTON_SAMPLE_START_DOWN:
case EDIT_BUTTON_SAMPLE_LENGTH_DOWN:
case EDIT_BUTTON_SAMPLE_LENGTH_UP:
{
float fDelta;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_SAMPLE_START_DOWN: fDelta = -0.02f; break;
case EDIT_BUTTON_SAMPLE_START_UP: fDelta = +0.02f; break;
case EDIT_BUTTON_SAMPLE_LENGTH_DOWN: fDelta = -0.02f; break;
case EDIT_BUTTON_SAMPLE_LENGTH_UP: fDelta = +0.02f; break;
}
if( input.type == IET_REPEAT )
{
if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
fDelta *= 10;
else
fDelta *= 40;
}
if( EditB == EDIT_BUTTON_SAMPLE_LENGTH_DOWN || EditB == EDIT_BUTTON_SAMPLE_LENGTH_UP )
{
m_pSong->m_fMusicSampleLengthSeconds += fDelta;
m_pSong->m_fMusicSampleLengthSeconds = max(m_pSong->m_fMusicSampleLengthSeconds,0);
}
else
{
m_pSong->m_fMusicSampleStartSeconds += fDelta;
m_pSong->m_fMusicSampleStartSeconds = max(m_pSong->m_fMusicSampleStartSeconds,0);
}
(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
SetDirty( true );
}
return true;
case EDIT_BUTTON_PLAY_SAMPLE_MUSIC:
PlayPreviewMusic();
return true;
case EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU:
case EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU:
if( !GAMESTATE->m_bIsUsingStepTiming )
{
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU: g_CurrentBGChangeLayer = BACKGROUND_LAYER_1; break;
case EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU: g_CurrentBGChangeLayer = BACKGROUND_LAYER_2; break;
}
{
// Fill in option names
vector<RString> vThrowAway;
MenuDef &menu = g_BackgroundChange;
menu.rows[layer].choices[0] = ssprintf("%d",g_CurrentBGChangeLayer);
BackgroundUtil::GetBackgroundTransitions( "", vThrowAway, menu.rows[transition].choices );
g_BackgroundChange.rows[transition].choices.insert( g_BackgroundChange.rows[transition].choices.begin(), "None" ); // add "no transition"
BackgroundUtil::GetBackgroundEffects( "", vThrowAway, menu.rows[effect].choices );
menu.rows[effect].choices.insert( menu.rows[effect].choices.begin(), "Default" ); // add "default effect"
BackgroundUtil::GetSongBGAnimations( m_pSong, "", vThrowAway, menu.rows[file1_song_bganimation].choices );
BackgroundUtil::GetSongMovies( m_pSong, "", vThrowAway, menu.rows[file1_song_movie].choices );
BackgroundUtil::GetSongBitmaps( m_pSong, "", vThrowAway, menu.rows[file1_song_still].choices );
BackgroundUtil::GetGlobalBGAnimations( m_pSong, "", vThrowAway, menu.rows[file1_global_bganimation].choices ); // NULL to get all background files
BackgroundUtil::GetGlobalRandomMovies( m_pSong, "", vThrowAway, menu.rows[file1_global_movie].choices, false, false ); // all backgrounds
BackgroundUtil::GetGlobalRandomMovies( m_pSong, "", vThrowAway, menu.rows[file1_global_movie_song_group].choices, false, true ); // song group's backgrounds
BackgroundUtil::GetGlobalRandomMovies( m_pSong, "", vThrowAway, menu.rows[file1_global_movie_song_group_and_genre].choices, true, true ); // song group and genre's backgrounds
menu.rows[file2_type].choices = menu.rows[file1_type].choices;
menu.rows[file2_song_bganimation].choices = menu.rows[file1_song_bganimation].choices;
menu.rows[file2_song_movie].choices = menu.rows[file1_song_movie].choices;
menu.rows[file2_song_still].choices = menu.rows[file1_song_still].choices;
menu.rows[file2_global_bganimation].choices = menu.rows[file1_global_bganimation].choices;
menu.rows[file2_global_movie].choices = menu.rows[file1_global_movie].choices;
menu.rows[file2_global_movie_song_group].choices = menu.rows[file1_global_movie_song_group].choices;
menu.rows[file2_global_movie_song_group_and_genre].choices = menu.rows[file1_global_movie_song_group_and_genre].choices;
// Fill in lines enabled/disabled
bool bAlreadyBGChangeHere = false;
BackgroundChange bgChange;
FOREACH( BackgroundChange, m_pSong->GetBackgroundChanges(g_CurrentBGChangeLayer), bgc )
{
if( bgc->m_fStartBeat == GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat )
{
bAlreadyBGChangeHere = true;
bgChange = *bgc;
}
}
#define FILL_ENABLED( x ) menu.rows[x].bEnabled = menu.rows[x].choices.size() > 0;
FILL_ENABLED( transition );
FILL_ENABLED( effect );
FILL_ENABLED( file1_song_bganimation );
FILL_ENABLED( file1_song_movie );
FILL_ENABLED( file1_song_still );
FILL_ENABLED( file1_global_bganimation );
FILL_ENABLED( file1_global_movie );
FILL_ENABLED( file1_global_movie_song_group );
FILL_ENABLED( file1_global_movie_song_group_and_genre );
FILL_ENABLED( file2_song_bganimation );
FILL_ENABLED( file2_song_movie );
FILL_ENABLED( file2_song_still );
FILL_ENABLED( file2_global_bganimation );
FILL_ENABLED( file2_global_movie );
FILL_ENABLED( file2_global_movie_song_group );
FILL_ENABLED( file2_global_movie_song_group_and_genre );
#undef FILL_ENABLED
menu.rows[delete_change].bEnabled = bAlreadyBGChangeHere;
// set default choices
menu.rows[rate]. SetDefaultChoiceIfPresent( ssprintf("%2.0f%%",bgChange.m_fRate*100) );
menu.rows[transition]. SetDefaultChoiceIfPresent( bgChange.m_sTransition );
menu.rows[effect]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sEffect );
menu.rows[file1_type].iDefaultChoice = none;
if( bgChange.m_def.m_sFile1 == RANDOM_BACKGROUND_FILE ) menu.rows[file1_type].iDefaultChoice = dynamic_random;
if( menu.rows[file1_song_bganimation]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = song_bganimation;
if( menu.rows[file1_song_movie]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = song_movie;
if( menu.rows[file1_song_still]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = song_bitmap;
if( menu.rows[file1_global_bganimation]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = global_bganimation;
if( menu.rows[file1_global_movie]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = global_movie;
if( menu.rows[file1_global_movie_song_group]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = global_movie_song_group;
if( menu.rows[file1_global_movie_song_group_and_genre]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) ) menu.rows[file1_type].iDefaultChoice = global_movie_song_group_and_genre;
menu.rows[file2_type].iDefaultChoice = none;
if( bgChange.m_def.m_sFile2 == RANDOM_BACKGROUND_FILE ) menu.rows[file2_type].iDefaultChoice = dynamic_random;
if( menu.rows[file2_song_bganimation]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = song_bganimation;
if( menu.rows[file2_song_movie]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = song_movie;
if( menu.rows[file2_song_still]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = song_bitmap;
if( menu.rows[file2_global_bganimation]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = global_bganimation;
if( menu.rows[file2_global_movie]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = global_movie;
if( menu.rows[file2_global_movie_song_group]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = global_movie_song_group;
if( menu.rows[file2_global_movie_song_group_and_genre]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) ) menu.rows[file2_type].iDefaultChoice = global_movie_song_group_and_genre;
menu.rows[color1]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sColor1 );
menu.rows[color2]. SetDefaultChoiceIfPresent( bgChange.m_def.m_sColor2 );
EditMiniMenu( &g_BackgroundChange, SM_BackFromBGChange );
}
}
else
{
SCREENMAN->SystemMessage( BG_CHANGE_STEP_TIMING );
SCREENMAN->PlayInvalidSound();
}
return true;
case EDIT_BUTTON_OPEN_COURSE_MENU:
{
g_CourseMode.rows[0].choices.clear();
g_CourseMode.rows[0].choices.push_back( CommonMetrics::LocalizeOptionItem("Off",false) );
g_CourseMode.rows[0].iDefaultChoice = 0;
vector<Course*> courses;
SONGMAN->GetAllCourses( courses, false );
for( unsigned i = 0; i < courses.size(); ++i )
{
const Course *crs = courses[i];
bool bUsesThisSong = false;
for( unsigned e = 0; e < crs->m_vEntries.size(); ++e )
{
if( crs->m_vEntries[e].songID.ToSong() != m_pSong )
continue;
bUsesThisSong = true;
}
if( bUsesThisSong )
{
g_CourseMode.rows[0].choices.push_back( crs->GetDisplayFullTitle() );
if( crs == GAMESTATE->m_pCurCourse )
g_CourseMode.rows[0].iDefaultChoice = g_CourseMode.rows[0].choices.size()-1;
}
}
EditMiniMenu( &g_CourseMode, SM_BackFromCourseModeMenu );
}
return true;
case EDIT_BUTTON_OPEN_STEP_ATTACK_MENU:
{
this->DoStepAttackMenu();
return true;
}
case EDIT_BUTTON_OPEN_COURSE_ATTACK_MENU:
{
// TODO: Give Song/Step Timing switches/functions here?
Course *pCourse = GAMESTATE->m_pCurCourse;
if( pCourse == NULL )
return false;
CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
float fStartTime = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat );
int iAttack = FindAttackAtTime( ce.attacks, fStartTime );
if( iAttack >= 0 )
{
const RString sDuration = FloatToString(ce.attacks[iAttack].fSecsRemaining );
g_InsertCourseAttack.rows[remove].bEnabled = true;
if( g_InsertCourseAttack.rows[duration].choices.size() == 9 )
g_InsertCourseAttack.rows[duration].choices.push_back( sDuration );
else
g_InsertCourseAttack.rows[duration].choices.back() = sDuration;
g_InsertCourseAttack.rows[duration].iDefaultChoice = 9;
}
else
{
if( g_InsertCourseAttack.rows[duration].choices.size() == 10 )
g_InsertCourseAttack.rows[duration].choices.pop_back();
g_InsertCourseAttack.rows[duration].iDefaultChoice = 3;
}
EditMiniMenu( &g_InsertCourseAttack, SM_BackFromInsertCourseAttack );
}
return true;
case EDIT_BUTTON_ADD_STEP_MODS:
{
float start = -1;
float end = -1;
PlayerOptions po;
if (m_NoteFieldEdit.m_iBeginMarker == -1) // not highlighted
{
po.FromString("");
}
else
{
const TimingData &timing = GetAppropriateTiming();
start = timing.GetElapsedTimeFromBeat(NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker));
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
int index = FindAttackAtTime(attacks, start);
if (index >= 0)
{
po.FromString("");
}
if (m_NoteFieldEdit.m_iEndMarker == -1)
{
end = m_pSong->m_fMusicLengthSeconds;
}
else
{
end = timing.GetElapsedTimeFromBeat(NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker));
}
}
ModsGroup<PlayerOptions> &toEdit = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
this->originalPlayerOptions.Assign(ModsLevel_Preferred, toEdit.GetPreferred());
g_fLastInsertAttackPositionSeconds = start;
g_fLastInsertAttackDurationSeconds = end - start;
toEdit.Assign( ModsLevel_Stage, po );
SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertStepAttackPlayerOptions );
return true;
}
case EDIT_BUTTON_ADD_COURSE_MODS:
{
float fStart, fEnd;
PlayerOptions po;
const Course *pCourse = GAMESTATE->m_pCurCourse;
if( pCourse == NULL )
return false;
const CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
if( m_NoteFieldEdit.m_iBeginMarker == -1 )
{
fStart = -1;
fEnd = -1;
po.FromString( ce.sModifiers );
}
else
{
// TODO: Give Song/Step Timing switches/functions here?
TimingData *timing = m_pSteps->GetTimingData();
fStart = timing->GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
int iAttack = FindAttackAtTime( ce.attacks, fStart );
if( iAttack >= 0 )
po.FromString( ce.attacks[iAttack].sModifiers );
if( m_NoteFieldEdit.m_iEndMarker == -1 )
fEnd = m_pSong->m_fMusicLengthSeconds;
else
fEnd = timing->GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
}
g_fLastInsertAttackPositionSeconds = fStart;
g_fLastInsertAttackDurationSeconds = fEnd - fStart;
GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.Assign( ModsLevel_Stage, po );
SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertCourseAttackPlayerOptions );
}
return true;
case EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP:
case EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE:
{
bool bTryGenre = EditB == EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE;
RString sName = GetOneBakedRandomFile(m_pSong, bTryGenre);
if( sName.empty() )
{
SCREENMAN->PlayInvalidSound();
SCREENMAN->SystemMessage( NO_BACKGROUNDS_AVAILABLE );
}
else
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
BackgroundChange bgChange;
bgChange.m_fStartBeat = GAMESTATE->m_Position.m_fSongBeat;
FOREACH( BackgroundChange, m_pSong->GetBackgroundChanges(iLayer), bgc )
{
if( bgc->m_fStartBeat == GAMESTATE->m_Position.m_fSongBeat )
{
bgChange = *bgc;
m_pSong->GetBackgroundChanges(iLayer).erase( bgc );
break;
}
}
bgChange.m_def.m_sFile1 = sName;
m_pSong->AddBackgroundChange( iLayer, bgChange );
m_soundMarker.Play();
}
}
return true;
case EDIT_BUTTON_PLAY_FROM_START:
HandleMainMenuChoice( play_whole_song );
return true;
case EDIT_BUTTON_PLAY_FROM_CURSOR:
HandleMainMenuChoice( play_current_beat_to_end );
return true;
case EDIT_BUTTON_PLAY_SELECTION:
HandleMainMenuChoice( play_selection );
return true;
case EDIT_BUTTON_RECORD_SELECTION:
if( m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1 )
{
HandleAlterMenuChoice( record );
}
else
{
if( g_iDefaultRecordLength.Get() == -1 )
{
m_iStartPlayingAt = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() + 1 );
}
else
{
m_iStartPlayingAt = BeatToNoteRow( ftruncf(GetAppropriatePosition().m_fSongBeat, g_iDefaultRecordLength.Get()) );
m_iStopPlayingAt = m_iStartPlayingAt + BeatToNoteRow( g_iDefaultRecordLength.Get() );
}
if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
m_iStopPlayingAt = min( m_iStopPlayingAt, BeatToNoteRow(GetMaximumBeatForNewNote()) );
if( m_iStartPlayingAt >= m_iStopPlayingAt )
{
SCREENMAN->PlayInvalidSound();
return true;
}
TransitionEditState( STATE_RECORDING );
}
return true;
case EDIT_BUTTON_RECORD_FROM_CURSOR:
m_iStartPlayingAt = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() );
TransitionEditState( STATE_RECORDING );
return true;
case EDIT_BUTTON_INSERT:
HandleAreaMenuChoice( insert_and_shift );
SCREENMAN->PlayInvalidSound();
return true;
case EDIT_BUTTON_INSERT_SHIFT_PAUSES:
HandleAreaMenuChoice( shift_pauses_forward );
SCREENMAN->PlayInvalidSound();
return true;
case EDIT_BUTTON_DELETE:
HandleAreaMenuChoice( delete_and_shift );
SCREENMAN->PlayInvalidSound();
return true;
case EDIT_BUTTON_DELETE_SHIFT_PAUSES:
HandleAreaMenuChoice( shift_pauses_backward );
SCREENMAN->PlayInvalidSound();
return true;
case EDIT_BUTTON_SAVE:
HandleMainMenuChoice( ScreenEdit::save );
return true;
case EDIT_BUTTON_UNDO:
Undo();
return true;
case EDIT_BUTTON_SWITCH_PLAYERS:
if( m_InputPlayerNumber == PLAYER_INVALID )
return false;
enum_add( m_InputPlayerNumber, 1 );
if( m_InputPlayerNumber == NUM_PLAYERS )
m_InputPlayerNumber = PLAYER_1;
m_soundSwitchPlayer.Play();
return true;
case EDIT_BUTTON_SWITCH_TIMINGS:
GAMESTATE->m_bIsUsingStepTiming = !GAMESTATE->m_bIsUsingStepTiming;
m_soundSwitchTiming.Play();
return true;
default:
return false;
}
}
bool ScreenEdit::InputRecord( const InputEventPlus &input, EditButton EditB )
{
if( input.type == IET_FIRST_PRESS && EditB == EDIT_BUTTON_RETURN_TO_EDIT )
{
TransitionEditState( STATE_EDITING );
return true;
}
if( input.pn != PLAYER_1 )
return false; // ignore
const int iCol = GAMESTATE->GetCurrentStyle()->GameInputToColumn( input.GameI );
//Is this actually a column? If not, ignore the input.
if( iCol == -1 )
return false;
switch( input.type )
{
DEFAULT_FAIL( input.type );
case IET_FIRST_PRESS:
{
if( EditIsBeingPressed(EDIT_BUTTON_REMOVE_NOTE) )
{
// Remove notes in Update.
return false;
}
// Add a tap
float fBeat = GetBeat();
fBeat = Quantize( fBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );
const int iRow = BeatToNoteRow( fBeat );
if( iRow < 0 )
return false;
// Don't add outside of the range.
if( iRow < m_iStartPlayingAt || iRow >= m_iStopPlayingAt )
return false;
// Remove hold if any so that we don't have taps inside of a hold.
int iHeadRow;
if( m_NoteDataRecord.IsHoldNoteAtRow( iCol, iRow, &iHeadRow ) )
m_NoteDataRecord.SetTapNote( iCol, iHeadRow, TAP_EMPTY );
TapNote tn = TAP_ORIGINAL_TAP;
tn.pn = m_InputPlayerNumber;
m_NoteDataRecord.SetTapNote( iCol, iRow, tn );
m_NoteFieldRecord.Step( iCol, TNS_W1 );
}
return true;
case IET_REPEAT:
case IET_RELEASE:
// don't add or extend holds here; we do it in Update()
return false;
}
}
bool ScreenEdit::InputRecordPaused( const InputEventPlus &input, EditButton EditB )
{
if( input.type != IET_FIRST_PRESS )
return false; // don't care
switch( EditB )
{
case EDIT_BUTTON_UNDO:
/* We've already actually committed changes to m_NoteDataEdit, so all we have
* to do to undo is Undo() as usual, and copy the note data back in. */
Undo();
m_NoteDataRecord.CopyAll( m_NoteDataEdit );
return true;
case EDIT_BUTTON_PLAY_SELECTION:
TransitionEditState( STATE_PLAYING );
return true;
case EDIT_BUTTON_RECORD_SELECTION:
TransitionEditState( STATE_RECORDING );
return true;
case EDIT_BUTTON_RECORD_FROM_CURSOR:
if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
{
float fMaximumBeat = GetMaximumBeatForNewNote();
if( NoteRowToBeat(m_iStopPlayingAt) >= fMaximumBeat )
{
// We're already at the end.
SCREENMAN->PlayInvalidSound();
return true;
}
}
// Pick up where we left off.
{
int iSize = m_iStopPlayingAt - m_iStartPlayingAt;
m_iStartPlayingAt = m_iStopPlayingAt;
m_iStopPlayingAt += iSize;
if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
m_iStopPlayingAt = min( m_iStopPlayingAt, BeatToNoteRow(GetMaximumBeatForNewNote()) );
}
TransitionEditState( STATE_RECORDING );
return true;
case EDIT_BUTTON_RETURN_TO_EDIT:
TransitionEditState( STATE_EDITING );
return true;
default:
return false;
}
}
bool ScreenEdit::InputPlay( const InputEventPlus &input, EditButton EditB )
{
switch( input.type )
{
case IET_RELEASE:
case IET_FIRST_PRESS:
break;
default:
return false;
}
GameButtonType gbt = GAMESTATE->m_pCurGame->GetPerButtonInfo(input.GameI.button)->m_gbt;
if( GamePreferences::m_AutoPlay == PC_HUMAN && GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay == 0 )
{
const int iCol = GAMESTATE->GetCurrentStyle()->GameInputToColumn( input.GameI );
bool bRelease = input.type == IET_RELEASE;
switch( input.pn )
{
case PLAYER_2:
// ignore player 2 input unless this mode requires it
if( GAMESTATE->GetCurrentStyle()->m_StyleType != StyleType_TwoPlayersSharedSides )
break;
// fall through to input handling logic:
case PLAYER_1:
{
switch( gbt )
{
case GameButtonType_Step:
if( iCol != -1 )
m_Player->Step( iCol, -1, input.DeviceI.ts, false, bRelease );
return true;
case GameButtonType_Fret:
if( iCol != -1 )
m_Player->Fret( iCol, -1, input.DeviceI.ts, false, bRelease );
return true;
case GameButtonType_Strum:
m_Player->Strum( iCol, -1, input.DeviceI.ts, false, bRelease );
return true;
default:
break;
}
}
default: break;
}
}
if( gbt == GameButtonType_INVALID && input.type == IET_FIRST_PRESS )
{
switch( EditB )
{
case EDIT_BUTTON_RETURN_TO_EDIT:
/* When exiting play mode manually, leave the cursor where it is. */
m_fBeatToReturnTo = GetAppropriatePosition().m_fSongBeat;
TransitionEditState( STATE_EDITING );
return true;
case EDIT_BUTTON_OFFSET_UP:
case EDIT_BUTTON_OFFSET_DOWN:
{
float fOffsetDelta;
switch( EditB )
{
DEFAULT_FAIL( EditB );
case EDIT_BUTTON_OFFSET_DOWN: fOffsetDelta = -0.020f; break;
case EDIT_BUTTON_OFFSET_UP: fOffsetDelta = +0.020f; break;
}
if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
{
fOffsetDelta /= 20; /* 1ms */
}
else if( input.type == IET_REPEAT )
{
if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
fOffsetDelta *= 10;
else
fOffsetDelta *= 40;
}
GetAppropriateTimingForUpdate().m_fBeat0OffsetInSeconds += fOffsetDelta;
if (!GAMESTATE->m_bIsUsingStepTiming)
{
GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += fOffsetDelta;
}
}
return true;
default: break;
}
}
return false;
}
void ScreenEdit::TransitionEditState( EditState em )
{
EditState old = m_EditState;
// If we're going from recording to paused, come back when we're done.
if( old == STATE_RECORDING_PAUSED && em == STATE_PLAYING )
m_bReturnToRecordMenuAfterPlay = true;
const bool bStateChanging = em != old;
#if 0
// If switching out of record, open the menu.
{
bool bGoToRecordMenu = (old == STATE_RECORDING);
if( m_bReturnToRecordMenuAfterPlay && old == STATE_PLAYING )
{
bGoToRecordMenu = true;
m_bReturnToRecordMenuAfterPlay = false;
}
if( bGoToRecordMenu )
em = STATE_RECORDING_PAUSED;
}
#endif
// If we're playing music or assist ticks when changing modes, stop.
SOUND->StopMusic();
if( m_pSoundMusic )
m_pSoundMusic->StopPlaying();
m_GameplayAssist.StopPlaying();
GAMESTATE->m_bGameplayLeadIn.Set( true );
if( bStateChanging )
{
switch( old )
{
case STATE_EDITING:
// If exiting EDIT mode, save the cursor position.
m_fBeatToReturnTo = GetAppropriatePosition().m_fSongBeat;
break;
case STATE_PLAYING:
AdjustSync::HandleSongEnd();
if (!GAMESTATE->m_bIsUsingStepTiming)
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = backupStepTiming;
if( AdjustSync::IsSyncDataChanged() )
ScreenSaveSync::PromptSaveSync();
break;
case STATE_RECORDING:
SetDirty( true );
if (!GAMESTATE->m_bIsUsingStepTiming)
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = backupStepTiming;
SaveUndo();
// delete old TapNotes in the range
m_NoteDataEdit.ClearRange( m_iStartPlayingAt, m_iStopPlayingAt );
m_NoteDataEdit.CopyRange( m_NoteDataRecord, m_iStartPlayingAt, m_iStopPlayingAt, m_iStartPlayingAt );
m_NoteDataRecord.ClearAll();
CheckNumberOfNotesAndUndo();
break;
default: break;
}
}
// Set up player options for this mode. (EDITING uses m_PlayerStateEdit,
// which we don't need to change.)
if( em != STATE_EDITING )
{
// Stop displaying course attacks, if any.
GAMESTATE->m_pPlayerState[PLAYER_1]->RemoveActiveAttacks();
// Load the player's default PlayerOptions.
GAMESTATE->m_pPlayerState[PLAYER_1]->RebuildPlayerOptionsFromActiveAttacks();
// Snap to current options.
GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.SetCurrentToLevel( ModsLevel_Stage );
}
switch( em )
{
DEFAULT_FAIL( em );
case STATE_EDITING:
// Important: people will stop playing, change the BG and start again; make sure we reload
m_Background.Unload();
m_Foreground.Unload();
// Restore the cursor position + Quantize + Clamp
SetBeat( max( 0, Quantize( m_fBeatToReturnTo, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) ) ) );
GAMESTATE->m_bInStepEditor = true;
break;
case STATE_PLAYING:
case STATE_RECORDING:
{
if( bStateChanging )
AdjustSync::ResetOriginalSyncData();
/* Give a 1 second lead-in. If we're loading Player, this must be done first.
* Also be sure to get the right timing. */
float fSeconds = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_iStartPlayingAt) ) - 1;
GAMESTATE->UpdateSongPosition( fSeconds, GetAppropriateTiming(), RageZeroTimer, true );
GAMESTATE->m_bGameplayLeadIn.Set( false );
if (!GAMESTATE->m_bIsUsingStepTiming)
{
// Substitute the song timing for the step timing during
// previuw if we're in song mode
backupStepTiming = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing;
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing.Clear();
}
/* Reset the note skin, in case preferences have changed. */
// XXX
// GAMESTATE->ResetNoteSkins();
//GAMESTATE->res
GAMESTATE->m_bInStepEditor = false;
break;
}
case STATE_RECORDING_PAUSED:
GAMESTATE->m_bInStepEditor = false;
break;
}
switch( em )
{
case STATE_PLAYING:
// If we're in course display mode, set that up.
SetupCourseAttacks();
m_Player.Load( m_NoteDataEdit );
if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay != 0 )
GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = PC_AUTOPLAY;
else
GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = GamePreferences::m_AutoPlay;
if( g_bEditorShowBGChangesPlay )
{
/* FirstBeat affects backgrounds, so commit changes to memory (not to disk)
* and recalc it. */
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
ASSERT( pSteps != NULL );
pSteps->SetNoteData( m_NoteDataEdit );
m_pSong->ReCalculateRadarValuesAndLastSecond();
m_Background.Unload();
m_Background.LoadFromSong( m_pSong );
m_Foreground.Unload();
m_Foreground.LoadFromSong( m_pSong );
}
break;
case STATE_RECORDING:
case STATE_RECORDING_PAUSED:
// initialize m_NoteFieldRecord
m_NoteDataRecord.CopyAll( m_NoteDataEdit );
// highlight the section being recorded
m_NoteFieldRecord.m_iBeginMarker = m_iStartPlayingAt;
m_NoteFieldRecord.m_iEndMarker = m_iStopPlayingAt;
break;
default: break;
}
// Show/hide depending on edit state (em)
m_sprOverlay->PlayCommand( EditStateToString(em) );
m_sprUnderlay->PlayCommand( EditStateToString(em) );
m_Background.SetVisible( g_bEditorShowBGChangesPlay && em != STATE_EDITING );
m_textInputTips.SetVisible( em == STATE_EDITING );
m_textInfo.SetVisible( em == STATE_EDITING );
// Play the OnCommands again so that these will be re-hidden if the OnCommand hides them.
if( em == STATE_EDITING )
{
m_textInputTips.PlayCommand( "On" );
m_textInfo.PlayCommand( "On" );
}
m_textPlayRecordHelp.SetVisible( em != STATE_EDITING );
m_SnapDisplay.SetVisible( em == STATE_EDITING );
m_NoteFieldEdit.SetVisible( em == STATE_EDITING );
m_NoteFieldRecord.SetVisible( em == STATE_RECORDING || em == STATE_RECORDING_PAUSED );
m_Player->SetVisible( em == STATE_PLAYING );
m_Foreground.SetVisible( g_bEditorShowBGChangesPlay && em != STATE_EDITING );
switch( em )
{
case STATE_PLAYING:
case STATE_RECORDING:
{
const float fStartSeconds = GetAppropriateTiming().GetElapsedTimeFromBeat( GetBeat() );
LOG->Trace( "Starting playback at %f", fStartSeconds );
RageSoundParams p;
p.m_fSpeed = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
p.m_StartSecond = fStartSeconds;
p.StopMode = RageSoundParams::M_CONTINUE;
m_pSoundMusic->SetProperty( "AccurateSync", true );
m_pSoundMusic->Play( &p );
break;
}
default: break;
}
m_EditState = em;
}
void ScreenEdit::ScrollTo( float fDestinationBeat )
{
CLAMP( fDestinationBeat, 0, GetMaximumBeatForMoving() );
// Don't play the sound and do the hold note logic below if our position didn't change.
const float fOriginalBeat = GetAppropriatePosition().m_fSongBeat;
if( fOriginalBeat == fDestinationBeat )
return;
SetBeat(fDestinationBeat);
// check to see if they're holding a button
for( int n=0; n<NUM_EDIT_BUTTON_COLUMNS; n++ )
{
int iCol = n;
// Ctrl + number = input to right half
if( EditIsBeingPressed(EDIT_BUTTON_RIGHT_SIDE) )
ShiftToRightSide( iCol, m_NoteDataEdit.GetNumTracks() );
if( iCol >= m_NoteDataEdit.GetNumTracks() )
continue; // skip
EditButton b = EditButton(EDIT_BUTTON_COLUMN_0+n);
if( !EditIsBeingPressed(b) )
continue;
// create a new hold note
int iStartRow = BeatToNoteRow( min(fOriginalBeat, fDestinationBeat) );
int iEndRow = BeatToNoteRow( max(fOriginalBeat, fDestinationBeat) );
// Don't SaveUndo. We want to undo the whole hold, not just the last segment
// that the user made. Dragging the hold bigger can only absorb and remove
// other taps, so dragging won't cause us to exceed the note limit.
TapNote tn = EditIsBeingPressed(EDIT_BUTTON_LAY_ROLL) ? TAP_ORIGINAL_ROLL_HEAD : TAP_ORIGINAL_HOLD_HEAD;
tn.pn = m_InputPlayerNumber;
m_NoteDataEdit.AddHoldNote( iCol, iStartRow, iEndRow, tn );
}
if( EditIsBeingPressed(EDIT_BUTTON_SCROLL_SELECT) )
{
/* Shift is being held.
* If this is the first time we've moved since shift was depressed,
* the old position (before this move) becomes the start pos: */
int iDestinationRow = BeatToNoteRow( fDestinationBeat );
if( m_iShiftAnchor == -1 )
m_iShiftAnchor = BeatToNoteRow(fOriginalBeat);
if( iDestinationRow == m_iShiftAnchor )
{
// We're back at the anchor, so we have nothing selected.
m_NoteFieldEdit.m_iBeginMarker = m_NoteFieldEdit.m_iEndMarker = -1;
}
else
{
m_NoteFieldEdit.m_iBeginMarker = m_iShiftAnchor;
m_NoteFieldEdit.m_iEndMarker = iDestinationRow;
if( m_NoteFieldEdit.m_iBeginMarker > m_NoteFieldEdit.m_iEndMarker )
swap( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker );
}
}
m_soundChangeLine.Play();
}
static LocalizedString NEW_KEYSOUND_FILE("ScreenEdit", "Enter New Keysound File");
void ScreenEdit::HandleMessage( const Message &msg )
{
if( msg == "Judgment" )
{
PlayerNumber pn;
msg.GetParam( "Player", pn );
if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_bMuteOnError )
{
RageSoundReader *pSoundReader = m_AutoKeysounds.GetPlayerSound( pn );
if( pSoundReader == NULL )
pSoundReader = m_AutoKeysounds.GetSharedSound();
HoldNoteScore hns;
msg.GetParam( "HoldNoteScore", hns );
TapNoteScore tns;
msg.GetParam( "TapNoteScore", tns );
bool bOn = false;
if( hns != HoldNoteScore_Invalid )
bOn = hns != HNS_LetGo;
else
bOn = tns != TNS_Miss;
if( pSoundReader )
pSoundReader->SetProperty( "Volume", bOn? 1.0f:0.0f );
}
}
if( msg == Message_SongModified )
{
SetDirty( true );
}
Screen::HandleMessage( msg );
}
static LocalizedString SAVE_SUCCESSFUL ( "ScreenEdit", "Save successful." );
static LocalizedString ADD_NEW_MOD ("ScreenEdit", "Adding New Mod");
static LocalizedString ADD_NEW_ATTACK ("ScreenEdit", "Adding New Attack");
static LocalizedString EDIT_EXISTING_MOD ("ScreenEdit", "Edit Existing Mod");
static LocalizedString EDIT_ATTACK_START ("ScreenEdit", "Edit Attack Start");
static LocalizedString EDIT_ATTACK_LENGTH ("ScreenEdit", "Edit Attack Length");
void ScreenEdit::HandleScreenMessage( const ScreenMessage SM )
{
if( SM != SM_UpdateTextInfo )
m_bTextInfoNeedsUpdate = true;
if( SM == SM_UpdateTextInfo )
{
UpdateTextInfo();
this->PostScreenMessage( SM_UpdateTextInfo, 0.1f );
}
else if( SM == SM_GoToNextScreen )
{
GAMESTATE->m_EditMode = EditMode_Invalid;
}
else if( SM == SM_BackFromMainMenu )
{
HandleMainMenuChoice( (MainMenuChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
}
else if( SM == SM_BackFromAreaMenu )
{
AreaMenuChoice amc = static_cast<AreaMenuChoice>(ScreenMiniMenu::s_iLastRowCode);
const vector<int> &answers = ScreenMiniMenu::s_viLastAnswers;
HandleAreaMenuChoice( amc, answers );
}
else if( SM == SM_BackFromAlterMenu )
{
HandleAlterMenuChoice( (AlterMenuChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
}
else if( SM == SM_BackFromStepsInformation )
{
HandleStepsInformationChoice( (StepsInformationChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
}
else if (SM == SM_BackFromStepsData)
{
HandleStepsDataChoice((StepsDataChoice)ScreenMiniMenu::s_iLastRowCode,
ScreenMiniMenu::s_viLastAnswers);
}
else if( SM == SM_BackFromSongInformation )
{
HandleSongInformationChoice( (SongInformationChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
}
else if( SM == SM_BackFromTimingDataInformation )
{
HandleTimingDataInformationChoice( (TimingDataInformationChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
}
else if( SM == SM_BackFromDifficultyMeterChange )
{
int i = StringToInt( ScreenTextEntry::s_sLastAnswer );
GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(i);
SetDirty( true );
}
else if( SM == SM_BackFromBeat0Change && !ScreenTextEntry::s_bCancelledLast )
{
float fBeat0 = StringToFloat( ScreenTextEntry::s_sLastAnswer );
TimingData &timing = GetAppropriateTimingForUpdate();
float old = timing.m_fBeat0OffsetInSeconds;
timing.m_fBeat0OffsetInSeconds = fBeat0;
float delta = timing.m_fBeat0OffsetInSeconds - old;
if (GAMESTATE->m_bIsUsingStepTiming)
{
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.UpdateStartTimes(delta);
}
else
{
GAMESTATE->m_pCurSong->m_Attacks.UpdateStartTimes(delta);
GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += delta;
}
SetDirty( true );
}
else if( SM == SM_BackFromBPMChange && !ScreenTextEntry::s_bCancelledLast )
{
float fBPM = StringToFloat( ScreenTextEntry::s_sLastAnswer );
if( fBPM > 0 )
GetAppropriateTimingForUpdate().AddSegment( BPMSegment(GetRow(), fBPM) );
SetDirty( true );
}
else if( SM == SM_BackFromStopChange && !ScreenTextEntry::s_bCancelledLast )
{
float fStop = StringToFloat( ScreenTextEntry::s_sLastAnswer );
if( fStop >= 0 )
GetAppropriateTimingForUpdate().AddSegment( StopSegment(GetRow(), fStop) );
SetDirty( true );
}
else if( SM == SM_BackFromDelayChange && !ScreenTextEntry::s_bCancelledLast )
{
float fDelay = StringToFloat( ScreenTextEntry::s_sLastAnswer );
if( fDelay >= 0 )
GetAppropriateTimingForUpdate().AddSegment( DelaySegment(GetRow(), fDelay) );
SetDirty( true );
}
else if( SM == SM_BackFromTimeSignatureChange && !ScreenTextEntry::s_bCancelledLast )
{
int iNum, iDen;
if( sscanf( ScreenTextEntry::s_sLastAnswer.c_str(), " %d / %d ", &iNum, &iDen ) == 2 )
GetAppropriateTimingForUpdate().AddSegment( TimeSignatureSegment(GetRow(), iNum, iDen) );
SetDirty( true );
}
else if ( SM == SM_BackFromTickcountChange && !ScreenTextEntry::s_bCancelledLast )
{
int iTick = StringToInt( ScreenTextEntry::s_sLastAnswer );
if ( iTick >= 0 && iTick <= ROWS_PER_BEAT )
GetAppropriateTimingForUpdate().AddSegment( TickcountSegment( GetRow(), iTick) );
SetDirty( true );
}
else if ( SM == SM_BackFromComboChange && !ScreenTextEntry::s_bCancelledLast )
{
int iCombo, iMiss;
if (sscanf(ScreenTextEntry::s_sLastAnswer.c_str(), " %d / %d ", &iCombo, &iMiss) == 2)
GetAppropriateTimingForUpdate().AddSegment( ComboSegment(GetRow(), iCombo, iMiss) );
SetDirty( true );
}
else if ( SM == SM_BackFromLabelChange && !ScreenTextEntry::s_bCancelledLast )
{
RString sLabel = ScreenTextEntry::s_sLastAnswer;
if ( !GetAppropriateTiming().DoesLabelExist(sLabel) )
{
// XXX: these should be in the NotesWriters where they're needed.
sLabel.Replace("=", "_");
sLabel.Replace(",", "_");
GetAppropriateTimingForUpdate().AddSegment( LabelSegment(GetRow(), sLabel) );
SetDirty( true );
}
}
else if ( SM == SM_BackFromWarpChange && !ScreenTextEntry::s_bCancelledLast )
{
float fWarp = StringToFloat( ScreenTextEntry::s_sLastAnswer );
if( fWarp >= 0 ) // allow 0 to kill a warp.
{
GetAppropriateTimingForUpdate().SetWarpAtBeat( GetBeat(), fWarp );
SetDirty( true );
}
}
else if( SM == SM_BackFromSpeedPercentChange && !ScreenTextEntry::s_bCancelledLast )
{
float fNum = StringToFloat( ScreenTextEntry::s_sLastAnswer );
GetAppropriateTimingForUpdate().SetSpeedPercentAtBeat( GetBeat(), fNum );
SetDirty( true );
}
else if ( SM == SM_BackFromSpeedWaitChange && !ScreenTextEntry::s_bCancelledLast )
{
float fDen = StringToFloat( ScreenTextEntry::s_sLastAnswer );
if( fDen >= 0)
{
GetAppropriateTimingForUpdate().SetSpeedWaitAtBeat( GetBeat(), fDen );
}
SetDirty( true );
}
else if ( SM == SM_BackFromSpeedModeChange && !ScreenTextEntry::s_bCancelledLast )
{
if( ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "b" || ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "B" )
{
GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_BEATS );
}
else if( ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "s" || ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "S" )
{
GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_SECONDS );
}
else
{
int tmp = StringToInt(ScreenTextEntry::s_sLastAnswer );
SpeedSegment::BaseUnit unit = (tmp == 0 ) ?
SpeedSegment::UNIT_BEATS : SpeedSegment::UNIT_SECONDS;
GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), unit );
}
SetDirty( true );
}
else if( SM == SM_BackFromScrollChange && !ScreenTextEntry::s_bCancelledLast )
{
float fNum = StringToFloat( ScreenTextEntry::s_sLastAnswer );
GetAppropriateTimingForUpdate().SetScrollAtBeat( GetBeat(), fNum );
SetDirty( true );
}
else if ( SM == SM_BackFromFakeChange && !ScreenTextEntry::s_bCancelledLast )
{
float fFake = StringToFloat( ScreenTextEntry::s_sLastAnswer );
if( fFake >= 0 ) // allow 0 to kill a fake.
{
GetAppropriateTimingForUpdate().AddSegment( FakeSegment(GetRow(), fFake) );
SetDirty( true );
}
}
else if( SM == SM_BackFromBGChange )
{
HandleBGChangeChoice( (BGChangeChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
SetDirty( true );
}
else if( SM == SM_BackFromCourseModeMenu )
{
const int num = ScreenMiniMenu::s_viLastAnswers[0];
GAMESTATE->m_pCurCourse.Set( NULL );
if( num != 0 )
{
const RString name = g_CourseMode.rows[0].choices[num];
Course *pCourse = SONGMAN->FindCourse( name );
int iCourseEntryIndex = -1;
FOREACH_CONST( CourseEntry, pCourse->m_vEntries, i )
{
if( i->songID.ToSong() == GAMESTATE->m_pCurSong.Get() )
iCourseEntryIndex = i - pCourse->m_vEntries.begin();
}
ASSERT( iCourseEntryIndex != -1 );
GAMESTATE->m_pCurCourse.Set( pCourse );
GAMESTATE->m_iEditCourseEntryIndex.Set( iCourseEntryIndex );
ASSERT( GAMESTATE->m_pCurCourse != NULL );
}
}
else if (SM == SM_BackFromKeysoundTrack)
{
const int track = ScreenMiniMenu::s_iLastRowCode;
const int tracks = m_NoteDataEdit.GetNumTracks();
const int row = this->GetRow();
unsigned int sound = ScreenMiniMenu::s_viLastAnswers[track];
vector<RString> &kses = m_pSong->m_vsKeysoundFile;
if (track < tracks)
{
if (sound == kses.size())
{
// create a new sound (filename), point it.
// if it's empty, make it an auto keysound.
ScreenTextEntry::TextEntry(SM_BackFromNewKeysound, NEW_KEYSOUND_FILE, "", 64);
return;
}
const TapNote &oldNote = m_NoteDataEdit.GetTapNote(track, row);
TapNote newNote = oldNote; // need to lose the const. not feeling like casting.
if (sound < kses.size())
{
// set note at this row to use this keysound file.
// if it's empty, make it an auto keysound.
newNote.iKeysoundIndex = sound;
if (newNote.type == TapNote::empty)
{
newNote.type = TapNote::autoKeysound; // keysounds need something non empty.
}
}
else // sound > kses.size()
{
// remove the sound. if it's an auto keysound, make it empty.
newNote.iKeysoundIndex = -1;
if (newNote.type == TapNote::autoKeysound)
{
newNote.type = TapNote::empty; // autoKeysound with no sound is pointless.
}
}
m_NoteDataEdit.SetTapNote(track, row, newNote);
}
else if (track == tracks)
{
kses.erase(kses.begin() + sound);
// TODO: Make the following a part of NoteData?
for (int t = 0; t < tracks; ++t)
{
FOREACH_NONEMPTY_ROW_IN_TRACK(m_NoteDataEdit, t, r)
{
const TapNote &oldNote = m_NoteDataEdit.GetTapNote(t, r);
TapNote newNote = oldNote; // need to lose the const. not feeling like casting.
if (newNote.iKeysoundIndex == static_cast<int>(sound))
{
newNote.iKeysoundIndex = -1;
if (newNote.type == TapNote::autoKeysound)
newNote.type = TapNote::empty;
}
else if (newNote.iKeysoundIndex > static_cast<int>(sound))
newNote.iKeysoundIndex--;
m_NoteDataEdit.SetTapNote(t, r, newNote);
}
}
}
SetDirty(true);
}
else if (SM == SM_BackFromNewKeysound && !ScreenTextEntry::s_bCancelledLast)
{
RString answer = ScreenTextEntry::s_sLastAnswer;
const int track = ScreenMiniMenu::s_iLastRowCode; // still keeps the same value.
const int row = this->GetRow();
const TapNote &oldNote = m_NoteDataEdit.GetTapNote(track, row);
TapNote newNote = oldNote; // need to lose the const. not feeling like casting.
vector<RString> &kses = m_pSong->m_vsKeysoundFile;
unsigned pos = find(kses.begin(), kses.end(), answer) - kses.begin();
if (pos == kses.size())
{
newNote.iKeysoundIndex = kses.size();
kses.push_back(answer);
}
else
{
newNote.iKeysoundIndex = pos;
}
if (newNote.type == TapNote::empty)
newNote.type = TapNote::autoKeysound; // keysounds need something non empty.
m_NoteDataEdit.SetTapNote(track, row, newNote);
SetDirty(true);
}
else if( SM == SM_BackFromOptions )
{
// The options may have changed the note skin.
m_NoteFieldRecord.CacheAllUsedNoteSkins();
m_Player->CacheAllUsedNoteSkins();
// stop any music that screen may have been playing
SOUND->StopMusic();
}
else if( SM == SM_BackFromInsertTapAttack )
{
int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0];
g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertTapAttack.rows[0].choices[iDurationChoice] );
SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertTapAttackPlayerOptions );
}
else if( SM == SM_BackFromInsertTapAttackPlayerOptions )
{
PlayerOptions poChosen = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetPreferred();
RString sMods = poChosen.GetString();
const int row = BeatToNoteRow( GAMESTATE->m_Position.m_fSongBeat );
TapNote tn(
TapNote::attack,
TapNote::SubType_Invalid,
TapNote::original,
sMods,
g_fLastInsertAttackDurationSeconds,
-1 );
tn.pn = m_InputPlayerNumber;
SetDirty( true );
SaveUndo();
m_NoteDataEdit.SetTapNote( g_iLastInsertTapAttackTrack, row, tn );
CheckNumberOfNotesAndUndo();
}
else if (SM == SM_BackFromEditingAttackStart && !ScreenTextEntry::s_bCancelledLast)
{
float time = StringToFloat(ScreenTextEntry::s_sLastAnswer);
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
Attack &attack = attacks[attackInProcess];
attack.fStartSecond = time;
SetDirty(true);
}
else if (SM == SM_BackFromEditingAttackLength && !ScreenTextEntry::s_bCancelledLast)
{
float time = StringToFloat(ScreenTextEntry::s_sLastAnswer);
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
Attack &attack = attacks[attackInProcess];
attack.fSecsRemaining = time;
SetDirty(true);
}
else if (SM == SM_BackFromAddingModToExistingAttack && !ScreenTextEntry::s_bCancelledLast)
{
RString mod = ScreenTextEntry::s_sLastAnswer;
Trim(mod);
if (mod.length() > 0)
{
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
Attack &attack = attacks[attackInProcess];
attack.sModifiers += "," + mod;
SetDirty(true);
}
}
else if (SM == SM_BackFromEditingModToExistingAttack && !ScreenTextEntry::s_bCancelledLast)
{
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
Attack &attack = attacks[attackInProcess];
vector<RString> mods;
split(attack.sModifiers, ",", mods);
RString mod = ScreenTextEntry::s_sLastAnswer;
Trim(mod);
if (mod.length() > 0)
{
mods[modInProcess - 2] = mod;
}
else
{
mods.erase(mods.begin() + (modInProcess - 2));
}
if (mods.size() > 0)
{
attack.sModifiers = join(",", mods);
}
else
{
attacks.erase(attacks.begin() + attackInProcess);
}
SetDirty(true);
}
else if (SM == SM_BackFromInsertStepAttack)
{
unsigned option = ScreenMiniMenu::s_iLastRowCode;
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
Attack &attack = attacks[attackInProcess];
vector<RString> mods;
split(attack.sModifiers, ",", mods);
modInProcess = option;
if (option == 0) // adjusting the starting time
{
ScreenTextEntry::TextEntry(SM_BackFromEditingAttackStart,
EDIT_ATTACK_START,
FloatToString(attack.fStartSecond),
10);
}
else if (option == 1) // adjusting the length of the attack
{
ScreenTextEntry::TextEntry(SM_BackFromEditingAttackLength,
EDIT_ATTACK_LENGTH,
FloatToString(attack.fSecsRemaining),
10);
}
else if (option >= 2 + mods.size()) // adding a new mod
{
ScreenTextEntry::TextEntry(SM_BackFromAddingModToExistingAttack,
ADD_NEW_MOD,
"",
64);
}
else // modifying existing mod.
{
ScreenTextEntry::TextEntry(SM_BackFromEditingModToExistingAttack,
EDIT_EXISTING_MOD,
mods[option - 2],
64);
}
}
else if (SM == SM_BackFromAddingAttackToChart && !ScreenTextEntry::s_bCancelledLast)
{
RString mod = ScreenTextEntry::s_sLastAnswer;
Trim(mod);
if (mod.length() > 0)
{
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
Attack attack;
attack.fStartSecond = GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat());
attack.fSecsRemaining = 5; // Users can change later.
attack.sModifiers = mod;
attacks.push_back(attack);
SetDirty(true);
}
}
else if (SM == SM_BackFromAttackAtTime)
{
int attackChoice = ScreenMiniMenu::s_iLastRowCode;
int attackDecision = ScreenMiniMenu::s_viLastAnswers[attackChoice];
const TimingData &timing = GetAppropriateTiming();
float startTime = timing.GetElapsedTimeFromBeat(GetBeat());
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
vector<int> points = FindAllAttacksAtTime(attacks, startTime);
if (attackChoice == (int)points.size())
{
// TODO: Add attack code.
ScreenTextEntry::TextEntry(SM_BackFromAddingAttackToChart,
ADD_NEW_ATTACK,
"",
64);
}
else
{
attackInProcess = points[attackChoice];
if (attackDecision == 1) // remove
{
attacks.erase(attacks.begin() + attackInProcess);
SetDirty(true);
}
else
{
Attack &attack = attacks[attackInProcess];
g_IndividualAttack.rows.clear();
g_IndividualAttack.rows.push_back(MenuRowDef(0,
"Starting Time",
true,
EditMode_CourseMods,
true,
true,
0,
NULL));
g_IndividualAttack.rows[0].SetOneUnthemedChoice(FloatToString(attack.fStartSecond));
g_IndividualAttack.rows.push_back(MenuRowDef(1,
"Secs Remaining",
true,
EditMode_CourseMods,
true,
true,
0,
NULL));
g_IndividualAttack.rows[1].SetOneUnthemedChoice(FloatToString(attack.fSecsRemaining));
vector<RString> mods;
split(attack.sModifiers, ",", mods);
for (unsigned i = 0; i < mods.size(); ++i)
{
unsigned col = i + 2;
g_IndividualAttack.rows.push_back(MenuRowDef(col,
ssprintf("Attack %d", i + 1),
true,
EditMode_CourseMods,
false,
true,
0,
NULL));
g_IndividualAttack.rows[col].SetOneUnthemedChoice(mods[i].c_str());
}
g_IndividualAttack.rows.push_back(MenuRowDef(mods.size() + 2,
"Add Mod",
true,
EditMode_CourseMods,
true,
true,
0,
NULL));
EditMiniMenu(&g_IndividualAttack, SM_BackFromInsertStepAttack);
}
}
}
else if (SM == SM_BackFromInsertStepAttack)
{
int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0];
const TimingData &timing = GetAppropriateTiming();
g_fLastInsertAttackPositionSeconds = timing.GetElapsedTimeFromBeat( GetBeat() );
g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertStepAttack.rows[0].choices[iDurationChoice] );
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks;
int iAttack = FindAttackAtTime(attacks, g_fLastInsertAttackPositionSeconds);
if (ScreenMiniMenu::s_iLastRowCode == ScreenEdit::remove )
{
if (iAttack > 0)
attacks.erase(attacks.begin() + iAttack);
}
else
{
ModsGroup<PlayerOptions> &toEdit = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
this->originalPlayerOptions.Assign(ModsLevel_Preferred, toEdit.GetPreferred());
PlayerOptions po;
if (iAttack >= 0)
po.FromString(attacks[iAttack].sModifiers);
toEdit.Assign( ModsLevel_Preferred, po );
SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertStepAttackPlayerOptions );
}
SetDirty(true);
}
else if( SM == SM_BackFromInsertCourseAttack )
{
int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0];
Course *pCourse = GAMESTATE->m_pCurCourse;
CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
int iAttack;
// TODO: Handle Song/Step Timing functions/switches here?
g_fLastInsertAttackPositionSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( GAMESTATE->m_Position.m_fSongBeat );
g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertCourseAttack.rows[0].choices[iDurationChoice] );
iAttack = FindAttackAtTime( ce.attacks, g_fLastInsertAttackPositionSeconds );
if( ScreenMiniMenu::s_iLastRowCode == ScreenEdit::remove )
{
ASSERT( iAttack >= 0 );
ce.attacks.erase( ce.attacks.begin() + iAttack );
}
else
{
PlayerOptions po;
if( iAttack >= 0 )
po.FromString( ce.attacks[iAttack].sModifiers );
GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.Assign( ModsLevel_Preferred, po );
SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertCourseAttackPlayerOptions );
}
}
else if (SM == SM_BackFromInsertStepAttackPlayerOptions)
{
ModsGroup<PlayerOptions> &toRestore = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
PlayerOptions poChosen = toRestore.GetPreferred();
RString mods = poChosen.GetString();
if (g_fLastInsertAttackPositionSeconds >= 0)
{
Attack a(ATTACK_LEVEL_1,
g_fLastInsertAttackPositionSeconds,
g_fLastInsertAttackDurationSeconds,
mods,
false,
false);
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks;
int index = FindAttackAtTime(attacks, g_fLastInsertAttackPositionSeconds);
if (index >= 0)
attacks[index] = a;
else
attacks.push_back(a);
}
toRestore.Assign(ModsLevel_Preferred, this->originalPlayerOptions.GetPreferred());
}
else if( SM == SM_BackFromInsertCourseAttackPlayerOptions )
{
PlayerOptions poChosen = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetPreferred();
RString sMods = poChosen.GetString();
Course *pCourse = GAMESTATE->m_pCurCourse;
CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
if( g_fLastInsertAttackPositionSeconds < 0 )
{
ce.sModifiers = sMods;
}
else
{
Attack a(
ATTACK_LEVEL_1,
g_fLastInsertAttackPositionSeconds,
g_fLastInsertAttackDurationSeconds,
sMods,
false,
false );
int iAttack = FindAttackAtTime( ce.attacks, g_fLastInsertAttackPositionSeconds );
if( iAttack >= 0 )
ce.attacks[iAttack] = a;
else
ce.attacks.push_back( a );
}
}
else if( SM == SM_DoRevertToLastSave )
{
if( ScreenPrompt::s_LastAnswer == ANSWER_YES )
{
SaveUndo();
CopyFromLastSave();
m_pSteps->GetNoteData( m_NoteDataEdit );
SetDirty( false );
}
}
else if( SM == SM_DoRevertFromDisk )
{
if( ScreenPrompt::s_LastAnswer == ANSWER_YES )
{
SaveUndo();
RevertFromDisk();
m_pSteps->GetNoteData( m_NoteDataEdit );
SetDirty( false );
}
}
else if( SM == SM_DoEraseStepTiming )
{
if( ScreenPrompt::s_LastAnswer == ANSWER_YES )
{
SaveUndo();
m_pSteps->m_Timing.Clear();
SetDirty( true );
}
}
else if( SM == SM_DoSaveAndExit ) // just asked "save before exiting? yes, no, cancel"
{
switch( ScreenPrompt::s_LastAnswer )
{
DEFAULT_FAIL( ScreenPrompt::s_LastAnswer );
case ANSWER_YES:
// This will send SM_SaveSuccessful or SM_SaveFailed.
HandleMainMenuChoice( ScreenEdit::save_on_exit );
return;
case ANSWER_NO:
// Don't save; just exit.
SCREENMAN->SendMessageToTopScreen( SM_DoExit );
return;
case ANSWER_CANCEL:
break; // do nothing
}
}
else if( SM == SM_SaveSuccessful )
{
LOG->Trace( "Save successful." );
CopyToLastSave();
SetDirty( false );
SONGMAN->Invalidate( GAMESTATE->m_pCurSong );
if( m_CurrentAction == save_on_exit )
ScreenPrompt::Prompt( SM_DoExit, SAVE_SUCCESSFUL );
else
SCREENMAN->SystemMessage( SAVE_SUCCESSFUL );
}
else if( SM == SM_SaveFailed ) // save failed; stay in the editor
{
/* We committed the steps to SongManager. Revert to the last save, and
* recommit the reversion to SongManager. */
LOG->Trace( "Save failed. Changes uncommitted from memory." );
CopyFromLastSave();
m_pSteps->SetNoteData( m_NoteDataEdit );
}
else if( SM == SM_DoExit )
{
// IMPORTANT: CopyFromLastSave before deleting the Steps below
CopyFromLastSave();
/* The user has been given a choice to save.
* Delete all unsaved steps before exiting the editor. */
/* FIXME: This code causes all the steps to be deleted if you quit
* without saving. However, without this code, any new steps will get
* saved on quit. -aj */
// At this point, the last good song copy is in use.
Song *pSong = GAMESTATE->m_pCurSong;
const vector<Steps*> &apSteps = pSong->GetAllSteps();
vector<Steps*> apToDelete;
FOREACH_CONST( Steps *, apSteps, s )
{
// If we're not on the same style, let it go.
if( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType != (*s)->m_StepsType )
continue;
// If autogenned, it isn't being saved.
if( (*s)->IsAutogen() )
continue;
// If the notedata has content, let it go.
if( !(*s)->GetNoteData().IsEmpty() )
continue;
// It's hard to say if these steps were saved to disk or not.
/*
if( !(*s)->GetSavedToDisk() )
continue;
*/
apToDelete.push_back( *s );
}
FOREACH_CONST( Steps *, apToDelete, s )
{
Steps *pSteps = *s;
pSong->DeleteSteps( pSteps );
if( m_pSteps == pSteps )
m_pSteps = NULL;
if( GAMESTATE->m_pCurSteps[PLAYER_1].Get() == pSteps )
GAMESTATE->m_pCurSteps[PLAYER_1].Set( NULL );
}
m_Out.StartTransitioning( SM_GoToNextScreen );
}
else if( SM == SM_GainFocus )
{
/* When another screen comes up, RageSounds takes over the sound timer. When we come
* back, put the timer back to where it was. */
SetBeat(m_fTrailingBeat);
}
else if( SM == SM_LoseFocus )
{
// Snap the trailing beat, in case we lose focus while tweening.
m_fTrailingBeat = GetBeat(); //GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat;
}
ScreenWithMenuElements::HandleScreenMessage( SM );
}
void ScreenEdit::OnSnapModeChange()
{
m_soundChangeSnap.Play();
NoteType nt = m_SnapDisplay.GetNoteType();
int iStepIndex = BeatToNoteRow( GetBeat() );
int iElementsPerNoteType = BeatToNoteRow( NoteTypeToBeat(nt) );
int iStepIndexHangover = iStepIndex % iElementsPerNoteType;
SetBeat( GetBeat() - NoteRowToBeat( iStepIndexHangover ) );
}
// Helper function for below
// Begin helper functions for InputEdit
static void ChangeDescription( const RString &sNew )
{
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
// Don't erase edit descriptions.
if( sNew.empty() && pSteps->GetDifficulty() == Difficulty_Edit )
return;
pSteps->SetDescription(sNew);
}
static void ChangeChartName( const RString &sNew )
{
Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
pSteps->SetChartName(sNew);
}
static void ChangeChartStyle( const RString &sNew )
{
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
pSteps->SetChartStyle(sNew);
}
static void ChangeStepCredit( const RString &sNew )
{
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
pSteps->SetCredit(sNew);
}
static void ChangeStepMeter( const RString &sNew )
{
int diff = StringToInt(sNew);
GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(max(diff, 1));
}
static void ChangeMainTitle( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sMainTitle = sNew;
}
static void ChangeSubTitle( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sSubTitle = sNew;
}
static void ChangeArtist( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sArtist = sNew;
}
static void ChangeGenre( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sGenre = sNew;
}
static void ChangeCredit( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sCredit = sNew;
}
static void ChangeMainTitleTranslit( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sMainTitleTranslit = sNew;
}
static void ChangeSubTitleTranslit( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sSubTitleTranslit = sNew;
}
static void ChangeArtistTranslit( const RString &sNew )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_sArtistTranslit = sNew;
}
static void ChangeLastSecondHint( const RString &sNew )
{
Song &s = *GAMESTATE->m_pCurSong;
s.SetSpecifiedLastSecond(StringToFloat(sNew));
}
static void ChangePreviewStart( const RString &sNew )
{
GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds = StringToFloat( sNew );
}
static void ChangePreviewLength( const RString &sNew )
{
GAMESTATE->m_pCurSong->m_fMusicSampleLengthSeconds = StringToFloat( sNew );
}
static void ChangeMinBPM( const RString &sNew )
{
GAMESTATE->m_pCurSong->m_fSpecifiedBPMMin = StringToFloat( sNew );
}
static void ChangeStepsMinBPM(const RString &sNew)
{
Steps *step = GAMESTATE->m_pCurSteps[PLAYER_1];
step->SetMinBPM(StringToFloat(sNew));
}
static void ChangeMaxBPM( const RString &sNew )
{
GAMESTATE->m_pCurSong->m_fSpecifiedBPMMax = StringToFloat( sNew );
}
static void ChangeStepsMaxBPM(const RString &sNew)
{
Steps *step = GAMESTATE->m_pCurSteps[PLAYER_1];
step->SetMaxBPM(StringToFloat(sNew));
}
const TimingData & ScreenEdit::GetAppropriateTiming() const
{
if( GAMESTATE->m_bIsUsingStepTiming )
{
return *m_pSteps->GetTimingData();
}
return m_pSong->m_SongTiming;
}
TimingData & ScreenEdit::GetAppropriateTimingForUpdate()
{
if( GAMESTATE->m_bIsUsingStepTiming )
{
// Copy from song if there is no step timing
if( m_pSteps->m_Timing.empty() )
m_pSteps->m_Timing = m_pSong->m_SongTiming;
return m_pSteps->m_Timing;
}
return m_pSong->m_SongTiming;
}
SongPosition & ScreenEdit::GetAppropriatePosition() const
{
if (GAMESTATE->m_bIsUsingStepTiming)
{
return GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position;
}
return GAMESTATE->m_Position;
}
inline void ScreenEdit::SetBeat(float fBeat)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
{
GAMESTATE->m_Position.m_fSongBeat = fBeat;
GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = m_pSteps->GetTimingData()->GetBeatFromElapsedTime(m_pSong->m_SongTiming.GetElapsedTimeFromBeat(fBeat));
}
else
{
GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = fBeat;
GAMESTATE->m_Position.m_fSongBeat = m_pSong->m_SongTiming.GetBeatFromElapsedTime(m_pSteps->GetTimingData()->GetElapsedTimeFromBeat(fBeat));
}
}
inline float ScreenEdit::GetBeat()
{
if( !GAMESTATE->m_bIsUsingStepTiming )
{
return GAMESTATE->m_Position.m_fSongBeat;
}
return GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat;
}
inline int ScreenEdit::GetRow()
{
return BeatToNoteRow(GetBeat());
}
void ScreenEdit::DisplayTimingMenu()
{
int row = GetRow();
const TimingData &pTime = GetAppropriateTiming();
bool bHasSpeedOnThisRow = pTime.GetSpeedSegmentAtRow( row )->GetRow() == row;
// bool bIsSelecting = ( (m_NoteFieldEdit.m_iEndMarker != -1) && (m_NoteFieldEdit.m_iBeginMarker != -1) );
g_TimingDataInformation.rows[beat_0_offset].SetOneUnthemedChoice( FloatToString(pTime.m_fBeat0OffsetInSeconds) );
g_TimingDataInformation.rows[bpm].SetOneUnthemedChoice( FloatToString(pTime.GetBPMAtRow( row ) ) );
g_TimingDataInformation.rows[stop].SetOneUnthemedChoice( FloatToString(pTime.GetStopAtRow( row ) ) ) ;
g_TimingDataInformation.rows[delay].SetOneUnthemedChoice( FloatToString(pTime.GetDelayAtRow( row ) ) );
g_TimingDataInformation.rows[time_signature].SetOneUnthemedChoice(
ssprintf( "%d / %d",
pTime.GetTimeSignatureSegmentAtRow(row)->GetNum(),
pTime.GetTimeSignatureSegmentAtRow(row)->GetDen()
)
);
g_TimingDataInformation.rows[label].SetOneUnthemedChoice( pTime.GetLabelAtRow( row ).c_str() );
g_TimingDataInformation.rows[tickcount].SetOneUnthemedChoice( ssprintf("%d", pTime.GetTickcountAtRow( row ) ) );
g_TimingDataInformation.rows[combo].SetOneUnthemedChoice( ssprintf("%d / %d",
pTime.GetComboAtRow( row ),
pTime.GetMissComboAtRow( row ) ) );
g_TimingDataInformation.rows[warp].SetOneUnthemedChoice( FloatToString(pTime.GetWarpAtRow( row ) ) );
g_TimingDataInformation.rows[speed_percent].SetOneUnthemedChoice( bHasSpeedOnThisRow ? FloatToString(pTime.GetSpeedPercentAtRow( row ) ) : "---" );
g_TimingDataInformation.rows[speed_wait].SetOneUnthemedChoice( bHasSpeedOnThisRow ? FloatToString(pTime.GetSpeedWaitAtRow( row ) ) : "---" );
RString starting = ( pTime.GetSpeedModeAtRow( row ) == 1 ? "Seconds" : "Beats" );
g_TimingDataInformation.rows[speed_mode].SetOneUnthemedChoice( starting.c_str() );
g_TimingDataInformation.rows[scroll].SetOneUnthemedChoice( FloatToString(pTime.GetScrollAtRow( row ) ) );
g_TimingDataInformation.rows[fake].SetOneUnthemedChoice( FloatToString(pTime.GetFakeAtRow( row ) ) );
// g_TimingDataInformation.rows[speed_percent].bEnabled = !bIsSelecting;
g_TimingDataInformation.rows[speed_wait].bEnabled = bHasSpeedOnThisRow;
g_TimingDataInformation.rows[speed_mode].bEnabled = bHasSpeedOnThisRow;
EditMiniMenu( &g_TimingDataInformation, SM_BackFromTimingDataInformation );
}
// End helper functions
static LocalizedString REVERT_LAST_SAVE ( "ScreenEdit", "Do you want to revert to your last save?" );
static LocalizedString DESTROY_ALL_UNSAVED_CHANGES ( "ScreenEdit", "This will destroy all unsaved changes." );
static LocalizedString REVERT_FROM_DISK ( "ScreenEdit", "Do you want to revert from disk?" );
static LocalizedString SAVE_CHANGES_BEFORE_EXITING ( "ScreenEdit", "Do you want to save changes before exiting?" );
void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector<int> &iAnswers )
{
GAMESTATE->SetProcessedTimingData(m_pSteps->GetTimingData());
switch( c )
{
DEFAULT_FAIL( c );
case play_selection:
if( m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1 )
HandleAlterMenuChoice( play );
else if( m_NoteFieldEdit.m_iBeginMarker!=-1 )
HandleMainMenuChoice( play_selection_start_to_end );
else
HandleMainMenuChoice( play_current_beat_to_end );
break;
case play_whole_song:
{
m_iStartPlayingAt = 0;
m_iStopPlayingAt = m_NoteDataEdit.GetLastRow();
TransitionEditState( STATE_PLAYING );
}
break;
case play_selection_start_to_end:
{
m_iStartPlayingAt = m_NoteFieldEdit.m_iBeginMarker;
m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() );
TransitionEditState( STATE_PLAYING );
}
break;
case play_current_beat_to_end:
{
m_iStartPlayingAt = BeatToNoteRow(GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat);
m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() );
TransitionEditState( STATE_PLAYING );
}
break;
case set_selection_start:
{
const int iCurrentRow = BeatToNoteRow(GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat);
if( m_NoteFieldEdit.m_iEndMarker!=-1 && iCurrentRow >= m_NoteFieldEdit.m_iEndMarker )
{
SCREENMAN->PlayInvalidSound();
}
else
{
m_NoteFieldEdit.m_iBeginMarker = iCurrentRow;
m_soundMarker.Play();
}
}
break;
case set_selection_end:
{
const int iCurrentRow = BeatToNoteRow(GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat);
if( m_NoteFieldEdit.m_iBeginMarker!=-1 && iCurrentRow <= m_NoteFieldEdit.m_iBeginMarker )
{
SCREENMAN->PlayInvalidSound();
}
else
{
m_NoteFieldEdit.m_iEndMarker = iCurrentRow;
m_soundMarker.Play();
}
}
break;
case edit_steps_information:
{
/* XXX: If the difficulty is changed from EDIT, and pSteps->WasLoadedFromProfile()
* is true, we should warn that the steps will no longer be saved to the profile. */
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
g_StepsInformation.rows[difficulty].choices.clear();
FOREACH_ENUM( Difficulty, dc )
{
g_StepsInformation.rows[difficulty].choices.push_back( "|" + CustomDifficultyToLocalizedString( GetCustomDifficulty(pSteps->m_StepsType, dc, CourseType_Invalid) ) );
}
g_StepsInformation.rows[difficulty].iDefaultChoice = pSteps->GetDifficulty();
g_StepsInformation.rows[difficulty].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
g_StepsInformation.rows[meter].SetOneUnthemedChoice( ssprintf("%d", pSteps->GetMeter()) );
g_StepsInformation.rows[meter].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Home);
g_StepsInformation.rows[predict_meter].SetOneUnthemedChoice( ssprintf("%.2f",pSteps->PredictMeter()) );
g_StepsInformation.rows[chartname].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
g_StepsInformation.rows[chartname].SetOneUnthemedChoice(pSteps->GetChartName());
g_StepsInformation.rows[description].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
g_StepsInformation.rows[description].SetOneUnthemedChoice( pSteps->GetDescription() );
g_StepsInformation.rows[chartstyle].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
g_StepsInformation.rows[chartstyle].SetOneUnthemedChoice( pSteps->GetChartStyle() );
g_StepsInformation.rows[step_credit].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
g_StepsInformation.rows[step_credit].SetOneUnthemedChoice( pSteps->GetCredit() );
g_StepsInformation.rows[step_display_bpm].iDefaultChoice = pSteps->GetDisplayBPM();
g_StepsInformation.rows[step_min_bpm].SetOneUnthemedChoice( FloatToString(pSteps->GetMinBPM()));
g_StepsInformation.rows[step_max_bpm].SetOneUnthemedChoice( FloatToString(pSteps->GetMaxBPM()));
EditMiniMenu( &g_StepsInformation, SM_BackFromStepsInformation, SM_None );
}
break;
case view_steps_data:
{
float fMusicSeconds = m_pSoundMusic->GetLengthSeconds();
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
const StepsTypeCategory &cat = GAMEMAN->GetStepsTypeInfo(pSteps->m_StepsType).m_StepsTypeCategory;
if (cat == StepsTypeCategory_Couple || cat == StepsTypeCategory_Routine)
{
pair<int, int> tmp = m_NoteDataEdit.GetNumTapNotesTwoPlayer();
g_StepsData.rows[tap_notes].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumJumpsTwoPlayer();
g_StepsData.rows[jumps].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumHandsTwoPlayer();
g_StepsData.rows[hands].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumQuadsTwoPlayer();
g_StepsData.rows[quads].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumHoldNotesTwoPlayer();
g_StepsData.rows[holds].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumMinesTwoPlayer();
g_StepsData.rows[mines].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumRollsTwoPlayer();
g_StepsData.rows[rolls].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumLiftsTwoPlayer();
g_StepsData.rows[lifts].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
tmp = m_NoteDataEdit.GetNumFakesTwoPlayer();
g_StepsData.rows[fakes].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
}
else
{
g_StepsData.rows[tap_notes].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumTapNotes()) );
g_StepsData.rows[jumps].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumJumps()) );
g_StepsData.rows[hands].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumHands()) );
g_StepsData.rows[quads].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumQuads()) );
g_StepsData.rows[holds].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumHoldNotes()) );
g_StepsData.rows[mines].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumMines()) );
g_StepsData.rows[rolls].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumRolls()) );
g_StepsData.rows[lifts].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumLifts()) );
g_StepsData.rows[fakes].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumFakes()) );
}
g_StepsData.rows[stream].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetStreamRadarValue(m_NoteDataEdit,fMusicSeconds)) );
g_StepsData.rows[voltage].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetVoltageRadarValue(m_NoteDataEdit,fMusicSeconds)) );
g_StepsData.rows[air].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetAirRadarValue(m_NoteDataEdit,fMusicSeconds)) );
g_StepsData.rows[freeze].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetFreezeRadarValue(m_NoteDataEdit,fMusicSeconds)) );
g_StepsData.rows[chaos].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetChaosRadarValue(m_NoteDataEdit,fMusicSeconds)) );
EditMiniMenu( &g_StepsData, SM_BackFromStepsData, SM_None );
break;
}
case save:
case save_on_exit:
{
m_CurrentAction = c;
// copy edit into current Steps
m_pSteps->SetNoteData( m_NoteDataEdit );
// don't forget the attacks.
m_pSong->m_Attacks = GAMESTATE->m_pCurSong->m_Attacks;
m_pSong->m_sAttackString = GAMESTATE->m_pCurSong->m_Attacks.ToVectorString();
m_pSteps->m_Attacks = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks;
m_pSteps->m_sAttackString = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.ToVectorString();
switch( EDIT_MODE.GetValue() )
{
DEFAULT_FAIL( EDIT_MODE.GetValue() );
case EditMode_Home:
{
ASSERT( m_pSteps->IsAnEdit() );
RString sError;
m_pSteps->CalculateRadarValues( m_pSong->m_fMusicLengthSeconds );
if( !NotesWriterSM::WriteEditFileToMachine(m_pSong, m_pSteps, sError) )
{
ScreenPrompt::Prompt( SM_None, sError );
break;
}
m_pSteps->SetSavedToDisk( true );
// HACK: clear undo, so "exit" below knows we don't need to save.
// This only works because important non-steps data can't be changed in
// home mode (BPMs, stops).
ClearUndo();
SCREENMAN->ZeroNextUpdate();
HandleScreenMessage( SM_SaveSuccessful );
/* FIXME
RString s;
switch( c )
{
case save: s = "ScreenMemcardSaveEditsAfterSave"; break;
case save_on_exit: s = "ScreenMemcardSaveEditsAfterExit"; break;
default: FAIL_M(ssprintf("Invalid menu choice: %i", c));
}
SCREENMAN->AddNewScreenToTop( s );
*/
}
break;
case EditMode_Full:
{
// This will recalculate radar values.
m_pSong->Save();
SCREENMAN->ZeroNextUpdate();
HandleScreenMessage( SM_SaveSuccessful );
}
break;
case EditMode_CourseMods:
case EditMode_Practice:
break;
}
m_soundSave.Play();
}
break;
case revert_to_last_save:
ScreenPrompt::Prompt( SM_DoRevertToLastSave, REVERT_LAST_SAVE.GetValue() + "\n\n" + DESTROY_ALL_UNSAVED_CHANGES.GetValue(), PROMPT_YES_NO, ANSWER_NO );
break;
case revert_from_disk:
ScreenPrompt::Prompt( SM_DoRevertFromDisk, REVERT_FROM_DISK.GetValue() + "\n\n" + DESTROY_ALL_UNSAVED_CHANGES.GetValue(), PROMPT_YES_NO, ANSWER_NO );
break;
case options:
SCREENMAN->AddNewScreenToTop( "ScreenEditOptions", SM_BackFromOptions );
break;
case edit_song_info:
{
const Song* pSong = GAMESTATE->m_pCurSong;
g_SongInformation.rows[main_title].SetOneUnthemedChoice( pSong->m_sMainTitle );
g_SongInformation.rows[sub_title].SetOneUnthemedChoice( pSong->m_sSubTitle );
g_SongInformation.rows[artist].SetOneUnthemedChoice( pSong->m_sArtist );
g_SongInformation.rows[genre].SetOneUnthemedChoice( pSong->m_sGenre );
g_SongInformation.rows[credit].SetOneUnthemedChoice( pSong->m_sCredit );
g_SongInformation.rows[main_title_transliteration].SetOneUnthemedChoice( pSong->m_sMainTitleTranslit );
g_SongInformation.rows[sub_title_transliteration].SetOneUnthemedChoice( pSong->m_sSubTitleTranslit );
g_SongInformation.rows[artist_transliteration].SetOneUnthemedChoice( pSong->m_sArtistTranslit );
g_SongInformation.rows[last_second_hint].SetOneUnthemedChoice( FloatToString(pSong->GetSpecifiedLastSecond()) );
g_SongInformation.rows[preview_start].SetOneUnthemedChoice( FloatToString(pSong->m_fMusicSampleStartSeconds) );
g_SongInformation.rows[preview_length].SetOneUnthemedChoice( FloatToString(pSong->m_fMusicSampleLengthSeconds) );
g_SongInformation.rows[display_bpm].iDefaultChoice = pSong->m_DisplayBPMType;
g_SongInformation.rows[min_bpm].SetOneUnthemedChoice( FloatToString(pSong->m_fSpecifiedBPMMin) );
g_SongInformation.rows[max_bpm].SetOneUnthemedChoice( FloatToString(pSong->m_fSpecifiedBPMMax) );
EditMiniMenu( &g_SongInformation, SM_BackFromSongInformation );
}
break;
case edit_timing_data:
{
DisplayTimingMenu();
}
break;
case play_preview_music:
PlayPreviewMusic();
break;
case exit:
switch( EDIT_MODE.GetValue() )
{
DEFAULT_FAIL( EDIT_MODE.GetValue() );
case EditMode_Full:
case EditMode_Home:
if( IsDirty() )
ScreenPrompt::Prompt( SM_DoSaveAndExit, SAVE_CHANGES_BEFORE_EXITING, PROMPT_YES_NO_CANCEL, ANSWER_CANCEL );
else
SCREENMAN->SendMessageToTopScreen( SM_DoExit );
break;
case EditMode_Practice:
case EditMode_CourseMods:
SCREENMAN->SendMessageToTopScreen( SM_DoExit );
break;
}
break;
};
GAMESTATE->SetProcessedTimingData(NULL);
}
void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector<int> &iAnswers, bool bAllowUndo)
{
ASSERT_M(m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1,
"You can only alter a selection of notes with a selection to begin with!");
bool bSaveUndo = true;
switch (c)
{
case play:
case record:
case cut:
case copy:
{
bSaveUndo = false;
}
default:
break;
}
if( bSaveUndo )
SetDirty( true );
/* We call HandleAreaMenuChoice recursively. Only the outermost
* HandleAreaMenuChoice should allow Undo so that the inner calls don't
* also save Undo and mess up the outermost */
if( !bAllowUndo )
bSaveUndo = false;
if( bSaveUndo )
SaveUndo();
switch(c)
{
case cut:
{
HandleAlterMenuChoice( copy );
HandleAlterMenuChoice( clear );
}
break;
case copy:
{
m_Clipboard.ClearAll();
m_Clipboard.CopyRange( m_NoteDataEdit, m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker );
}
break;
case clear:
{
m_NoteDataEdit.ClearRange( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker );
}
break;
case quantize:
{
NoteType nt = (NoteType)iAnswers[c];
NoteDataUtil::SnapToNearestNoteType(m_NoteDataEdit, nt, nt,
m_NoteFieldEdit.m_iBeginMarker,
m_NoteFieldEdit.m_iEndMarker );
break;
}
case turn:
{
const NoteData OldClipboard( m_Clipboard );
HandleAlterMenuChoice( cut );
StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
TurnType tt = (TurnType)iAnswers[c];
switch( tt )
{
DEFAULT_FAIL( tt );
case left: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::left ); break;
case right: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::right ); break;
case mirror: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::mirror ); break;
case turn_backwards: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::backwards ); break;
case shuffle: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::shuffle ); break;
case super_shuffle: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::super_shuffle ); break;
}
HandleAreaMenuChoice( paste_at_begin_marker );
m_Clipboard = OldClipboard;
}
break;
case transform:
{
int iBeginRow = m_NoteFieldEdit.m_iBeginMarker;
int iEndRow = m_NoteFieldEdit.m_iEndMarker;
TransformType tt = (TransformType)iAnswers[c];
StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
switch( tt )
{
DEFAULT_FAIL( tt );
case noholds: NoteDataUtil::RemoveHoldNotes( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case nomines: NoteDataUtil::RemoveMines( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case little: NoteDataUtil::Little( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case wide: NoteDataUtil::Wide( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case big: NoteDataUtil::Big( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case quick: NoteDataUtil::Quick( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case skippy: NoteDataUtil::Skippy( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case add_mines: NoteDataUtil::AddMines( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case echo: NoteDataUtil::Echo( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case stomp: NoteDataUtil::Stomp( m_NoteDataEdit, st, iBeginRow, iEndRow ); break;
case planted: NoteDataUtil::Planted( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case floored: NoteDataUtil::Floored( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case twister: NoteDataUtil::Twister( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case nojumps: NoteDataUtil::RemoveJumps( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case nohands: NoteDataUtil::RemoveHands( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case noquads: NoteDataUtil::RemoveQuads( m_NoteDataEdit, iBeginRow, iEndRow ); break;
case nostretch: NoteDataUtil::RemoveStretch( m_NoteDataEdit, st, iBeginRow, iBeginRow );break;
}
// bake in the additions
NoteDataUtil::ConvertAdditionsToRegular( m_NoteDataEdit );
break;
}
case alter:
{
const NoteData OldClipboard( m_Clipboard );
HandleAlterMenuChoice( cut );
AlterType at = (AlterType)iAnswers[c];
switch( at )
{
DEFAULT_FAIL( at );
case autogen_to_fill_width:
{
NoteData temp( m_Clipboard );
int iMaxNonEmptyTrack = NoteDataUtil::GetMaxNonEmptyTrack( temp );
if( iMaxNonEmptyTrack == -1 )
break;
temp.SetNumTracks( iMaxNonEmptyTrack+1 );
NoteDataUtil::LoadTransformedSlidingWindow( temp, m_Clipboard, m_Clipboard.GetNumTracks() );
NoteDataUtil::RemoveStretch( m_Clipboard, GAMESTATE->m_pCurSteps[0]->m_StepsType );
}
break;
case backwards: NoteDataUtil::Backwards( m_Clipboard ); break;
case swap_sides: NoteDataUtil::SwapSides( m_Clipboard ); break;
case copy_left_to_right: NoteDataUtil::CopyLeftToRight( m_Clipboard ); break;
case copy_right_to_left: NoteDataUtil::CopyRightToLeft( m_Clipboard ); break;
case clear_left: NoteDataUtil::ClearLeft( m_Clipboard ); break;
case clear_right: NoteDataUtil::ClearRight( m_Clipboard ); break;
case collapse_to_one: NoteDataUtil::CollapseToOne( m_Clipboard ); break;
case collapse_left: NoteDataUtil::CollapseLeft( m_Clipboard ); break;
case shift_left: NoteDataUtil::ShiftLeft( m_Clipboard ); break;
case shift_right: NoteDataUtil::ShiftRight( m_Clipboard ); break;
}
HandleAreaMenuChoice( paste_at_begin_marker );
m_Clipboard = OldClipboard;
break;
}
case tempo:
{
// This affects all steps.
AlterType at = (AlterType)iAnswers[c];
float fScale = -1;
switch( at )
{
DEFAULT_FAIL( at );
case compress_2x: fScale = 0.5f; break;
case compress_3_2: fScale = 2.0f/3; break;
case compress_4_3: fScale = 0.75f; break;
case expand_4_3: fScale = 4.0f/3; break;
case expand_3_2: fScale = 1.5f; break;
case expand_2x: fScale = 2; break;
}
int iStartIndex = m_NoteFieldEdit.m_iBeginMarker;
int iEndIndex = m_NoteFieldEdit.m_iEndMarker;
int iNewEndIndex = iEndIndex + lrintf( (iEndIndex - iStartIndex) * (fScale - 1) );
// scale currently editing notes
NoteDataUtil::ScaleRegion( m_NoteDataEdit, fScale, iStartIndex, iEndIndex );
// scale timing data
GetAppropriateTimingForUpdate().ScaleRegion(fScale,
m_NoteFieldEdit.m_iBeginMarker,
m_NoteFieldEdit.m_iEndMarker, true );
m_NoteFieldEdit.m_iEndMarker = iNewEndIndex;
break;
}
case play:
m_iStartPlayingAt = m_NoteFieldEdit.m_iBeginMarker;
m_iStopPlayingAt = m_NoteFieldEdit.m_iEndMarker;
TransitionEditState( STATE_PLAYING );
break;
case record:
m_iStartPlayingAt = m_NoteFieldEdit.m_iBeginMarker;
m_iStopPlayingAt = m_NoteFieldEdit.m_iEndMarker;
TransitionEditState( STATE_RECORDING );
break;
case preview_designation:
{
float fMarkerStart = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
float fMarkerEnd = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds = fMarkerStart;
GAMESTATE->m_pCurSong->m_fMusicSampleLengthSeconds = fMarkerEnd - fMarkerStart;
break;
}
case convert_to_pause:
{
float fMarkerStart = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
float fMarkerEnd = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
// The length of the stop segment we're going to create. This includes time spent in any
// stops in the selection, which will be deleted and subsumed into the new stop.
float fStopLength = fMarkerEnd - fMarkerStart;
// be sure not to clobber the row at the start - a row at the end
// can be dropped safely, though
NoteDataUtil::DeleteRows( m_NoteDataEdit,
m_NoteFieldEdit.m_iBeginMarker + 1,
m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker
);
GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker + 1,
m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker );
GetAppropriateTimingForUpdate().SetStopAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength );
m_NoteFieldEdit.m_iBeginMarker = -1;
m_NoteFieldEdit.m_iEndMarker = -1;
break;
}
case convert_to_delay:
{
float fMarkerStart = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
float fMarkerEnd = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
// The length of the delay segment we're going to create. This includes time spent in any
// stops in the selection, which will be deleted and subsumed into the new stop.
float fStopLength = fMarkerEnd - fMarkerStart;
NoteDataUtil::DeleteRows( m_NoteDataEdit,
m_NoteFieldEdit.m_iBeginMarker,
m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker
);
GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker,
m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker );
GetAppropriateTimingForUpdate().SetDelayAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength );
m_NoteFieldEdit.m_iBeginMarker = -1;
m_NoteFieldEdit.m_iEndMarker = -1;
break;
}
case convert_to_warp:
{
float startBeat = NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker);
float lengthBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) - startBeat;
GetAppropriateTimingForUpdate().SetWarpAtBeat(startBeat,lengthBeat);
SetDirty(true);
break;
}
case convert_to_attack:
{
float startBeat = NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker);
float endBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker);
const TimingData &timing = GetAppropriateTiming();
float &start = g_fLastInsertAttackPositionSeconds;
float &length = g_fLastInsertAttackDurationSeconds;
start = timing.GetElapsedTimeFromBeat(startBeat);
length = timing.GetElapsedTimeFromBeat(endBeat) - start;
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
m_pSteps->m_Attacks : m_pSong->m_Attacks;
int iAttack = FindAttackAtTime(attacks, start);
ModsGroup<PlayerOptions> &toEdit = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
this->originalPlayerOptions.Assign(ModsLevel_Preferred, toEdit.GetPreferred());
PlayerOptions po;
if (iAttack >= 0)
po.FromString(attacks[iAttack].sModifiers);
toEdit.Assign( ModsLevel_Preferred, po );
SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertStepAttackPlayerOptions );
SetDirty(true);
break;
}
case convert_to_fake:
{
int startRow = m_NoteFieldEdit.m_iBeginMarker;
float lengthBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) - NoteRowToBeat(startRow);
GetAppropriateTimingForUpdate().AddSegment( FakeSegment(startRow,lengthBeat) );
SetDirty(true);
break;
}
case routine_invert_notes:
{
NoteData &nd = this->m_NoteDataEdit;
NoteField &nf = this->m_NoteFieldEdit;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE(nd, r,
nf.m_iBeginMarker,
nf.m_iEndMarker)
{
for (int t = 0; t < nd.GetNumTracks(); t++)
{
const TapNote &tn = nd.GetTapNote(t, r);
if (tn.type != TapNote::empty)
{
TapNote nTap = tn;
nTap.pn = (tn.pn == PLAYER_1 ?
PLAYER_2 : PLAYER_1);
m_NoteDataEdit.SetTapNote(t, r, nTap);
}
}
}
break;
}
case routine_mirror_1_to_2:
case routine_mirror_2_to_1:
{
PlayerNumber oPN = (c == routine_mirror_1_to_2 ?
PLAYER_1 : PLAYER_2);
PlayerNumber nPN = (c == routine_mirror_1_to_2 ?
PLAYER_2 : PLAYER_1);
int nTrack = -1;
NoteData &nd = this->m_NoteDataEdit;
NoteField &nf = this->m_NoteFieldEdit;
int tracks = nd.GetNumTracks();
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE(nd, r,
nf.m_iBeginMarker,
nf.m_iEndMarker)
{
for (int t = 0; t < tracks; t++)
{
const TapNote &tn = nd.GetTapNote(t, r);
if (tn.type != TapNote::empty && tn.pn == oPN)
{
TapNote nTap = tn;
nTap.pn = nPN;
StepsType curType = GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType;
// TODO: Find a better way to do this.
if (curType == StepsType_dance_routine)
{
nTrack = tracks - t - 1;
}
else if (curType == StepsType_pump_routine)
{
switch (t)
{
case 0: nTrack = 8; break;
case 1: nTrack = 9; break;
case 2: nTrack = 7; break;
case 3: nTrack = 5; break;
case 4: nTrack = 6; break;
case 5: nTrack = 3; break;
case 6: nTrack = 4; break;
case 7: nTrack = 2; break;
case 8: nTrack = 0; break;
case 9: nTrack = 1; break;
default: FAIL_M(ssprintf("Invalid column %d for pump-routine", t)); break;
}
}
m_NoteDataEdit.SetTapNote(nTrack, r, nTap);
}
}
}
break;
}
default: break;
}
}
void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector<int> &iAnswers, bool bAllowUndo )
{
bool bSaveUndo = true;
switch( c )
{
case clear_clipboard:
case undo:
bSaveUndo = false;
break;
default:
break;
}
if( bSaveUndo )
SetDirty( true );
/* We call HandleAreaMenuChoice recursively. Only the outermost
* HandleAreaMenuChoice should allow Undo so that the inner calls don't
* also save Undo and mess up the outermost */
if( !bAllowUndo )
bSaveUndo = false;
if( bSaveUndo )
SaveUndo();
switch( c )
{
DEFAULT_FAIL( c );
case paste_at_current_beat:
case paste_at_begin_marker:
{
int iDestFirstRow = -1;
switch( c )
{
DEFAULT_FAIL( c );
case paste_at_current_beat:
iDestFirstRow = BeatToNoteRow( GetAppropriatePosition().m_fSongBeat );
break;
case paste_at_begin_marker:
ASSERT( m_NoteFieldEdit.m_iBeginMarker!=-1 );
iDestFirstRow = m_NoteFieldEdit.m_iBeginMarker;
break;
}
int iRowsToCopy = m_Clipboard.GetLastRow()+1;
m_NoteDataEdit.CopyRange( m_Clipboard, 0, iRowsToCopy, iDestFirstRow );
}
break;
case insert_and_shift:
NoteDataUtil::InsertRows( m_NoteDataEdit, GetRow(),
iAnswers.size() > 0 ?
NoteTypeToRow((NoteType)iAnswers[c]) : 48);
break;
case delete_and_shift:
NoteDataUtil::DeleteRows( m_NoteDataEdit, GetRow(),
iAnswers.size() > 0 ?
NoteTypeToRow((NoteType)iAnswers[c]) : 48);
break;
case shift_pauses_forward:
GetAppropriateTimingForUpdate().InsertRows( GetRow(),
iAnswers.size() > 0 ?
NoteTypeToRow((NoteType)iAnswers[c]) : 48);
break;
case shift_pauses_backward:
GetAppropriateTimingForUpdate().DeleteRows( GetRow() + 1,
iAnswers.size() > 0 ?
NoteTypeToRow((NoteType)iAnswers[c]) : 48);
break;
case convert_pause_to_beat:
{
float fStopSeconds = GetAppropriateTiming().GetStopAtRow(GetRow());
GetAppropriateTimingForUpdate().SetStopAtBeat( GetBeat() , 0 );
float fStopBeats = fStopSeconds * GetAppropriateTiming().GetBPMAtBeat( GetBeat() ) / 60;
// don't move the step from where it is, just move everything later
NoteDataUtil::InsertRows( m_NoteDataEdit, GetRow() + 1, BeatToNoteRow(fStopBeats) );
GetAppropriateTimingForUpdate().InsertRows( GetRow() + 1, BeatToNoteRow(fStopBeats) );
}
break;
case convert_delay_to_beat:
{
TimingData &timing = GetAppropriateTimingForUpdate();
float pause = timing.GetDelayAtRow(GetRow());
timing.SetDelayAtRow(GetRow(), 0);
float pauseBeats = pause * timing.GetBPMAtBeat(GetBeat()) / 60;
NoteDataUtil::InsertRows(m_NoteDataEdit, GetRow(), BeatToNoteRow(pauseBeats));
timing.InsertRows(GetRow(), BeatToNoteRow(pauseBeats));
break;
}
case last_second_at_beat:
{
const TimingData &timing = GetAppropriateTiming();
Song &s = *GAMESTATE->m_pCurSong;
s.SetSpecifiedLastSecond(timing.GetElapsedTimeFromBeat(GetBeat()));
break;
}
case undo:
Undo();
break;
case clear_clipboard:
{
m_Clipboard.ClearAll();
break;
}
case modify_attacks_at_row:
{
this->DoStepAttackMenu();
break;
}
case modify_keysounds_at_row:
{
this->DoKeyboardTrackMenu();
break;
}
};
if( bSaveUndo )
CheckNumberOfNotesAndUndo();
}
void ScreenEdit::HandleStepsDataChoice( StepsDataChoice c, const vector<int> &iAnswers )
{
return; // nothing is done with the choices. Yet.
}
static LocalizedString ENTER_NEW_DESCRIPTION( "ScreenEdit", "Enter a new description." );
static LocalizedString ENTER_NEW_CHART_NAME("ScreenEdit", "Enter a new chart name.");
static LocalizedString ENTER_NEW_CHART_STYLE( "ScreenEdit", "Enter a new chart style." );
static LocalizedString ENTER_NEW_STEP_AUTHOR( "ScreenEdit", "Enter the author who made this step pattern." );
static LocalizedString ENTER_NEW_METER( "ScreenEdit", "Enter a new meter." );
static LocalizedString ENTER_MIN_BPM ("ScreenEdit","Enter a new min BPM.");
static LocalizedString ENTER_MAX_BPM ("ScreenEdit","Enter a new max BPM.");
void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const vector<int> &iAnswers )
{
Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
Difficulty dc = (Difficulty)iAnswers[difficulty];
pSteps->SetDifficulty( dc );
pSteps->SetDisplayBPM(static_cast<DisplayBPM>(iAnswers[step_display_bpm]));
switch( c )
{
case chartname:
{
ScreenTextEntry::TextEntry(SM_None,
ENTER_NEW_CHART_NAME,
m_pSteps->GetChartName(),
MAX_STEPS_DESCRIPTION_LENGTH,
SongUtil::ValidateCurrentStepsChartName,
ChangeChartName,
NULL);
break;
}
case description:
{
ScreenTextEntry::TextEntry(SM_None,
ENTER_NEW_DESCRIPTION,
m_pSteps->GetDescription(),
MAX_STEPS_DESCRIPTION_LENGTH,
SongUtil::ValidateCurrentStepsDescription,
ChangeDescription,
NULL);
break;
}
case chartstyle:
{
ScreenTextEntry::TextEntry(SM_None,
ENTER_NEW_CHART_STYLE,
m_pSteps->GetChartStyle(),
255,
NULL,
ChangeChartStyle,
NULL);
break;
}
case step_credit:
{
ScreenTextEntry::TextEntry(SM_None,
ENTER_NEW_STEP_AUTHOR,
m_pSteps->GetCredit(),
255,
SongUtil::ValidateCurrentStepsCredit,
ChangeStepCredit,
NULL);
break;
}
case meter:
{
ScreenTextEntry::TextEntry(SM_BackFromDifficultyMeterChange,
ENTER_NEW_METER,
ssprintf("%d", m_pSteps->GetMeter()),
4,
NULL,
ChangeStepMeter,
NULL);
break;
}
case step_min_bpm:
{
ScreenTextEntry::TextEntry(SM_None, ENTER_MIN_BPM,
FloatToString(pSteps->GetMinBPM()), 20,
ScreenTextEntry::FloatValidate,
ChangeStepsMinBPM, NULL);
break;
}
case step_max_bpm:
{
ScreenTextEntry::TextEntry(SM_None, ENTER_MAX_BPM,
FloatToString(pSteps->GetMaxBPM()), 20,
ScreenTextEntry::FloatValidate,
ChangeStepsMaxBPM, NULL);
break;
}
default:
break;
}
SetDirty(true);
}
static LocalizedString ENTER_MAIN_TITLE ("ScreenEdit","Enter a new main title.");
static LocalizedString ENTER_SUB_TITLE ("ScreenEdit","Enter a new sub title.");
static LocalizedString ENTER_ARTIST ("ScreenEdit","Enter a new artist.");
static LocalizedString ENTER_GENRE ("ScreenEdit","Enter a new genre.");
static LocalizedString ENTER_CREDIT ("ScreenEdit","Enter a new credit.");
static LocalizedString ENTER_MAIN_TITLE_TRANSLIT ("ScreenEdit","Enter a new main title transliteration.");
static LocalizedString ENTER_SUB_TITLE_TRANSLIT ("ScreenEdit","Enter a new sub title transliteration.");
static LocalizedString ENTER_ARTIST_TRANSLIT ("ScreenEdit","Enter a new artist transliteration.");
static LocalizedString ENTER_LAST_SECOND_HINT ("ScreenEdit","Enter a new last second hint.");
static LocalizedString ENTER_PREVIEW_START ("ScreenEdit","Enter a new preview start.");
static LocalizedString ENTER_PREVIEW_LENGTH ("ScreenEdit","Enter a new preview length.");
void ScreenEdit::HandleSongInformationChoice( SongInformationChoice c, const vector<int> &iAnswers )
{
Song* pSong = GAMESTATE->m_pCurSong;
pSong->m_DisplayBPMType = static_cast<DisplayBPM>(iAnswers[display_bpm]);
switch( c )
{
case main_title:
ScreenTextEntry::TextEntry( SM_None, ENTER_MAIN_TITLE, pSong->m_sMainTitle, 100, NULL, ChangeMainTitle, NULL );
break;
case sub_title:
ScreenTextEntry::TextEntry( SM_None, ENTER_SUB_TITLE, pSong->m_sSubTitle, 100, NULL, ChangeSubTitle, NULL );
break;
case artist:
ScreenTextEntry::TextEntry( SM_None, ENTER_ARTIST, pSong->m_sArtist, 100, NULL, ChangeArtist, NULL );
break;
case genre:
ScreenTextEntry::TextEntry( SM_None, ENTER_GENRE, pSong->m_sGenre, 100, NULL, ChangeGenre, NULL );
break;
case credit:
ScreenTextEntry::TextEntry( SM_None, ENTER_CREDIT, pSong->m_sCredit, 100, NULL, ChangeCredit, NULL );
break;
case main_title_transliteration:
ScreenTextEntry::TextEntry( SM_None, ENTER_MAIN_TITLE_TRANSLIT, pSong->m_sMainTitleTranslit, 100, NULL, ChangeMainTitleTranslit, NULL );
break;
case sub_title_transliteration:
ScreenTextEntry::TextEntry( SM_None, ENTER_SUB_TITLE_TRANSLIT, pSong->m_sSubTitleTranslit, 100, NULL, ChangeSubTitleTranslit, NULL );
break;
case artist_transliteration:
ScreenTextEntry::TextEntry( SM_None, ENTER_ARTIST_TRANSLIT, pSong->m_sArtistTranslit, 100, NULL, ChangeArtistTranslit, NULL );
break;
case last_second_hint:
ScreenTextEntry::TextEntry( SM_None, ENTER_LAST_SECOND_HINT,
FloatToString(pSong->GetSpecifiedLastSecond()), 20,
ScreenTextEntry::FloatValidate, ChangeLastSecondHint, NULL );
break;
case preview_start:
ScreenTextEntry::TextEntry( SM_None, ENTER_PREVIEW_START,
FloatToString(pSong->m_fMusicSampleStartSeconds), 20,
ScreenTextEntry::FloatValidate, ChangePreviewStart, NULL );
break;
case preview_length:
ScreenTextEntry::TextEntry( SM_None, ENTER_PREVIEW_LENGTH,
FloatToString(pSong->m_fMusicSampleLengthSeconds), 20,
ScreenTextEntry::FloatValidate, ChangePreviewLength, NULL );
break;
case min_bpm:
ScreenTextEntry::TextEntry( SM_None, ENTER_MIN_BPM,
FloatToString(pSong->m_fSpecifiedBPMMin), 20,
ScreenTextEntry::FloatValidate, ChangeMinBPM, NULL );
break;
case max_bpm:
ScreenTextEntry::TextEntry( SM_None, ENTER_MAX_BPM,
FloatToString(pSong->m_fSpecifiedBPMMax), 20,
ScreenTextEntry::FloatValidate, ChangeMaxBPM, NULL );
break;
default: break;
};
SetDirty(true);
}
static LocalizedString ENTER_BEAT_0_OFFSET ( "ScreenEdit", "Enter the offset for the song.");
static LocalizedString ENTER_BPM_VALUE ( "ScreenEdit", "Enter a new BPM value." );
static LocalizedString ENTER_STOP_VALUE ( "ScreenEdit", "Enter a new Stop value." );
static LocalizedString ENTER_DELAY_VALUE ( "ScreenEdit", "Enter a new Delay value." );
static LocalizedString ENTER_TIME_SIGNATURE_VALUE ( "ScreenEdit", "Enter a new Time Signature." );
static LocalizedString ENTER_TICKCOUNT_VALUE ( "ScreenEdit", "Enter a new Tickcount value." );
static LocalizedString ENTER_COMBO_VALUE ( "ScreenEdit", "Enter a new Combo value." );
static LocalizedString ENTER_LABEL_VALUE ( "ScreenEdit", "Enter a new Label value." );
static LocalizedString ENTER_WARP_VALUE ( "ScreenEdit", "Enter a new Warp value." );
static LocalizedString ENTER_SPEED_PERCENT_VALUE ( "ScreenEdit", "Enter a new Speed percent value." );
static LocalizedString ENTER_SPEED_WAIT_VALUE ( "ScreenEdit", "Enter a new Speed wait value." );
static LocalizedString ENTER_SPEED_MODE_VALUE ( "ScreenEdit", "Enter a new Speed mode value." );
static LocalizedString ENTER_SCROLL_VALUE ( "ScreenEdit", "Enter a new Scroll value." );
static LocalizedString ENTER_FAKE_VALUE ( "ScreenEdit", "Enter a new Fake value." );
static LocalizedString CONFIRM_TIMING_ERASE ( "ScreenEdit", "Are you sure you want to erase this chart's timing data?" );
void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice c, const vector<int> &iAnswers )
{
switch( c )
{
DEFAULT_FAIL( c );
case beat_0_offset:
ScreenTextEntry::TextEntry(
SM_BackFromBeat0Change,
ENTER_BEAT_0_OFFSET,
FloatToString(GetAppropriateTiming().m_fBeat0OffsetInSeconds),
20
);
break;
case bpm:
ScreenTextEntry::TextEntry(
SM_BackFromBPMChange,
ENTER_BPM_VALUE,
FloatToString( GetAppropriateTiming().GetBPMAtBeat( GetBeat() ) ),
10
);
break;
case stop:
ScreenTextEntry::TextEntry(
SM_BackFromStopChange,
ENTER_STOP_VALUE,
FloatToString( GetAppropriateTiming().GetStopAtBeat( GetBeat() ) ),
10
);
break;
case delay:
ScreenTextEntry::TextEntry(
SM_BackFromDelayChange,
ENTER_DELAY_VALUE,
FloatToString( GetAppropriateTiming().GetDelayAtBeat( GetBeat() ) ),
10
);
break;
case time_signature:
{
const TimeSignatureSegment *ts = GetAppropriateTiming().GetTimeSignatureSegmentAtBeat( GetBeat() );
ScreenTextEntry::TextEntry(
SM_BackFromTimeSignatureChange,
ENTER_TIME_SIGNATURE_VALUE,
ssprintf( "%d/%d", ts->GetNum(), ts->GetDen() ),
8
);
break;
}
case tickcount:
ScreenTextEntry::TextEntry(
SM_BackFromTickcountChange,
ENTER_TICKCOUNT_VALUE,
ssprintf( "%d", GetAppropriateTiming().GetTickcountAtBeat( GetBeat() ) ),
2
);
break;
case combo:
{
const ComboSegment *cs = GetAppropriateTiming().GetComboSegmentAtBeat(GetBeat());
ScreenTextEntry::TextEntry(SM_BackFromComboChange,
ENTER_COMBO_VALUE,
ssprintf( "%d/%d",
cs->GetCombo(),
cs->GetMissCombo()),
7);
break;
}
case label:
ScreenTextEntry::TextEntry(
SM_BackFromLabelChange,
ENTER_LABEL_VALUE,
ssprintf( "%s", GetAppropriateTiming().GetLabelAtBeat( GetBeat() ).c_str() ),
64
);
break;
case warp:
ScreenTextEntry::TextEntry(
SM_BackFromWarpChange,
ENTER_WARP_VALUE,
FloatToString( GetAppropriateTiming().GetWarpAtBeat( GetBeat() ) ),
10
);
break;
case speed_percent:
ScreenTextEntry::TextEntry(
SM_BackFromSpeedPercentChange,
ENTER_SPEED_PERCENT_VALUE,
FloatToString( GetAppropriateTiming().GetSpeedSegmentAtBeat( GetBeat() )->GetRatio() ),
10
);
break;
case scroll:
ScreenTextEntry::TextEntry(
SM_BackFromScrollChange,
ENTER_SCROLL_VALUE,
FloatToString( GetAppropriateTiming().GetScrollSegmentAtBeat( GetBeat() )->GetRatio() ),
10
);
break;
case speed_wait:
ScreenTextEntry::TextEntry(
SM_BackFromSpeedWaitChange,
ENTER_SPEED_WAIT_VALUE,
FloatToString( GetAppropriateTiming().GetSpeedSegmentAtBeat( GetBeat() )->GetDelay() ),
10
);
break;
case speed_mode:
{
ScreenTextEntry::TextEntry(
SM_BackFromSpeedModeChange,
ENTER_SPEED_MODE_VALUE,
"",
3
);
break;
}
case fake:
{
ScreenTextEntry::TextEntry(
SM_BackFromFakeChange,
ENTER_FAKE_VALUE,
FloatToString(GetAppropriateTiming().GetFakeAtBeat( GetBeat() ) ),
10
);
break;
}
case copy_full_timing:
{
clipboardFullTiming = GetAppropriateTiming();
break;
}
case paste_full_timing:
{
if (GAMESTATE->m_bIsUsingStepTiming)
{
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = clipboardFullTiming;
}
else
{
GAMESTATE->m_pCurSong->m_SongTiming = clipboardFullTiming;
}
SetDirty(true);
break;
}
case erase_step_timing:
ScreenPrompt::Prompt( SM_DoEraseStepTiming, CONFIRM_TIMING_ERASE , PROMPT_YES_NO, ANSWER_NO );
break;
}
}
void ScreenEdit::HandleBGChangeChoice( BGChangeChoice c, const vector<int> &iAnswers )
{
BackgroundChange newChange;
FOREACH( BackgroundChange, m_pSong->GetBackgroundChanges(g_CurrentBGChangeLayer), iter )
{
if( iter->m_fStartBeat == GAMESTATE->m_Position.m_fSongBeat )
{
newChange = *iter;
// delete the old change. We'll add a new one below.
m_pSong->GetBackgroundChanges(g_CurrentBGChangeLayer).erase( iter );
break;
}
}
newChange.m_fStartBeat = GAMESTATE->m_Position.m_fSongBeat;
newChange.m_fRate = StringToFloat( g_BackgroundChange.rows[rate].choices[iAnswers[rate]] )/100.f;
newChange.m_sTransition = iAnswers[transition] ? g_BackgroundChange.rows[transition].choices[iAnswers[transition]] : RString();
newChange.m_def.m_sEffect = iAnswers[effect] ? g_BackgroundChange.rows[effect].choices[iAnswers[effect]] : RString();
newChange.m_def.m_sColor1 = iAnswers[color1] ? g_BackgroundChange.rows[color1].choices[iAnswers[color1]] : RString();
newChange.m_def.m_sColor2 = iAnswers[color2] ? g_BackgroundChange.rows[color2].choices[iAnswers[color2]] : RString();
switch( iAnswers[file1_type] )
{
DEFAULT_FAIL( iAnswers[file1_type] );
case none: newChange.m_def.m_sFile1 = ""; break;
case dynamic_random: newChange.m_def.m_sFile1 = RANDOM_BACKGROUND_FILE; break;
case baked_random: newChange.m_def.m_sFile1 = GetOneBakedRandomFile( m_pSong ); break;
case song_bganimation:
case song_movie:
case song_bitmap:
case global_bganimation:
case global_movie:
case global_movie_song_group:
case global_movie_song_group_and_genre:
{
BGChangeChoice row1 = (BGChangeChoice)(file1_song_bganimation + iAnswers[file1_type]);
newChange.m_def.m_sFile1 = g_BackgroundChange.rows[row1].choices.empty() ? "" : g_BackgroundChange.rows[row1].choices[iAnswers[row1]];
}
break;
}
switch( iAnswers[file2_type] )
{
DEFAULT_FAIL(iAnswers[file2_type]);
case none: newChange.m_def.m_sFile2 = ""; break;
case dynamic_random: newChange.m_def.m_sFile2 = RANDOM_BACKGROUND_FILE; break;
case baked_random: newChange.m_def.m_sFile2 = GetOneBakedRandomFile( m_pSong ); break;
case song_bganimation:
case song_movie:
case song_bitmap:
case global_bganimation:
case global_movie:
case global_movie_song_group:
case global_movie_song_group_and_genre:
{
BGChangeChoice row2 = (BGChangeChoice)(file2_song_bganimation + iAnswers[file2_type]);
newChange.m_def.m_sFile2 = g_BackgroundChange.rows[row2].choices.empty() ? "" : g_BackgroundChange.rows[row2].choices[iAnswers[row2]];
}
break;
}
if( c == delete_change || newChange.m_def.m_sFile1.empty() )
{
// don't add
}
else
{
m_pSong->AddBackgroundChange( g_CurrentBGChangeLayer, newChange );
}
g_CurrentBGChangeLayer = BACKGROUND_LAYER_Invalid;
}
void ScreenEdit::SetupCourseAttacks()
{
/* This is the first beat that can be changed without it being visible. Until
* we draw for the first time, any beat can be changed. */
GAMESTATE->m_pPlayerState[PLAYER_1]->m_fLastDrawnBeat = -100;
// Put course options into effect.
GAMESTATE->m_pPlayerState[PLAYER_1]->m_ModsToApply.clear();
GAMESTATE->m_pPlayerState[PLAYER_1]->RemoveActiveAttacks();
if( GAMESTATE->m_pCurCourse )
{
AttackArray Attacks;
if( EDIT_MODE == EditMode_CourseMods )
{
Attacks = GAMESTATE->m_pCurCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex].attacks;
}
else
{
GAMESTATE->m_pCurCourse->RevertFromDisk(); // Remove this and have a separate reload key?
for( unsigned e = 0; e < GAMESTATE->m_pCurCourse->m_vEntries.size(); ++e )
{
if( GAMESTATE->m_pCurCourse->m_vEntries[e].songID.ToSong() != m_pSong )
continue;
Attacks = GAMESTATE->m_pCurCourse->m_vEntries[e].attacks;
break;
}
}
FOREACH( Attack, Attacks, attack )
GAMESTATE->m_pPlayerState[PLAYER_1]->LaunchAttack( *attack );
}
else
{
const PlayerOptions &p = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent();
if (GAMESTATE->m_pCurSong && p.m_fNoAttack == 0 && p.m_fRandAttack == 0 )
{
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks :
GAMESTATE->m_pCurSong->m_Attacks;
if (attacks.size() > 0)
{
FOREACH(Attack, attacks, attack)
{
float fBeat = GetAppropriateTiming().GetBeatFromElapsedTime(attack->fStartSecond);
if (fBeat >= GetBeat())
GAMESTATE->m_pPlayerState[PLAYER_1]->LaunchAttack( *attack );
}
}
}
}
GAMESTATE->m_pPlayerState[PLAYER_1]->RebuildPlayerOptionsFromActiveAttacks();
}
void ScreenEdit::CopyToLastSave()
{
ASSERT( GAMESTATE->m_pCurSong != NULL );
ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != NULL );
m_SongLastSave = *GAMESTATE->m_pCurSong;
m_vStepsLastSave.clear();
const vector<Steps*> &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType );
FOREACH_CONST( Steps*, vSteps, it )
m_vStepsLastSave.push_back( **it );
}
void ScreenEdit::CopyFromLastSave()
{
// We are assuming two things here:
// 1) No steps can be created by ScreenEdit
// 2) No steps can be deleted by ScreenEdit (except possibly when we exit)
*GAMESTATE->m_pCurSong = m_SongLastSave;
const vector<Steps*> &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType );
ASSERT_M( vSteps.size() == m_vStepsLastSave.size(), ssprintf("Step sizes don't match: %d, %d", int(vSteps.size()), int(m_vStepsLastSave.size())) );
for( unsigned i = 0; i < vSteps.size(); ++i )
*vSteps[i] = m_vStepsLastSave[i];
}
void ScreenEdit::RevertFromDisk()
{
ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != NULL );
StepsID id;
id.FromSteps( GAMESTATE->m_pCurSteps[PLAYER_1] );
ASSERT( id.IsValid() );
GAMESTATE->m_pCurSong->ReloadFromSongDir( GAMESTATE->m_pCurSong->GetSongDir() );
Steps *pNewSteps = id.ToSteps( GAMESTATE->m_pCurSong, true );
if( !pNewSteps )
{
// If the Steps we were currently editing vanished when we did the revert,
// put a blank Steps in its place. Note that this does not have to be the
// work of someone maliciously changing the simfile; it could happen to
// someone editing a new stepchart and reverting from disk, for example.
pNewSteps = GAMESTATE->m_pCurSong->CreateSteps();
pNewSteps->CreateBlank( id.GetStepsType() );
pNewSteps->SetDifficulty( id.GetDifficulty() );
GAMESTATE->m_pCurSong->AddSteps( pNewSteps );
}
GAMESTATE->m_pCurSteps[PLAYER_1].Set( pNewSteps );
m_pSteps = pNewSteps;
CopyToLastSave();
SetDirty( false );
SONGMAN->Invalidate( GAMESTATE->m_pCurSong );
}
void ScreenEdit::SaveUndo()
{
m_bHasUndo = true;
m_Undo.CopyAll( m_NoteDataEdit );
}
static LocalizedString UNDO ("ScreenEdit", "Undo");
static LocalizedString CANT_UNDO ("ScreenEdit", "Can't undo - no undo data.");
void ScreenEdit::Undo()
{
if( m_bHasUndo )
{
swap( m_Undo, m_NoteDataEdit );
SCREENMAN->SystemMessage( UNDO );
}
else
{
SCREENMAN->SystemMessage( CANT_UNDO );
SCREENMAN->PlayInvalidSound();
}
}
void ScreenEdit::ClearUndo()
{
m_bHasUndo = false;
m_Undo.ClearAll();
}
static LocalizedString CREATES_MORE_THAN_NOTES ( "ScreenEdit", "This change creates more than %d notes in a measure." );
static LocalizedString CREATES_NOTES_PAST_END ( "ScreenEdit", "This change creates notes past the end of the music and is not allowed." );
static LocalizedString CHANGE_REVERTED ( "ScreenEdit", "The change has been reverted." );
void ScreenEdit::CheckNumberOfNotesAndUndo()
{
if( EDIT_MODE.GetValue() != EditMode_Home )
return;
const float fBeat = GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat;
const TimeSignatureSegment * curTime = GAMESTATE->m_pCurSong->m_SongTiming.GetTimeSignatureSegmentAtBeat( fBeat );
int rowsPerMeasure = curTime->GetDen() * curTime->GetNum();
for( int row=0; row<=m_NoteDataEdit.GetLastRow(); row+=rowsPerMeasure )
{
int iNumNotesThisMeasure = 0;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( m_NoteDataEdit, r, row, row+rowsPerMeasure )
iNumNotesThisMeasure += m_NoteDataEdit.GetNumTapNonEmptyTracks( r );
}
if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
{
/* Check that the action didn't push notes any farther past the last
* measure. This blocks Insert Beat from pushing past the end, but allows
* Delete Beat to pull back the notes that are already past the end.
*/
float fNewLastBeat = m_NoteDataEdit.GetLastBeat();
bool bLastBeatIncreased = fNewLastBeat > m_Undo.GetLastBeat();
bool bPassedTheEnd = fNewLastBeat > GetMaximumBeatForNewNote();
if( bLastBeatIncreased && bPassedTheEnd )
{
Undo();
m_bHasUndo = false;
RString sError = CREATES_NOTES_PAST_END.GetValue() + "\n\n" + CHANGE_REVERTED.GetValue();
ScreenPrompt::Prompt( SM_None, sError );
return;
}
}
}
float ScreenEdit::GetMaximumBeatForNewNote() const
{
switch( EDIT_MODE.GetValue() )
{
DEFAULT_FAIL( EDIT_MODE.GetValue() );
case EditMode_Practice:
case EditMode_CourseMods:
case EditMode_Home:
{
Song &s = *GAMESTATE->m_pCurSong;
float fEndBeat = s.GetLastBeat();
/* Round up to the next measure end. Some songs end on weird beats
* mid-measure, and it's odd to have movement capped to these weird
* beats. */
TimingData &timing = s.m_SongTiming;
float playerBeat = GetAppropriatePosition().m_fSongBeat;
int beatsPerMeasure = timing.GetTimeSignatureSegmentAtBeat( playerBeat )->GetNum();
fEndBeat += beatsPerMeasure;
fEndBeat = ftruncf( fEndBeat, (float)beatsPerMeasure );
return fEndBeat;
}
case EditMode_Full:
return NoteRowToBeat(MAX_NOTE_ROW);
}
}
float ScreenEdit::GetMaximumBeatForMoving() const
{
float fEndBeat = GetMaximumBeatForNewNote();
/* Jump to GetLastBeat even if it's past the song's last beat
* so that users can delete garbage steps past then end that they have
* have inserted in a text editor. Once they delete all steps on
* GetLastBeat() and move off of that beat, they won't be able to return. */
fEndBeat = max( fEndBeat, m_NoteDataEdit.GetLastBeat() );
return fEndBeat;
}
struct EditHelpLine
{
const char *szEnglishDescription;
vector<EditButton> veb;
EditHelpLine(
const char *_szEnglishDescription,
EditButton eb0,
EditButton eb1 = EditButton_Invalid,
EditButton eb2 = EditButton_Invalid,
EditButton eb3 = EditButton_Invalid,
EditButton eb4 = EditButton_Invalid,
EditButton eb5 = EditButton_Invalid,
EditButton eb6 = EditButton_Invalid,
EditButton eb7 = EditButton_Invalid,
EditButton eb8 = EditButton_Invalid,
EditButton eb9 = EditButton_Invalid )
{
szEnglishDescription = _szEnglishDescription;
#define PUSH_IF_VALID( x ) if( x != EditButton_Invalid ) veb.push_back( x );
PUSH_IF_VALID( eb0 );
PUSH_IF_VALID( eb1 );
PUSH_IF_VALID( eb2 );
PUSH_IF_VALID( eb3 );
PUSH_IF_VALID( eb4 );
PUSH_IF_VALID( eb5 );
PUSH_IF_VALID( eb6 );
PUSH_IF_VALID( eb7 );
PUSH_IF_VALID( eb8 );
PUSH_IF_VALID( eb9 );
#undef PUSH_IF_VALID
}
};
// TODO: Identify which of these can be removed and sent to a readme.
static const EditHelpLine g_EditHelpLines[] =
{
EditHelpLine( "Move cursor", EDIT_BUTTON_SCROLL_UP_LINE, EDIT_BUTTON_SCROLL_DOWN_LINE ),
EditHelpLine( "Jump measure", EDIT_BUTTON_SCROLL_UP_PAGE, EDIT_BUTTON_SCROLL_DOWN_PAGE ),
EditHelpLine( "Jump measure", EDIT_BUTTON_SCROLL_PREV_MEASURE, EDIT_BUTTON_SCROLL_NEXT_MEASURE ),
EditHelpLine( "Select region", EDIT_BUTTON_SCROLL_SELECT ),
EditHelpLine( "Jump to first/last beat", EDIT_BUTTON_SCROLL_HOME, EDIT_BUTTON_SCROLL_END ),
EditHelpLine( "Change zoom", EDIT_BUTTON_SCROLL_SPEED_UP, EDIT_BUTTON_SCROLL_SPEED_DOWN ),
EditHelpLine( "Play", EDIT_BUTTON_PLAY_SELECTION ),
EditHelpLine( "Play current beat to end", EDIT_BUTTON_PLAY_FROM_CURSOR ),
EditHelpLine( "Play whole song", EDIT_BUTTON_PLAY_FROM_START ),
EditHelpLine( "Record", EDIT_BUTTON_RECORD_SELECTION ),
EditHelpLine( "Set selection", EDIT_BUTTON_LAY_SELECT ),
EditHelpLine( "Next/prev steps of same StepsType", EDIT_BUTTON_OPEN_NEXT_STEPS, EDIT_BUTTON_OPEN_PREV_STEPS ),
EditHelpLine( "Decrease/increase BPM at cur beat", EDIT_BUTTON_BPM_DOWN, EDIT_BUTTON_BPM_UP ),
EditHelpLine( "Decrease/increase stop at cur beat", EDIT_BUTTON_STOP_DOWN, EDIT_BUTTON_STOP_UP ),
EditHelpLine( "Decrease/increase delay at cur beat", EDIT_BUTTON_DELAY_DOWN, EDIT_BUTTON_DELAY_UP ),
EditHelpLine( "Decrease/increase music offset", EDIT_BUTTON_OFFSET_DOWN, EDIT_BUTTON_OFFSET_UP ),
EditHelpLine( "Decrease/increase sample music start", EDIT_BUTTON_SAMPLE_START_DOWN, EDIT_BUTTON_SAMPLE_START_UP ),
EditHelpLine( "Decrease/increase sample music length", EDIT_BUTTON_SAMPLE_LENGTH_DOWN, EDIT_BUTTON_SAMPLE_LENGTH_UP ),
EditHelpLine( "Play sample music", EDIT_BUTTON_PLAY_SAMPLE_MUSIC ),
EditHelpLine( "Add/Edit Background Change", EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU ),
EditHelpLine( "Insert beat and shift down", EDIT_BUTTON_INSERT ),
EditHelpLine( "Shift BPM changes and stops down one beat", EDIT_BUTTON_INSERT_SHIFT_PAUSES ),
EditHelpLine( "Delete beat and shift up", EDIT_BUTTON_DELETE ),
EditHelpLine( "Shift BPM changes and stops up one beat", EDIT_BUTTON_DELETE_SHIFT_PAUSES ),
EditHelpLine( "Cycle between tap notes", EDIT_BUTTON_CYCLE_TAP_LEFT, EDIT_BUTTON_CYCLE_TAP_RIGHT ),
EditHelpLine( "Add to/remove from right half", EDIT_BUTTON_RIGHT_SIDE ),
EditHelpLine( "Switch Timing", EDIT_BUTTON_SWITCH_TIMINGS ),
EditHelpLine( "Switch player (Routine only)", EDIT_BUTTON_SWITCH_PLAYERS ),
};
static bool IsMapped( EditButton eb, const MapEditToDI &editmap )
{
for( int s=0; s<NUM_EDIT_TO_DEVICE_SLOTS; s++ )
{
DeviceInput diPress = editmap.button[eb][s];
if( diPress.IsValid() )
return true;
}
return false;
}
static void ProcessKeyName( RString &s )
{
s.Replace( "Key_", "" );
}
static void ProcessKeyNames( vector<RString> &vs )
{
FOREACH( RString, vs, s )
ProcessKeyName( *s );
sort( vs.begin(), vs.end() );
vector<RString>::iterator toDelete = unique( vs.begin(), vs.end() );
vs.erase(toDelete, vs.end());
}
static RString GetDeviceButtonsLocalized( const vector<EditButton> &veb, const MapEditToDI &editmap )
{
vector<RString> vsPress;
vector<RString> vsHold;
FOREACH_CONST( EditButton, veb, eb )
{
if( !IsMapped( *eb, editmap ) )
continue;
for( int s=0; s<NUM_EDIT_TO_DEVICE_SLOTS; s++ )
{
DeviceInput diPress = editmap.button[*eb][s];
DeviceInput diHold = editmap.hold[*eb][s];
if( diPress.IsValid() )
vsPress.push_back( Capitalize(INPUTMAN->GetLocalizedInputString(diPress)) );
if( diHold.IsValid() )
vsHold.push_back( Capitalize(INPUTMAN->GetLocalizedInputString(diHold)) );
}
}
ProcessKeyNames( vsPress );
ProcessKeyNames( vsHold );
RString s = join("/",vsPress);
if( !vsHold.empty() )
s = join("/",vsHold) + " + " + s;
return s;
}
void ScreenEdit::DoStepAttackMenu()
{
const TimingData &timing = GetAppropriateTiming();
float startTime = timing.GetElapsedTimeFromBeat(GetBeat());
AttackArray &attacks =
(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
vector<int> points = FindAllAttacksAtTime(attacks, startTime);
g_AttackAtTimeMenu.rows.clear();
unsigned index = 0;
FOREACH(int, points, i)
{
const Attack &attack = attacks[*i];
RString desc = ssprintf("%g -> %g (%d mod[s])",
startTime, startTime + attack.fSecsRemaining,
attack.GetNumAttacks());
g_AttackAtTimeMenu.rows.push_back(MenuRowDef(index++,
desc,
true,
EditMode_CourseMods,
false,
false,
0,
"Modify",
"Delete"));
}
g_AttackAtTimeMenu.rows.push_back(MenuRowDef(index,
"Add Attack",
true,
EditMode_CourseMods,
true,
true,
0,
NULL));
EditMiniMenu(&g_AttackAtTimeMenu, SM_BackFromAttackAtTime);
}
static LocalizedString TRACK_NUM("ScreenEdit", "Track %d");
static LocalizedString NO_KEYSND("ScreenEdit", "None");
static LocalizedString NEWKEYSND("ScreenEdit", "New Sound");
void ScreenEdit::DoKeyboardTrackMenu()
{
g_KeysoundTrack.rows.clear();
vector<RString> &kses = m_pSong->m_vsKeysoundFile;
vector<RString> choices;
FOREACH(RString, kses, ks)
{
choices.push_back(*ks);
}
choices.push_back(NEWKEYSND);
choices.push_back(NO_KEYSND);
int numKeysounds = kses.size();
for (int i = 0; i < m_NoteDataEdit.GetNumTracks(); ++i)
{
const TapNote &tn = m_NoteDataEdit.GetTapNote(i, this->GetRow());
int keyIndex = tn.iKeysoundIndex;
if (keyIndex == -1)
{
keyIndex = numKeysounds;
}
g_KeysoundTrack.rows.push_back(MenuRowDef(i, ssprintf(TRACK_NUM.GetValue(), i + 1),
true, EditMode_Full, false, false, keyIndex, choices));
}
g_KeysoundTrack.rows.push_back(MenuRowDef(m_NoteDataEdit.GetNumTracks(), "Remove Keysound",
true, EditMode_Full, false, false, 0, kses));
EditMiniMenu(&g_KeysoundTrack, SM_BackFromKeysoundTrack);
}
void ScreenEdit::DoHelp()
{
g_EditHelp.rows.clear();
for( unsigned i=0; i<ARRAYLEN(g_EditHelpLines); ++i )
{
const EditHelpLine &hl = g_EditHelpLines[i];
if( !IsMapped(hl.veb[0],m_EditMappingsDeviceInput) )
continue;
RString sButtons = GetDeviceButtonsLocalized( hl.veb, m_EditMappingsDeviceInput );
RString sDescription = THEME->GetString( "EditHelpDescription", hl.szEnglishDescription );
// TODO: Better way of hiding routine only key on non-routine.
if( hl.veb[0] == EDIT_BUTTON_SWITCH_PLAYERS && m_InputPlayerNumber == PLAYER_INVALID )
{
continue;
}
g_EditHelp.rows.push_back( MenuRowDef( -1, sDescription, false, EditMode_Practice, false, false, 0, sButtons ) );
}
EditMiniMenu( &g_EditHelp );
}
// lua start
#include "LuaBinding.h"
/** @brief Allow Lua to have access to ScreenEdit. */
class LunaScreenEdit: public Luna<ScreenEdit>
{
public:
DEFINE_METHOD( GetEditState, GetEditState() )
LunaScreenEdit()
{
ADD_METHOD( GetEditState );
}
};
LUA_REGISTER_DERIVED_CLASS( ScreenEdit, ScreenWithMenuElements )
/*
* (c) 2001-2004 Chris Danford
* 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.
*/