From cb19eef701b5c9e27d0464795fffcf8a4d795a21 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:31:04 +1000 Subject: [PATCH] Add character swapping (no GUI yet) --- source/client/StarClientApplication.cpp | 8 ++ source/core/CMakeLists.txt | 2 + source/core/StarText.cpp | 89 +++++++++++++++++++ source/core/StarText.hpp | 26 ++++++ source/core/StarTime.cpp | 2 +- .../frontend/StarClientCommandProcessor.cpp | 17 +++- .../frontend/StarClientCommandProcessor.hpp | 1 + source/frontend/StarInventory.cpp | 13 ++- source/frontend/StarInventory.hpp | 1 + source/frontend/StarTeamBar.cpp | 6 +- source/game/StarClientContext.cpp | 7 +- source/game/StarClientContext.hpp | 7 +- source/game/StarEntityMap.cpp | 19 ++++ source/game/StarEntityMap.hpp | 4 + source/game/StarPlayer.cpp | 84 ++++++++++++----- source/game/StarPlayer.hpp | 11 ++- source/game/StarPlayerInventory.cpp | 16 +++- source/game/StarPlayerStorage.cpp | 48 ++++++++-- source/game/StarPlayerStorage.hpp | 8 +- source/game/StarQuestManager.cpp | 2 +- source/game/StarSystemWorld.hpp | 1 + source/game/StarSystemWorldClient.cpp | 4 + source/game/StarSystemWorldClient.hpp | 1 + source/game/StarSystemWorldServer.cpp | 4 + source/game/StarSystemWorldServer.hpp | 1 + source/game/StarUniverseClient.cpp | 88 +++++++++++++++++- source/game/StarUniverseClient.hpp | 13 +++ source/game/StarWorldClient.cpp | 70 +++++++-------- source/game/StarWorldClient.hpp | 4 +- source/game/items/StarUnlockItem.cpp | 2 +- .../game/scripting/StarPlayerLuaBindings.cpp | 7 +- source/rendering/StarTextPainter.cpp | 89 +------------------ source/rendering/StarTextPainter.hpp | 11 --- source/windowing/StarItemGridWidget.cpp | 7 +- source/windowing/StarWidget.hpp | 1 + 35 files changed, 478 insertions(+), 196 deletions(-) create mode 100644 source/core/StarText.cpp create mode 100644 source/core/StarText.hpp diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 460c17b..5483591 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -506,9 +506,17 @@ void ClientApplication::changeState(MainAppState newState) { m_playerStorage = make_shared(m_root->toStoragePath("player")); m_statistics = make_shared(m_root->toStoragePath("player"), appController()->statisticsService()); m_universeClient = make_shared(m_playerStorage, m_statistics); + m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks()); + m_universeClient->playerReloadCallback() = [&]() { + if (auto paneManager = m_mainInterface->paneManager()) { + if (auto inventory = paneManager->registeredPane(MainInterfacePanes::Inventory)) + inventory->clearChangedSlots(); + } + }; + m_mainMixer->setUniverseClient(m_universeClient); m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer()); if (auto renderer = Application::renderer()) diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index 1fd2aac..8c2964e 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -112,6 +112,7 @@ SET (star_core_HEADERS StarStringView.hpp StarStrongTypedef.hpp StarTcp.hpp + StarText.hpp StarThread.hpp StarTickRateMonitor.hpp StarTime.hpp @@ -171,6 +172,7 @@ SET (star_core_SOURCES StarString.cpp StarStringView.cpp StarTcp.cpp + StarText.cpp StarThread.cpp StarTime.cpp StarTickRateMonitor.cpp diff --git a/source/core/StarText.cpp b/source/core/StarText.cpp new file mode 100644 index 0000000..11fca27 --- /dev/null +++ b/source/core/StarText.cpp @@ -0,0 +1,89 @@ +#include "StarText.hpp" + +#include + +namespace Star { + +namespace Text { + static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc)); + String stripEscapeCodes(String const& s) { + return std::regex_replace(s.utf8(), stripEscapeRegex, ""); + } + + static std::string escapeChars = strf("{:c}{:c}", CmdEsc, StartEsc); + + bool processText(StringView text, TextCallback textFunc, CommandsCallback commandsFunc, bool includeCommandSides) { + std::string_view escChars(escapeChars); + + std::string_view str = text.utf8(); + while (true) { + size_t escape = str.find_first_of(escChars); + if (escape != NPos) { + escape = str.find_first_not_of(escChars, escape) - 1; // jump to the last ^ + + size_t end = str.find_first_of(EndEsc, escape); + if (end != NPos) { + if (escape && !textFunc(str.substr(0, escape))) + return false; + if (commandsFunc) { + StringView commands = includeCommandSides + ? str.substr(escape, end - escape + 1) + : str.substr(escape + 1, end - escape - 1); + if (!commands.empty() && !commandsFunc(commands)) + return false; + } + str = str.substr(end + 1); + continue; + } + } + + if (!str.empty()) + return textFunc(str); + + return true; + } + } + + // The below two functions aren't used anymore, not bothering with StringView for them + String preprocessEscapeCodes(String const& s) { + bool escape = false; + std::string result = s.utf8(); + + size_t escapeStartIdx = 0; + for (size_t i = 0; i < result.size(); i++) { + auto& c = result[i]; + if (isEscapeCode(c)) { + escape = true; + escapeStartIdx = i; + } + if ((c <= SpecialCharLimit) && !(c == StartEsc)) + escape = false; + if ((c == EndEsc) && escape) + result[escapeStartIdx] = StartEsc; + } + return {result}; + } + + String extractCodes(String const& s) { + bool escape = false; + StringList result; + String escapeCode; + for (auto c : preprocessEscapeCodes(s)) { + if (c == StartEsc) + escape = true; + if (c == EndEsc) { + escape = false; + for (auto command : escapeCode.split(',')) + result.append(command); + escapeCode = ""; + } + if (escape && (c != StartEsc)) + escapeCode.append(c); + } + if (!result.size()) + return ""; + return "^" + result.join(",") + ";"; + } +} + +} \ No newline at end of file diff --git a/source/core/StarText.hpp b/source/core/StarText.hpp new file mode 100644 index 0000000..e318484 --- /dev/null +++ b/source/core/StarText.hpp @@ -0,0 +1,26 @@ +#ifndef STAR_TEXT_HPP +#define STAR_TEXT_HPP +#include "StarString.hpp" +#include "StarStringView.hpp" + +namespace Star { + +namespace Text { + unsigned char const StartEsc = '\x1b'; + unsigned char const EndEsc = ';'; + unsigned char const CmdEsc = '^'; + unsigned char const SpecialCharLimit = ' '; + + String stripEscapeCodes(String const& s); + inline bool isEscapeCode(char c) { return c == CmdEsc || c == StartEsc; } + + typedef function TextCallback; + typedef function CommandsCallback; + bool processText(StringView text, TextCallback textFunc, CommandsCallback commandsFunc = CommandsCallback(), bool includeCommandSides = false); + String preprocessEscapeCodes(String const& s); + String extractCodes(String const& s); +} + +} + +#endif \ No newline at end of file diff --git a/source/core/StarTime.cpp b/source/core/StarTime.cpp index 622ea50..f64b850 100644 --- a/source/core/StarTime.cpp +++ b/source/core/StarTime.cpp @@ -43,7 +43,7 @@ String Time::printDuration(double time) { seconds = strf("{} second{}", numSeconds, numSeconds == 1 ? "" : "s"); } - int numMilliseconds = round(time * 1000); + int numMilliseconds = round(fmod(time, 1.0) * 1000); milliseconds = strf("{} millisecond{}", numMilliseconds, numMilliseconds == 1 ? "" : "s"); return String::joinWith(", ", hours, minutes, seconds, milliseconds); diff --git a/source/frontend/StarClientCommandProcessor.cpp b/source/frontend/StarClientCommandProcessor.cpp index ec5b27c..c7a510f 100644 --- a/source/frontend/StarClientCommandProcessor.cpp +++ b/source/frontend/StarClientCommandProcessor.cpp @@ -50,7 +50,8 @@ ClientCommandProcessor::ClientCommandProcessor(UniverseClientPtr universeClient, {"giveessentialitem", bind(&ClientCommandProcessor::giveEssentialItem, this, _1)}, {"maketechavailable", bind(&ClientCommandProcessor::makeTechAvailable, this, _1)}, {"enabletech", bind(&ClientCommandProcessor::enableTech, this, _1)}, - {"upgradeship", bind(&ClientCommandProcessor::upgradeShip, this, _1)} + {"upgradeship", bind(&ClientCommandProcessor::upgradeShip, this, _1)}, + {"swap", bind(&ClientCommandProcessor::swap, this, _1)} }; } @@ -304,7 +305,7 @@ String ClientCommandProcessor::playTime() { String ClientCommandProcessor::deathCount() { auto deaths = m_universeClient->mainPlayer()->log()->deathCount(); - return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : ""); + return deaths ? strf("Total deaths: {}", deaths) : "Total deaths: 0. Well done!"; } String ClientCommandProcessor::cinema(String const& argumentsString) { @@ -408,4 +409,16 @@ String ClientCommandProcessor::upgradeShip(String const& argumentsString) { return strf("Upgraded ship"); } +String ClientCommandProcessor::swap(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); + + if (arguments.size() == 0) + return "Not enouch arguments to /swap"; + + if (m_universeClient->switchPlayer(arguments[0])) + return "Successfully swapped player"; + else + return "Failed to swap player"; +} + } diff --git a/source/frontend/StarClientCommandProcessor.hpp b/source/frontend/StarClientCommandProcessor.hpp index e9e8962..7446e70 100644 --- a/source/frontend/StarClientCommandProcessor.hpp +++ b/source/frontend/StarClientCommandProcessor.hpp @@ -58,6 +58,7 @@ private: String makeTechAvailable(String const& argumentsString); String enableTech(String const& argumentsString); String upgradeShip(String const& argumentsString); + String swap(String const& argumentsString); UniverseClientPtr m_universeClient; CinematicPtr m_cinematicOverlay; diff --git a/source/frontend/StarInventory.cpp b/source/frontend/StarInventory.cpp index a7626a7..a983f3e 100644 --- a/source/frontend/StarInventory.cpp +++ b/source/frontend/StarInventory.cpp @@ -210,7 +210,7 @@ bool InventoryPane::giveContainerResult(ContainerResult result) { if (!m_expectingSwap) return false; - for (auto item : result) { + for (auto& item : result) { auto inv = m_player->inventory(); m_player->triggerPickupEvents(item); @@ -224,18 +224,25 @@ bool InventoryPane::giveContainerResult(ContainerResult result) { } void InventoryPane::updateItems() { - for (auto p : m_itemGrids) + for (auto& p : m_itemGrids) p.second->updateItemState(); } bool InventoryPane::containsNewItems() const { - for (auto p : m_itemGrids) { + for (auto& p : m_itemGrids) { if (p.second->slotsChanged()) return true; } return false; } +void InventoryPane::clearChangedSlots() { + for (auto& p : m_itemGrids) { + p.second->updateItemState(); + p.second->clearChangedSlots(); + } +} + void InventoryPane::update(float dt) { auto inventory = m_player->inventory(); auto context = Widget::context(); diff --git a/source/frontend/StarInventory.hpp b/source/frontend/StarInventory.hpp index cb5deed..807e2ec 100644 --- a/source/frontend/StarInventory.hpp +++ b/source/frontend/StarInventory.hpp @@ -33,6 +33,7 @@ public: // this is a little hacky and should probably be checked in the player inventory instead void updateItems(); bool containsNewItems() const; + void clearChangedSlots(); protected: virtual void update(float dt) override; diff --git a/source/frontend/StarTeamBar.cpp b/source/frontend/StarTeamBar.cpp index 192edfa..a378040 100644 --- a/source/frontend/StarTeamBar.cpp +++ b/source/frontend/StarTeamBar.cpp @@ -40,7 +40,7 @@ TeamBar::TeamBar(MainInterface* mainInterface, UniverseClientPtr client) { return; auto position = jsonToVec2I(Root::singleton().assets()->json("/interface/windowconfig/teambar.config:selfMenuOffset")); position[1] += windowHeight() / m_guiContext->interfaceScale(); - showMemberMenu(m_client->mainPlayer()->clientContext()->serverUuid(), position); + showMemberMenu(m_client->mainPlayer()->clientContext()->playerUuid(), position); }); reader.construct(assets->json("/interface/windowconfig/teambar.config:paneLayout"), this); @@ -155,7 +155,7 @@ void TeamBar::buildTeamBar() { int memberSize = assets->json("/interface/windowconfig/teambar.config:memberSize").toInt(); int memberSpacing = assets->json("/interface/windowconfig/teambar.config:memberSpacing").toInt(); - Uuid myUuid = player->clientContext()->serverUuid(); + Uuid myUuid = player->clientContext()->playerUuid(); for (auto member : teamClient->members()) { if (member.uuid == myUuid) { memberIndex++; @@ -360,7 +360,7 @@ void TeamMemberMenu::update(float dt) { void TeamMemberMenu::updateWidgets() { bool isLeader = m_owner->m_client->teamClient()->isTeamLeader(); - bool isSelf = m_owner->m_client->mainPlayer()->clientContext()->serverUuid() == m_memberUuid; + bool isSelf = m_owner->m_client->mainPlayer()->clientContext()->playerUuid() == m_memberUuid; fetchChild("beamToShip")->setEnabled(m_canBeam); fetchChild("makeLeader")->setEnabled(isLeader && !isSelf); diff --git a/source/game/StarClientContext.cpp b/source/game/StarClientContext.cpp index 1482168..748b864 100644 --- a/source/game/StarClientContext.cpp +++ b/source/game/StarClientContext.cpp @@ -24,8 +24,9 @@ DataStream& operator<<(DataStream& ds, ShipUpgrades const& upgrades) { return ds; } -ClientContext::ClientContext(Uuid serverUuid) { +ClientContext::ClientContext(Uuid serverUuid, Uuid playerUuid) { m_serverUuid = move(serverUuid); + m_playerUuid = move(playerUuid); m_rpc = make_shared(); m_netGroup.addNetElement(&m_orbitWarpActionNetState); @@ -40,6 +41,10 @@ Uuid ClientContext::serverUuid() const { return m_serverUuid; } +Uuid ClientContext::playerUuid() const { + return m_playerUuid; +} + CelestialCoordinate ClientContext::shipCoordinate() const { return m_shipCoordinate.get(); } diff --git a/source/game/StarClientContext.hpp b/source/game/StarClientContext.hpp index 22db10f..425bfd3 100644 --- a/source/game/StarClientContext.hpp +++ b/source/game/StarClientContext.hpp @@ -17,9 +17,12 @@ STAR_CLASS(ClientContext); class ClientContext { public: - ClientContext(Uuid serverUuid); + ClientContext(Uuid serverUuid, Uuid playerUuid); Uuid serverUuid() const; + // The player Uuid can differ from the mainPlayer's Uuid + // if the player has swapped character - use this for ship saving. + Uuid playerUuid() const; // The coordinate for the world which the player's ship is currently // orbiting. @@ -43,6 +46,8 @@ public: private: Uuid m_serverUuid; + Uuid m_playerUuid; + JsonRpcPtr m_rpc; NetElementTopGroup m_netGroup; diff --git a/source/game/StarEntityMap.cpp b/source/game/StarEntityMap.cpp index 66d09a5..2232099 100644 --- a/source/game/StarEntityMap.cpp +++ b/source/game/StarEntityMap.cpp @@ -27,6 +27,25 @@ EntityId EntityMap::reserveEntityId() { return id; } +Maybe EntityMap::maybeReserveEntityId(EntityId entityId) { + if (m_spatialMap.size() >= (size_t)(m_endIdSpace - m_beginIdSpace)) + throw EntityMapException("No more entity id space in EntityMap::reserveEntityId"); + + if (m_spatialMap.contains(entityId)) + return {}; + else + return entityId; +} + +EntityId EntityMap::reserveEntityId(EntityId entityId) { + if (auto reserved = maybeReserveEntityId(entityId)) + return *reserved; + + m_nextId = entityId; + return reserveEntityId(); +} + + void EntityMap::addEntity(EntityPtr entity) { auto position = entity->position(); auto boundBox = entity->metaBoundBox(); diff --git a/source/game/StarEntityMap.hpp b/source/game/StarEntityMap.hpp index d1adc49..bf8a12a 100644 --- a/source/game/StarEntityMap.hpp +++ b/source/game/StarEntityMap.hpp @@ -30,6 +30,10 @@ public: // Get the next free id in the entity id space. EntityId reserveEntityId(); + // Or a specific one, can fail. + Maybe maybeReserveEntityId(EntityId entityId); + // If it doesn't matter that we don't get the one want + EntityId reserveEntityId(EntityId entityId); // Add an entity to this EntityMap. The entity must already be initialized // and have a unique EntityId returned by reserveEntityId. diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index 54af56f..7bfa224 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -56,6 +56,7 @@ Player::Player(PlayerConfigPtr config, Uuid uuid) { auto assets = Root::singleton().assets(); m_config = config; + m_client = nullptr; m_state = State::Idle; m_emoteState = HumanoidEmote::Idle; @@ -186,13 +187,35 @@ Player::Player(PlayerConfigPtr config, Uuid uuid) { m_netGroup.setNeedsStoreCallback(bind(&Player::setNetStates, this)); } +Player::Player(PlayerConfigPtr config, ByteArray const& netStore) : Player(config) { + DataStreamBuffer ds(netStore); + + setUniqueId(ds.read()); + + ds.read(m_description); + ds.read(m_modeType); + ds.read(m_identity); + + m_humanoid = make_shared(Root::singleton().speciesDatabase()->species(m_identity.species)->humanoidConfig()); + m_humanoid->setIdentity(m_identity); + m_movementController->resetBaseParameters(ActorMovementParameters(jsonMerge(m_humanoid->defaultMovementParameters(), m_config->movementParameters))); +} + + Player::Player(PlayerConfigPtr config, Json const& diskStore) : Player(config) { + diskLoad(diskStore); +} + +void Player::diskLoad(Json const& diskStore) { setUniqueId(diskStore.getString("uuid")); m_description = diskStore.getString("description"); setModeType(PlayerModeNames.getLeft(diskStore.getString("modeType"))); m_shipUpgrades = ShipUpgrades(diskStore.get("shipUpgrades")); m_blueprints = make_shared(diskStore.get("blueprints")); m_universeMap = make_shared(diskStore.get("universeMap")); + if (m_clientContext) + m_universeMap->setServerUuid(m_clientContext->serverUuid()); + m_codexes = make_shared(diskStore.get("codexes")); m_techs = make_shared(diskStore.get("techs")); m_identity = HumanoidIdentity(diskStore.get("identity")); @@ -208,48 +231,39 @@ Player::Player(PlayerConfigPtr config, Json const& diskStore) : Player(config) { m_log = make_shared(diskStore.get("log")); - m_codexes->learnInitialCodexes(species()); - - // Make sure to merge the stored player blueprints with what a new player - // would get as default. - for (auto const& descriptor : m_config->defaultBlueprints) - m_blueprints->add(descriptor); - for (auto const& descriptor : Root::singleton().speciesDatabase()->species(m_identity.species)->defaultBlueprints()) - m_blueprints->add(descriptor); + auto speciesDef = Root::singleton().speciesDatabase()->species(m_identity.species); m_questManager->diskLoad(diskStore.get("quests", JsonObject{})); m_companions->diskLoad(diskStore.get("companions", JsonObject{})); m_deployment->diskLoad(diskStore.get("deployment", JsonObject{})); - m_humanoid = make_shared(Root::singleton().speciesDatabase()->species(m_identity.species)->humanoidConfig()); + m_humanoid = make_shared(speciesDef->humanoidConfig()); m_humanoid->setIdentity(m_identity); m_movementController->resetBaseParameters(ActorMovementParameters(jsonMerge(m_humanoid->defaultMovementParameters(), m_config->movementParameters))); - m_effectsAnimator->setGlobalTag("effectDirectives", Root::singleton().speciesDatabase()->species(m_identity.species)->effectDirectives()); + m_effectsAnimator->setGlobalTag("effectDirectives", speciesDef->effectDirectives()); m_genericProperties = diskStore.getObject("genericProperties"); refreshEquipment(); + m_codexes->learnInitialCodexes(species()); + m_aiState = AiState(diskStore.get("aiState", JsonObject{})); + for (auto& script : m_genericScriptContexts) + script.second->setScriptStorage({}); + for (auto& p : diskStore.get("genericScriptStorage", JsonObject{}).toObject()) { if (auto script = m_genericScriptContexts.maybe(p.first).value({})) { script->setScriptStorage(p.second.toObject()); } } -} -Player::Player(PlayerConfigPtr config, ByteArray const& netStore) : Player(config) { - DataStreamBuffer ds(netStore); - - setUniqueId(ds.read()); - - ds.read(m_description); - ds.read(m_modeType); - ds.read(m_identity); - - m_humanoid = make_shared(Root::singleton().speciesDatabase()->species(m_identity.species)->humanoidConfig()); - m_humanoid->setIdentity(m_identity); - m_movementController->resetBaseParameters(ActorMovementParameters(jsonMerge(m_humanoid->defaultMovementParameters(), m_config->movementParameters))); + // Make sure to merge the stored player blueprints with what a new player + // would get as default. + for (auto const& descriptor : m_config->defaultBlueprints) + m_blueprints->add(descriptor); + for (auto const& descriptor : speciesDef->defaultBlueprints()) + m_blueprints->add(descriptor); } ClientContextPtr Player::clientContext() const { @@ -279,6 +293,10 @@ EntityType Player::entityType() const { return EntityType::Player; } +ClientEntityMode Player::clientEntityMode() const { + return ClientEntityMode::ClientPresenceMaster; +} + void Player::init(World* world, EntityId entityId, EntityMode mode) { Entity::init(world, entityId, mode); @@ -1961,6 +1979,13 @@ void Player::setShipUpgrades(ShipUpgrades shipUpgrades) { m_shipUpgrades = move(shipUpgrades); } +void Player::applyShipUpgrades(Json const& upgrades) { + if (m_clientContext->playerUuid() == uuid()) + m_clientContext->rpcInterface()->invokeRemote("ship.applyShipUpgrades", upgrades); + else + m_shipUpgrades.apply(upgrades); +} + String Player::name() const { return m_identity.name; } @@ -2176,6 +2201,19 @@ List Player::forceRegions() const { return m_tools->forceRegions(); } + +StatusControllerPtr Player::statusControllerPtr() { + return m_statusController; +} + +ActorMovementControllerPtr Player::movementControllerPtr() { + return m_movementController; +} + +PlayerConfigPtr Player::config() { + return m_config; +} + SongbookPtr Player::songbook() const { return m_songbook; } diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp index 7bc0a73..99962cd 100644 --- a/source/game/StarPlayer.hpp +++ b/source/game/StarPlayer.hpp @@ -62,8 +62,10 @@ class Player : public: Player(PlayerConfigPtr config, Uuid uuid = Uuid()); - Player(PlayerConfigPtr config, Json const& diskStore); Player(PlayerConfigPtr config, ByteArray const& netStore); + Player(PlayerConfigPtr config, Json const& diskStore); + + void diskLoad(Json const& diskStore); ClientContextPtr clientContext() const; void setClientContext(ClientContextPtr clientContext); @@ -79,6 +81,7 @@ public: ByteArray netStore(); EntityType entityType() const override; + ClientEntityMode clientEntityMode() const override; void init(World* world, EntityId entityId, EntityMode mode) override; void uninit() override; @@ -283,6 +286,7 @@ public: ShipUpgrades shipUpgrades(); void setShipUpgrades(ShipUpgrades shipUpgrades); + void applyShipUpgrades(Json const& upgrades); String name() const override; void setName(String const& name); @@ -395,6 +399,11 @@ public: List forceRegions() const override; + StatusControllerPtr statusControllerPtr(); + ActorMovementControllerPtr movementControllerPtr(); + + PlayerConfigPtr config(); + SongbookPtr songbook() const; void finalizeCreation(); diff --git a/source/game/StarPlayerInventory.cpp b/source/game/StarPlayerInventory.cpp index 0ebb18c..58e2b31 100644 --- a/source/game/StarPlayerInventory.cpp +++ b/source/game/StarPlayerInventory.cpp @@ -755,9 +755,18 @@ void PlayerInventory::load(Json const& store) { m_equipment[EquipmentSlot::LegsCosmetic] = itemDatabase->diskLoad(store.get("legsCosmeticSlot")); m_equipment[EquipmentSlot::BackCosmetic] = itemDatabase->diskLoad(store.get("backCosmeticSlot")); - auto itemBags = store.get("itemBags"); - for (String const& bagType : itemBags.toObject().keys()) - m_bags[bagType] = make_shared(ItemBag::loadStore(itemBags.get(bagType))); + //reuse ItemBags so the Inventory pane still works after load()'ing into the same PlayerInventory again (from swap) + auto itemBags = store.get("itemBags").toObject(); + eraseWhere(m_bags, [&](auto const& p) { return !itemBags.contains(p.first); }); + for (auto const& p : itemBags) { + auto& bagType = p.first; + auto newBag = ItemBag::loadStore(p.second); + auto& bagPtr = m_bags[bagType]; + if (bagPtr) + *bagPtr = move(newBag); + else + bagPtr = make_shared(move(newBag)); + } m_swapSlot = itemDatabase->diskLoad(store.get("swapSlot")); m_trashSlot = itemDatabase->diskLoad(store.get("trashSlot")); @@ -778,6 +787,7 @@ void PlayerInventory::load(Json const& store) { m_selectedActionBar = jsonToSelectedActionBarLocation(store.get("selectedActionBar")); + m_essential.clear(); m_essential[EssentialItem::BeamAxe] = itemDatabase->diskLoad(store.get("beamAxe")); m_essential[EssentialItem::WireTool] = itemDatabase->diskLoad(store.get("wireTool")); m_essential[EssentialItem::PaintTool] = itemDatabase->diskLoad(store.get("paintTool")); diff --git a/source/game/StarPlayerStorage.cpp b/source/game/StarPlayerStorage.cpp index dfccc88..c6ae2a3 100644 --- a/source/game/StarPlayerStorage.cpp +++ b/source/game/StarPlayerStorage.cpp @@ -8,6 +8,7 @@ #include "StarAssets.hpp" #include "StarEntityFactory.hpp" #include "StarRoot.hpp" +#include "StarText.hpp" namespace Star { @@ -98,7 +99,28 @@ Maybe PlayerStorage::playerUuidAt(size_t index) { return {}; } -void PlayerStorage::savePlayer(PlayerPtr const& player) { +Maybe PlayerStorage::playerUuidByName(String const& name) { + String cleanMatch = Text::stripEscapeCodes(name).toLower(); + Maybe uuid; + + RecursiveMutexLocker locker(m_mutex); + + size_t longest = SIZE_MAX; + for (auto& cache : m_savedPlayersCache) { + if (auto name = cache.second.optQueryString("identity.name")) { + auto cleanName = Text::stripEscapeCodes(*name).toLower(); + auto len = cleanName.size(); + if (len < longest && cleanName.utf8().rfind(cleanMatch.utf8()) == 0) { + longest = len; + uuid = cache.first; + } + } + } + + return uuid; +} + +Json PlayerStorage::savePlayer(PlayerPtr const& player) { auto entityFactory = Root::singleton().entityFactory(); auto versioningDatabase = Root::singleton().versioningDatabase(); @@ -113,15 +135,28 @@ void PlayerStorage::savePlayer(PlayerPtr const& player) { VersionedJson versionedJson = entityFactory->storeVersionedJson(EntityType::Player, playerCacheData); VersionedJson::writeFile(versionedJson, File::relativeTo(m_storageDirectory, strf("{}.player", uuid.hex()))); } + return newPlayerData; +} + +Maybe PlayerStorage::maybeGetPlayerData(Uuid const& uuid) { + RecursiveMutexLocker locker(m_mutex); + if (auto cache = m_savedPlayersCache.ptr(uuid)) + return *cache; + else + return {}; +} + +Json PlayerStorage::getPlayerData(Uuid const& uuid) { + auto data = maybeGetPlayerData(uuid); + if (!data) + throw PlayerException(strf("No such stored player with uuid '{}'", uuid.hex())); + else + return *data; } PlayerPtr PlayerStorage::loadPlayer(Uuid const& uuid) { - RecursiveMutexLocker locker(m_mutex); - if (!m_savedPlayersCache.contains(uuid)) - throw PlayerException(strf("No such stored player with uuid '{}'", uuid.hex())); - + auto playerCacheData = getPlayerData(uuid); auto entityFactory = Root::singleton().entityFactory(); - auto const& playerCacheData = m_savedPlayersCache.get(uuid); try { auto player = convert(entityFactory->diskLoadEntity(EntityType::Player, playerCacheData)); if (player->uuid() != uuid) @@ -129,6 +164,7 @@ PlayerPtr PlayerStorage::loadPlayer(Uuid const& uuid) { return player; } catch (std::exception const& e) { Logger::error("Error loading player file, ignoring! {}", outputException(e, false)); + RecursiveMutexLocker locker(m_mutex); m_savedPlayersCache.remove(uuid); return {}; } diff --git a/source/game/StarPlayerStorage.hpp b/source/game/StarPlayerStorage.hpp index 68dda4d..4b9edc7 100644 --- a/source/game/StarPlayerStorage.hpp +++ b/source/game/StarPlayerStorage.hpp @@ -18,8 +18,14 @@ public: size_t playerCount() const; // Returns nothing if index is out of bounds. Maybe playerUuidAt(size_t index); + // Returns nothing if name doesn't match a player. + Maybe playerUuidByName(String const& name); - void savePlayer(PlayerPtr const& player); + // Also returns the diskStore Json if needed. + Json savePlayer(PlayerPtr const& player); + + Maybe maybeGetPlayerData(Uuid const& uuid); + Json getPlayerData(Uuid const& uuid); PlayerPtr loadPlayer(Uuid const& uuid); void deletePlayer(Uuid const& uuid); diff --git a/source/game/StarQuestManager.cpp b/source/game/StarQuestManager.cpp index 6ea4689..e9736c7 100644 --- a/source/game/StarQuestManager.cpp +++ b/source/game/StarQuestManager.cpp @@ -110,7 +110,7 @@ bool QuestManager::canStart(QuestArcDescriptor const& questArc) const { if (!m_player->inventory()->hasItem(item)) return false; if (questTemplate->requiredShipLevel) { - if (m_player->clientContext()->shipUpgrades().shipLevel < *questTemplate->requiredShipLevel) + if (m_player->shipUpgrades().shipLevel < *questTemplate->requiredShipLevel) return false; } } diff --git a/source/game/StarSystemWorld.hpp b/source/game/StarSystemWorld.hpp index be9a30a..c68bc59 100644 --- a/source/game/StarSystemWorld.hpp +++ b/source/game/StarSystemWorld.hpp @@ -94,6 +94,7 @@ public: Maybe objectWarpAction(Uuid const& uuid) const; virtual List objects() const = 0; + virtual List objectKeys() const = 0; virtual SystemObjectPtr getObject(Uuid const& uuid) const = 0; SystemObjectConfig systemObjectConfig(String const& name, Uuid const& uuid) const; diff --git a/source/game/StarSystemWorldClient.cpp b/source/game/StarSystemWorldClient.cpp index d4f8c15..da28c98 100644 --- a/source/game/StarSystemWorldClient.cpp +++ b/source/game/StarSystemWorldClient.cpp @@ -82,6 +82,10 @@ List SystemWorldClient::objects() const { return m_objects.values(); } +List SystemWorldClient::objectKeys() const { + return m_objects.keys(); +} + SystemObjectPtr SystemWorldClient::getObject(Uuid const& uuid) const { return m_objects.maybe(uuid).value({}); } diff --git a/source/game/StarSystemWorldClient.hpp b/source/game/StarSystemWorldClient.hpp index 1ec059a..a222ff5 100644 --- a/source/game/StarSystemWorldClient.hpp +++ b/source/game/StarSystemWorldClient.hpp @@ -28,6 +28,7 @@ public: void update(float dt); List objects() const override; + List objectKeys() const override; SystemObjectPtr getObject(Uuid const& uuid) const override; List ships() const; diff --git a/source/game/StarSystemWorldServer.cpp b/source/game/StarSystemWorldServer.cpp index f430bcb..39a1aad 100644 --- a/source/game/StarSystemWorldServer.cpp +++ b/source/game/StarSystemWorldServer.cpp @@ -247,6 +247,10 @@ List SystemWorldServer::objects() const { return m_objects.values(); } +List SystemWorldServer::objectKeys() const { + return m_objects.keys(); +} + SystemObjectPtr SystemWorldServer::getObject(Uuid const& uuid) const { return m_objects.maybe(uuid).value({}); } diff --git a/source/game/StarSystemWorldServer.hpp b/source/game/StarSystemWorldServer.hpp index f1bc30f..26a703c 100644 --- a/source/game/StarSystemWorldServer.hpp +++ b/source/game/StarSystemWorldServer.hpp @@ -40,6 +40,7 @@ public: void update(float dt); List objects() const override; + List objectKeys() const override; SystemObjectPtr getObject(Uuid const& uuid) const override; List pullShipFlights(); diff --git a/source/game/StarUniverseClient.cpp b/source/game/StarUniverseClient.cpp index 3adc95d..90d5f07 100644 --- a/source/game/StarUniverseClient.cpp +++ b/source/game/StarUniverseClient.cpp @@ -109,7 +109,7 @@ Maybe UniverseClient::connect(UniverseConnection connection, bool allowA if (auto success = as(packet)) { m_universeClock = make_shared(); - m_clientContext = make_shared(success->serverUuid); + m_clientContext = make_shared(success->serverUuid, m_mainPlayer->uuid()); m_teamClient = make_shared(m_mainPlayer, m_clientContext); m_mainPlayer->setClientContext(m_clientContext); m_mainPlayer->setStatistics(m_statistics); @@ -397,6 +397,10 @@ bool UniverseClient::playerOnOwnShip() const { return playerWorld().is() && playerWorld().get() == mainPlayer()->uuid(); } +bool UniverseClient::playerIsOriginal() const { + return m_clientContext->playerUuid() == mainPlayer()->uuid(); +} + WorldId UniverseClient::playerWorld() const { return m_clientContext->playerWorldId(); } @@ -473,6 +477,80 @@ void UniverseClient::stopLua() { m_scriptContexts.clear(); } +bool UniverseClient::reloadPlayer(Json const& data, Uuid const& uuid) { + auto player = mainPlayer(); + auto world = worldClient(); + bool inWorld = player->inWorld(); + EntityId entityId = player->entityId(); + + if (m_playerReloadPreCallback) + m_playerReloadPreCallback(); + + if (inWorld) + world->removeEntity(entityId, false); + else { + m_respawning = false; + m_respawnTimer.reset(); + } + + Json originalData = m_playerStorage->savePlayer(player); + std::exception_ptr exception; + + try { + auto newData = data.set("movementController", originalData.get("movementController")); + player->diskLoad(newData); + } + catch (std::exception const& e) { + player->diskLoad(originalData); + exception = std::current_exception(); + } + + world->addEntity(player); + + CelestialCoordinate coordinate = m_systemWorldClient->location(); + player->universeMap()->addMappedCoordinate(coordinate); + player->universeMap()->filterMappedObjects(coordinate, m_systemWorldClient->objectKeys()); + + if (m_playerReloadCallback) + m_playerReloadCallback(); + + if (exception) + std::rethrow_exception(exception); + + return true; +} + +bool UniverseClient::switchPlayer(Uuid const& uuid) { + if (uuid == mainPlayer()->uuid()) + return false; + else if (auto data = m_playerStorage->maybeGetPlayerData(uuid)) + return reloadPlayer(*data, uuid); + else + return false; +} + +bool UniverseClient::switchPlayer(size_t index) { + if (auto uuid = m_playerStorage->playerUuidAt(index)) + return switchPlayer(*uuid); + else + return false; +} + +bool UniverseClient::switchPlayer(String const& name) { + if (auto uuid = m_playerStorage->playerUuidByName(name)) + return switchPlayer(*uuid); + else + return false; +} + +UniverseClient::Callback& UniverseClient::playerReloadPreCallback() { + return m_playerReloadPreCallback; +} + +UniverseClient::Callback& UniverseClient::playerReloadCallback() { + return m_playerReloadCallback; +} + ClockConstPtr UniverseClient::universeClock() const { return m_universeClock; } @@ -518,10 +596,14 @@ void UniverseClient::handlePackets(List const& packets) { for (auto const& packet : packets) { if (auto clientContextUpdate = as(packet)) { m_clientContext->readUpdate(clientContextUpdate->updateData); - m_playerStorage->applyShipUpdates(m_mainPlayer->uuid(), m_clientContext->newShipUpdates()); - m_mainPlayer->setShipUpgrades(m_clientContext->shipUpgrades()); + m_playerStorage->applyShipUpdates(m_clientContext->playerUuid(), m_clientContext->newShipUpdates()); + + if (playerIsOriginal()) + m_mainPlayer->setShipUpgrades(m_clientContext->shipUpgrades()); + m_mainPlayer->setAdmin(m_clientContext->isAdmin()); m_mainPlayer->setTeam(m_clientContext->team()); + } else if (auto chatReceivePacket = as(packet)) { m_pendingMessages.append(chatReceivePacket->receivedMessage); diff --git a/source/game/StarUniverseClient.hpp b/source/game/StarUniverseClient.hpp index 9fda492..911061c 100644 --- a/source/game/StarUniverseClient.hpp +++ b/source/game/StarUniverseClient.hpp @@ -69,6 +69,7 @@ public: CelestialCoordinate shipCoordinate() const; bool playerOnOwnShip() const; + bool playerIsOriginal() const; WorldId playerWorld() const; bool isAdmin() const; @@ -90,6 +91,15 @@ public: void startLua(); void stopLua(); + bool reloadPlayer(Json const& data, Uuid const& uuid); + bool switchPlayer(Uuid const& uuid); + bool switchPlayer(size_t index); + bool switchPlayer(String const& name); + + typedef std::function Callback; + Callback& playerReloadPreCallback(); + Callback& playerReloadCallback(); + ClockConstPtr universeClock() const; CelestialLogConstPtr celestialLog() const; JsonRpcInterfacePtr rpcInterface() const; @@ -150,6 +160,9 @@ private: typedef LuaUpdatableComponent ScriptComponent; typedef shared_ptr ScriptComponentPtr; StringMap m_scriptContexts; + + Callback m_playerReloadPreCallback; + Callback m_playerReloadCallback; }; } diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 56f28a2..3f80682 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -136,6 +136,40 @@ bool WorldClient::respawnInWorld() const { return m_respawnInWorld; } +void WorldClient::removeEntity(EntityId entityId, bool andDie) { + auto entity = m_entityMap->entity(entityId); + if (!entity) + return; + + if (andDie) { + ClientRenderCallback renderCallback; + entity->destroy(&renderCallback); + + const List* directives = nullptr; + if (auto& worldTemplate = m_worldTemplate) { + if (const auto& parameters = worldTemplate->worldParameters()) + if (auto& globalDirectives = m_worldTemplate->worldParameters()->globalDirectives) + directives = &globalDirectives.get(); + } + if (directives) { + int directiveIndex = unsigned(entity->entityId()) % directives->size(); + for (auto& p : renderCallback.particles) + p.directives.append(directives->get(directiveIndex)); + } + + m_particles->addParticles(move(renderCallback.particles)); + m_samples.appendAll(move(renderCallback.audios)); + } + + if (auto version = m_masterEntitiesNetVersion.maybeTake(entity->entityId())) { + ByteArray finalNetState = entity->writeNetState(*version).first; + m_outgoingPackets.append(make_shared(entity->entityId(), move(finalNetState), andDie)); + } + + m_entityMap->removeEntity(entityId); + entity->uninit(); +} + WorldTemplateConstPtr WorldClient::currentTemplate() const { return m_worldTemplate; } @@ -989,7 +1023,7 @@ void WorldClient::update(float dt) { action(this); List toRemove; - List clientPresenceEntities{m_mainPlayer->entityId()}; + List clientPresenceEntities; m_entityMap->updateAllEntities([&](EntityPtr const& entity) { entity->update(dt, m_currentStep); @@ -1415,40 +1449,6 @@ void WorldClient::sparkDamagedBlocks() { } } -void WorldClient::removeEntity(EntityId entityId, bool andDie) { - auto entity = m_entityMap->entity(entityId); - if (!entity) - return; - - if (andDie) { - ClientRenderCallback renderCallback; - entity->destroy(&renderCallback); - - const List* directives = nullptr; - if (auto& worldTemplate = m_worldTemplate) { - if (const auto& parameters = worldTemplate->worldParameters()) - if (auto& globalDirectives = m_worldTemplate->worldParameters()->globalDirectives) - directives = &globalDirectives.get(); - } - if (directives) { - int directiveIndex = unsigned(entity->entityId()) % directives->size(); - for (auto& p : renderCallback.particles) - p.directives.append(directives->get(directiveIndex)); - } - - m_particles->addParticles(move(renderCallback.particles)); - m_samples.appendAll(move(renderCallback.audios)); - } - - if (auto version = m_masterEntitiesNetVersion.maybeTake(entity->entityId())) { - ByteArray finalNetState = entity->writeNetState(*version).first; - m_outgoingPackets.append(make_shared(entity->entityId(), move(finalNetState), andDie)); - } - - m_entityMap->removeEntity(entityId); - entity->uninit(); -} - InteractiveEntityPtr WorldClient::getInteractiveInRange(Vec2F const& targetPosition, Vec2F const& sourcePosition, float maxRange) const { if (!inWorld()) return {}; diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 34efc1c..6a241ae 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -108,6 +108,8 @@ public: void reviveMainPlayer(); bool respawnInWorld() const; + void removeEntity(EntityId entityId, bool andDie); + WorldTemplateConstPtr currentTemplate() const; SkyConstPtr currentSky() const; @@ -214,8 +216,6 @@ private: void sparkDamagedBlocks(); - void removeEntity(EntityId entityId, bool andDie); - Vec2I environmentBiomeTrackPosition() const; AmbientNoisesDescriptionPtr currentAmbientNoises() const; WeatherNoisesDescriptionPtr currentWeatherNoises() const; diff --git a/source/game/items/StarUnlockItem.cpp b/source/game/items/StarUnlockItem.cpp index 471c39c..ee067fb 100644 --- a/source/game/items/StarUnlockItem.cpp +++ b/source/game/items/StarUnlockItem.cpp @@ -40,7 +40,7 @@ void UnlockItem::fireTriggered() { if (auto clientContext = player->clientContext()) { if (m_shipUpgrade) - clientContext->rpcInterface()->invokeRemote("ship.applyShipUpgrades", JsonObject{{"shipLevel", *m_shipUpgrade}}); + player->applyShipUpgrades(JsonObject{ {"shipLevel", *m_shipUpgrade} }); } if (!m_unlockMessage.empty()) { diff --git a/source/game/scripting/StarPlayerLuaBindings.cpp b/source/game/scripting/StarPlayerLuaBindings.cpp index fe1c06d..5980a1d 100644 --- a/source/game/scripting/StarPlayerLuaBindings.cpp +++ b/source/game/scripting/StarPlayerLuaBindings.cpp @@ -112,11 +112,8 @@ LuaCallbacks LuaBindings::makePlayerCallbacks(Player* player) { player->interact(InteractAction(type, sourceEntityId.value(NullEntityId), configData)); }); - callbacks.registerCallback("shipUpgrades", [player]() { return player->clientContext()->shipUpgrades().toJson(); }); - - callbacks.registerCallback("upgradeShip", [player](Json const& upgrades) { - player->clientContext()->rpcInterface()->invokeRemote("ship.applyShipUpgrades", upgrades); - }); + callbacks.registerCallback("shipUpgrades", [player]() { return player->shipUpgrades().toJson(); }); + callbacks.registerCallback("upgradeShip", [player](Json const& upgrades) { player->applyShipUpgrades(upgrades); }); callbacks.registerCallback("setUniverseFlag", [player](String const& flagName) { player->clientContext()->rpcInterface()->invokeRemote("universe.setFlag", flagName); diff --git a/source/rendering/StarTextPainter.cpp b/source/rendering/StarTextPainter.cpp index 3e6c618..f0ef53d 100644 --- a/source/rendering/StarTextPainter.cpp +++ b/source/rendering/StarTextPainter.cpp @@ -1,98 +1,11 @@ #include "StarTextPainter.hpp" #include "StarJsonExtra.hpp" +#include "StarText.hpp" #include namespace Star { -namespace Text { - static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc)); - String stripEscapeCodes(String const& s) { - return std::regex_replace(s.utf8(), stripEscapeRegex, ""); - } - - inline bool isEscapeCode(char c) { - return c == CmdEsc || c == StartEsc; - } - - static std::string escapeChars = strf("{:c}{:c}", CmdEsc, StartEsc); - - typedef function TextCallback; - typedef function CommandsCallback; - bool processText(StringView text, TextCallback textFunc, CommandsCallback commandsFunc = CommandsCallback(), bool includeCommandSides = false) { - std::string_view escChars(escapeChars); - - std::string_view str = text.utf8(); - while (true) { - size_t escape = str.find_first_of(escChars); - if (escape != NPos) { - escape = str.find_first_not_of(escChars, escape) - 1; // jump to the last ^ - - size_t end = str.find_first_of(EndEsc, escape); - if (end != NPos) { - if (escape && !textFunc(str.substr(0, escape))) - return false; - if (commandsFunc) { - StringView commands = includeCommandSides - ? str.substr(escape, end - escape + 1) - : str.substr(escape + 1, end - escape - 1); - if (!commands.empty() && !commandsFunc(commands)) - return false; - } - str = str.substr(end + 1); - continue; - } - } - - if (!str.empty()) - return textFunc(str); - - return true; - } - } - - // The below two functions aren't used anymore, not bothering with StringView for them - String preprocessEscapeCodes(String const& s) { - bool escape = false; - std::string result = s.utf8(); - - size_t escapeStartIdx = 0; - for (size_t i = 0; i < result.size(); i++) { - auto& c = result[i]; - if (isEscapeCode(c)) { - escape = true; - escapeStartIdx = i; - } - if ((c <= SpecialCharLimit) && !(c == StartEsc)) - escape = false; - if ((c == EndEsc) && escape) - result[escapeStartIdx] = StartEsc; - } - return {result}; - } - - String extractCodes(String const& s) { - bool escape = false; - StringList result; - String escapeCode; - for (auto c : preprocessEscapeCodes(s)) { - if (c == StartEsc) - escape = true; - if (c == EndEsc) { - escape = false; - for (auto command : escapeCode.split(',')) - result.append(command); - escapeCode = ""; - } - if (escape && (c != StartEsc)) - escapeCode.append(c); - } - if (!result.size()) - return ""; - return "^" + result.join(",") + ";"; - } -} - TextPositioning::TextPositioning() { pos = Vec2F(); hAnchor = HorizontalAnchor::LeftAnchor; diff --git a/source/rendering/StarTextPainter.hpp b/source/rendering/StarTextPainter.hpp index 7f132fd..381a358 100644 --- a/source/rendering/StarTextPainter.hpp +++ b/source/rendering/StarTextPainter.hpp @@ -10,17 +10,6 @@ namespace Star { STAR_CLASS(TextPainter); -namespace Text { - unsigned char const StartEsc = '\x1b'; - unsigned char const EndEsc = ';'; - unsigned char const CmdEsc = '^'; - unsigned char const SpecialCharLimit = ' '; - - String stripEscapeCodes(String const& s); - String preprocessEscapeCodes(String const& s); - String extractCodes(String const& s); -} - enum class FontMode { Normal, Shadow diff --git a/source/windowing/StarItemGridWidget.cpp b/source/windowing/StarItemGridWidget.cpp index 494d09c..3302ca6 100644 --- a/source/windowing/StarItemGridWidget.cpp +++ b/source/windowing/StarItemGridWidget.cpp @@ -198,12 +198,9 @@ void ItemGridWidget::updateItemState() { updateAllItemSlots(); auto newState = slotItemNames(); for (size_t i = 0; i < newState.size(); ++i) { - if (newState[i].empty()) { + if (newState[i].empty()) m_changedSlots.remove(i); - continue; - } - - if (newState[i].compare(m_itemNames[i]) != 0) + else if (newState[i].compare(m_itemNames[i]) != 0) m_changedSlots.insert(i); } m_itemNames = newState; diff --git a/source/windowing/StarWidget.hpp b/source/windowing/StarWidget.hpp index a9ea4ac..f6e9928 100644 --- a/source/windowing/StarWidget.hpp +++ b/source/windowing/StarWidget.hpp @@ -5,6 +5,7 @@ #include "StarCasting.hpp" #include "StarInputEvent.hpp" #include "StarGuiContext.hpp" +#include "StarText.hpp" namespace Star {