Вот простой пример инвентаря на Node.js + MongoDB
- коллекция items (владелец, тип, количество, слот);
- CRUD: список, добавить (в первый свободный слот), перенести, удалить, изменить кол-во;
- команды: /inv, /giveitem <type> <qty>, /move <from> <to>, /drop <slot>.
1) server/mongo/connect.js
// Простое подключение к MongoDB (mongoose)
const mongoose = require('mongoose');
module.exports = async function connectDB(
url = process.env.MONGO_URL || 'mongodb://localhost:27017/ragemp'
) {
try {
await mongoose.connect(url, { autoIndex: true });
console.log('[mongo] connected');
} catch (err) {
console.error('[mongo] connection error:', err);
}
};
2) server/models/Item.js
const { Schema, model } = require('mongoose');
// Один предмет = одна запись (владелец + слот уникальны)
const ItemSchema = new Schema(
{
owner: { type: Number, required: true, index: true }, // id персонажа/игрока
type: { type: String, required: true }, // "Water" | "Phone" | ...
qty: { type: Number, required: true, min: 0, default: 1 },
slot: { type: Number, required: true, min: 0 }, // 0..MAX_SLOTS-1
meta: { type: Object, default: {} },
},
{ timestamps: true }
);
// У одного владельца нельзя положить два предмета в один слот
ItemSchema.index({ owner: 1, slot: 1 }, { unique: true });
module.exports = model('Item', ItemSchema);
3) server/inventory/index.js
const Item = require('../models/Item');
const MAX_SLOTS = 20;
// ===== helpers =====
function firstFreeSlot(used) {
const set = new Set(used);
for (let i = 0; i < MAX_SLOTS; i++) if (!set.has(i)) return i;
return -1;
}
function ownerIdOf(player) {
// подставь свой способ получения id персонажа
return player.getVariable?.('characterId') ?? player.id;
}
// ===== repo / сервис =====
const Inventory = {
async list(owner) {
return Item.find({ owner }).sort({ slot: 1 }).lean();
},
async add(owner, type, qty = 1) {
const items = await Item.find({ owner }, { slot: 1 }).lean();
const slot = firstFreeSlot(items.map(i => i.slot));
if (slot < 0) throw new Error('Нет свободных слотов');
return Item.create({ owner, type, qty, slot });
},
// упрощённый swap через «временный» слот -1 (без транзакций)
async move(owner, from, to) {
if (from === to) return;
// шаг 1: сдвигаем from -> -1 (если предмет существует)
const a = await Item.findOneAndUpdate(
{ owner, slot: from },
{ $set: { slot: -1 } },
{ new: true }
);
if (!a) throw new Error('Нет предмета в исходном слоте');
// шаг 2: to -> from (если занят)
await Item.findOneAndUpdate(
{ owner, slot: to },
{ $set: { slot: from } },
{ new: true }
);
// шаг 3: -1 -> to
await Item.updateOne({ _id: a._id }, { $set: { slot: to } });
},
async remove(owner, slot) {
await Item.deleteOne({ owner, slot });
},
async setQty(owner, slot, qty) {
qty = Math.max(0, Number(qty) || 0);
if (qty === 0) return this.remove(owner, slot);
await Item.updateOne({ owner, slot }, { $set: { qty } });
},
};
module.exports = { Inventory, ownerIdOf, MAX_SLOTS };
4) server/index.js — подключаем БД и команды
const connectDB = require('./mongo/connect');
const { Inventory, ownerIdOf, MAX_SLOTS } = require('./inventory');
mp.events.add('packagesLoaded', async () => {
await connectDB();
console.log('[server] ready');
});
// /inv — показать инвентарь
mp.events.addCommand('inv', async (player) => {
const owner = ownerIdOf(player);
const items = await Inventory.list(owner);
const text = items.length
? items.map(i =>
${i.slot}:${i.type}x${i.qty}
).join(' | ')
: 'пусто';
player.outputChatBox(
!{#7fbfff}[INV]!{#ffffff} ${text}
);
});
// /giveitem <type> <qty>
mp.events.addCommand('giveitem', async (player, full) => {
const [type, qtyStr] = (full || '').trim().split(/\s+/);
if (!type) return player.outputChatBox('Использование: /giveitem <type> <qty?>');
const owner = ownerIdOf(player);
try {
await Inventory.add(owner, type, Number(qtyStr) || 1);
player.outputChatBox(
Добавлено: ${type}
);
} catch (e) {
player.outputChatBox(
Ошибка: ${e.message}
);
}
});
// /move <from> <to>
mp.events.addCommand('move', async (player, full) => {
const [fromStr, toStr] = (full || '').trim().split(/\s+/);
const from = Number(fromStr); const to = Number(toStr);
if (Number.isNaN(from) || Number.isNaN(to))
return player.outputChatBox('Использование: /move <from> <to>');
if (from < 0 || to < 0 || from >= MAX_SLOTS || to >= MAX_SLOTS)
return player.outputChatBox(
Слоты: 0..${MAX_SLOTS - 1}
);
try {
await Inventory.move(ownerIdOf(player), from, to);
player.outputChatBox(
Перемещено: ${from} -> ${to}
);
} catch (e) {
player.outputChatBox(
Ошибка: ${e.message}
);
}
});
// /drop <slot>
mp.events.addCommand('drop', async (player, full) => {
const slot = Number((full || '').trim());
if (Number.isNaN(slot)) return player.outputChatBox('Использование: /drop <slot>');
await Inventory.remove(ownerIdOf(player), slot);
player.outputChatBox(
Удалён слот ${slot}
);
});