8#include "absl/strings/str_format.h"
24 const std::vector<zelda3::OverworldMap>& maps, ImVec2 world_position,
27 current_map >=
static_cast<int>(maps.size())) {
28 return absl::InvalidArgumentError(
"Invalid entity insertion map");
30 if (!std::isfinite(world_position.x) || !std::isfinite(world_position.y) ||
31 world_position.x < 0 || world_position.y < 0 ||
32 world_position.x >= 4096 || world_position.y >= 4096) {
33 return absl::InvalidArgumentError(
"Invalid entity insertion position");
36 const int x =
static_cast<int>(world_position.x) / 16 * 16;
37 const int y =
static_cast<int>(world_position.y) / 16 * 16;
38 const int world = current_map / 64;
39 const int screen = world * 64 + x / 512 + (y / 512) * 8;
40 if (screen != current_map) {
41 return absl::InvalidArgumentError(
42 "Entity insertion position does not belong to the selected screen");
45 const int raw_parent = maps[current_map].parent();
46 const int parent = raw_parent == 0xFF ? current_map : raw_parent;
48 parent >=
static_cast<int>(maps.size()) || parent / 64 != world) {
49 return absl::InvalidArgumentError(
"Invalid entity insertion parent map");
51 const int parent_parent = maps[parent].parent();
52 if (parent_parent != parent && parent_parent != 0xFF) {
53 return absl::InvalidArgumentError(
54 "Entity insertion parent is not a root map");
60 const int game_x = (x - (parent % 8) * 512) / 16;
61 const int game_y = (y - ((parent % 64) / 8) * 512) / 16;
62 if (game_x < 0 || game_x > 63 || game_y < 0 || game_y > 63) {
63 return absl::InvalidArgumentError(
64 "Entity insertion position is outside the parent coordinate range");
67 static_cast<uint8_t
>(game_y)};
80 const std::vector<zelda3::OverworldMap>& maps, ImVec2 world_position,
81 int current_map, uint8_t item_id) {
83 ResolveEntityInsertionPosition(maps, world_position, current_map);
85 return position.status();
89 static_cast<uint16_t
>(position->parent_map),
90 position->x, position->y,
false);
94 const std::vector<zelda3::OverworldMap>& maps, ImVec2 world_position,
95 int current_map, uint8_t sprite_id) {
97 ResolveEntityInsertionPosition(maps, world_position, current_map);
99 return position.status();
102 static_cast<uint8_t
>(position->parent_map), sprite_id,
103 position->game_x, position->game_y, position->x,
110 if (!overworld || !overworld->
is_loaded()) {
111 return absl::FailedPreconditionError(
"Overworld not loaded");
118 auto* current_ow_map = overworld->
overworld_map(current_map);
123 auto& holes = overworld->
holes();
124 for (
size_t i = 0; i < holes.size(); ++i) {
125 if (holes[i].deleted) {
127 holes[i].deleted =
false;
128 holes[i].map_id_ = map_id;
129 holes[i].x_ =
static_cast<int>(snapped_pos.x);
130 holes[i].y_ =
static_cast<int>(snapped_pos.y);
131 holes[i].entrance_id_ = 0;
132 holes[i].is_hole_ =
true;
135 holes[i].UpdateMapProperties(map_id, overworld);
138 "Inserted hole at slot %zu: pos=(%d,%d) map=0x%02X", i,
139 holes[i].x_, holes[i].y_, map_id);
144 return absl::ResourceExhaustedError(
145 "No space available for new hole. Delete one first.");
150 for (
size_t i = 0; i < entrances->size(); ++i) {
151 if (entrances->at(i).deleted) {
153 entrances->at(i).deleted =
false;
154 entrances->at(i).map_id_ = map_id;
155 entrances->at(i).x_ =
static_cast<int>(snapped_pos.x);
156 entrances->at(i).y_ =
static_cast<int>(snapped_pos.y);
157 entrances->at(i).entrance_id_ = 0;
158 entrances->at(i).is_hole_ =
false;
161 entrances->at(i).UpdateMapProperties(map_id, overworld);
164 "Inserted entrance at slot %zu: pos=(%d,%d) map=0x%02X", i,
165 entrances->at(i).x_, entrances->at(i).y_, map_id);
167 return &entrances->at(i);
170 return absl::ResourceExhaustedError(
171 "No space available for new entrance. Delete one first.");
178 if (!overworld || !overworld->
is_loaded()) {
179 return absl::FailedPreconditionError(
"Overworld not loaded");
186 auto* current_ow_map = overworld->
overworld_map(current_map);
191 for (
size_t i = 0; i < exits.size(); ++i) {
192 if (exits[i].deleted_) {
194 exits[i].deleted_ =
false;
195 exits[i].map_id_ = map_id;
196 exits[i].x_ =
static_cast<int>(snapped_pos.x);
197 exits[i].y_ =
static_cast<int>(snapped_pos.y);
201 exits[i].room_id_ = 0;
202 exits[i].x_scroll_ = 0;
203 exits[i].y_scroll_ = 0;
204 exits[i].x_camera_ = 0;
205 exits[i].y_camera_ = 0;
206 exits[i].x_player_ =
static_cast<uint16_t
>(snapped_pos.x);
207 exits[i].y_player_ =
static_cast<uint16_t
>(snapped_pos.y);
208 exits[i].scroll_mod_x_ = 0;
209 exits[i].scroll_mod_y_ = 0;
210 exits[i].door_type_1_ = 0;
211 exits[i].door_type_2_ = 0;
214 exits[i].UpdateMapProperties(map_id, overworld);
217 "Inserted exit at slot %zu: pos=(%d,%d) map=0x%02X", i,
218 exits[i].x_, exits[i].y_, map_id);
224 return absl::ResourceExhaustedError(
225 "No space available for new exit. Delete one first.");
229 ImVec2 mouse_pos,
int current_map,
232 if (!overworld || !overworld->
is_loaded()) {
233 return absl::FailedPreconditionError(
"Overworld not loaded");
236 if (game_state < 0 || game_state > 2) {
237 return absl::InvalidArgumentError(
"Invalid game state (must be 0-2)");
243 return sprite.status();
249 sprites.push_back(std::move(*sprite));
256 "Inserted sprite at game_state=%d: pos=(%d,%d) map=0x%02X id=0x%02X",
257 game_state, inserted_sprite->
x_, inserted_sprite->
y_,
258 inserted_sprite->
map_id(), sprite_id);
260 return inserted_sprite;
267 if (!overworld || !overworld->
is_loaded()) {
268 return absl::FailedPreconditionError(
"Overworld not loaded");
272 mouse_pos, current_map, item_id);
274 return item.status();
280 items.push_back(std::move(*item));
285 "Inserted item: pos=(%d,%d) game=(%d,%d) map=0x%02X id=0x%02X",
286 inserted_item->
x_, inserted_item->
y_, inserted_item->
game_x_,
289 return inserted_item;
295 return absl::InvalidArgumentError(
"Overworld is null");
298 return absl::InvalidArgumentError(
"Item pointer is null");
302 auto it = std::find_if(items->begin(), items->end(),
304 return &item == item_ptr;
306 if (it == items->end()) {
307 return absl::NotFoundError(
"Item pointer not found in overworld item list");
311 return absl::OkStatus();
317 return absl::InvalidArgumentError(
"Overworld is null");
321 auto it = std::find_if(items->begin(), items->end(),
323 return MatchesItemIdentity(item, item_identity);
325 if (it == items->end()) {
326 return absl::NotFoundError(
"No matching item identity in overworld list");
330 return absl::OkStatus();
340 auto it = std::find_if(items->begin(), items->end(),
342 return !item.deleted &&
343 MatchesItemIdentity(item, item_identity);
345 if (it == items->end()) {
353 int offset_x,
int offset_y) {
355 return absl::InvalidArgumentError(
"Overworld is null");
360 return absl::NotFoundError(
"No matching item identity in overworld list");
365 ImVec2(
static_cast<float>(source_item->x_ + offset_x),
366 static_cast<float>(source_item->y_ + offset_y)));
367 duplicate.
x_ =
static_cast<int>(clamped_pos.x);
368 duplicate.
y_ =
static_cast<int>(clamped_pos.y);
373 items->push_back(duplicate);
374 return &items->back();
379 return absl::InvalidArgumentError(
"Item pointer is null");
381 const ImVec2 clamped_pos =
383 static_cast<float>(item->
y_ + delta_y)));
384 item->
x_ =
static_cast<int>(clamped_pos.x);
385 item->
y_ =
static_cast<int>(clamped_pos.y);
387 return absl::OkStatus();
398 if (!items || items->empty()) {
402 const int anchor_x = anchor_identity.
x_;
403 const int anchor_y = anchor_identity.
y_;
404 const uint16_t anchor_map = anchor_identity.
room_map_id_;
407 const int same_map_rank = (item.room_map_id_ == anchor_map) ? 0 : 1;
408 const std::int64_t dx =
static_cast<std::int64_t
>(item.x_) - anchor_x;
409 const std::int64_t dy =
static_cast<std::int64_t
>(item.y_) - anchor_y;
410 const std::int64_t dist2 = (dx * dx) + (dy * dy);
411 return std::pair<int, std::int64_t>(same_map_rank, dist2);
414 auto best_it = items->end();
415 std::pair<int, std::int64_t> best_rank = {2, 0};
416 for (
auto it = items->begin(); it != items->end(); ++it) {
420 const auto current_rank = rank(*it);
421 if (best_it == items->end() || current_rank < best_rank) {
423 best_rank = current_rank;
427 if (best_it == items->end()) {
void UpdateMapProperties(uint16_t room_map_id, const void *context=nullptr) override
Update entity properties based on map position.
const std::vector< OverworldEntrance > & holes() const
const std::vector< OverworldMap > & overworld_maps() const
auto overworld_map(int i) const
auto mutable_sprites(int state)
A class for managing sprites in the overworld and underworld.
#define LOG_DEBUG(category, format,...)
bool MatchesItemIdentity(const zelda3::OverworldItem &lhs, const zelda3::OverworldItem &rhs)
absl::StatusOr< EntityInsertionPosition > ResolveEntityInsertionPosition(const std::vector< zelda3::OverworldMap > &maps, ImVec2 world_position, int current_map)
absl::StatusOr< zelda3::OverworldItem * > InsertItem(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map, uint8_t item_id)
Insert a new item at the specified position.
absl::Status RemoveItemByIdentity(zelda3::Overworld *overworld, const zelda3::OverworldItem &item_identity)
Remove an item by value identity instead of pointer identity.
absl::StatusOr< zelda3::OverworldEntrance * > InsertEntrance(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map, bool is_hole)
Flat helper functions for entity insertion/manipulation.
absl::Status NudgeItem(zelda3::OverworldItem *item, int delta_x, int delta_y)
Move an item by pixel deltas with overworld bounds clamping.
zelda3::OverworldItem * FindNearestItemForSelection(zelda3::Overworld *overworld, const zelda3::OverworldItem &anchor_identity)
Find the best next item to keep selection continuity after deletion.
absl::Status RemoveItem(zelda3::Overworld *overworld, const zelda3::OverworldItem *item_ptr)
Remove an item from the overworld item list by pointer identity.
zelda3::OverworldItem * FindItemByIdentity(zelda3::Overworld *overworld, const zelda3::OverworldItem &item_identity)
Find a live item by value identity.
absl::StatusOr< zelda3::OverworldItem > BuildOverworldItemForInsertion(const std::vector< zelda3::OverworldMap > &maps, ImVec2 world_position, int current_map, uint8_t item_id)
absl::StatusOr< zelda3::OverworldExit * > InsertExit(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map)
Insert a new exit at the specified position.
ImVec2 SnapToEntityGrid(ImVec2 pos)
Snap position to 16x16 grid (standard entity positioning)
absl::StatusOr< zelda3::OverworldItem * > DuplicateItemByIdentity(zelda3::Overworld *overworld, const zelda3::OverworldItem &item_identity, int offset_x, int offset_y)
Duplicate an existing item by identity with a positional offset.
absl::StatusOr< zelda3::Sprite * > InsertSprite(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map, int game_state, uint8_t sprite_id)
Insert a new sprite at the specified position.
ImVec2 ClampToOverworldBounds(ImVec2 pos)
Clamp position to valid overworld bounds.
uint8_t GetParentMapId(const zelda3::OverworldMap *map, int current_map)
Helper to get parent map ID for multi-area maps.
absl::StatusOr< zelda3::Sprite > BuildOverworldSpriteForInsertion(const std::vector< zelda3::OverworldMap > &maps, ImVec2 world_position, int current_map, uint8_t sprite_id)
constexpr int kNumOverworldMaps