Add character swapping (no GUI yet)
This commit is contained in:
parent
4fbd67dacc
commit
cb19eef701
@ -506,9 +506,17 @@ void ClientApplication::changeState(MainAppState newState) {
|
||||
m_playerStorage = make_shared<PlayerStorage>(m_root->toStoragePath("player"));
|
||||
m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService());
|
||||
m_universeClient = make_shared<UniverseClient>(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<InventoryPane>(MainInterfacePanes::Inventory))
|
||||
inventory->clearChangedSlots();
|
||||
}
|
||||
};
|
||||
|
||||
m_mainMixer->setUniverseClient(m_universeClient);
|
||||
m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer());
|
||||
if (auto renderer = Application::renderer())
|
||||
|
@ -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
|
||||
|
89
source/core/StarText.cpp
Normal file
89
source/core/StarText.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include "StarText.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
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(",") + ";";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
26
source/core/StarText.hpp
Normal file
26
source/core/StarText.hpp
Normal file
@ -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<bool(StringView text)> TextCallback;
|
||||
typedef function<bool(StringView commands)> CommandsCallback;
|
||||
bool processText(StringView text, TextCallback textFunc, CommandsCallback commandsFunc = CommandsCallback(), bool includeCommandSides = false);
|
||||
String preprocessEscapeCodes(String const& s);
|
||||
String extractCodes(String const& s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -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);
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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<ButtonWidget>("beamToShip")->setEnabled(m_canBeam);
|
||||
fetchChild<ButtonWidget>("makeLeader")->setEnabled(isLeader && !isSelf);
|
||||
|
@ -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<JsonRpc>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -27,6 +27,25 @@ EntityId EntityMap::reserveEntityId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
Maybe<EntityId> 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();
|
||||
|
@ -30,6 +30,10 @@ public:
|
||||
|
||||
// Get the next free id in the entity id space.
|
||||
EntityId reserveEntityId();
|
||||
// Or a specific one, can fail.
|
||||
Maybe<EntityId> 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.
|
||||
|
@ -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<String>());
|
||||
|
||||
ds.read(m_description);
|
||||
ds.read(m_modeType);
|
||||
ds.read(m_identity);
|
||||
|
||||
m_humanoid = make_shared<Humanoid>(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<PlayerBlueprints>(diskStore.get("blueprints"));
|
||||
m_universeMap = make_shared<PlayerUniverseMap>(diskStore.get("universeMap"));
|
||||
if (m_clientContext)
|
||||
m_universeMap->setServerUuid(m_clientContext->serverUuid());
|
||||
|
||||
m_codexes = make_shared<PlayerCodexes>(diskStore.get("codexes"));
|
||||
m_techs = make_shared<PlayerTech>(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<PlayerLog>(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<Humanoid>(Root::singleton().speciesDatabase()->species(m_identity.species)->humanoidConfig());
|
||||
m_humanoid = make_shared<Humanoid>(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<String>());
|
||||
|
||||
ds.read(m_description);
|
||||
ds.read(m_modeType);
|
||||
ds.read(m_identity);
|
||||
|
||||
m_humanoid = make_shared<Humanoid>(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<PhysicsForceRegion> 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;
|
||||
}
|
||||
|
@ -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<PhysicsForceRegion> forceRegions() const override;
|
||||
|
||||
StatusControllerPtr statusControllerPtr();
|
||||
ActorMovementControllerPtr movementControllerPtr();
|
||||
|
||||
PlayerConfigPtr config();
|
||||
|
||||
SongbookPtr songbook() const;
|
||||
|
||||
void finalizeCreation();
|
||||
|
@ -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>(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<ItemBag>(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"));
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarEntityFactory.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarText.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
@ -98,7 +99,28 @@ Maybe<Uuid> PlayerStorage::playerUuidAt(size_t index) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void PlayerStorage::savePlayer(PlayerPtr const& player) {
|
||||
Maybe<Uuid> PlayerStorage::playerUuidByName(String const& name) {
|
||||
String cleanMatch = Text::stripEscapeCodes(name).toLower();
|
||||
Maybe<Uuid> 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<Json> 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<Player>(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 {};
|
||||
}
|
||||
|
@ -18,8 +18,14 @@ public:
|
||||
size_t playerCount() const;
|
||||
// Returns nothing if index is out of bounds.
|
||||
Maybe<Uuid> playerUuidAt(size_t index);
|
||||
// Returns nothing if name doesn't match a player.
|
||||
Maybe<Uuid> playerUuidByName(String const& name);
|
||||
|
||||
void savePlayer(PlayerPtr const& player);
|
||||
// Also returns the diskStore Json if needed.
|
||||
Json savePlayer(PlayerPtr const& player);
|
||||
|
||||
Maybe<Json> maybeGetPlayerData(Uuid const& uuid);
|
||||
Json getPlayerData(Uuid const& uuid);
|
||||
PlayerPtr loadPlayer(Uuid const& uuid);
|
||||
void deletePlayer(Uuid const& uuid);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ public:
|
||||
Maybe<WarpAction> objectWarpAction(Uuid const& uuid) const;
|
||||
|
||||
virtual List<SystemObjectPtr> objects() const = 0;
|
||||
virtual List<Uuid> objectKeys() const = 0;
|
||||
virtual SystemObjectPtr getObject(Uuid const& uuid) const = 0;
|
||||
|
||||
SystemObjectConfig systemObjectConfig(String const& name, Uuid const& uuid) const;
|
||||
|
@ -82,6 +82,10 @@ List<SystemObjectPtr> SystemWorldClient::objects() const {
|
||||
return m_objects.values();
|
||||
}
|
||||
|
||||
List<Uuid> SystemWorldClient::objectKeys() const {
|
||||
return m_objects.keys();
|
||||
}
|
||||
|
||||
SystemObjectPtr SystemWorldClient::getObject(Uuid const& uuid) const {
|
||||
return m_objects.maybe(uuid).value({});
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ public:
|
||||
void update(float dt);
|
||||
|
||||
List<SystemObjectPtr> objects() const override;
|
||||
List<Uuid> objectKeys() const override;
|
||||
SystemObjectPtr getObject(Uuid const& uuid) const override;
|
||||
|
||||
List<SystemClientShipPtr> ships() const;
|
||||
|
@ -247,6 +247,10 @@ List<SystemObjectPtr> SystemWorldServer::objects() const {
|
||||
return m_objects.values();
|
||||
}
|
||||
|
||||
List<Uuid> SystemWorldServer::objectKeys() const {
|
||||
return m_objects.keys();
|
||||
}
|
||||
|
||||
SystemObjectPtr SystemWorldServer::getObject(Uuid const& uuid) const {
|
||||
return m_objects.maybe(uuid).value({});
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
void update(float dt);
|
||||
|
||||
List<SystemObjectPtr> objects() const override;
|
||||
List<Uuid> objectKeys() const override;
|
||||
SystemObjectPtr getObject(Uuid const& uuid) const override;
|
||||
|
||||
List<ConnectionId> pullShipFlights();
|
||||
|
@ -109,7 +109,7 @@ Maybe<String> UniverseClient::connect(UniverseConnection connection, bool allowA
|
||||
|
||||
if (auto success = as<ConnectSuccessPacket>(packet)) {
|
||||
m_universeClock = make_shared<Clock>();
|
||||
m_clientContext = make_shared<ClientContext>(success->serverUuid);
|
||||
m_clientContext = make_shared<ClientContext>(success->serverUuid, m_mainPlayer->uuid());
|
||||
m_teamClient = make_shared<TeamClient>(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<ClientShipWorldId>() && playerWorld().get<ClientShipWorldId>() == 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<PacketPtr> const& packets) {
|
||||
for (auto const& packet : packets) {
|
||||
if (auto clientContextUpdate = as<ClientContextUpdatePacket>(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<ChatReceivePacket>(packet)) {
|
||||
m_pendingMessages.append(chatReceivePacket->receivedMessage);
|
||||
|
||||
|
@ -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<void()> Callback;
|
||||
Callback& playerReloadPreCallback();
|
||||
Callback& playerReloadCallback();
|
||||
|
||||
ClockConstPtr universeClock() const;
|
||||
CelestialLogConstPtr celestialLog() const;
|
||||
JsonRpcInterfacePtr rpcInterface() const;
|
||||
@ -150,6 +160,9 @@ private:
|
||||
typedef LuaUpdatableComponent<LuaBaseComponent> ScriptComponent;
|
||||
typedef shared_ptr<ScriptComponent> ScriptComponentPtr;
|
||||
StringMap<ScriptComponentPtr> m_scriptContexts;
|
||||
|
||||
Callback m_playerReloadPreCallback;
|
||||
Callback m_playerReloadCallback;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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>* 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<EntityDestroyPacket>(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<EntityId> toRemove;
|
||||
List<EntityId> clientPresenceEntities{m_mainPlayer->entityId()};
|
||||
List<EntityId> 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>* 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<EntityDestroyPacket>(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 {};
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -1,98 +1,11 @@
|
||||
#include "StarTextPainter.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarText.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
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<bool(StringView text)> TextCallback;
|
||||
typedef function<bool(StringView commands)> 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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "StarCasting.hpp"
|
||||
#include "StarInputEvent.hpp"
|
||||
#include "StarGuiContext.hpp"
|
||||
#include "StarText.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user