Skip to content

Commit

Permalink
Merge branch 'main' into ag=race-timer
Browse files Browse the repository at this point in the history
  • Loading branch information
jadebenn committed Dec 14, 2024
2 parents 610eba9 + 3461860 commit 21b3b5d
Show file tree
Hide file tree
Showing 18 changed files with 114 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ CLIENT_PATH=./client
# Updates NET_VERSION in CMakeVariables.txt
NET_VERSION=171022
# make sure this is a long random string
# grab a "SHA 256-bit Key" from here: https://keygen.io/
# generate a "SHA 256-bit Key" from here: https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex')
ACCOUNT_MANAGER_SECRET=
# Should be the externally facing IP of your server host
EXTERNAL_IP=localhost
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ ipch/

# Exceptions:
CMakeSettings.json
CMakeUserPresets.json
*.vcxproj
*.filters
*.cmake
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ at once. For that:
- Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env`
- You may get warnings that this name starts with a dot, acknowledge those, this is intentional. Depending on your operating system, you may need to activate showing hidden files (e.g. Ctrl-H in Gnome on Linux) and/or file extensions ("File name extensions" in the "View" tab on Windows).
- Update the `ACCOUNT_MANAGER_SECRET` and `MARIADB_PASSWORD` with strong random passwords.
- Use a password generator like <https://keygen.io>
- Use a password generator <https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex')>
- Avoid `:` and `@` characters
- Once the database user is created, changing the password will not update it, so the server will just fail to connect.
- Set `EXTERNAL_IP` to your LAN IP or public IP if you want to host the game for friends & family
Expand Down
1 change: 1 addition & 0 deletions dDatabase/GameDatabase/ITables/ILeaderboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class ILeaderboard {
virtual void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
virtual void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
virtual void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) = 0;
virtual void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) = 0;
};

#endif //!__ILEADERBOARD__H__
1 change: 1 addition & 0 deletions dDatabase/GameDatabase/MySQL/MySQLDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class MySQLDatabase : public GameDatabase {
void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
std::optional<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override;
void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override;
void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override;
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
private:
Expand Down
4 changes: 4 additions & 0 deletions dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ void MySQLDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId,
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
}

void MySQLDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) {
ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
}

std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
std::optional<ILeaderboard::Score> toReturn = std::nullopt;
auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId);
Expand Down
1 change: 1 addition & 0 deletions dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class TestSQLDatabase : public GameDatabase {
void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override {};
std::optional<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override { return {}; };
void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override {};
void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override {};
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override {};
void DeleteUgcBuild(const LWOOBJID bigId) override {};
};
Expand Down
3 changes: 3 additions & 0 deletions dGame/Entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
#include "ItemComponent.h"
#include "GhostComponent.h"
#include "AchievementVendorComponent.h"
#include "VanityUtilities.h"

// Table includes
#include "CDComponentsRegistryTable.h"
Expand Down Expand Up @@ -1271,6 +1272,7 @@ void Entity::Update(const float deltaTime) {
auto timerName = timer.GetName();
m_Timers.erase(m_Timers.begin() + timerPosition);
GetScript()->OnTimerDone(this, timerName);
VanityUtilities::OnTimerDone(this, timerName);

TriggerEvent(eTriggerEventType::TIMER_DONE, this);
} else {
Expand Down Expand Up @@ -1334,6 +1336,7 @@ void Entity::OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxN
if (!other) return;

GetScript()->OnProximityUpdate(this, other, proxName, status);
VanityUtilities::OnProximityUpdate(this, other, proxName, status);

RocketLaunchpadControlComponent* rocketComp = GetComponent<RocketLaunchpadControlComponent>();
if (!rocketComp) return;
Expand Down
2 changes: 2 additions & 0 deletions dGame/LeaderboardManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi

if (newHighScore) {
Database::Get()->UpdateScore(playerID, activityId, newScore);
} else {
Database::Get()->IncrementTimesPlayed(playerID, activityId);
}
} else {
Database::Get()->SaveScore(playerID, activityId, newScore);
Expand Down
19 changes: 19 additions & 0 deletions dGame/dComponents/InventoryComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,25 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
SetSkill(slot, skill);
}

void InventoryComponent::FixInvisibleItems() {
const auto numberItemsLoadedPerFrame = 12.0f;
const auto callbackTime = 0.125f;
const auto arbitaryInventorySize = 300.0f; // max in live + dlu is less than 300, seems like a good number.
auto* const items = GetInventory(eInventoryType::ITEMS);
if (!items) return;

// Add an extra update to make sure the client can see all the items.
const auto something = static_cast<int32_t>(std::ceil(items->GetItems().size() / arbitaryInventorySize)) + 1;
LOG_DEBUG("Fixing invisible items with %i updates", something);

for (int32_t i = 1; i < something + 1; i++) {
// client loads 12 items every 1/8 seconds, we're adding a small hack to fix invisible inventory items due to closing the news screen too fast.
m_Parent->AddCallbackTimer((arbitaryInventorySize / numberItemsLoadedPerFrame) * callbackTime * i, [this]() {
GameMessages::SendUpdateInventoryUi(m_Parent->GetObjectID(), m_Parent->GetSystemAddress());
});
}
}

void InventoryComponent::RemoveItemSkills(const LOT lot) {
const auto info = Inventory::FindItemComponent(lot);

Expand Down
2 changes: 2 additions & 0 deletions dGame/dComponents/InventoryComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ class InventoryComponent final : public Component {
void UpdateGroup(const GroupUpdate& groupUpdate);
void RemoveGroup(const std::string& groupId);

void FixInvisibleItems();

~InventoryComponent() override;

private:
Expand Down
2 changes: 1 addition & 1 deletion dGame/dComponents/ProximityMonitorComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void ProximityMonitorComponent::SetProximityRadius(dpEntity* entity, const std::
m_ProximitiesData.insert(std::make_pair(name, entity));
}

const std::unordered_set<LWOOBJID>& ProximityMonitorComponent::GetProximityObjects(const std::string& name) {
const std::unordered_set<LWOOBJID>& ProximityMonitorComponent::GetProximityObjects(const std::string& name) const {
const auto iter = m_ProximitiesData.find(name);

if (iter == m_ProximitiesData.cend()) {
Expand Down
2 changes: 1 addition & 1 deletion dGame/dComponents/ProximityMonitorComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ProximityMonitorComponent final : public Component {
* @param name the proximity name to retrieve physics objects for
* @return a set of physics entity object IDs for this name
*/
const std::unordered_set<LWOOBJID>& GetProximityObjects(const std::string& name);
const std::unordered_set<LWOOBJID>& GetProximityObjects(const std::string& name) const;

/**
* Checks if the passed object is in proximity of the named proximity sensor
Expand Down
12 changes: 12 additions & 0 deletions dGame/dGameMessages/GameMessageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
break;
}

// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
case MessageType::Game::SELECT_SKILL: {
auto var = entity->GetVar<bool>(u"dlu_first_time_load");
if (var) {
entity->SetVar<bool>(u"dlu_first_time_load", false);
InventoryComponent* inventoryComponent = entity->GetComponent<InventoryComponent>();

if (inventoryComponent) inventoryComponent->FixInvisibleItems();
}
break;
}

case MessageType::Game::PLAYER_LOADED: {
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
entity->SetPlayerReadyForUpdates();
Expand Down
25 changes: 21 additions & 4 deletions dGame/dGameMessages/GameMessages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ void GameMessages::SendResurrect(Entity* entity) {
destroyableComponent->SetImagination(imaginationToRestore);
}
}
});
});

CBITSTREAM;
CMSGHEADER;
Expand Down Expand Up @@ -5080,6 +5080,12 @@ void GameMessages::HandleSetFlag(RakNet::BitStream& inStream, Entity* entity) {

auto character = entity->GetCharacter();
if (character) character->SetPlayerFlag(iFlagID, bFlag);

// This is always set the first time a player loads into a world from character select
// and is used to know when to refresh the players inventory items so they show up.
if (iFlagID == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && bFlag) {
entity->SetVar<bool>(u"dlu_first_time_load", true);
}
}

void GameMessages::HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity) {
Expand Down Expand Up @@ -5147,12 +5153,12 @@ void GameMessages::HandleMissionDialogOK(RakNet::BitStream& inStream, Entity* en
}

if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|| !player->GetCharacter()
|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
|| !player->GetCharacter()
|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
});
});
}

void GameMessages::HandleRequestLinkedMission(RakNet::BitStream& inStream, Entity* entity) {
Expand Down Expand Up @@ -6324,3 +6330,14 @@ void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling
auto sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}


void GameMessages::SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr) {
CBITSTREAM;
CMSGHEADER;

bitStream.Write(objectId);
bitStream.Write(MessageType::Game::UPDATE_INVENTORY_UI);

SEND_PACKET;
}
3 changes: 3 additions & 0 deletions dGame/dGameMessages/GameMessages.h
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,9 @@ namespace GameMessages {
void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);

// This is a client gm however its default values are exactly what we need to get around the invisible inventory item issues.
void SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr);
};

#endif // GAMEMESSAGES_H
59 changes: 36 additions & 23 deletions dGame/dUtilities/VanityUtilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ void VanityUtilities::SpawnVanity() {

for (const auto& npc : objects) {
if (npc.m_ID == LWOOBJID_EMPTY) continue;
if (npc.m_LOT == 176){
if (npc.m_LOT == 176) {
Game::zoneManager->RemoveSpawner(npc.m_ID);
} else{
} else {
auto* entity = Game::entityManager->GetEntity(npc.m_ID);
if (!entity) continue;
entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT);
Expand All @@ -86,14 +86,14 @@ void VanityUtilities::SpawnVanity() {
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (location.m_Chance < rate) continue;

if (object.m_LOT == 176){
if (object.m_LOT == 176) {
object.m_ID = SpawnSpawner(object, location);
} else {
// Spawn the NPC
auto* objectEntity = SpawnObject(object, location);
if (!objectEntity) continue;
object.m_ID = objectEntity->GetObjectID();
if (!object.m_Phrases.empty()){
if (!object.m_Phrases.empty()) {
objectEntity->SetVar<std::vector<std::string>>(u"chats", object.m_Phrases);
SetupNPCTalk(objectEntity);
}
Expand All @@ -107,7 +107,7 @@ LWOOBJID SpawnSpawner(const VanityObject& object, const VanityObjectLocation& lo
// guratantee we have no collisions
do {
obj.id = ObjectIDManager::GenerateObjectID();
} while(Game::zoneManager->GetSpawner(obj.id));
} while (Game::zoneManager->GetSpawner(obj.id));
obj.position = location.m_Position;
obj.rotation = location.m_Rotation;
obj.settings = object.m_Config;
Expand Down Expand Up @@ -146,7 +146,7 @@ Entity* SpawnObject(const VanityObject& object, const VanityObjectLocation& loca
}

void ParseXml(const std::string& file) {
if (loadedFiles.contains(file)){
if (loadedFiles.contains(file)) {
LOG("Trying to load vanity file %s twice!!!", file.c_str());
return;
}
Expand Down Expand Up @@ -232,15 +232,15 @@ void ParseXml(const std::string& file) {
auto* configElement = object->FirstChildElement("config");
std::vector<std::u16string> keys = {};
std::vector<LDFBaseData*> config = {};
if(configElement) {
if (configElement) {
for (auto* key = configElement->FirstChildElement("key"); key != nullptr;
key = key->NextSiblingElement("key")) {
// Get the config data
auto* data = key->GetText();
if (!data) continue;

LDFBaseData* configData = LDFBaseData::DataFromString(data);
if (configData->GetKey() == u"useLocationsAsRandomSpawnPoint" && configData->GetValueType() == eLDFType::LDF_TYPE_BOOLEAN){
if (configData->GetKey() == u"useLocationsAsRandomSpawnPoint" && configData->GetValueType() == eLDFType::LDF_TYPE_BOOLEAN) {
useLocationsAsRandomSpawnPoint = static_cast<bool>(configData);
continue;
}
Expand All @@ -250,7 +250,7 @@ void ParseXml(const std::string& file) {
}
if (!keys.empty()) config.push_back(new LDFData<std::vector<std::u16string>>(u"syncLDF", keys));

VanityObject objectData {
VanityObject objectData{
.m_Name = name,
.m_LOT = lot,
.m_Equipment = inventory,
Expand Down Expand Up @@ -288,7 +288,7 @@ void ParseXml(const std::string& file) {
continue;
}

VanityObjectLocation locationData {
VanityObjectLocation locationData{
.m_Position = { x.value(), y.value(), z.value() },
.m_Rotation = { rw.value(), rx.value(), ry.value(), rz.value() },
};
Expand Down Expand Up @@ -403,26 +403,39 @@ void SetupNPCTalk(Entity* npc) {
npc->SetProximityRadius(20.0f, "talk");
}

void NPCTalk(Entity* npc) {
auto* proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();

if (!proximityMonitorComponent->GetProximityObjects("talk").empty()) {
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");
void VanityUtilities::OnProximityUpdate(Entity* entity, Entity* other, const std::string& proxName, const std::string& name) {
if (proxName != "talk") return;
const auto* const proximityMonitorComponent = entity->GetComponent<ProximityMonitorComponent>();
if (!proximityMonitorComponent) return;

if (chats.empty()) {
return;
}
if (name == "ENTER" && !entity->HasTimer("talk")) {
NPCTalk(entity);
}
}

const auto& selected
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];
void VanityUtilities::OnTimerDone(Entity* npc, const std::string& name) {
if (name == "talk") {
const auto* const proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
if (!proximityMonitorComponent || proximityMonitorComponent->GetProximityObjects("talk").empty()) return;

GameMessages::SendNotifyClientZoneObject(
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);
NPCTalk(npc);
}
}

void NPCTalk(Entity* npc) {
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");

if (chats.empty()) return;

const auto& selected
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];

GameMessages::SendNotifyClientZoneObject(
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);

Game::entityManager->SerializeEntity(npc);

const float nextTime = GeneralUtils::GenerateRandomNumber<float>(15, 60);

npc->AddCallbackTimer(nextTime, [npc]() { NPCTalk(npc); });
npc->AddTimer("talk", nextTime);
}
4 changes: 4 additions & 0 deletions dGame/dUtilities/VanityUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ namespace VanityUtilities {
std::string ParseMarkdown(
const std::string& file
);

void OnProximityUpdate(Entity* entity, Entity* other, const std::string& proxName, const std::string& name);

void OnTimerDone(Entity* entity, const std::string& name);
};

0 comments on commit 21b3b5d

Please sign in to comment.