Compare commits

..

1 Commits

Author SHA1 Message Date
Aronwk
277b415196 feat: limit user sent mail so that it can't be spammed 2025-08-31 02:21:10 -05:00
34 changed files with 118 additions and 251 deletions

View File

@@ -1,154 +0,0 @@
# Darkflame Universe Server Development Instructions
Darkflame Universe (DLU) is a LEGO Universe server emulator written in C++ with a multi-server architecture (AuthServer, ChatServer, MasterServer, WorldServer). Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Working Effectively
### Bootstrap, Build, and Test - REQUIRED STEPS
Execute these commands in order for ANY development work. NEVER CANCEL builds - they take time but work reliably:
```bash
# 1. Install system dependencies (Ubuntu/Debian)
sudo apt update && sudo apt install -y build-essential gcc zlib1g-dev libssl-dev openssl mariadb-server cmake
# 2. Initialize git submodules (CRITICAL - project won't build without this)
git submodule update --init --recursive
# 3. Build using the provided script
./build.sh -j2
```
- **Build time: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds (12+ minutes).**
- **Uses CMake 3.25-3.31 (confirmed working with 3.31.6)**
- **Requires g++11+ (confirmed working with g++ 13.3.0)**
### Alternative Build Methods
```bash
# Using CMake presets (CI-style)
cmake --workflow --preset ci-ubuntu-22.04
# Manual CMake (for custom configurations)
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE="Release" ..
cmake --build . --config Release -j2
```
- **Same timing: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds.**
### Run Tests
```bash
cd build
ctest --output-on-failure
```
- **Test time: <4 seconds. Set timeout to 30+ seconds.**
- **91 tests run, all should pass**
- **Tests are built automatically when ENABLE_TESTING=1 in CMakeVariables.txt**
### Database Setup (for runtime testing)
```bash
# Start MariaDB
sudo systemctl start mysql
# Create test database and user
sudo mysql -e "CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass'; GRANT ALL ON *.* TO 'testuser'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES; CREATE DATABASE testdarkflame;"
```
## Validation
### Build Validation
- **ALWAYS run the bootstrapping steps first** before making any code changes
- **ALWAYS build and test your changes** before considering them complete
- Build output should include all server binaries: AuthServer, ChatServer, MasterServer, WorldServer
- Build directory contains required files: `*.ini` configs, `navmeshes/`, `migrations/`, `vanity/`, `blocklist.dcf`, `libmariadbcpp.so`
### Runtime Validation
The servers can be started for basic validation:
```bash
cd build
./MasterServer
```
- **Server will start but complain about missing client files (this is expected)**
- **Database connections work with proper configuration in sharedconfig.ini**
- **For full server testing, LEGO Universe client files are required (not available in this repository)**
### Code Validation
**ALWAYS validate your changes by**:
1. Building successfully with no new compilation errors
2. Running the test suite and confirming all tests pass
3. Starting MasterServer to verify basic functionality
4. **Use .editorconfig** - code style uses tabs (width=4), Unix line endings, trailing whitespace removal
## Common Tasks
### Project Structure
```
/home/runner/work/DarkflameServer/DarkflameServer/
├── dAuthServer/ # Authentication server code
├── dChatServer/ # Chat server code
├── dMasterServer/ # Master server (main coordinator)
├── dWorldServer/ # World/game server code
├── dCommon/ # Shared common utilities
├── dDatabase/ # Database abstraction layer
├── dGame/ # Core game logic, components, behaviors
├── dScripts/ # Game scripts (NPCs, quests, etc.)
├── dNet/ # Network utilities
├── dPhysics/ # Physics integration
├── tests/ # Unit tests (GoogleTest)
├── migrations/ # Database schema migrations
├── thirdparty/ # External dependencies
├── build.sh # Main build script
├── CMakeVariables.txt # Build configuration variables
└── CMakePresets.json # CMake preset configurations
```
### Key Files to Know
- **CMakeVariables.txt**: Build configuration (testing enabled, MariaDB jobs, etc.)
- **build/sharedconfig.ini**: Database connection, client location, server settings
- **build/masterconfig.ini**: Master server port and startup configuration
- **CONTRIBUTING.md**: Code style guidelines and commit message format
- **docs/Commands.md**: Complete list of in-game server commands
### Build Configuration
Located in `CMakeVariables.txt`:
- `ENABLE_TESTING=1` - Unit tests enabled (keep enabled)
- `MARIADB_CONNECTOR_COMPILE_JOBS=1` - Parallel compilation jobs for MariaDB connector
- `CDCLIENT_CACHE_ALL=0` - Database caching strategy
### Common Commands Reference
```bash
# Build from clean state
rm -rf build && ./build.sh -j2
# Run specific test
cd build && ctest -R "TestName" --output-on-failure
# Check which servers were built
cd build && ls -la *Server
# View build configuration
cat CMakeVariables.txt
# Check git submodules status
git submodule status
```
### Important Notes
- **Client files are NOT included** - this is only the server emulator
- **Database can use SQLite or MariaDB** - SQLite recommended for development since it's lighter and doesn't require an external service
- **Multi-server architecture** requires all 4 servers to run a complete setup
- **Network ports**: Auth (1001), Chat (2005), Master (2000), World (3000+)
- **Development uses Debug builds**, production uses Release builds
- **GM level 0** = normal player, **GM level 8-9** = admin privileges
## Troubleshooting
- **"Asset bundle not found"**: Expected without LEGO Universe client files
- **"Submodule errors"**: Run `git submodule update --init --recursive`
- **"CMake version errors"**: Requires CMake 3.25-3.31
- **"MariaDB connection errors"**: Check database setup and sharedconfig.ini
- **"Permission denied on port"**: Run `sudo setcap 'cap_net_bind_service=+ep' AuthServer` for ports <1024
### CI Information
- **GitHub Actions** runs builds on Windows, Ubuntu, and macOS
- **Build matrix** tests multiple configurations via CMake presets
- **All tests must pass** for CI to succeed
- **Build artifacts** are automatically generated and uploaded
**Remember: This is a complex game server requiring LEGO Universe client files for full functionality, but the server has the capability to mock everything that's needed to test without the client since cdclient can be mocked and the database can be mocked as well.**

View File

@@ -24,6 +24,9 @@ public:
// Get the number of unread mail for the given character id.
virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0;
// Get the number of mail for a given character id.
virtual uint32_t GetMailCount(const uint32_t characterId) = 0;
// Mark the given mail as read.
virtual void MarkMailRead(const uint64_t mailId) = 0;

View File

@@ -89,6 +89,7 @@ public:
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
uint32_t GetMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;

View File

@@ -71,6 +71,16 @@ uint32_t MySQLDatabase::GetUnreadMailCount(const uint32_t characterId) {
return res->getInt("number_unread");
}
uint32_t MySQLDatabase::GetMailCount(const uint32_t characterId) {
auto res = ExecuteSelect("SELECT COUNT(*) AS mail_count FROM mail WHERE receiver_id=?;", characterId);
if (!res->next()) {
return 0;
}
return res->getInt("mail_count");
}
void MySQLDatabase::MarkMailRead(const uint64_t mailId) {
ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=? LIMIT 1;", mailId);
}

View File

@@ -87,6 +87,7 @@ public:
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
uint32_t GetMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;

View File

@@ -70,6 +70,16 @@ uint32_t SQLiteDatabase::GetUnreadMailCount(const uint32_t characterId) {
return res.getIntField("number_unread");
}
uint32_t SQLiteDatabase::GetMailCount(const uint32_t characterId) {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) AS mail_count FROM mail WHERE receiver_id=?;", characterId);
if (res.eof()) {
return 0;
}
return res.getIntField("mail_count");
}
void SQLiteDatabase::MarkMailRead(const uint64_t mailId) {
ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=?;", mailId);
}
@@ -81,3 +91,4 @@ void SQLiteDatabase::ClaimMailItem(const uint64_t mailId) {
void SQLiteDatabase::DeleteMail(const uint64_t mailId) {
ExecuteDelete("DELETE FROM mail WHERE id=?;", mailId);
}

View File

@@ -66,6 +66,7 @@ class TestSQLDatabase : public GameDatabase {
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
uint32_t GetMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;

View File

@@ -496,7 +496,7 @@ void Character::OnZoneLoad() {
// Remove all GM items
for (const auto lot : Inventory::GetAllGMItems()) {
inventoryComponent->RemoveItem(lot, inventoryComponent->GetLotCount(lot), eInventoryType::ALL);
inventoryComponent->RemoveItem(lot, inventoryComponent->GetLotCount(lot));
}
}

View File

@@ -175,11 +175,8 @@ Entity::~Entity() {
CancelAllTimers();
CancelCallbackTimers();
for (auto& component : m_Components | std::views::values) {
if (component) {
delete component;
component = nullptr;
}
for (const auto& component : m_Components | std::views::values) {
if (component) delete component;
}
for (auto* const child : m_ChildEntities) {

View File

@@ -12,7 +12,7 @@ void ConsumeItemBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bi
auto inventoryComponent = caster->GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
if (inventoryComponent->RemoveItem(this->m_ConsumeLOT, this->m_NumToConsume, eInventoryType::ALL, false, true)){
if (inventoryComponent->RemoveItem(this->m_ConsumeLOT, this->m_NumToConsume, eInventoryType::INVALID, false, true)){
action_to_cast = m_ActionConsumed;
}
}

View File

@@ -64,11 +64,12 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
}
const uint32_t altCurrencyCost = itemComp.commendationCost * count;
if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost || !inventoryComponent->RemoveItem(costLOT, altCurrencyCost, eInventoryType::ALL)) {
if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost) {
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
return;
}
inventoryComponent->RemoveItem(costLOT, altCurrencyCost);
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);

View File

@@ -354,7 +354,10 @@ bool ActivityComponent::CheckCost(Entity* player) const {
bool ActivityComponent::TakeCost(Entity* player) const {
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
return CheckCost(player) && inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount, eInventoryType::ALL);
if (CheckCost(player)) {
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
return true;
} else return false;
}
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {

View File

@@ -164,17 +164,10 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
return;
}
}
bool success = true;
for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) {
success = inventoryComponent->RemoveItem(crafintCurrencyLOT, crafintCurrencyCount * count, eInventoryType::ALL);
if (!success) break;
inventoryComponent->RemoveItem(crafintCurrencyLOT, crafintCurrencyCount * count);
}
if (!success) {
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
return;
}
float buyScalar = GetBuyScalar();
const auto coinCost = static_cast<uint32_t>(std::floor((itemComp.baseValue * buyScalar) * count));
@@ -191,7 +184,7 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
return;
}
inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL);
inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
}
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR);

View File

@@ -4823,10 +4823,11 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream& inStream, Entity*
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * sellScalar) * count;
if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost || !inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL)) {
if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) {
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL);
return;
}
inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
}
//inv->RemoveItem(count, -1, iObjID);
@@ -5507,18 +5508,10 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
modules += u"1:" + (modToStr);
if (k + 1 != count) modules += u"+";
bool hasItem = false;
if (temp->GetLotCount(mod) > 0) {
hasItem = inv->RemoveItem(mod, 1, TEMP_MODELS);
inv->RemoveItem(mod, 1, TEMP_MODELS);
} else {
hasItem = inv->RemoveItem(mod, 1, eInventoryType::ALL);
}
if (!hasItem) {
LOG("Player (%llu) attempted to finish a modular build without having all the required parts.", character->GetObjectID());
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
return;
inv->RemoveItem(mod, 1);
}
// Doing this check for 1 singular mission that needs to know when you've swapped every part out during a car modular build.

View File

@@ -289,10 +289,11 @@ bool Item::Consume() {
GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success);
const auto myLot = this->lot;
if (success && inventory->GetComponent()->RemoveItem(lot, 1, eInventoryType::ALL)) {
if (success) {
// Save this because if this is the last item in the inventory
// we may delete ourself (lol)
const auto myLot = this->lot;
inventory->GetComponent()->RemoveItem(lot, 1);
auto* missionComponent = inventory->GetComponent()->GetParent()->GetComponent<MissionComponent>();
if (missionComponent) missionComponent->Progress(eMissionTaskType::GATHER, myLot, LWOOBJID_EMPTY, "", -1);
}

View File

@@ -483,7 +483,7 @@ void Mission::YieldRewards() {
// If a mission rewards zero of an item, make it reward 1.
auto count = pair.second > 0 ? pair.second : 1;
LOG("Player %llu is receiving %i of item %i from repeatable mission %i", entity->GetObjectID(), count, pair.first, info.id);
inventoryComponent->AddItem(pair.first, count, IsMission() ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT);
}
@@ -511,7 +511,7 @@ void Mission::YieldRewards() {
// If a mission rewards zero of an item, make it reward 1.
auto count = pair.second > 0 ? pair.second : 1;
LOG("Player %llu is receiving %i of item %i from mission %i", entity->GetObjectID(), count, pair.first, info.id);
inventoryComponent->AddItem(pair.first, count, IsMission() ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT);
}

View File

@@ -129,10 +129,6 @@ std::unordered_map<LOT, int32_t> Loot::RollLootMatrix(Entity* player, uint32_t m
}
}
for (const auto& drop : drops) {
LOG("Player %llu has rolled %i of item %i from loot matrix %i", player->GetObjectID(), drop.second, drop.first, matrixIndex);
}
return drops;
}

View File

@@ -81,56 +81,64 @@ namespace Mail {
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf;
} else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
// check if recipient mailbox is full
if (Database::Get()->GetMailCount(receiverID->id) >= 20) {
// There is no Mailbox full response, so we will do this instead
response.status = eSendResponse::UnknownError;
// send system chat message to player
ChatPackets::SendSystemMessage(player->GetSystemAddress(), u"Recipient's mailbox is full. Please try again later.");
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, INVALID, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
response.status = eSendResponse::AttachmentNotFound;
}
}
}
} else {
response.status = eSendResponse::SenderAccountIsMuted;
}
} else {
response.status = eSendResponse::SenderAccountIsMuted;
}
LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr);

View File

@@ -130,7 +130,9 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat
case PreconditionType::HasItem:
if (evaluateCosts) // As far as I know this is only used for quickbuilds, and removal shouldn't actually be handled here.
{
return inventoryComponent->RemoveItem(value, count, eInventoryType::ALL);
inventoryComponent->RemoveItem(value, count);
return true;
}
return inventoryComponent->GetLotCount(value) >= count;

View File

@@ -24,6 +24,6 @@ void AgCagedBricksServer::OnUse(Entity* self, Entity* user) {
auto inv = static_cast<InventoryComponent*>(user->GetComponent(eReplicaComponentType::INVENTORY));
if (inv) {
inv->RemoveItem(14553, 1, eInventoryType::ALL);
inv->RemoveItem(14553, 1);
}
}

View File

@@ -22,7 +22,7 @@ void NpcCowboyServer::OnMissionDialogueOK(Entity* self, Entity* target, int miss
inventoryComponent->AddItem(14378, 1, eLootSourceType::NONE);
}
} else if (missionState == eMissionState::READY_TO_COMPLETE || missionState == eMissionState::COMPLETE_READY_TO_COMPLETE) {
inventoryComponent->RemoveItem(14378, 1, eInventoryType::ALL);
inventoryComponent->RemoveItem(14378, 1);
}
// Next up hide or show the samples based on the mission state

View File

@@ -22,7 +22,7 @@ void NpcWispServer::OnMissionDialogueOK(Entity* self, Entity* target, int missio
&& maelstromVacuum == nullptr) {
inventory->AddItem(maelstromVacuumLot, 1, eLootSourceType::NONE);
} else if (missionState == eMissionState::READY_TO_COMPLETE || missionState == eMissionState::COMPLETE_READY_TO_COMPLETE) {
inventory->RemoveItem(maelstromVacuumLot, 1, eInventoryType::ALL);
inventory->RemoveItem(maelstromVacuumLot, 1);
}
// Next up hide or show the samples based on the mission state

View File

@@ -31,7 +31,7 @@ void RemoveRentalGear::OnMissionDialogueOK(Entity* self, Entity* target, int mis
auto* id = inv->FindItemByLot(item);
if (id) {
inv->UnEquipItem(id);
inv->RemoveItem(id->GetLot(), id->GetCount(), eInventoryType::ALL);
inv->RemoveItem(id->GetLot(), id->GetCount());
}
}

View File

@@ -74,13 +74,13 @@ void ImgBrickConsoleQB::OnUse(Entity* self, Entity* user) {
if (missionComponent != nullptr && inventoryComponent != nullptr) {
if (missionComponent->GetMissionState(1302) == eMissionState::ACTIVE) {
inventoryComponent->RemoveItem(13074, 1, eInventoryType::ALL);
inventoryComponent->RemoveItem(13074, 1);
missionComponent->ForceProgressTaskType(1302, 1, 1);
}
if (missionComponent->GetMissionState(1926) == eMissionState::ACTIVE) {
inventoryComponent->RemoveItem(14472, 1, eInventoryType::ALL);
inventoryComponent->RemoveItem(14472, 1);
missionComponent->ForceProgressTaskType(1926, 1, 1);
}

View File

@@ -14,7 +14,7 @@ void TokenConsoleServer::OnUse(Entity* self, Entity* user) {
//make sure the user has the required amount of infected bricks
if (inv && inv->GetLotCount(6194) >= bricksToTake) {
//yeet the bricks
inv->RemoveItem(6194, bricksToTake, eInventoryType::ALL);
inv->RemoveItem(6194, bricksToTake);
//play sound
if (self->HasVar(u"sound1")) {

View File

@@ -34,7 +34,7 @@ void NsTokenConsoleServer::OnUse(Entity* self, Entity* user) {
return;
}
inventoryComponent->RemoveItem(6194, 25, eInventoryType::ALL);
inventoryComponent->RemoveItem(6194, 25);
const auto useSound = self->GetVar<std::string>(u"sound1");

View File

@@ -51,7 +51,7 @@ void NtCombatChallengeServer::OnMessageBoxResponse(Entity* self, Entity* sender,
auto* inventoryComponent = sender->GetComponent<InventoryComponent>();
if (inventoryComponent != nullptr) {
inventoryComponent->RemoveItem(3039, 1, eInventoryType::ALL);
inventoryComponent->RemoveItem(3039, 1);
}
GameMessages::SendPlayNDAudioEmitter(self, sender->GetSystemAddress(), startSound);

View File

@@ -34,7 +34,7 @@ void NtDukeServer::OnMissionDialogueOK(Entity* self, Entity* target, int mission
if ((state == eMissionState::AVAILABLE || state == eMissionState::ACTIVE) && lotCount < 1) {
inventoryComponent->AddItem(m_SwordLot, 1, eLootSourceType::NONE);
} else if (state == eMissionState::READY_TO_COMPLETE) {
inventoryComponent->RemoveItem(m_SwordLot, lotCount, eInventoryType::ALL);
inventoryComponent->RemoveItem(m_SwordLot, lotCount);
}
}
NtBcSubmitServer::OnMissionDialogueOK(self, target, missionID, missionState);

View File

@@ -8,7 +8,7 @@ void NtVandaServer::OnMissionDialogueOK(Entity* self, Entity* target, int missio
if (missionID == m_AlienPartMissionID && missionState == eMissionState::READY_TO_COMPLETE) {
auto* inventoryComponent = target->GetComponent<InventoryComponent>();
for (const auto& alienPartLot : m_AlienPartLots) {
inventoryComponent->RemoveItem(alienPartLot, 1, eInventoryType::ALL);
inventoryComponent->RemoveItem(alienPartLot, 1);
}
}
NtBcSubmitServer::OnMissionDialogueOK(self, target, missionID, missionState);

View File

@@ -20,7 +20,7 @@ void SpawnGryphonServer::OnUse(Entity* self, Entity* user) {
// Little extra for handling the case of the egg being placed the first time
if (missionComponent != nullptr && inventoryComponent != nullptr
&& missionComponent->GetMissionState(1391) == eMissionState::ACTIVE) {
inventoryComponent->RemoveItem(12483, inventoryComponent->GetLotCount(12483), eInventoryType::ALL);
inventoryComponent->RemoveItem(12483, inventoryComponent->GetLotCount(12483));
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, self->GetObjectID());
return;
}

View File

@@ -39,7 +39,7 @@ void NjColeNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID,
}
if (inventoryComponent->GetLotCount(14499) > 0) {
inventoryComponent->RemoveItem(14499, 1, eInventoryType::ALL);
inventoryComponent->RemoveItem(14499, 1);
} else {
return;
}

View File

@@ -9,7 +9,7 @@ void NjScrollChestServer::OnUse(Entity* self, Entity* user) {
if (playerInventory != nullptr && playerInventory->GetLotCount(keyLOT) == 1) {
// Check for the key and remove
playerInventory->RemoveItem(keyLOT, 1, eInventoryType::ALL);
playerInventory->RemoveItem(keyLOT, 1);
// Reward the player with the item set
playerInventory->AddItem(rewardItemLOT, 1, eLootSourceType::NONE);

View File

@@ -14,7 +14,7 @@ void NPCAddRemoveItem::OnMissionDialogueOK(Entity* self, Entity* target, int mis
if (itemSetting.add && (missionState == eMissionState::AVAILABLE || missionState == eMissionState::COMPLETE_AVAILABLE)) {
inventory->AddItem(lot, 1, eLootSourceType::NONE);
} else if (itemSetting.remove && (missionState == eMissionState::READY_TO_COMPLETE || missionState == eMissionState::COMPLETE_READY_TO_COMPLETE)) {
inventory->RemoveItem(lot, 1, eInventoryType::ALL);
inventory->RemoveItem(lot, 1);
}
}
}

View File

@@ -24,7 +24,7 @@ void AgPropGuard::OnMissionDialogueOK(Entity* self, Entity* target, int missionI
if (id) {
inventoryComponent->UnEquipItem(id);
inventoryComponent->RemoveItem(id->GetLot(), id->GetCount(), eInventoryType::ALL);
inventoryComponent->RemoveItem(id->GetLot(), id->GetCount());
}
}
} else if (