24 std::function<
void(
GameState)> changeState)
25 : _uiRegistry(UiRegistry),
27 _translationManager(translationManager),
28 _uiFactory(uiFactory),
29 _changeState(changeState),
30 _customSpritesPath(
"assets/sprites/custom"),
31 _spriteMappingPath(
"config/client/sprite_mappings.json"),
32 _selectedCategoryIndex(0),
33 _showingCategoryList(true)
57 if (
const auto* kp = e.getIf<sf::Event::KeyPressed>()) {
58 if (kp->code == sf::Keyboard::Key::Escape) {
90 std::vector<std::string> subdirs = {
91 "players",
"enemies",
"projectiles",
"effects"
94 for (
const auto& subdir : subdirs) {
96 if (!std::filesystem::exists(path)) {
97 std::filesystem::create_directories(path);
103 if (!std::filesystem::exists(readmePath)) {
104 std::ofstream readme(readmePath);
105 readme <<
"Custom Sprites Directory\n";
106 readme <<
"========================\n\n";
107 readme <<
"Place your custom sprite files here to replace game sprites.\n\n";
108 readme <<
"Supported formats: PNG, JPG\n\n";
109 readme <<
"Categories:\n";
110 readme <<
" - players/ : Player ship sprites\n";
111 readme <<
" - enemies/ : Enemy ship sprites\n";
112 readme <<
" - projectiles/ : Bullet and missile sprites\n";
113 readme <<
" - effects/ : Explosion and effect sprites\n\n";
114 readme <<
"Note: Make sure your sprites have transparent backgrounds (PNG recommended)\n";
117 }
catch (
const std::exception& e) {
118 log::error(
"Failed to initialize mod directory: {}", e.what());
126 players.
name =
"Players";
128 {
"player_ship",
"assets/sprites/r-typesheet1.gif", 101, 3, 33, 14}
133 enemies.
name =
"Enemies";
135 {
"enemy_1",
"assets/sprites/r-typesheet2.gif", 159, 35, 24, 16},
136 {
"enemy_2",
"assets/sprites/r-typesheet2.gif", 300, 71, 30, 18},
141 projectiles.
name =
"Projectiles";
143 {
"player_shot_6",
"assets/sprites/r-typesheet2.gif", 300, 58, 18, 6},
144 {
"enemy_shot_6",
"assets/sprites/r-typesheet2.gif", 300, 58, 18, 6},
145 {
"shot_insane",
"assets/sprites/r-typesheet1.gif", 134, 18, 33, 32},
146 {
"shot_1",
"assets/sprites/r-typesheet1.gif", 215, 85, 18, 12},
147 {
"shot_2",
"assets/sprites/r-typesheet1.gif", 232, 103, 17, 12},
148 {
"shot_3",
"assets/sprites/r-typesheet1.gif", 200, 121, 33, 10},
149 {
"shot_4",
"assets/sprites/r-typesheet1.gif", 168, 137, 49, 12},
150 {
"shot_5",
"assets/sprites/r-typesheet1.gif", 104, 171, 81, 14},
151 {
"shot_7",
"assets/sprites/r-typesheet2.gif", 266, 94, 17, 10},
156 effects.
name =
"Effects";
158 {
"effect_1",
"assets/sprites/r-typesheet1.gif", 2, 51, 33, 32},
159 {
"effect_2",
"assets/sprites/r-typesheet1.gif", 211, 276, 16, 12},
160 {
"effect_3",
"assets/sprites/r-typesheet1.gif", 72, 296, 37, 30},
161 {
"effect_4",
"assets/sprites/r-typesheet2.gif", 101, 118, 17, 14},
162 {
"effect_5",
"assets/sprites/r-typesheet2.gif", 157, 316, 18, 14},
163 {
"power_up",
"assets/sprites/r-typesheet3.gif", 0, 0, 16, 16}
166 _categories = {players, enemies, projectiles, effects};
175 "Mod Menu - Entity Categories",
176 "assets/fonts/title.ttf",
186 "Select a category to view and customize entity sprites",
187 "assets/fonts/main.ttf",
195 const float buttonSpacing = 80.0f;
199 std::string buttonText = category.name +
" (" + std::to_string(category.entities.size()) +
")";
213 yPos += buttonSpacing;
232 log::error(
"Invalid category index: {}", categoryIndex);
242 category.name +
" Codex",
243 "assets/fonts/title.ttf",
250 const float startX = 100.0f;
251 const float startY = 120.0f;
252 const float spacingX = 200.0f;
253 const float spacingY = 240.0f;
254 const int itemsPerRow = 6;
256 for (
size_t i = 0; i < category.entities.size(); ++i) {
257 const auto& entity = category.entities[i];
259 int row = i / itemsPerRow;
260 int col = i % itemsPerRow;
262 float x = startX + col * spacingX;
263 float y = startY + row * spacingY;
267 std::string displayTexturePath = entity.texturePath;
268 int displayLeft = entity.rectLeft;
269 int displayTop = entity.rectTop;
270 int displayWidth = entity.rectWidth;
271 int displayHeight = entity.rectHeight;
276 displayTexturePath = customIt->second;
285 {x + 50.0f, y + 10.0f},
296 std::string displayName = entity.name;
305 "assets/fonts/main.ttf",
314 {x + 10.0f, y + 170.0f},
317 [
this, entity, categoryName = category.name, customIt]() {
332 log::info(
"Reset sprite for: {}", entity.name);
363 if (texture.loadFromFile(texturePath)) {
368 log::error(
"Failed to load texture: {}", texturePath);
375 std::string key = entity.
name;
376 std::replace(key.begin(), key.end(),
' ',
'_');
388 }
catch (
const std::exception& e) {
389 log::error(
"Failed to save sprite mappings: {}", e.what());
397 log::info(
"No sprite mapping file found, using defaults");
403 }
catch (
const std::exception& e) {
404 log::error(
"Failed to load sprite mappings: {}", e.what());
412 std::string command = R
"(powershell -Command "Add-Type -AssemblyName System.Windows.Forms; $f = New-Object System.Windows.Forms.OpenFileDialog; $f.Filter = 'Images|*.png;*.jpg;*.jpeg;*.gif'; $f.Title = 'Select Custom Sprite'; if($f.ShowDialog() -eq 'OK'){$f.FileName}")";
414 FILE* pipe = _popen(command.c_str(), "r");
422 if (fgets(buffer,
sizeof(buffer), pipe) !=
nullptr) {
425 while (!result.empty() && (result.back() ==
'\n' || result.back() ==
'\r')) {
434 std::string command =
"zenity --file-selection --title='Select Custom Sprite' --file-filter='Images | *.png *.jpg *.jpeg *.gif'";
436 FILE* pipe = popen(command.c_str(),
"r");
444 if (fgets(buffer,
sizeof(buffer), pipe) !=
nullptr) {
447 if (!result.empty() && result.back() ==
'\n') {
459 sf::Vector2u sourceSize = sourceImage.getSize();
460 sf::Image resizedImage(sf::Vector2u(targetWidth, targetHeight));
463 for (
unsigned int y = 0; y < targetHeight; ++y) {
464 for (
unsigned int x = 0; x < targetWidth; ++x) {
465 unsigned int srcX = (x * sourceSize.x) / targetWidth;
466 unsigned int srcY = (y * sourceSize.y) / targetHeight;
467 resizedImage.setPixel(sf::Vector2u(x, y), sourceImage.getPixel(sf::Vector2u(srcX, srcY)));
478 if (selectedFile.empty()) {
483 log::info(
"Selected sprite file: {}", selectedFile);
487 sf::Image customImage;
488 if (!customImage.loadFromFile(selectedFile)) {
489 log::error(
"Failed to load custom sprite: {}", selectedFile);
497 std::string categoryLower = categoryName;
498 std::transform(categoryLower.begin(), categoryLower.end(), categoryLower.begin(), ::tolower);
500 if (!std::filesystem::exists(categoryPath)) {
501 std::filesystem::create_directories(categoryPath);
506 auto outputPath = categoryPath / (entityKey +
".png");
508 if (resizedImage.saveToFile(outputPath.string())) {
509 log::info(
"Saved custom sprite to: {}", outputPath.string());
528 log::info(
"Custom sprite applied successfully!");
532 }
catch (
const std::exception& e) {
533 log::error(
"Error applying custom sprite: {}", e.what());
Manages game settings and preferences.
static void clearTextureCaches()
Clear all texture caches in render systems This should be called when sprites are modified to ensure ...
void reloadMappings()
Reload sprite mappings (useful after modifications) This clears the cache and reloads from disk.
static SpriteCustomizer & getInstance()
Get the singleton instance.
Manages game translations for all UI elements.
std::string get(const std::string &key) const
Get translated string for a key.
Factory class for creating UI components in the ECS registry.
static ecs::Entity createSpritePreview(ecs::Registry ®istry, const position &position, const std::string &texturePath, int rectLeft, int rectTop, int rectWidth, int rectHeight, float scale=2.0f, int zIndex=0)
Create a sprite preview UI component.
static ecs::Entity createText(ecs::Registry ®istry, const position &position, const std::string &content, const std::string &fontPath, unsigned int fontSize, const std::uint8_t zIndex=0, const color &textColor={255, 255, 255})
static ecs::Entity createButton(ecs::Registry ®istry, const position &position, const size &size, const std::string &label, std::function< void()> onClick=nullptr)
Create a button UI component.
void clear(void) noexcept
bool saveSpriteMappings(const std::unordered_map< std::string, std::string > &mappings, const std::string &filepath="config/client/sprite_mappings.json")
Save sprite mappings to a JSON file.
std::unordered_map< std::string, std::string > loadSpriteMappings(const std::string &filepath="config/client/sprite_mappings.json")
Load sprite mappings from a JSON file.
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
Category of entities with their sprites.
std::vector< EntitySpriteInfo > entities
List of entities in this category.
std::string name
Category name.
Information about an entity's sprite for the mod menu.
int rectWidth
Width of the sprite.
int rectHeight
Height of the sprite.
std::string name
Display name of the entity.