#include "global.h" #include #include #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 "RageSoundReader_FileReader.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 "SpecialFiles.h" #include "StepsUtil.h" #include "Style.h" #include "ThemeManager.h" #include "ThemeMetric.h" #include "TimingData.h" #include "Game.h" #include "RageSoundReader.h" static Preference g_iDefaultRecordLength( "DefaultRecordLength", 4 ); static Preference g_bEditorShowBGChangesPlay( "EditorShowBGChangesPlay", true ); /** @brief How long must the button be held to generate a hold in record mode? */ const float record_hold_default= 0.3f; float record_hold_seconds = record_hold_default; const float time_between_autosave= 300.0f; // 5 minutes. -Kyz #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") #define SET_MOD_SCREEN THEME->GetMetric("ScreenEdit", "SetModScreen") #define OPTIONS_SCREEN THEME->GetMetric("ScreenEdit", "OptionsScreen") AutoScreenMessage( SM_UpdateTextInfo ); AutoScreenMessage( SM_BackFromMainMenu ); AutoScreenMessage( SM_BackFromAreaMenu ); AutoScreenMessage( SM_BackFromAlterMenu ); AutoScreenMessage( SM_BackFromArbitraryRemap ); 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_ConfirmClearArea ); AutoScreenMessage( SM_BackFromTimingDataInformation ); AutoScreenMessage(SM_BackFromTimingDataChangeInformation); AutoScreenMessage( SM_BackFromDifficultyMeterChange ); AutoScreenMessage( SM_BackFromBeat0Change ); AutoScreenMessage( SM_BackFromBPMChange ); AutoScreenMessage( SM_BackFromStopChange ); AutoScreenMessage( SM_BackFromDelayChange ); 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_BackFromStepMusicChange ); AutoScreenMessage( SM_DoEraseStepTiming ); AutoScreenMessage( SM_DoSaveAndExit ); AutoScreenMessage( SM_DoExit ); AutoScreenMessage( SM_AutoSaveSuccessful ); AutoScreenMessage( SM_SaveSuccessful ); AutoScreenMessage( SM_SaveSuccessNoSM ); AutoScreenMessage( SM_SaveFailed ); static const char *EditStateNames[] = { "Edit", "Record", "RecordPaused", "Playing" }; XToString( EditState ); LuaXType( EditState ); map name_to_edit_button; void ScreenEdit::InitEditMappings() { // Created courtesy of query replace regex. name_to_edit_button["COLUMN_0"]= EDIT_BUTTON_COLUMN_0; name_to_edit_button["COLUMN_1"]= EDIT_BUTTON_COLUMN_1; name_to_edit_button["COLUMN_2"]= EDIT_BUTTON_COLUMN_2; name_to_edit_button["COLUMN_3"]= EDIT_BUTTON_COLUMN_3; name_to_edit_button["COLUMN_4"]= EDIT_BUTTON_COLUMN_4; name_to_edit_button["COLUMN_5"]= EDIT_BUTTON_COLUMN_5; name_to_edit_button["COLUMN_6"]= EDIT_BUTTON_COLUMN_6; name_to_edit_button["COLUMN_7"]= EDIT_BUTTON_COLUMN_7; name_to_edit_button["COLUMN_8"]= EDIT_BUTTON_COLUMN_8; name_to_edit_button["COLUMN_9"]= EDIT_BUTTON_COLUMN_9; name_to_edit_button["RIGHT_SIDE"]= EDIT_BUTTON_RIGHT_SIDE; name_to_edit_button["LAY_ROLL"]= EDIT_BUTTON_LAY_ROLL; name_to_edit_button["LAY_TAP_ATTACK"]= EDIT_BUTTON_LAY_TAP_ATTACK; name_to_edit_button["REMOVE_NOTE"]= EDIT_BUTTON_REMOVE_NOTE; name_to_edit_button["CYCLE_TAP_LEFT"]= EDIT_BUTTON_CYCLE_TAP_LEFT; name_to_edit_button["CYCLE_TAP_RIGHT"]= EDIT_BUTTON_CYCLE_TAP_RIGHT; name_to_edit_button["CYCLE_SEGMENT_LEFT"]= EDIT_BUTTON_CYCLE_SEGMENT_LEFT; name_to_edit_button["CYCLE_SEGMENT_RIGHT"]= EDIT_BUTTON_CYCLE_SEGMENT_RIGHT; name_to_edit_button["SCROLL_UP_LINE"]= EDIT_BUTTON_SCROLL_UP_LINE; name_to_edit_button["SCROLL_UP_PAGE"]= EDIT_BUTTON_SCROLL_UP_PAGE; name_to_edit_button["SCROLL_UP_TS"]= EDIT_BUTTON_SCROLL_UP_TS; name_to_edit_button["SCROLL_DOWN_LINE"]= EDIT_BUTTON_SCROLL_DOWN_LINE; name_to_edit_button["SCROLL_DOWN_PAGE"]= EDIT_BUTTON_SCROLL_DOWN_PAGE; name_to_edit_button["SCROLL_DOWN_TS"]= EDIT_BUTTON_SCROLL_DOWN_TS; name_to_edit_button["SCROLL_NEXT_MEASURE"]= EDIT_BUTTON_SCROLL_NEXT_MEASURE; name_to_edit_button["SCROLL_PREV_MEASURE"]= EDIT_BUTTON_SCROLL_PREV_MEASURE; name_to_edit_button["SCROLL_HOME"]= EDIT_BUTTON_SCROLL_HOME; name_to_edit_button["SCROLL_END"]= EDIT_BUTTON_SCROLL_END; name_to_edit_button["SCROLL_NEXT"]= EDIT_BUTTON_SCROLL_NEXT; name_to_edit_button["SCROLL_PREV"]= EDIT_BUTTON_SCROLL_PREV; name_to_edit_button["SEGMENT_NEXT"]= EDIT_BUTTON_SEGMENT_NEXT; name_to_edit_button["SEGMENT_PREV"]= EDIT_BUTTON_SEGMENT_PREV; name_to_edit_button["SCROLL_SELECT"]= EDIT_BUTTON_SCROLL_SELECT; name_to_edit_button["LAY_SELECT"]= EDIT_BUTTON_LAY_SELECT; name_to_edit_button["SCROLL_SPEED_UP"]= EDIT_BUTTON_SCROLL_SPEED_UP; name_to_edit_button["SCROLL_SPEED_DOWN"]= EDIT_BUTTON_SCROLL_SPEED_DOWN; name_to_edit_button["SNAP_NEXT"]= EDIT_BUTTON_SNAP_NEXT; name_to_edit_button["SNAP_PREV"]= EDIT_BUTTON_SNAP_PREV; name_to_edit_button["OPEN_EDIT_MENU"]= EDIT_BUTTON_OPEN_EDIT_MENU; name_to_edit_button["OPEN_TIMING_MENU"]= EDIT_BUTTON_OPEN_TIMING_MENU; name_to_edit_button["OPEN_ALTER_MENU"]= EDIT_BUTTON_OPEN_ALTER_MENU; name_to_edit_button["OPEN_AREA_MENU"]= EDIT_BUTTON_OPEN_AREA_MENU; name_to_edit_button["OPEN_BGCHANGE_LAYER1_MENU"]= EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU; name_to_edit_button["OPEN_BGCHANGE_LAYER2_MENU"]= EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU; name_to_edit_button["OPEN_COURSE_MENU"]= EDIT_BUTTON_OPEN_COURSE_MENU; name_to_edit_button["OPEN_COURSE_ATTACK_MENU"]= EDIT_BUTTON_OPEN_COURSE_ATTACK_MENU; name_to_edit_button["OPEN_STEP_ATTACK_MENU"]= EDIT_BUTTON_OPEN_STEP_ATTACK_MENU; name_to_edit_button["ADD_STEP_MODS"]= EDIT_BUTTON_ADD_STEP_MODS; name_to_edit_button["OPEN_INPUT_HELP"]= EDIT_BUTTON_OPEN_INPUT_HELP; name_to_edit_button["BAKE_RANDOM_FROM_SONG_GROUP"]= EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP; name_to_edit_button["BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE"]= EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE; name_to_edit_button["PLAY_FROM_START"]= EDIT_BUTTON_PLAY_FROM_START; name_to_edit_button["PLAY_FROM_CURSOR"]= EDIT_BUTTON_PLAY_FROM_CURSOR; name_to_edit_button["PLAY_SELECTION"]= EDIT_BUTTON_PLAY_SELECTION; name_to_edit_button["RECORD_FROM_CURSOR"]= EDIT_BUTTON_RECORD_FROM_CURSOR; name_to_edit_button["RECORD_SELECTION"]= EDIT_BUTTON_RECORD_SELECTION; name_to_edit_button["RECORD_HOLD_RESET"]= EDIT_BUTTON_RECORD_HOLD_RESET; name_to_edit_button["RECORD_HOLD_OFF"]= EDIT_BUTTON_RECORD_HOLD_OFF; name_to_edit_button["RECORD_HOLD_MORE"]= EDIT_BUTTON_RECORD_HOLD_MORE; name_to_edit_button["RECORD_HOLD_LESS"]= EDIT_BUTTON_RECORD_HOLD_LESS; name_to_edit_button["RETURN_TO_EDIT"]= EDIT_BUTTON_RETURN_TO_EDIT; name_to_edit_button["INSERT"]= EDIT_BUTTON_INSERT; name_to_edit_button["DELETE"]= EDIT_BUTTON_DELETE; name_to_edit_button["INSERT_SHIFT_PAUSES"]= EDIT_BUTTON_INSERT_SHIFT_PAUSES; name_to_edit_button["DELETE_SHIFT_PAUSES"]= EDIT_BUTTON_DELETE_SHIFT_PAUSES; name_to_edit_button["OPEN_NEXT_STEPS"]= EDIT_BUTTON_OPEN_NEXT_STEPS; name_to_edit_button["OPEN_PREV_STEPS"]= EDIT_BUTTON_OPEN_PREV_STEPS; name_to_edit_button["PLAY_SAMPLE_MUSIC"]= EDIT_BUTTON_PLAY_SAMPLE_MUSIC; name_to_edit_button["BPM_UP"]= EDIT_BUTTON_BPM_UP; name_to_edit_button["BPM_DOWN"]= EDIT_BUTTON_BPM_DOWN; name_to_edit_button["STOP_UP"]= EDIT_BUTTON_STOP_UP; name_to_edit_button["STOP_DOWN"]= EDIT_BUTTON_STOP_DOWN; name_to_edit_button["DELAY_UP"]= EDIT_BUTTON_DELAY_UP; name_to_edit_button["DELAY_DOWN"]= EDIT_BUTTON_DELAY_DOWN; name_to_edit_button["OFFSET_UP"]= EDIT_BUTTON_OFFSET_UP; name_to_edit_button["OFFSET_DOWN"]= EDIT_BUTTON_OFFSET_DOWN; name_to_edit_button["SAMPLE_START_UP"]= EDIT_BUTTON_SAMPLE_START_UP; name_to_edit_button["SAMPLE_START_DOWN"]= EDIT_BUTTON_SAMPLE_START_DOWN; name_to_edit_button["SAMPLE_LENGTH_UP"]= EDIT_BUTTON_SAMPLE_LENGTH_UP; name_to_edit_button["SAMPLE_LENGTH_DOWN"]= EDIT_BUTTON_SAMPLE_LENGTH_DOWN; name_to_edit_button["ADJUST_FINE"]= EDIT_BUTTON_ADJUST_FINE; name_to_edit_button["SAVE"]= EDIT_BUTTON_SAVE; name_to_edit_button["UNDO"]= EDIT_BUTTON_UNDO; name_to_edit_button["ADD_COURSE_MODS"]= EDIT_BUTTON_ADD_COURSE_MODS; name_to_edit_button["SWITCH_PLAYERS"]= EDIT_BUTTON_SWITCH_PLAYERS; name_to_edit_button["SWITCH_TIMINGS"]= EDIT_BUTTON_SWITCH_TIMINGS; 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); m_EditMappingsDeviceInput.button[EDIT_BUTTON_RECORD_HOLD_LESS][0]= DeviceInput(DEVICE_KEYBOARD, KEY_Cq); m_EditMappingsDeviceInput.button[EDIT_BUTTON_RECORD_HOLD_MORE][0]= DeviceInput(DEVICE_KEYBOARD, KEY_Cw); m_EditMappingsDeviceInput.button[EDIT_BUTTON_RECORD_HOLD_RESET][0]= DeviceInput(DEVICE_KEYBOARD, KEY_Ce); m_EditMappingsDeviceInput.button[EDIT_BUTTON_RECORD_HOLD_OFF][0]= DeviceInput(DEVICE_KEYBOARD, KEY_Cr); 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); IniFile mapping_ini; // Only use the mappings file if it exists. It's meant to be optional, and // only used in rare cases like someone having critical keys broken. -Kyz if(mapping_ini.ReadFile(SpecialFiles::EDIT_MODE_KEYMAPS_PATH)) { LoadKeymapSectionIntoMappingsMember(mapping_ini.GetChild("Edit"), m_EditMappingsDeviceInput); LoadKeymapSectionIntoMappingsMember(mapping_ini.GetChild("Play"), m_PlayMappingsDeviceInput); LoadKeymapSectionIntoMappingsMember(mapping_ini.GetChild("Record"), m_RecordMappingsDeviceInput); LoadKeymapSectionIntoMappingsMember(mapping_ini.GetChild("RecordPaused"), m_RecordPausedMappingsDeviceInput); } } void ScreenEdit::LoadKeymapSectionIntoMappingsMember(XNode const* section, MapEditToDI& mappings) { if(section == NULL) {return;} // Not an error, sections are optional. -Kyz FOREACH_CONST_Attr(section, attr) { map::iterator name_entry= name_to_edit_button.find(attr->first); if(name_entry != name_to_edit_button.end()) { RString joined_names; attr->second->GetValue(joined_names); vector key_names; split(joined_names, DEVICE_INPUT_SEPARATOR, key_names, false); for(size_t k= 0; k < key_names.size() && k < NUM_EDIT_TO_DEVICE_SLOTS; ++k) { DeviceInput devi; devi.FromString(key_names[k]); if(devi.IsValid()) { mappings.button[name_entry->second][k]= devi; } } } } } /* 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; } } enum RowCountChoice { RCC_4M, RCC_2M, RCC_1M, RCC_2ND, RCC_4TH, RCC_8TH, RCC_12TH, RCC_16TH, RCC_24TH, RCC_32ND, RCC_48TH, RCC_64TH, RCC_192ND, }; #define RCC_CHOICES RCC_4TH, "4m", "2m", "1m", "2nd", "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" int GetRowsFromAnswers(int choice, const vector& answers); int GetRowsFromAnswers(int choice, const vector& answers) { if(answers.empty()) { return 192 / 8; } switch(answers[choice]) { case RCC_4M: return 192 * 4; case RCC_2M: return 192 * 2; case RCC_1M: return 192; case RCC_2ND: return 192 / 2; case RCC_4TH: default: return 192 / 4; case RCC_8TH: return 192 / 8; case RCC_12TH: return 192 / 12; case RCC_16TH: return 192 / 16; case RCC_24TH: return 192 / 24; case RCC_32ND: return 192 / 32; case RCC_48TH: return 192 / 48; case RCC_64TH: return 3; case RCC_192ND: return 1; } } 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", "Swap Up/Down", "Arbitrary Remap Columns" ), 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, RCC_CHOICES ), MenuRowDef(ScreenEdit::delete_and_shift, "Delete beat and shift up", true, EditMode_Practice, true, true, RCC_CHOICES ), MenuRowDef(ScreenEdit::shift_pauses_forward, "Shift all timing changes down", true, EditMode_Full, true, true, RCC_CHOICES ), MenuRowDef(ScreenEdit::shift_pauses_backward, "Shift all timing changes up", true, EditMode_Full, true, true, RCC_CHOICES ), 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 ), MenuRowDef(ScreenEdit::step_music, "Music File", 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::preview, "Preview", true, EditMode_Full, 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::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::shift_timing_in_region_down, "Shift timing in region down", true, EditMode_Full, true, true, RCC_CHOICES), MenuRowDef(ScreenEdit::shift_timing_in_region_up, "Shift timing in region up", true, EditMode_Full, true, true, RCC_CHOICES), MenuRowDef(ScreenEdit::copy_timing_in_region, "Copy timing in region", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::paste_timing_from_clip, "Paste timing from clipboard", 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 ) ); static MenuDef g_TimingDataChangeInformation( "ScreenMiniMenuTimingDataChangeInformation", MenuRowDef(ScreenEdit::timing_all, "All timing", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_bpm, "BPM changes", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_stop, "Stops", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_delay, "Delays", true, EditMode_Full, true, true, 0, NULL), // Time signatures disabled because they don't fully work. -Kyz // MenuRowDef(ScreenEdit::timing_time_sig, // "Time Signatures", // true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_warp, "Warps", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_label, "Labels", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_tickcount, "Tickcount", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_combo, "Combo segments", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_speed, "Speed segments", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_scroll, "Scroll segments", true, EditMode_Full, true, true, 0, NULL), MenuRowDef(ScreenEdit::timing_fake, "Fakes", 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 vsPaths; vector 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 EDITOR_NOTE_SKINS( SetDefaultEditorNoteSkin, NUM_PLAYERS ); static ThemeMetric EDIT_MODIFIERS ("ScreenEdit","EditModifiers"); static ThemeMetric 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(GAMESTATE->GetMasterPlayerNumber())->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.SetPlayerNumber(PLAYER_1); m_PlayerStateEdit.m_NotefieldZoom= 1.0f; // 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, false ); 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() ); SetDirty(m_NoteDataEdit.IsEmpty()); // require saving if empty. if(GAMESTATE->m_pCurSong->WasLoadedFromAutosave()) { SetDirty(true); } 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 FADE_IN_PREVIEW("ScreenEdit", "FadeInPreview"); static ThemeMetric FADE_OUT_PREVIEW("ScreenEdit", "FadeOutPreview"); void ScreenEdit::PlayPreviewMusic() { SOUND->StopMusic(); SOUND->PlayMusic( m_pSong->GetPreviewMusicPath(), NULL, false, m_pSong->GetPreviewStartSeconds(), m_pSong->m_fMusicSampleLengthSeconds, FADE_IN_PREVIEW, FADE_OUT_PREVIEW ); } void ScreenEdit::MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu ) { menu = *pDef; menu.rows.clear(); vector 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 ); } if(m_EditState == STATE_EDITING) { if(IsDirty() && m_next_autosave_time > -1.0f && RageTimer::GetTimeSinceStartFast() > m_next_autosave_time) { PerformSave(true); } } if( m_EditState == STATE_RECORDING ) { // TODO: Find a way to prevent STATE_RECORDING when in Song Timing. for( int t=0; tGetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->m_iColsPerPlayer; t++ ) // for each track { vector GameI; GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->StyleInputToGameInput( t, PLAYER_1, GameI ); float fSecsHeld= 0.0f; for(size_t i= 0; i < GameI.size(); ++i) { fSecsHeld= max(fSecsHeld, INPUTMAPPER->GetSecsHeld(GameI[i])); } 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; tGetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->m_iColsPerPlayer; t++ ) // for each track { vector GameI; GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->StyleInputToGameInput( t, PLAYER_1, GameI ); 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 FindAllAttacksAtTime(const AttackArray& attacks, float fStartTime) { vector 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 LocalizedString RECORD_HOLD_TIME("ScreenEdit", "Record Hold Time"); static ThemeMetric CURRENT_BEAT_FORMAT("ScreenEdit", "CurrentBeatFormat"); static ThemeMetric CURRENT_SECOND_FORMAT("ScreenEdit", "CurrentSecondFormat"); static ThemeMetric SNAP_TO_FORMAT("ScreenEdit", "SnapToFormat"); static ThemeMetric SELECTION_BEAT_BEGIN_FORMAT("ScreenEdit", "SelectionBeatBeginFormat"); static ThemeMetric SELECTION_BEAT_UNFINISHED_FORMAT("ScreenEdit", "SelectionBeatUnfinishedFormat"); static ThemeMetric SELECTION_BEAT_END_FORMAT("ScreenEdit", "SelectionBeatEndFormat"); static ThemeMetric DIFFICULTY_FORMAT("ScreenEdit", "DifficultyFormat"); static ThemeMetric ROUTINE_PLAYER_FORMAT("ScreenEdit", "RoutinePlayerFormat"); static ThemeMetric CHART_NAME_FORMAT("ScreenEdit", "ChartNameFormat"); static ThemeMetric DESCRIPTION_FORMAT("ScreenEdit", "DescriptionFormat"); static ThemeMetric CHART_STYLE_FORMAT("ScreenEdit", "ChartStyleFormat"); static ThemeMetric STEP_AUTHOR_FORMAT("ScreenEdit", "StepAuthorFormat"); static ThemeMetric MAIN_TITLE_FORMAT("ScreenEdit", "MainTitleFormat"); static ThemeMetric SUBTITLE_FORMAT("ScreenEdit", "SubtitleFormat"); static ThemeMetric TAP_NOTE_TYPE_FORMAT("ScreenEdit", "TapNoteTypeFormat"); static ThemeMetric SEGMENT_TYPE_FORMAT("ScreenEdit", "SegmentTypeFormat"); static ThemeMetric NUM_STEPS_FORMAT("ScreenEdit", "NumStepsFormat"); static ThemeMetric NUM_JUMPS_FORMAT("ScreenEdit", "NumJumpsFormat"); static ThemeMetric NUM_HOLDS_FORMAT("ScreenEdit", "NumHoldsFormat"); static ThemeMetric NUM_MINES_FORMAT("ScreenEdit", "NumMinesFormat"); static ThemeMetric NUM_HANDS_FORMAT("ScreenEdit", "NumHandsFormat"); static ThemeMetric NUM_ROLLS_FORMAT("ScreenEdit", "NumRollsFormat"); static ThemeMetric NUM_LIFTS_FORMAT("ScreenEdit", "NumLiftsFormat"); static ThemeMetric NUM_FAKES_FORMAT("ScreenEdit", "NumFakesFormat"); static ThemeMetric NUM_STEPS_FORMAT_TWO_PLAYER("ScreenEdit", "NumStepsFormatTwoPlayer"); static ThemeMetric NUM_JUMPS_FORMAT_TWO_PLAYER("ScreenEdit", "NumJumpsFormatTwoPlayer"); static ThemeMetric NUM_HOLDS_FORMAT_TWO_PLAYER("ScreenEdit", "NumHoldsFormatTwoPlayer"); static ThemeMetric NUM_MINES_FORMAT_TWO_PLAYER("ScreenEdit", "NumMinesFormatTwoPlayer"); static ThemeMetric NUM_HANDS_FORMAT_TWO_PLAYER("ScreenEdit", "NumHandsFormatTwoPlayer"); static ThemeMetric NUM_ROLLS_FORMAT_TWO_PLAYER("ScreenEdit", "NumRollsFormatTwoPlayer"); static ThemeMetric NUM_LIFTS_FORMAT_TWO_PLAYER("ScreenEdit", "NumLiftsFormatTwoPlayer"); static ThemeMetric NUM_FAKES_FORMAT_TWO_PLAYER("ScreenEdit", "NumFakesFormatTwoPlayer"); static ThemeMetric TIMING_MODE_FORMAT("ScreenEdit", "TimingModeFormat"); static ThemeMetric BEAT_0_OFFSET_FORMAT("ScreenEdit", "Beat0OffsetFormat"); static ThemeMetric PREVIEW_START_FORMAT("ScreenEdit", "PreviewStartFormat"); static ThemeMetric PREVIEW_LENGTH_FORMAT("ScreenEdit", "PreviewLengthFormat"); static ThemeMetric RECORD_HOLD_TIME_FORMAT("ScreenEdit", "RecordHoldTimeFormat"); 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() ); float second= GetAppropriateTiming().GetElapsedTimeFromBeatNoOffset(GetBeat()); sText += ssprintf( CURRENT_SECOND_FORMAT.GetValue(), CURRENT_SECOND.GetValue().c_str(), second ); 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 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 ); if(record_hold_seconds < record_hold_default - .001f || record_hold_seconds > record_hold_default + .001f) { sText += ssprintf(RECORD_HOLD_TIME_FORMAT.GetValue(), RECORD_HOLD_TIME.GetValue().c_str(), record_hold_seconds); } 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 (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(GAMESTATE->GetMasterPlayerNumber())->m_StyleType ) { DEFAULT_FAIL( GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->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 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(true); 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 != TapNoteType_Empty ) { m_soundRemoveNote.Play(true); 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(true); 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 TapNoteType_Tap: m_selectedTap = TAP_ORIGINAL_FAKE; break; case TapNoteType_Mine: m_selectedTap = TAP_ORIGINAL_TAP; break; case TapNoteType_Lift: m_selectedTap = TAP_ORIGINAL_MINE; break; case TapNoteType_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 TapNoteType_Tap: m_selectedTap = TAP_ORIGINAL_MINE; break; case TapNoteType_Mine: m_selectedTap = TAP_ORIGINAL_LIFT; break; case TapNoteType_Lift: m_selectedTap = TAP_ORIGINAL_FAKE; break; case TapNoteType_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(&tmp), NUM_TimingSegmentType ); currentCycleSegment = (TimingSegmentType)tmp; return true; } case EDIT_BUTTON_CYCLE_SEGMENT_RIGHT: { int tmp = enum_add2( this->currentCycleSegment, +1 ); wrap( *ConvertValue(&tmp), NUM_TimingSegmentType ); currentCycleSegment = (TimingSegmentType)tmp; return true; } case EDIT_BUTTON_SCROLL_SPEED_UP: case EDIT_BUTTON_SCROLL_SPEED_DOWN: { PlayerState *pPlayerState = const_cast (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(true); 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(true); } 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 { 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 vSteps; SongUtil::GetSteps( GAMESTATE->m_pCurSong, vSteps, st ); // Sort them by difficulty. StepsUtil::SortStepsByTypeAndDifficulty( vSteps ); // Find out what index the current Steps are vector::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(true); // Reload the music because it can be different for every steps. -Kyz m_AutoKeysounds.FinishLoading(); m_pSoundMusic = m_AutoKeysounds.GetSound(); 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; if(fNewBPM > 0.0f) { GetAppropriateTimingForUpdate().AddSegment(BPMSegment(GetRow(), fNewBPM)); } (fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play(true); 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 &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(true); 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 &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(true); 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(true); 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(true); 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 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 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 &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( SET_MOD_SCREEN, 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( SET_MOD_SCREEN, 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(true); } } 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_RECORD_HOLD_LESS: record_hold_seconds-= .01f; if(record_hold_seconds <= 0.0f) { record_hold_seconds= 0.0f; } return true; case EDIT_BUTTON_RECORD_HOLD_MORE: record_hold_seconds+= .01f; return true; case EDIT_BUTTON_RECORD_HOLD_RESET: record_hold_seconds= record_hold_default; return true; case EDIT_BUTTON_RECORD_HOLD_OFF: record_hold_seconds= 120.0f; 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(true); return true; case EDIT_BUTTON_SWITCH_TIMINGS: GAMESTATE->m_bIsUsingStepTiming = !GAMESTATE->m_bIsUsingStepTiming; m_soundSwitchTiming.Play(true); 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(GAMESTATE->GetMasterPlayerNumber())->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(GAMESTATE->GetMasterPlayerNumber())->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(GAMESTATE->GetMasterPlayerNumber())->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; 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: { m_NoteDataEdit.RevalidateATIs(vector(), false); if( bStateChanging ) AdjustSync::ResetOriginalSyncData(); /* Give a 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) ) - PREFSMAN->m_EditRecordModeLeadIn; GAMESTATE->UpdateSongPosition( fSeconds, GetAppropriateTiming(), RageZeroTimer ); GAMESTATE->m_bGameplayLeadIn.Set( false ); if (!GAMESTATE->m_bIsUsingStepTiming) { // Substitute the song timing for the step timing during // preview 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(false, &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= 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(true); } 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 AUTOSAVE_SUCCESSFUL ( "ScreenEdit", "Autosave successful." ); static LocalizedString SAVE_SUCCESS_NO_SM_SPLIT_TIMING("ScreenEdit", "save_success_no_sm_split_timing"); 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(ScreenMiniMenu::s_iLastRowCode); const vector &answers = ScreenMiniMenu::s_viLastAnswers; HandleAreaMenuChoice( amc, answers ); } else if( SM == SM_BackFromAlterMenu ) { HandleAlterMenuChoice( (AlterMenuChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers ); } else if( SM == SM_BackFromArbitraryRemap ) { HandleArbitraryRemapping(ScreenTextEntry::s_sLastAnswer); } 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_BackFromTimingDataChangeInformation) { HandleTimingDataChangeChoice((TimingDataChangeChoice)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_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_BackFromStepMusicChange && !ScreenTextEntry::s_bCancelledLast ) { // Reload the music because it just changed. -Kyz m_AutoKeysounds.FinishLoading(); m_pSoundMusic = m_AutoKeysounds.GetSound(); } 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 &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 == TapNoteType_Empty) { newNote.type = TapNoteType_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 == TapNoteType_AutoKeysound) { newNote.type = TapNoteType_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(sound)) { newNote.iKeysoundIndex = -1; if (newNote.type == TapNoteType_AutoKeysound) newNote.type = TapNoteType_Empty; } else if (newNote.iKeysoundIndex > static_cast(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 &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 == TapNoteType_Empty) newNote.type = TapNoteType_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( SET_MOD_SCREEN, 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( TapNoteType_Attack, TapNoteSubType_Invalid, TapNoteSource_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 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 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 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 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 &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( SET_MOD_SCREEN, 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( SET_MOD_SCREEN, SM_BackFromInsertCourseAttackPlayerOptions ); } } else if (SM == SM_BackFromInsertStepAttackPlayerOptions) { ModsGroup &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_ConfirmClearArea) { if(ScreenPrompt::s_LastAnswer == ANSWER_YES) { m_NoteDataEdit.ClearRange( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker); } } 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. m_pSong->RemoveAutosave(); SCREENMAN->SendMessageToTopScreen( SM_DoExit ); return; case ANSWER_CANCEL: break; // do nothing } } else if(SM == SM_SaveSuccessful || SM == SM_SaveSuccessNoSM) { LOG->Trace( "Save successful." ); CopyToLastSave(); SetDirty( false ); SONGMAN->Invalidate( GAMESTATE->m_pCurSong ); LocalizedString const* message= &SAVE_SUCCESSFUL; if(SM == SM_SaveSuccessNoSM) { message= &SAVE_SUCCESS_NO_SM_SPLIT_TIMING; } if( m_CurrentAction == save_on_exit ) { ScreenPrompt::Prompt( SM_DoExit, *message ); } else { SCREENMAN->SystemMessage( *message ); } } else if( SM == SM_AutoSaveSuccessful ) { LOG->Trace("AutoSave successful."); m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; SCREENMAN->SystemMessage(AUTOSAVE_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 &apSteps = pSong->GetAllSteps(); vector 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::SetDirty(bool dirty) { if(dirty) { if(!m_dirty) { m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; } } else { m_next_autosave_time= -1.0f; } m_dirty= dirty; } void ScreenEdit::PerformSave(bool autosave) { // 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(); // If one of the charts uses split timing, then it cannot be accurately // saved in the .sm format. So saving the .sm is disabled. bool uses_split= m_pSong->AnyChartUsesSplitTiming(); const ScreenMessage save_message= autosave ? SM_AutoSaveSuccessful : (uses_split ? SM_SaveSuccessNoSM : SM_SaveSuccessful); 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(save_message); /* 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(autosave); SCREENMAN->ZeroNextUpdate(); HandleScreenMessage(save_message); } break; case EditMode_CourseMods: case EditMode_Practice: break; } m_soundSave.Play(true); } void ScreenEdit::OnSnapModeChange() { m_soundChangeSnap.Play(true); 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 ChangeStepMusic(const RString& sNew) { Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; pSteps->SetMusicFile(sNew); } 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 ChangePreview(const RString& sNew) { Song* pSong = GAMESTATE->m_pCurSong; if(!sNew.empty()) { RString error; RageSoundReader* sample= RageSoundReader_FileReader::OpenFile(pSong->GetPreviewMusicPath(), error); if(sample == NULL) { LOG->UserLog( "Preview file", pSong->GetPreviewMusicPath(), "couldn't be opened: %s", error.c_str() ); } else { pSong->m_fMusicSampleLengthSeconds= sample->GetLength() / 1000.0f; pSong->m_PreviewFile= sNew; delete sample; } } else { pSong->m_PreviewFile= 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[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 ); } void ScreenEdit::DisplayTimingChangeMenu() { int row= GetRow(); EditMiniMenu(&g_TimingDataChangeInformation, SM_BackFromTimingDataChangeInformation); } // 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?" ); int ScreenEdit::GetSongOrNotesEnd() { return max(m_iStartPlayingAt, max(m_NoteDataEdit.GetLastRow(), BeatToNoteRow(m_pSteps->GetTimingData()->GetBeatFromElapsedTime( GAMESTATE->m_pCurSong->m_fMusicLengthSeconds)))); } void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &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= GetSongOrNotesEnd(); 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= GetSongOrNotesEnd(); 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(true); } } 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(true); } } 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())); g_StepsInformation.rows[step_music].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); g_StepsInformation.rows[step_music].SetOneUnthemedChoice( pSteps->GetMusicFile() ); 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 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()) ); } RadarValues radar; radar.Zero(); NoteDataUtil::CalculateRadarValues(m_NoteDataEdit, fMusicSeconds, radar); g_StepsData.rows[stream].SetOneUnthemedChoice(ssprintf("%.2f", radar[RadarCategory_Stream])); g_StepsData.rows[voltage].SetOneUnthemedChoice(ssprintf("%.2f", radar[RadarCategory_Voltage])); g_StepsData.rows[air].SetOneUnthemedChoice(ssprintf("%.2f", radar[RadarCategory_Air])); g_StepsData.rows[freeze].SetOneUnthemedChoice(ssprintf("%.2f", radar[RadarCategory_Freeze])); g_StepsData.rows[chaos].SetOneUnthemedChoice(ssprintf("%.2f", radar[RadarCategory_Chaos])); EditMiniMenu( &g_StepsData, SM_BackFromStepsData, SM_None ); break; } case save: case save_on_exit: m_CurrentAction = c; PerformSave(false); 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( OPTIONS_SCREEN, 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[preview].SetOneUnthemedChoice(pSong->m_PreviewFile); 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); } static LocalizedString ENTER_ARBITRARY_MAPPING( "ScreenEdit", "Enter the new track mapping." ); static LocalizedString TOO_MANY_TRACKS("ScreenEdit", "Too many tracks specified."); static LocalizedString NOT_A_TRACK("ScreenEdit", "'%s' is not a track id."); static LocalizedString OUT_OF_RANGE_ID("ScreenEdit", "Entry %d, '%d', is out of range 1 to %d."); static LocalizedString CONFIRM_CLEAR("ScreenEdit", "Are you sure you want to clear %d notes?"); static bool ConvertMappingInputToMapping(RString const& mapstr, int* mapping, RString& error) { vector mapping_input; split(mapstr, ",", mapping_input); size_t tracks_for_type= GAMEMAN->GetStepsTypeInfo(GAMESTATE->m_pCurSteps[0]->m_StepsType).iNumTracks; if(mapping_input.size() > tracks_for_type) { error= TOO_MANY_TRACKS; return false; } // mapping_input.size() < tracks_for_type is not checked because // unspecified tracks are mapped directly. -Kyz size_t track= 0; // track will be used for filling in the unspecified part of the mapping. for(; track < mapping_input.size(); ++track) { if(mapping_input[track].empty() || mapping_input[track] == " ") { // This allows blank entries to mean "pass through". mapping[track]= track+1; } else if(!(mapping_input[track] >> mapping[track])) { error= ssprintf(NOT_A_TRACK.GetValue(), mapping_input[track].c_str()); return false; } if(mapping[track] < 1 || mapping[track] > static_cast(tracks_for_type)) { error= ssprintf(OUT_OF_RANGE_ID.GetValue(), track+1, mapping[track], tracks_for_type); return false; } // Simpler for the user if they input track ids starting at 1. --mapping[track]; } for(; track < tracks_for_type; ++track) { mapping[track]= track; } return true; } static bool ArbitraryRemapValidate(const RString& answer, RString& error_out) { int mapping[MAX_NOTE_TRACKS]; return ConvertMappingInputToMapping(answer, mapping, error_out); } void ScreenEdit::HandleArbitraryRemapping(RString const& mapstr) { const NoteData OldClipboard( m_Clipboard ); HandleAlterMenuChoice( cut, false ); int mapping[MAX_NOTE_TRACKS]; RString error; // error is actually reported by the validate function, and unused here. if(ConvertMappingInputToMapping(mapstr, mapping, error)) { NoteDataUtil::ArbitraryRemap(m_Clipboard, mapping); } HandleAreaMenuChoice( paste_at_begin_marker, false ); m_Clipboard = OldClipboard; } void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &answers, bool allow_undo, bool prompt_clear) { 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(!allow_undo) bSaveUndo = false; if( bSaveUndo ) SaveUndo(); switch(c) { case cut: { HandleAlterMenuChoice(copy, false, false); HandleAlterMenuChoice(clear, false, false); } break; case copy: { m_Clipboard.ClearAll(); m_Clipboard.CopyRange( m_NoteDataEdit, m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker ); } break; case clear: { int note_count= m_NoteDataEdit.GetNumTapNotesNoTiming( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker); if(note_count >= PREFSMAN->m_EditClearPromptThreshold && prompt_clear) { ScreenPrompt::Prompt(SM_ConfirmClearArea, ssprintf(CONFIRM_CLEAR.GetValue(), note_count), PROMPT_YES_NO); } else { m_NoteDataEdit.ClearRange( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker); } } break; case quantize: { NoteType nt = (NoteType)answers[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, false ); StepsType st = GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->m_StepsType; TurnType tt = (TurnType)answers[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, false ); m_Clipboard = OldClipboard; } break; case transform: { int iBeginRow = m_NoteFieldEdit.m_iBeginMarker; int iEndRow = m_NoteFieldEdit.m_iEndMarker; TransformType tt = (TransformType)answers[c]; StepsType st = GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->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, false ); AlterType at = (AlterType)answers[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; case swap_up_down: NoteDataUtil::SwapUpDown(m_Clipboard, GAMESTATE->m_pCurSteps[0]->m_StepsType); break; case arbitrary_remap: ScreenTextEntry::TextEntry( SM_BackFromArbitraryRemap, ENTER_ARBITRARY_MAPPING, "1, 2, 3, 4", MAX_NOTE_TRACKS * 4, // 2 chars for digit, one for comma, one for space. ArbitraryRemapValidate ); break; } HandleAreaMenuChoice( paste_at_begin_marker, false ); m_Clipboard = OldClipboard; break; } case tempo: { // This affects all steps. AlterType at = (AlterType)answers[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 ); // For TimingData, it makes more sense not to offset by a row GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker, 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 &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( SET_MOD_SCREEN, 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 != TapNoteType_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 != TapNoteType_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 &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(), GetRowsFromAnswers(c, iAnswers)); break; case delete_and_shift: NoteDataUtil::DeleteRows( m_NoteDataEdit, GetRow(), GetRowsFromAnswers(c, iAnswers)); break; case shift_pauses_forward: GetAppropriateTimingForUpdate().InsertRows( GetRow(), GetRowsFromAnswers(c, iAnswers)); break; case shift_pauses_backward: GetAppropriateTimingForUpdate().DeleteRows( GetRow(), GetRowsFromAnswers(c, iAnswers)); 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 &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."); static LocalizedString ENTER_NEW_STEP_MUSIC("ScreenEdit", "Enter the music file for this chart."); void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const vector &iAnswers ) { Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; Difficulty dc = (Difficulty)iAnswers[difficulty]; pSteps->SetDifficulty( dc ); pSteps->SetDisplayBPM(static_cast(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, ScreenTextEntry::IntValidate, 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; } case step_music: { ScreenTextEntry::TextEntry(SM_BackFromStepMusicChange, ENTER_NEW_STEP_MUSIC, m_pSteps->GetMusicFile(), 255, SongUtil::ValidateCurrentStepsMusic, ChangeStepMusic, 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_PREVIEW ("ScreenEdit", "Enter a preview file."); 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 &iAnswers ) { Song* pSong = GAMESTATE->m_pCurSong; pSong->m_DisplayBPMType = static_cast(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 preview: ScreenTextEntry::TextEntry(SM_None, ENTER_PREVIEW, pSong->m_PreviewFile, 100, SongUtil::ValidateCurrentSongPreview, ChangePreview, 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_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 &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 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 shift_timing_in_region_down: m_timing_is_being_copied= false; m_timing_rows_being_shitted= GetRowsFromAnswers(c, iAnswers); DisplayTimingChangeMenu(); break; case shift_timing_in_region_up: m_timing_is_being_copied= false; m_timing_rows_being_shitted= -GetRowsFromAnswers(c, iAnswers); DisplayTimingChangeMenu(); break; case copy_timing_in_region: m_timing_is_being_copied= true; DisplayTimingChangeMenu(); break; case paste_timing_from_clip: clipboardFullTiming.CopyRange(0, MAX_NOTE_ROW, TimingSegmentType_Invalid, GetRow(), GetAppropriateTimingForUpdate()); 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::HandleTimingDataChangeChoice(TimingDataChangeChoice choice, const vector& answers) { TimingSegmentType change_type= TimingSegmentType_Invalid; switch(choice) { case timing_all: change_type= TimingSegmentType_Invalid; break; case timing_bpm: change_type= SEGMENT_BPM; break; case timing_stop: change_type= SEGMENT_STOP; break; case timing_delay: change_type= SEGMENT_DELAY; break; case timing_time_sig: change_type= SEGMENT_TIME_SIG; break; case timing_warp: change_type= SEGMENT_WARP; break; case timing_label: change_type= SEGMENT_LABEL; break; case timing_tickcount: change_type= SEGMENT_TICKCOUNT; break; case timing_combo: change_type= SEGMENT_COMBO; break; case timing_speed: change_type= SEGMENT_SPEED; break; case timing_scroll: change_type= SEGMENT_SCROLL; break; case timing_fake: change_type= SEGMENT_FAKE; break; default: break; } int begin= m_NoteFieldEdit.m_iBeginMarker; int end= m_NoteFieldEdit.m_iEndMarker; if(begin < 0) { begin= GetRow(); } if(end < 0) { end= MAX_NOTE_ROW; } if(m_timing_is_being_copied) { clipboardFullTiming.Clear(); GetAppropriateTiming().CopyRange(begin, end, change_type, 0, clipboardFullTiming); } else { GetAppropriateTimingForUpdate().ShiftRange(begin, end, change_type, m_timing_rows_being_shitted); } } void ScreenEdit::HandleBGChangeChoice( BGChangeChoice c, const vector &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) { // LaunchAttack is actually a misnomer. The function actually adds // the attack to a list in the PlayerState which is checked and // updated every tick to see which ones to actually activate. -Kyz 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 &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 &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() ); // If m_bInStepEditor is true while the song is reloaded, it screws up // loading and results in the steps being cleared. -Kyz GAMESTATE->m_bInStepEditor= false; GAMESTATE->m_pCurSong->ReloadFromSongDir(); GAMESTATE->m_bInStepEditor= true; 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 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_PREV_STEPS, EDIT_BUTTON_OPEN_NEXT_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 &vs, bool doSort ) { FOREACH( RString, vs, s ) ProcessKeyName( *s ); if (doSort) sort( vs.begin(), vs.end() ); vector::iterator toDelete = unique( vs.begin(), vs.end() ); vs.erase(toDelete, vs.end()); } static RString GetDeviceButtonsLocalized( const vector &veb, const MapEditToDI &editmap ) { vector vsPress; vector vsHold; FOREACH_CONST( EditButton, veb, eb ) { if( !IsMapped( *eb, editmap ) ) continue; for( int s=0; sGetLocalizedInputString(diPress)) ); if( diHold.IsValid() ) vsHold.push_back( Capitalize(INPUTMAN->GetLocalizedInputString(diHold)) ); } } ProcessKeyNames( vsPress, false ); ProcessKeyNames( vsHold, true ); 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 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 &kses = m_pSong->m_vsKeysoundFile; vector choices; FOREACH(RString, kses, ks) { choices.push_back(*ks); } choices.push_back(NEWKEYSND); choices.push_back(NO_KEYSND); int numKeysounds = kses.size(); int foundKeysounds = 0; 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; } else { ++foundKeysounds; } 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", foundKeysounds > 0, EditMode_Full, false, false, 0, kses)); EditMiniMenu(&g_KeysoundTrack, SM_BackFromKeysoundTrack); } void ScreenEdit::DoHelp() { g_EditHelp.rows.clear(); for( unsigned i=0; iGetString( "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 { 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. */