diff --git a/core/shared/core/editor.h b/core/shared/core/editor.h index 7d73c7a..628af1c 100644 --- a/core/shared/core/editor.h +++ b/core/shared/core/editor.h @@ -13,6 +13,7 @@ public: virtual void input(PlayerInput&& inputdata) = 0; virtual void update(UpdateData&& updatedata) = 0; virtual void draw() const = 0; + virtual void recalculate(const microsec& timestamp) = 0; inline void setBPMSections(const std::set& sections) noexcept { diff --git a/core/shared/core/note.h b/core/shared/core/note.h index 53e22d6..43e4552 100644 --- a/core/shared/core/note.h +++ b/core/shared/core/note.h @@ -9,7 +9,7 @@ public: _perfect_offset(perfect_offset) {} virtual ~Note() = default; - virtual bool isActive() const = 0; + virtual bool isActive(const microsec& offset) const = 0; virtual void update(const microsec& music_offset) = 0; virtual void draw() const = 0; diff --git a/core/shared/core/timeline.h b/core/shared/core/timeline.h index f235105..c0cb411 100644 --- a/core/shared/core/timeline.h +++ b/core/shared/core/timeline.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "tools/mathutils.h" #include "core/note.h" @@ -18,15 +19,46 @@ public: typedef typename std::set::const_iterator Iterator; + void recalculate(const microsec& offset) + { + _current_offset = offset; + expire(_first_visible_note); + expire(_last_visible_note); + expire(_top_note); + + if (!_timeline.empty()) + { + Iterator head_iterator = _timeline.begin(); + + while (!isExpired(head_iterator)) + { + if ((*head_iterator)->offset() >= offset) + { + Iterator pre_head = head_iterator; + --pre_head; + + _top_note = !isExpired(pre_head) && (*pre_head)->isActive(offset) + ? pre_head + : head_iterator; + break; + } + + ++head_iterator; + } + + if (isExpired(_top_note)) + _top_note = _timeline.begin(); + } + + fetchVisibleNotes(); + } + void setNotes(const std::set& notes, const microsec& visibility) { _visibility_offset = visibility; _timeline = std::move(notes); - _top_note = _timeline.begin(); - expire(_first_visible_note); - expire(_last_visible_note); - expire(_active_note); + recalculate(_current_offset); if (isExpired(_top_note)) return; @@ -37,13 +69,16 @@ public: void insertNote(TNote* note) { _top_note = _timeline.insert(note).first; + recalculate(_current_offset); update(_current_offset); } void insertNotes(const std::set& notes) { _timeline.insert(notes.begin(), notes.end()); + recalculate(_current_offset); update(_current_offset); + } inline void clear() @@ -57,13 +92,7 @@ public: void update(const microsec& offset) { _current_offset = offset; - - if (isExpired(_top_note)) - return; - - checkTopNote(_current_offset); - checkCurrentActiveNote(); - checkForNextActiveNote(); + updateTopNote(_current_offset); updateVisibleSprites(_current_offset); } @@ -83,9 +112,6 @@ public: void findLastVisibleNote(const microsec& music_offset) { - if (isExpired(_top_note)) - return; - Iterator note_iterator = _top_note; while (!isExpired(note_iterator) && isVisiblyClose(note_iterator, music_offset)) { @@ -113,15 +139,34 @@ public: { auto note = *note_iterator; if (note->shouldRemove()) + { ++_first_visible_note; + } ++note_iterator; } } - inline Iterator getActiveNote() noexcept + Iterator getActiveNote(const microsec& music_offset) noexcept { - return _active_note; + Iterator return_note = _timeline.end(); + + auto note_iterator = _top_note; + while (!isExpired(note_iterator)) + { + const auto& note = *note_iterator; + if (note->isActive(music_offset)) + { + return_note = note_iterator; + break; + } + else if (note->offset() > music_offset) + break; + + ++note_iterator; + } + + return return_note; } inline Iterator getNoteBy(const microsec& music_offset) noexcept @@ -133,12 +178,12 @@ public: }); } - inline bool isExpired(const Iterator& iterator) const + inline bool isExpired(const Iterator& iterator) const noexcept { return iterator == _timeline.end(); } - inline void expire(Iterator& iterator) + inline void expire(Iterator& iterator) noexcept { iterator = _timeline.end(); } @@ -148,6 +193,16 @@ private: microsec _visibility_offset; microsec _current_offset; + inline void updateTopNote(const microsec& music_offset) noexcept + { + if ((*_top_note)->offset() < music_offset // + && _top_note == _first_visible_note // Maybe simplify + && !(*_top_note)->isActive(music_offset)) // + { + ++_top_note; + } + } + void updateVisibleSprites(const microsec& music_offset) { if (nothingToDraw()) @@ -160,40 +215,7 @@ private: }); } - void checkCurrentActiveNote() - { - if (isExpired(_active_note)) - return; - - auto note = *_active_note; - - if (!note->isActive()) - { - expire(_active_note); - ++_top_note; - } - } - - void checkTopNote(const microsec& offset) - { - if (isExpired(_top_note) || !isExpired(_active_note)) - return; - - while ((*_top_note)->offset() < offset) - ++_top_note; - } - - void checkForNextActiveNote() - { - if (!isExpired(_active_note)) - return; - - auto top_note = *_top_note; - if (top_note->isActive()) - _active_note = _top_note; - } - - inline bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const + inline bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const noexcept { return ((*iterator)->offset() - _visibility_offset) <= music_offset; } @@ -203,22 +225,7 @@ private: return isExpired(_first_visible_note); } - /* Difference between top and active note is that - * top note is the note handling input right now - * OR it's the closest note from current music offset - * position, not necessarily active. A note stops being top only - * after dying or being tapped by player, even if it's already - * past her perfect offset. - * - * Meanwhile active note is the note which is currently handling - * player input for grade. - * - * An active note is always top note but a top note - * is not always active note. - * */ - Iterator _top_note; - Iterator _active_note; Iterator _last_visible_note; Iterator _first_visible_note; }; diff --git a/modes/classicmode/editor/classiceditor.cpp b/modes/classicmode/editor/classiceditor.cpp index cbc9cfa..db8ade5 100644 --- a/modes/classicmode/editor/classiceditor.cpp +++ b/modes/classicmode/editor/classiceditor.cpp @@ -12,23 +12,20 @@ ClassicEditor::ClassicEditor(std::shared_ptr&& manager) std::set _set = {}; - for (int i = 1; i < 5; ++i) - { - NoteInitializer init; - init.context = &_context; - init.intervals = {}; - init.perfect_offset = basic_offset + (500000 * i); - - ElementInitializer elem_init; - elem_init.type = _selected_type; - elem_init.coordinates = Coordinates{ 700 - (i * 120), 550 }; - elem_init.falling_curve_interpolation = {}; - - MockArrowNoteInitializer mock_init; - mock_init.elements = {elem_init}; - mock_init.initializer = init; - _set.insert(new MockClassicNote(std::move(mock_init))); - } + NoteInitializer init; + init.context = &_context; + init.intervals = {}; + init.perfect_offset = basic_offset + (500000 * 20); + + ElementInitializer elem_init; + elem_init.type = _selected_type; + elem_init.coordinates = Coordinates{ 700 - (5 * 120), 550 }; + elem_init.falling_curve_interpolation = {}; + + MockArrowNoteInitializer mock_init; + mock_init.elements = {elem_init}; + mock_init.initializer = init; + _set.insert(new MockClassicNote(std::move(mock_init))); _timeline.setNotes(_set, 1648648); } @@ -89,6 +86,11 @@ void ClassicEditor::draw() const }); } +void ClassicEditor::recalculate(const microsec& timestamp) +{ + _timeline.recalculate(timestamp); +} + void ClassicEditor::selectNoteType(Type type) noexcept { _selected_type = type; diff --git a/modes/classicmode/editor/classiceditor.h b/modes/classicmode/editor/classiceditor.h index 02893c2..90a04b2 100644 --- a/modes/classicmode/editor/classiceditor.h +++ b/modes/classicmode/editor/classiceditor.h @@ -17,6 +17,7 @@ public: virtual void input(PlayerInput&& inputdata) override; virtual void update(UpdateData&& updatedata) override; virtual void draw() const override; + virtual void recalculate(const microsec& timestamp) override; void selectNoteType(Type type) noexcept; diff --git a/modes/classicmode/editor/mockclassicnote.cpp b/modes/classicmode/editor/mockclassicnote.cpp index fd4d5c8..6bdd878 100644 --- a/modes/classicmode/editor/mockclassicnote.cpp +++ b/modes/classicmode/editor/mockclassicnote.cpp @@ -30,10 +30,9 @@ MockClassicNote::MockClassicNote(MockArrowNoteInitializer&& init) : } } -bool MockClassicNote::isActive() const +bool MockClassicNote::isActive(const microsec& offset) const { - return _state != State::DEAD - && _state != State::NONE; + return offset == Note::offset(); } bool MockClassicNote::isInGame() const @@ -44,21 +43,20 @@ bool MockClassicNote::isInGame() const bool MockClassicNote::shouldRemove() const { - return _state == State::DEAD; + return _state == State::DEAD + || _state == State::NONE; } void MockClassicNote::putToGame(const microsec &music_offset) { - _state = State::FLYING; - - std::cout << "Put to game " << this << ": " << music_offset << '\n'; + _state = State::FLYING; (void)music_offset; for (auto& element : _elements) { element.sprite = _context->graphics_manager->getSprite(element.type); element.sprite->setCoordinates(element.coordinates); element.sprite->setTrailCoordinates(Coordinates(0.f, 9.f)); - element.animations[_state]->launch(element.sprite, music_offset, offset()); + element.animations[_state]->launch(element.sprite, offset() - 1648648, offset()); } } diff --git a/modes/classicmode/editor/mockclassicnote.h b/modes/classicmode/editor/mockclassicnote.h index 10f5226..2e935b0 100644 --- a/modes/classicmode/editor/mockclassicnote.h +++ b/modes/classicmode/editor/mockclassicnote.h @@ -25,7 +25,7 @@ public: explicit MockClassicNote(MockArrowNoteInitializer&& init); virtual ~MockClassicNote() override = default; - virtual bool isActive() const override final; + virtual bool isActive(const microsec& offset) const override final; virtual bool isInGame() const override final; virtual bool shouldRemove() const override final; diff --git a/modes/classicmode/game/classicarrownote.cpp b/modes/classicmode/game/classicarrownote.cpp index 5c0f728..fe26576 100644 --- a/modes/classicmode/game/classicarrownote.cpp +++ b/modes/classicmode/game/classicarrownote.cpp @@ -22,7 +22,6 @@ ClassicArrowNote::ClassicArrowNote(ArrowNoteInitializer&& init) : // Animations will be injected into note. _elements[i].animations[State::NONE] = nullptr; _elements[i].animations[State::FLYING] = std::make_shared(); - _elements[i].animations[State::ACTIVE] = _elements[i].animations[State::FLYING]; _elements[i].animations[State::DYING] = std::make_shared(); _elements[i].animations[State::DEAD] = nullptr; } @@ -100,9 +99,11 @@ void ClassicArrowNote::update(const microsec& music_offset) break; case State::FLYING: - if (_evaluator.isActive(music_offset)) { - _state = State::ACTIVE; - + if (!_evaluator.isActive(music_offset) && music_offset > offset()) + { + _state = State::DYING; + for (auto& element : _elements) + element.animations[_state]->launch(element.sprite, music_offset, offset()); } break; @@ -110,15 +111,6 @@ void ClassicArrowNote::update(const microsec& music_offset) if (_elements[0].animations[_state]->isDone()) _state = State::DEAD; break; - - case State::ACTIVE: - if (!_evaluator.isActive(music_offset)) - { - _state = State::DYING; - for (auto& element : _elements) - element.animations[_state]->launch(element.sprite, music_offset, offset()); - } - break; } for (auto& element : _elements) @@ -138,7 +130,7 @@ bool ClassicArrowNote::allElementsPressed() const bool ClassicArrowNote::isPressedAs(sf::Keyboard::Key key) const { return std::any_of(_elements.begin(), _elements.end(), - [key=key](const auto& element) + [key](const auto& element) { return key == element.pressed_as; }); diff --git a/modes/classicmode/game/classicgame.cpp b/modes/classicmode/game/classicgame.cpp index 0081d79..e5637d5 100644 --- a/modes/classicmode/game/classicgame.cpp +++ b/modes/classicmode/game/classicgame.cpp @@ -73,7 +73,7 @@ void ClassicGame::input(PlayerInput&& inputdata) case sf::Event::KeyPressed: { - auto note_it = _timeline.getActiveNote(); + auto note_it = _timeline.getActiveNote(inputdata.timestamp); if (!_timeline.isExpired(note_it)) { diff --git a/modes/classicmode/game/classicnote.cpp b/modes/classicmode/game/classicnote.cpp index fb9d903..78cffd6 100644 --- a/modes/classicmode/game/classicnote.cpp +++ b/modes/classicmode/game/classicnote.cpp @@ -14,15 +14,15 @@ ClassicNote::ClassicNote(NoteInitializer &&init) : _context(init.context) {} -bool ClassicNote::isActive() const +bool ClassicNote::isActive(const microsec& offset) const { - return _state == State::ACTIVE; + return _evaluator.isActive(offset) + && _state != State::DYING; } bool ClassicNote::isInGame() const { return _state == State::FLYING - || _state == State::ACTIVE || _state == State::DYING; } diff --git a/modes/classicmode/game/classicnote.h b/modes/classicmode/game/classicnote.h index a252b9b..935848d 100644 --- a/modes/classicmode/game/classicnote.h +++ b/modes/classicmode/game/classicnote.h @@ -24,7 +24,6 @@ public: NONE, FLYING, - ACTIVE, DYING, DEAD }; @@ -32,7 +31,7 @@ public: explicit ClassicNote(NoteInitializer&& init); virtual ~ClassicNote() override = default; - virtual bool isActive() const override final; + virtual bool isActive(const microsec& offset) const override final; virtual bool isInGame() const override final; virtual bool shouldRemove() const override final; diff --git a/src/application/editorstate.cpp b/src/application/editorstate.cpp index 6fde314..5de65b9 100644 --- a/src/application/editorstate.cpp +++ b/src/application/editorstate.cpp @@ -23,9 +23,6 @@ EditorState::~EditorState() void EditorState::input(const sf::Event& event) { - if (event.key.code == sf::Keyboard::Space && event.type == sf::Event::KeyReleased) - _music.isPaused() ? _music.play() : _music.pause(); - _group->input(event); } @@ -156,8 +153,13 @@ void EditorState::enter() callbacks.onInput = [&editor, &music](const sf::Event& event) { - if (event.type == sf::Event::MouseWheelScrolled) + if (event.key.code == sf::Keyboard::Space && event.type == sf::Event::KeyReleased) + music.isPaused() ? music.play() : music.pause(); + else if (event.type == sf::Event::MouseWheelScrolled) + { music.moveOffset(event.mouseWheelScroll.delta > 0 ? 500000 : -500000); + editor->recalculate(music.fetchOffset()); + } else editor->input(PlayerInput{music.fetchOffset(), event}); };