git repos / saland

src/ImGuiPlayerSelect.cpp

browsing at commit = fe8b32a69d503efc1a6c7337cbf97e0c37553a18

raw · blame · history

/*
===========================================================================
 * Saland Adventures
Copyright (C) 2014-2024 Poul Sander

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see http://www.gnu.org/licenses/

Source information and contacts persons can be found at
https://github.com/sago007/saland
===========================================================================
 */

#include "ImGuiPlayerSelect.hpp"
#include "Libs/imgui/imgui.h"
#include "common.h"
#include "saland/globals.hpp"
#include "sago/SagoMisc.hpp"
#include "sago/SagoSpriteHolder.hpp"
#include <SDL.h>
#include <algorithm>
#include <cstring>
#include <format>

ImGuiPlayerSelect::ImGuiPlayerSelect() {
	// Define available hair options
	hairOptions = {
		{"standard_hair", "Redhead"},
		{"hair_blonde", "Blonde"},
		{"hair_brunette", "Brunette"},
		{"hair_raven", "Black"},
		{"hair_blue", "Blue"},
		{"hair_1", "Short Blue"}
	};

	LoadPlayerList();
	LoadSelectedPlayer();
}

void ImGuiPlayerSelect::LoadPlayerList() {
	playerNames.clear();
	auto files = sago::GetFileList("players");
	for (const auto& file : files) {
		if (file.size() > 5 && file.substr(file.size() - 5) == ".json") {
			std::string playerName = file.substr(0, file.size() - 5);
			playerNames.push_back(playerName);
		}
	}
	std::sort(playerNames.begin(), playerNames.end());

	// Find current player in list
	std::string currentPlayer = Config::getInstance()->getString("player");
	selectedPlayerIndex = 0;
	for (size_t i = 0; i < playerNames.size(); i++) {
		if (playerNames[i] == currentPlayer) {
			selectedPlayerIndex = i;
			break;
		}
	}
}

void ImGuiPlayerSelect::LoadSelectedPlayer() {
	if (selectedPlayerIndex >= 0 && selectedPlayerIndex < static_cast<int>(playerNames.size())) {
		std::string filename = std::format("players/{}.json", playerNames[selectedPlayerIndex]);
		if (sago::FileExists(filename.c_str())) {
			nlohmann::json j = nlohmann::json::parse(sago::GetFileContent(filename.c_str()));
			editingPlayer = j;
			editingPlayer.save_name = playerNames[selectedPlayerIndex];
		} else {
			editingPlayer = Player();
			editingPlayer.save_name = playerNames[selectedPlayerIndex];
		}
	} else {
		editingPlayer = Player();
		editingPlayer.save_name = "player1";
	}

	// Update UI buffers
	strncpy(nameBuffer, editingPlayer.save_name.c_str(), sizeof(nameBuffer) - 1);
	nameBuffer[sizeof(nameBuffer) - 1] = '\0';

	// Set race index
	selectedRaceIndex = (editingPlayer.race == "male") ? 0 : 1;

	// Set hair index
	selectedHairIndex = 0;
	for (size_t i = 0; i < hairOptions.size(); i++) {
		if (hairOptions[i].id == editingPlayer.hair) {
			selectedHairIndex = i;
			break;
		}
	}

	needsSave = false;
}

void ImGuiPlayerSelect::SaveCurrentPlayer() {
	editingPlayer.save_name = std::string(nameBuffer);
	nlohmann::json j = editingPlayer;
	sago::WriteFileContent(std::format("players/{}.json", editingPlayer.save_name).c_str(), j.dump());
	needsSave = false;
}

void ImGuiPlayerSelect::CreateNewPlayer() {
	int newPlayerNum = 1;
	std::string newName;
	do {
		newName = std::format("player{}", newPlayerNum);
		newPlayerNum++;
	} while (std::find(playerNames.begin(), playerNames.end(), newName) != playerNames.end());

	playerNames.push_back(newName);
	std::sort(playerNames.begin(), playerNames.end());

	// Find the new player index
	for (size_t i = 0; i < playerNames.size(); i++) {
		if (playerNames[i] == newName) {
			selectedPlayerIndex = i;
			break;
		}
	}

	editingPlayer = Player();
	editingPlayer.save_name = newName;
	strncpy(nameBuffer, newName.c_str(), sizeof(nameBuffer) - 1);
	nameBuffer[sizeof(nameBuffer) - 1] = '\0';

	SaveCurrentPlayer();
}

void ImGuiPlayerSelect::DeleteCurrentPlayer() {
	if (playerNames.size() <= 1) {
		return; // Don't delete the last player
	}

	std::string toDelete = playerNames[selectedPlayerIndex];
	playerNames.erase(playerNames.begin() + selectedPlayerIndex);

	if (selectedPlayerIndex >= static_cast<int>(playerNames.size())) {
		selectedPlayerIndex = playerNames.size() - 1;
	}

	needsRefresh = true;
}

bool ImGuiPlayerSelect::IsActive() {
	return active;
}

void ImGuiPlayerSelect::Draw(SDL_Renderer* target) {
	// Center the menu window
	ImGuiIO& io = ImGui::GetIO();
	ImVec2 window_size(600, 500);
	ImVec2 window_pos(
		(io.DisplaySize.x - window_size.x) * 0.5f,
		(io.DisplaySize.y - window_size.y) * 0.5f
	);

	ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always);
	ImGui::SetNextWindowSize(window_size, ImGuiCond_Always);

	if (!ImGui::Begin("Player Management", &active,
		ImGuiWindowFlags_NoResize |
		ImGuiWindowFlags_NoMove |
		ImGuiWindowFlags_NoCollapse)) {
		ImGui::End();
		return;
	}

	// Left panel - Player list
	ImGui::BeginChild("PlayerList", ImVec2(200, 0), true);
	ImGui::Text("Players");
	ImGui::Separator();

	for (size_t i = 0; i < playerNames.size(); i++) {
		if (ImGui::Selectable(playerNames[i].c_str(), selectedPlayerIndex == static_cast<int>(i))) {
			if (needsSave) {
				SaveCurrentPlayer();
			}
			selectedPlayerIndex = i;
			LoadSelectedPlayer();
		}
	}

	ImGui::Dummy(ImVec2(0.0f, 10.0f));

	if (ImGui::Button("New Player", ImVec2(-1, 30))) {
		if (needsSave) {
			SaveCurrentPlayer();
		}
		CreateNewPlayer();
	}

	if (playerNames.size() > 1) {
		if (ImGui::Button("Delete", ImVec2(-1, 30))) {
			DeleteCurrentPlayer();
		}
	}

	ImGui::EndChild();

	ImGui::SameLine();

	// Right panel - Player details
	ImGui::BeginChild("PlayerDetails", ImVec2(0, 0), true);
	ImGui::Text("Player Details");
	ImGui::Separator();

	ImGui::Dummy(ImVec2(0.0f, 10.0f));

	// Name input
	ImGui::Text("Name:");
	ImGui::PushItemWidth(-1);
	if (ImGui::InputText("##name", nameBuffer, sizeof(nameBuffer))) {
		needsSave = true;
	}
	ImGui::PopItemWidth();

	ImGui::Dummy(ImVec2(0.0f, 10.0f));

	// Gender/Race selection
	ImGui::Text("Gender:");
	const char* races[] = { "Male", "Female" };
	if (ImGui::Combo("##race", &selectedRaceIndex, races, 2)) {
		editingPlayer.race = (selectedRaceIndex == 0) ? "male" : "female";
		needsSave = true;
	}

	ImGui::Dummy(ImVec2(0.0f, 10.0f));

	// Hair selection
	ImGui::Text("Hair Style:");
	std::vector<const char*> hairNames;
	for (const auto& option : hairOptions) {
		hairNames.push_back(option.displayName.c_str());
	}
	if (ImGui::Combo("##hair", &selectedHairIndex, hairNames.data(), hairNames.size())) {
		editingPlayer.hair = hairOptions[selectedHairIndex].id;
		needsSave = true;
	}

	ImGui::Dummy(ImVec2(0.0f, 20.0f));

	// Action buttons
	ImGui::Separator();
	ImGui::Dummy(ImVec2(0.0f, 10.0f));

	float button_width = (ImGui::GetContentRegionAvail().x - 10) / 2;

	if (ImGui::Button("Save & Select", ImVec2(button_width, 40))) {
		SaveCurrentPlayer();
		Config::getInstance()->setString("player", editingPlayer.save_name);
		globalData.player = editingPlayer;
		active = false;
	}

	ImGui::SameLine();

	if (ImGui::Button("Cancel", ImVec2(button_width, 40))) {
		active = false;
	}

	ImGui::EndChild();

	ImGui::End();

	// Update animation time for character preview
	Uint32 animationTime = SDL_GetTicks();
	// Draw character preview to the right of the menu
	if (globalData.spriteHolder) {
		// Calculate position to the right of the menu window
		float previewX = window_pos.x + window_size.x + 50;
		float previewY = window_pos.y + window_size.y / 2;

		// Get the sprite for the character walking west
		std::string spriteName = editingPlayer.get_visible_race() + "_walkcycle_W";
		const sago::SagoSprite& baseSprite = globalData.spriteHolder->GetSprite(spriteName);

		// Draw base character
		baseSprite.Draw(target, animationTime, static_cast<int>(previewX), static_cast<int>(previewY), nullptr);

		// Draw pants if available
		std::string bottom = editingPlayer.get_visible_bottom();
		if (bottom.length()) {
			std::string pantsSpriteName = editingPlayer.get_visible_race() + "_walkcycle_" + bottom + "_W";
			const sago::SagoSprite& pantsSprite = globalData.spriteHolder->GetSprite(pantsSpriteName);
			pantsSprite.Draw(target, animationTime, static_cast<int>(previewX), static_cast<int>(previewY), nullptr);
		}

		// Draw top if available
		std::string top = editingPlayer.get_visible_top();
		if (top.length()) {
			std::string topSpriteName = editingPlayer.get_visible_race() + "_walkcycle_" + top + "_W";
			const sago::SagoSprite& topSprite = globalData.spriteHolder->GetSprite(topSpriteName);
			topSprite.Draw(target, animationTime, static_cast<int>(previewX), static_cast<int>(previewY), nullptr);
		}

		// Draw hair if available
		std::string hair = editingPlayer.get_visible_hair();
		if (hair.length()) {
			std::string hairSpriteName = editingPlayer.get_visible_race() + "_walkcycle_" + hair + "_W";
			const sago::SagoSprite& hairSprite = globalData.spriteHolder->GetSprite(hairSpriteName);
			hairSprite.Draw(target, animationTime, static_cast<int>(previewX), static_cast<int>(previewY), nullptr);
		}
	}
}

void ImGuiPlayerSelect::ProcessInput(const SDL_Event& event, bool& processed) {
	// Handle Escape key to close menu
	if (event.type == SDL_KEYDOWN) {
		if (event.key.keysym.sym == SDLK_ESCAPE) {
			active = false;
			processed = true;
		}
	}
}

void ImGuiPlayerSelect::Update() {
	if (needsRefresh) {
		LoadPlayerList();
		LoadSelectedPlayer();
		needsRefresh = false;
	}
}