#include "game/entities/entity_writer.hpp" #ifdef _DEBUG #include #include #include #include #include #include namespace EntityWriter { namespace { // Cerca la línia que comença per `:` a columna 0. Mateix // patró que TilemapWriter::findDataLine però parametritzat. Si // un dia paga la pena, podem extreure'l a un helper compartit. auto findKeyLine(const std::string& content, const std::string& key) -> std::size_t { const std::string PREFIX = key + ":"; std::size_t pos = 0; while (pos < content.size()) { const bool AT_LINE_START = (pos == 0 || content[pos - 1] == '\n'); if (AT_LINE_START && content.compare(pos, PREFIX.size(), PREFIX) == 0) { const std::size_t AFTER = pos + PREFIX.size(); if (AFTER >= content.size() || content[AFTER] == '\n' || content[AFTER] == '\r' || content[AFTER] == ' ' || content[AFTER] == '\t' || content[AFTER] == '#') { return pos; } } const auto NL = content.find('\n', pos); if (NL == std::string::npos) { break; } pos = NL + 1; } return std::string::npos; } // Avança mentre trobe línies indentades (espai/tab) o línies buides. // A diferència del TilemapWriter, ací incloem línies buides dins // del bloc: l'editor de YAML deixa sovint blanc entre grups // d'entitats, i volem absorbir-lo per no deixar restes orfes // entre el bloc nou i el següent top-level. auto advancePastBlock(const std::string& content, std::size_t pos) -> std::size_t { while (pos < content.size()) { const char C = content[pos]; const bool IS_INDENT = (C == ' ' || C == '\t'); const bool IS_BLANK = (C == '\n' || C == '\r'); if (!IS_INDENT && !IS_BLANK) { break; } const auto NL = content.find('\n', pos); pos = (NL == std::string::npos) ? content.size() : NL + 1; } return pos; } auto buildBlock(const std::vector& spawns) -> std::string { std::string out; for (const auto& s : spawns) { out += " - type: " + s.type + "\n"; if (s.sarc_col.has_value() && s.sarc_row.has_value()) { out += " sarc_position: [" + std::to_string(*s.sarc_col) + ", " + std::to_string(*s.sarc_row) + "]\n"; } else if (s.col.has_value() && s.row.has_value()) { out += " position: [" + std::to_string(*s.col) + ", " + std::to_string(*s.row) + "]\n"; } } return out; } } // namespace auto saveEntitiesBlock(const std::string& path, const std::vector& spawns) -> bool { std::ifstream in(path, std::ios::binary); if (!in) { return false; } std::ostringstream ss; ss << in.rdbuf(); const std::string CONTENT = ss.str(); in.close(); const auto KEY_POS = findKeyLine(CONTENT, "entities"); if (KEY_POS == std::string::npos) { return false; } const auto LINE_END = CONTENT.find('\n', KEY_POS); const auto AFTER_KEY = (LINE_END == std::string::npos) ? CONTENT.size() : LINE_END + 1; const auto BLOCK_END = advancePastBlock(CONTENT, AFTER_KEY); // Mantenim una línia en blanc darrere del bloc si abans n'hi // havia: ajuda a separar visualment el `entities:` de la pròxima // secció. Si no n'hi havia, no afegim res. const std::string TAIL = CONTENT.substr(BLOCK_END); const std::string NEW_CONTENT = CONTENT.substr(0, AFTER_KEY) + buildBlock(spawns) + TAIL; const auto TMP_PATH = path + ".tmp"; { std::ofstream out(TMP_PATH, std::ios::binary | std::ios::trunc); if (!out) { return false; } out.write(NEW_CONTENT.data(), static_cast(NEW_CONTENT.size())); out.close(); if (!out) { std::error_code ec; std::filesystem::remove(TMP_PATH, ec); return false; } } std::error_code ec; std::filesystem::rename(TMP_PATH, path, ec); if (ec) { std::filesystem::remove(TMP_PATH, ec); return false; } return true; } } // namespace EntityWriter #endif // _DEBUG