diff --git a/src/GameState.cpp b/src/GameState.cpp index 09b4ae443e..0253d9dd92 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -3094,6 +3094,32 @@ public: return 0; } + static int GetAutoGenFarg(T* p, lua_State *L) + { + int i= IArg(1) - 1; + if(i < 0) { lua_pushnil(L); return 1; } + size_t si= static_cast(i); + if(si >= p->m_autogen_fargs.size()) { lua_pushnil(L); return 1; } + lua_pushnumber(L, p->GetAutoGenFarg(si)); + return 1; + } + static int SetAutoGenFarg(T* p, lua_State* L) + { + int i= IArg(1) - 1; + if(i < 0) + { + luaL_error(L, "%i is not a valid autogen arg index.", i); + } + float v= FArg(2); + size_t si= static_cast(i); + while(si >= p->m_autogen_fargs.size()) + { + p->m_autogen_fargs.push_back(0.0f); + } + p->m_autogen_fargs[si]= v; + COMMON_RETURN_SELF; + } + LunaGameState() { ADD_METHOD( IsPlayerEnabled ); @@ -3218,6 +3244,8 @@ public: ADD_METHOD( SetCurrentStyle ); ADD_METHOD( SetCurrentPlayMode ); ADD_METHOD( SetStepsForEditMode ); + ADD_METHOD( GetAutoGenFarg ); + ADD_METHOD( SetAutoGenFarg ); } }; diff --git a/src/GameState.h b/src/GameState.h index d449f17e69..0001dd15f1 100644 --- a/src/GameState.h +++ b/src/GameState.h @@ -404,6 +404,15 @@ public: bool m_bDopefish; + // Autogen stuff. This should probably be moved to its own singleton or + // something when autogen is generalized and more customizable. -Kyz + float GetAutoGenFarg(size_t i) + { + if(i >= m_autogen_fargs.size()) { return 0.0f; } + return m_autogen_fargs[i]; + } + vector m_autogen_fargs; + // Lua void PushSelf( lua_State *L ); diff --git a/src/NoteDataUtil.cpp b/src/NoteDataUtil.cpp index 14078d39f4..f6766ee6c7 100644 --- a/src/NoteDataUtil.cpp +++ b/src/NoteDataUtil.cpp @@ -781,6 +781,174 @@ void NoteDataUtil::LoadTransformedLightsFromTwo( const NoteData &marquee, const NoteDataUtil::RemoveMines( out ); } +// This kickbox_limb enum should not be used anywhere outside the +// Autogenkickbox function. -Kyz +enum kickbox_limb +{ + left_foot, left_fist, right_fist, right_foot, num_kickbox_limbs, invalid_limb= -1 +}; +void NoteDataUtil::AutogenKickbox(const NoteData& in, NoteData& out, const TimingData& timing, StepsType out_type, int nonrandom_seed) +{ + // Each limb has its own list of tracks it is used for. This allows + // abstract handling of the different styles. + // By convention, the lower panels are pushed first. This gives the upper + // panels a higher index, which is mnemonically useful. + vector > limb_tracks(num_kickbox_limbs); + bool have_feet= true; + switch(out_type) + { + case StepsType_kickbox_human: + out.SetNumTracks(4); + limb_tracks[left_foot].push_back(0); + limb_tracks[left_fist].push_back(1); + limb_tracks[right_fist].push_back(2); + limb_tracks[right_foot].push_back(3); + break; + case StepsType_kickbox_quadarm: + out.SetNumTracks(4); + have_feet= false; + limb_tracks[left_fist].push_back(1); + limb_tracks[left_fist].push_back(0); + limb_tracks[right_fist].push_back(2); + limb_tracks[right_fist].push_back(3); + break; + case StepsType_kickbox_insect: + out.SetNumTracks(6); + limb_tracks[left_foot].push_back(0); + limb_tracks[left_fist].push_back(2); + limb_tracks[left_fist].push_back(1); + limb_tracks[right_fist].push_back(3); + limb_tracks[right_fist].push_back(4); + limb_tracks[right_foot].push_back(5); + break; + case StepsType_kickbox_arachnid: + out.SetNumTracks(8); + limb_tracks[left_foot].push_back(0); + limb_tracks[left_foot].push_back(1); + limb_tracks[left_fist].push_back(3); + limb_tracks[left_fist].push_back(2); + limb_tracks[right_fist].push_back(4); + limb_tracks[right_fist].push_back(5); + limb_tracks[right_foot].push_back(7); + limb_tracks[right_foot].push_back(6); + break; + DEFAULT_FAIL(out_type); + } + // prev_limb_panels keeps track of which panel in the track list the limb + // hit last. + vector prev_limb_panels(num_kickbox_limbs, 0); + vector panel_repeat_counts(num_kickbox_limbs, 0); + vector panel_repeat_goals(num_kickbox_limbs, 0); + RandomGen rnd(nonrandom_seed); + kickbox_limb prev_limb_used= invalid_limb; + // Kicks are only allowed if there is enough setup/recovery time. + float kick_recover_time= GAMESTATE->GetAutoGenFarg(0); + if(kick_recover_time <= 0.0f) + { + kick_recover_time= .25f; + } + float prev_note_time= -1.0f; + int rows_done= 0; +#define RAND_FIST ((rnd() % 2) ? left_fist : right_fist) +#define RAND_FOOT ((rnd() % 2) ? left_foot : right_foot) + FOREACH_NONEMPTY_ROW_ALL_TRACKS(in, r) + { + // Arbitrary: Drop everything except taps and hold heads out entirely, + // convert holds to tap just the head. + bool has_valid_tapnote= false; + for(int t= 0; t < in.GetNumTracks(); ++t) + { + const TapNote& tn= in.GetTapNote(t, r); + if(tn.type == TapNoteType_Tap || tn.type == TapNoteType_HoldHead) + { + has_valid_tapnote= true; + break; + } + } + if(!has_valid_tapnote) { continue; } + int next_row= r; + bool next_has_valid= false; + while(!next_has_valid) + { + if(!in.GetNextTapNoteRowForAllTracks(next_row)) + { + next_row= -1; + next_has_valid= true; + } + else + { + for(int t= 0; t < in.GetNumTracks(); ++t) + { + const TapNote& tn= in.GetTapNote(t, next_row); + if(tn.type == TapNoteType_Tap || tn.type == TapNoteType_HoldHead) + { + next_has_valid= true; + break; + } + } + } + } + float this_note_time= timing.GetElapsedTimeFromBeat(NoteRowToBeat(r)); + float next_note_time= timing.GetElapsedTimeFromBeat(NoteRowToBeat(next_row)); + kickbox_limb this_limb= invalid_limb; + switch(prev_limb_used) + { + case invalid_limb: + // First limb is arbitrarily always a fist. + this_limb= RAND_FIST; + break; + case left_foot: + case right_foot: + // Multiple kicks in a row are allowed if they're on the same foot. + // Allow the last note to be a kick. + // Switch feet if there's enough time. + if(next_note_time - this_note_time > kick_recover_time * 2.0f) + { + this_limb= prev_limb_used == left_foot ? right_foot : left_foot; + } + else if((next_note_time - this_note_time > kick_recover_time * .5f || + next_note_time < 0.0f) && (rnd() % 2)) + { + this_limb= prev_limb_used; + } + else + { + this_limb= RAND_FIST; + } + break; + case left_fist: + case right_fist: + if(this_note_time - prev_note_time > kick_recover_time && + (next_note_time - this_note_time > kick_recover_time || + next_note_time < 0.0f) && have_feet) + { + this_limb= RAND_FOOT; + } + else + { + // Alternate fists. + this_limb= prev_limb_used == left_fist ? right_fist : left_fist; + } + break; + } + size_t this_panel= prev_limb_panels[this_limb]; + if(panel_repeat_counts[this_limb] + 1 > panel_repeat_goals[this_limb]) + { + // Use a different panel. + this_panel= (this_panel + 1) % limb_tracks[this_limb].size(); + panel_repeat_counts[this_limb]= 0; + panel_repeat_goals[this_limb]= (rnd() % 8) + 1; + } + out.SetTapNote(limb_tracks[this_limb][this_panel], r, TAP_ORIGINAL_TAP); + ++panel_repeat_counts[this_limb]; + prev_limb_panels[this_limb]= this_panel; + prev_note_time= this_note_time; + prev_limb_used= this_limb; + ++rows_done; + } + out.RevalidateATIs(vector(), false); +} + RadarStats CalculateRadarStatsFast( const NoteData &in, RadarStats &out ) { out.taps = 0; diff --git a/src/NoteDataUtil.h b/src/NoteDataUtil.h index 9b25e718b8..15f4f5c2e2 100644 --- a/src/NoteDataUtil.h +++ b/src/NoteDataUtil.h @@ -63,6 +63,11 @@ namespace NoteDataUtil void LoadTransformedLightsFromTwo( const NoteData &marquee, const NoteData &bass, NoteData &out ); void InsertHoldTails( NoteData &inout ); + // Special case so that kickbox can have autogen steps that are playable. + // Hopefully I'll replace this with a good generalized autogen system + // later. -Kyz + void AutogenKickbox(const NoteData& in, NoteData& out, const TimingData& timing, StepsType out_type, int nonrandom_seed); + // radar values - return between 0.0 and 1.2 float GetStreamRadarValue( const NoteData &in, float fSongSeconds ); float GetVoltageRadarValue( const NoteData &in, float fSongSeconds ); diff --git a/src/Steps.cpp b/src/Steps.cpp index c7640e2e88..0dbc71c05c 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -352,6 +352,12 @@ void Steps::Decompress() const const_cast(this)->Decompress(); } +bool stepstype_is_kickbox(StepsType st) +{ + return st == StepsType_kickbox_human || st == StepsType_kickbox_quadarm || + st == StepsType_kickbox_insect || st == StepsType_kickbox_arachnid; +} + void Steps::Decompress() { if( m_bNoteDataIsFilled ) @@ -373,9 +379,24 @@ void Steps::Decompress() } else { - NoteDataUtil::LoadTransformedSlidingWindow( notedata, *m_pNoteData, iNewTracks ); + // Special case so that kickbox can have autogen steps that are playable. + // Hopefully I'll replace this with a good generalized autogen system + // later. -Kyz + if(stepstype_is_kickbox(this->m_StepsType)) + { + // Number of notes seems like a useful "random" input so that charts + // from different sources come out different, but autogen always + // makes the same thing from one source. -Kyz + NoteDataUtil::AutogenKickbox(notedata, *m_pNoteData, *GetTimingData(), + this->m_StepsType, + static_cast(GetRadarValues(PLAYER_1)[RadarCategory_TapsAndHolds])); + } + else + { + NoteDataUtil::LoadTransformedSlidingWindow( notedata, *m_pNoteData, iNewTracks ); - NoteDataUtil::RemoveStretch( *m_pNoteData, m_StepsType ); + NoteDataUtil::RemoveStretch( *m_pNoteData, m_StepsType ); + } } return; }