أكثر ما يربك مطوّر المودات هو أن اللعبة تعمل، لكن مودًا آخر يتعارض معها بعد إضافة تعديل صغير. الحل ليس تجنب كل API متقدم، بل معرفة مستوى التدخل المناسب. ابدأ بالـEvents، ثم استخدم Mixin عندما لا توجد نقطة ربط، واكتب حدودًا واضحة لما يتغير.
ما هو Event؟
Event هو hook يستدعي كودك عندما يحدث شيء معروف: تفاعل، tick، تحميل loot table، أو حدث آخر توفره Fabric API. توثيق Fabric يصف Events بأنها نقاط ربط لحالات شائعة وتحسين التوافق والأداء بين مودات تتدخل في المنطقة نفسها [1]. هذا يعني أن مودك لا يحتاج إلى نسخ جسم دالة اللعبة أو الاعتماد على ترتيب داخلي هش.
ما هو Mixin؟
Mixin يحقن سلوكًا في كلاس موجود وقت التشغيل. القوة هنا كبيرة، لكنها تعني أنك تتعامل مع تفاصيل قد تتغير مع إصدار اللعبة أو mappings. استخدمه عندما لا يوفّر Fabric hook مناسبًا، وليس لأنه أقصر من Event.
| الحالة | الخيار المفضل | لماذا |
|---|---|---|
| رد على حدث شائع | Fabric Event | أوضح وأقل هشاشة |
| تعديل loot table موجودة | LootTableEvents.MODIFY | لا تستبدل الملف كاملًا |
| لا توجد نقطة ربط | Mixin محدود | تدخل دقيق مع اختبار |
| منطق يحتاجه مودات أخرى | Custom Event | عقد واضح بين المستمعين |
1. ابدأ بالـEvent
الـcallback يجب أن يكون قصيرًا، واضحًا، وألا يطلق عمليات ثقيلة في كل استدعاء. افحص side: هل الحدث على server أم client؟ إذا كنت تعدّل عالمًا أو مخزونًا، فالمنطق عادة يجب أن يمر عبر الخادم حتى لا تصبح حالة العميل مختلفة عن الحقيقة.
AttackBlockCallback.EVENT.register((player, level, hand, pos, direction) -> {
if (level.isClientSide()) return InteractionResult.PASS;
// نفّذ منطقًا صغيرًا ومحددًا هنا
return InteractionResult.PASS;
});القيمة PASS تعني “دع بقية المستمعين يكملون”. إذا أعدت قيمة توقف المعالجة، افعل ذلك فقط عندما تملك سببًا واضحًا، لأن مستمعًا آخر قد يعتمد على الحدث.
2. تعديل Loot Table بدون استبدالها
توضح وثائق Events أن LootTableEvents.MODIFY يسمح بإضافة pool إلى loot table مدمجة، وأن فحص المصدر يساعدك على عدم لمس data pack tables التي أنشأها اللاعب [1]. هذه نقطة توافق مهمة: استبدال الملف الكامل قد يمسح تعديل مود آخر، أما الإضافة المحدودة فتترك المجال مفتوحًا.
صمّم شرطك بثلاثة أجزاء: تحقق من أن المصدر built-in، تحقق من key الجدول، ثم أضف pool صغيرًا. لا تفعل loop على كل loot table في اللعبة من غير شرط.
3. عندما تحتاج Custom Event
إذا لم يوجد Event لحالتك، يمكنك إنشاء callback interface خاص بك. النمط الجيد يحدد contract: ما البيانات التي تصل للمستمع، وما معنى PASS وSUCCESS وFAIL. توثيق Fabric يعرض نموذجًا لـcustom event يمر على المستمعين حتى يعيد أحدهم نتيجة غير PASS [1].
public interface IgnisBreakCallback {
Event<IgnisBreakCallback> EVENT = EventFactory.createArrayBacked(
IgnisBreakCallback.class,
listeners -> (player, pos) -> {
for (IgnisBreakCallback listener : listeners) {
InteractionResult result = listener.interact(player, pos);
if (result != InteractionResult.PASS) return result;
}
return InteractionResult.PASS;
}
);
InteractionResult interact(Player player, BlockPos pos);
}وجود Javadoc يشرح النتائج ليس ترفًا. بعد شهر، أو عندما يثبت مود آخر listener، يجب أن يعرف المطوّر هل SUCCESS يلغي السلوك الأصلي أم يسمح به.
4. استخدام Mixin بأقل مساحة ممكنة
قبل كتابة Mixin، ابحث في docs عن Event، ثم افحص source المرتبط بالإصدار نفسه، وحدد method وinjection point. لا تختار HEAD دائمًا؛ ربما تحتاج بعد تحقق أو قبل استدعاء محدد. استخدم cancellable فقط إذا كان إلغاء الدالة جزءًا من التصميم.
قسّم Mixin إلى صنف صغير لكل هدف. لا تجعل Mixin واحدًا يحقن في عشرين دالة؛ ذلك يصعب قراءة stacktrace ويزيد أثر التحديثات. ضع تعليقًا يشرح لماذا لا يكفي Event.
5. side والـthread
من أكبر مصادر الأعطال تشغيل كود عميل داخل الخادم أو لمس world state من thread غير مناسب. اجعل كود rendering والـkeybind في client entrypoint. اجعل تغيير البيانات على الخادم، ثم أرسل ما يلزم للعميل عبر القنوات المناسبة. لا تعتمد على قيمة محلية في العميل لتأكيد عملية تؤثر في العالم.
6. التوافق مع مودات أخرى
- لا تستبدل loot table أو recipe كاملة إذا كان hook يسمح بالإضافة.
- لا تستهلك event وتعيد SUCCESS إلا عند الضرورة.
- استخدم namespace ومعرّفات فريدة.
- وثّق ترتيب التوقعات إذا كان callback قابلًا للإلغاء.
- اختبر وجود مودات شائعة حول نفس الميكانيكية، لكن لا تدّعِ توافقًا لم تختبره.
سيناريو عملي لاتخاذ القرار
تريد منع إسقاط معين من خام Vanilla وإضافة إسقاطك بدلًا منه. أول سؤال: هل يوجد Event لتحميل loot table؟ إذا نعم، استخدم MODIFY مع شرط key. تريد منع طريقة داخلية لا تملك Event؟ استخدم Mixin محدودًا عند الاستدعاء الذي يقرر الإسقاط. تريد أن تسمح لمودات أخرى بإضافة قواعد؟ أنشئ Custom Event بعقد واضح بدل أن تجعل كل مود يحقن في نفس المكان.
قائمة مراجعة قبل الإصدار
| المراجعة | تم؟ |
|---|---|
| بحثت عن Event مناسب قبل Mixin | □ |
| اختبرت client وserver sides | □ |
| لا يوجد loop ثقيل في tick | □ |
| الـMixin له هدف وسبب موثق | □ |
| جربت المود مع نسخة نظيفة | □ |
9. اكتب حدودًا واضحة للـcallback
عند إنشاء Custom Event، وثّق متى يُستدعى وهل يسمح بتغيير البيانات أو يقرأها فقط. حدّد هل الاستدعاء على thread اللعبة، وهل يمكن للمستمع أن يرمي exception. كلما كان العقد واضحًا، استطاع مود آخر الاشتراك دون قراءة source كامل.
10. اختبر التوافق قبل النشر
أنشئ مصفوفة صغيرة: المود وحده، المود مع dependency، المود مع مود يعدّل نفس الميكانيكية، والعميل مع server. سجّل ما إذا كان الحدث يتكرر أو يُستدعى مرتين. في حالة Mixin، اختبر نقطة الحقن بعد كل تحديث mappings؛ اسم method قد يتغير حتى لو بقيت الفكرة نفسها.
إذا أمكن تنفيذ الميزة ببيانات أو Event، لا تحولها إلى Mixin لمجرد أنك تعرف طريقة الحقن. التدخل الأقل هو غالبًا الأسهل في الدعم.
مراجع موثوقة
راجِع هذه الصفحات الرسمية عند اختلاف الإصدار أو تغيّر أسماء الملفات؛ توثيق اللعبة والمنصة يتغير مع الزمن.