Skip to content

Commit 1a62d66

Browse files
committed
2 parents fa77bf3 + 9d73c4f commit 1a62d66

18 files changed

Lines changed: 461 additions & 285 deletions

ImguiMapEditor/Domain/ItemType.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ class ItemType {
251251
bool is_wall = false;
252252
bool is_locked = false; // Door key lock status (for highlight_locked_doors)
253253

254+
// Lying object (multi-tile corpses) — drawn before creatures
255+
bool is_lying_object = false;
256+
254257
// Sprite IDs for rendering
255258
std::vector<uint32_t> sprite_ids;
256259

ImguiMapEditor/Rendering/Animation/ItemAnimation.cpp

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2835
int 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

59130
int 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

ImguiMapEditor/Rendering/Animation/ItemAnimation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct ItemAnimation {
1616

1717
private:
1818
static int getPhaseDuration(const Domain::ItemType &item, int phase);
19+
static int getPingPongPhase(int raw_phase, int frames);
1920
};
2021

2122
} // namespace Rendering

ImguiMapEditor/Rendering/Map/TileRenderer.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,14 @@ void TileRenderer::queueTile(const Domain::Tile &tile, int tile_x, int tile_y,
189189
}
190190
}
191191

192-
// Queue all items - delegate to ItemRenderer
193-
// Pass cached items with pre-resolved types
192+
// Queue all items
194193
item_renderer_.queueAll(render_cache_, screen_x, screen_y, size, tile_x,
195194
tile_y, tile_z, anim_ticks, ground_color, item_alpha,
196195
isItemSelected, view_settings_, missing_sprites,
197196
&accumulated_elevation, tile_has_hook_south,
198197
tile_has_hook_east, check_tooltips, &tile_needs_tooltip);
199198

200-
// CREATURE RENDERING - Per-tile immediate (OTClient parity)
199+
// Creature rendering (per-tile for correct isometric depth)
201200
// With diagonal tile iteration, creatures must be rendered PER-TILE to
202201
// maintain correct isometric depth. NW tiles (drawn first) should have
203202
// creatures that can be overlapped by SE tile items (drawn later). Deferring

0 commit comments

Comments
 (0)