@@ -25,42 +25,113 @@ int ItemAnimation::getPhaseFromFrames(int frames, int64_t global_ms,
2525 return tick % frames;
2626}
2727
28+ int ItemAnimation::getPingPongPhase (int raw_phase, int frames) {
29+ if (frames <= 1 ) return 0 ;
30+ int cycle = frames * 2 - 2 ;
31+ int mod = raw_phase % cycle;
32+ return mod >= frames ? cycle - mod : mod;
33+ }
34+
2835int ItemAnimation::getPhase (const Domain::ItemType &item, int64_t global_ms,
2936 int tile_x, int tile_y, int tile_z) {
3037 int frames = std::max<int >(item.frames , 1 );
3138 if (frames <= 1 )
3239 return 0 ;
3340
34- bool per_instance = !item.frame_durations .empty () && item.animation_mode == 0 ;
35- int tile_offset = per_instance ? (tile_x * 17 + tile_y * 31 + tile_z * 7 ) : 0 ;
41+ bool is_async = !item.frame_durations .empty () && item.animation_mode == 0 ;
42+ int tile_offset = is_async ? (tile_x * 17 + tile_y * 31 + tile_z * 7 ) : 0 ;
43+
44+ int start_phase = 0 ;
45+ if (item.start_frame == 255 && is_async) {
46+ start_phase = (tile_x * 17 + tile_y * 31 + tile_z * 7 ) % frames;
47+ } else if (item.start_frame > 0 ) {
48+ start_phase = item.start_frame % frames;
49+ }
50+
51+ if (item.loop_count == -1 ) {
52+ // Ping-pong: apply start_phase to raw phase BEFORE reflection
53+ if (item.frame_durations .empty ()) {
54+ int tick = static_cast <int >(global_ms / 500 );
55+ return getPingPongPhase (tick + tile_offset + start_phase, frames);
56+ }
57+
58+ // Build explicit ping-pong timeline from per-phase durations
59+ const int num_phases = static_cast <int >(item.frame_durations .size ());
60+ if (num_phases == 0 ) return start_phase;
61+
62+ // Per-phase durations (stack allocation — clamp to prevent overflow)
63+ constexpr int MAX_PHASES = 32 ;
64+ const int effective_phases = std::min<int >(num_phases, MAX_PHASES );
65+ int phase_durs[MAX_PHASES ];
66+ int cycle_length = 0 ;
67+ for (int i = 0 ; i < effective_phases; ++i) {
68+ phase_durs[i] = getPhaseDuration (item, i);
69+ cycle_length += phase_durs[i];
70+ }
71+
72+ // Ping-pong timeline: [0, 1, ..., N-1, N-2, ..., 1] (omit duplicate endpoints)
73+ constexpr int MAX_TIMELINE = MAX_PHASES * 2 - 2 ;
74+ int timeline[MAX_TIMELINE ];
75+ int timeline_len = 0 ;
76+ for (int i = 0 ; i < effective_phases; ++i)
77+ timeline[timeline_len++] = i;
78+ for (int i = effective_phases - 2 ; i >= 1 ; --i)
79+ timeline[timeline_len++] = i;
80+
81+ // Backward portion cycle length
82+ int backward_length = 0 ;
83+ for (int i = effective_phases - 2 ; i >= 1 ; --i)
84+ backward_length += phase_durs[i];
85+ cycle_length += backward_length;
3686
87+ if (cycle_length <= 0 ) return start_phase;
88+
89+ // Start offset: sum durations of first start_phase phases
90+ int start_offset = 0 ;
91+ for (int i = 0 ; i < start_phase && i < effective_phases; ++i)
92+ start_offset += phase_durs[i];
93+
94+ int elapsed = static_cast <int >(
95+ (global_ms + static_cast <int64_t >(tile_offset) * 500 + start_offset) % cycle_length);
96+
97+ // Walk the timeline to find the active phase
98+ for (int i = 0 ; i < timeline_len; ++i) {
99+ int dur = phase_durs[timeline[i]];
100+ if (elapsed < dur)
101+ return timeline[i] % frames;
102+ elapsed -= dur;
103+ }
104+ return 0 ;
105+ }
106+
107+ // Forward loop
37108 if (item.frame_durations .empty ()) {
38109 int tick = static_cast <int >(global_ms / 500 );
39- return (tick + tile_offset) % frames;
110+ return (tick + tile_offset + start_phase ) % frames;
40111 }
41112
42113 int total = static_cast <int >(item.total_duration );
43114 if (total <= 0 )
44- return 0 ;
115+ return start_phase ;
45116
46117 int64_t elapsed_ms = global_ms + static_cast <int64_t >(tile_offset) * 500 ;
47118 int elapsed = static_cast <int >(elapsed_ms % total);
48119
49120 for (int phase = 0 ; phase < static_cast <int >(item.frame_durations .size ()); ++phase) {
50121 int dur = getPhaseDuration (item, phase);
51122 if (elapsed < dur)
52- return phase % frames;
123+ return ( phase + start_phase) % frames;
53124 elapsed -= dur;
54125 }
55126
56- return 0 ;
127+ return start_phase ;
57128}
58129
59130int ItemAnimation::getPhaseDuration (const Domain::ItemType &item, int phase) {
60131 if (phase < 0 || phase >= static_cast <int >(item.frame_durations .size ()))
61132 return 500 ;
62133 const auto &d = item.frame_durations [phase];
63- return ( d.first + d.second ) / 2 ;
134+ return static_cast < int >(( d.first + d.second ) / 2 ) ;
64135}
65136
66137} // namespace Rendering
0 commit comments