osrs-tools
Version:
A comprehensive TypeScript library for Old School RuneScape (OSRS) data and utilities, including quest data, skill requirements, and game item information
365 lines (364 loc) • 12.3 kB
JavaScript
/**
* NPC Drop Implementation Utilities
* Helpers for efficiently implementing drops across all NPCs
*
* Usage:
* - Use templates for quick NPC generation
* - Leverage shared drop definitions
* - Validate drops before committing
* - Batch update similar NPCs
*/
import { NpcDrop } from "../NpcDrop";
import { CompleteDropTable, WeightedDropTable } from "../ComplexDropSystems";
/**
* ============================================================================
* SECTION 1: Common Drop Definitions (DRY Principle)
* ============================================================================
*/
/**
* Standard drops used by many NPCs
* Avoids duplication and keeps data consistent
*/
export const STANDARD_DROPS = {
// Bones by type
bones: new NpcDrop("Bones", 1, "Always"),
bigBones: new NpcDrop("Big Bones", 1, "Always"),
babyBones: new NpcDrop("Baby Dragon Bones", 1, "Always"),
// Common currency
coinsSmall: () => new NpcDrop("Coins", [1, 50], "Always"),
coinsMedium: () => new NpcDrop("Coins", [50, 200], "Always"),
coinsLarge: () => new NpcDrop("Coins", [200, 500], "Always"),
// Common tertiary drops
bones_tertiary: new NpcDrop("Bones", 1, "1/32"),
ashesGrimy: new NpcDrop("Ashes", [1, 2], "1/32"),
clueScrollEasy: new NpcDrop("Clue Scroll (Easy)", 1, "1/64"),
clueScrollMedium: new NpcDrop("Clue Scroll (Medium)", 1, "1/128"),
};
/**
* ============================================================================
* SECTION 2: Drop Probability Helpers
* ============================================================================
*/
/**
* Common drop rate constants for easy reference
* Makes probabilities more readable in code
*/
export const DROP_RATES = {
// Always
ALWAYS: "Always",
// Common (player farming areas)
COMMON_1_2: "1/2", // 50%
COMMON_1_4: "1/4", // 25%
COMMON_1_8: "1/8", // 12.5%
// Frequent (Slayer creatures)
FREQUENT_1_16: "1/16", // 6.25%
FREQUENT_1_32: "1/32", // 3.125%
FREQUENT_1_64: "1/64", // 1.56%
// Uncommon (Tertiary drops)
UNCOMMON_1_128: "1/128", // 0.78%
UNCOMMON_1_256: "1/256", // 0.39%
// Rare (Boss unique)
RARE_1_512: "1/512", // 0.195%
RARE_1_1024: "1/1024", // 0.0976%
// Very rare
VERY_RARE_1_5000: "1/5000",
};
/**
* Create a simple NPC drop list from template
*/
export function createSimpleNPCDrops(template) {
const drops = [];
if (template.guaranteed) {
drops.push(...template.guaranteed);
}
if (template.tertiary) {
drops.push(...template.tertiary);
}
if (template.questOnly) {
drops.push(...template.questOnly);
}
return drops;
}
/**
* Create a boss drop table from template
*/
export function createBossNPCDrops(template) {
const table = new CompleteDropTable("Boss");
// Primary roll
if (template.primaryDrops?.length ?? 0 > 0) {
table.addSimpleRoll("Primary", template.primaryDrops);
}
// Secondary roll
if ((template.secondaryDrops?.length ?? 0) > 0) {
table.addSimpleRoll("Secondary", template.secondaryDrops);
}
// Tertiary roll
if ((template.tertiaryDrops?.length ?? 0) > 0) {
table.addSimpleRoll("Tertiary", template.tertiaryDrops);
}
// Rare/Unique drops
if (template.rareDrops && template.rareDrops.length > 0) {
const rareTable = new WeightedDropTable();
for (const rare of template.rareDrops) {
rareTable.addDrop(rare.item, rare.weight);
}
table.addWeightedRoll("Unique", rareTable, template.uniqueRate || "1/512");
}
return table;
}
/**
* ============================================================================
* SECTION 4: NPC Category Builders
* ============================================================================
*/
/**
* Build drops for dragon types
* Dragons share similar drop patterns with variations
*/
export function createDragonDrops(options) {
const drops = [];
// Tier determines bone type and drop quantities
const boneTypes = {
baby: "Baby Dragon Bones",
chromatic: "Dragon Bones",
metallic: "Dragon Bones",
elder: "Elder Dragon Bones",
};
// All dragons drop bones
drops.push(new NpcDrop(boneTypes[options.tier], 1, "Always"));
// Coins
const coinRange = {
baby: [50, 150],
chromatic: [100, 250],
metallic: [150, 400],
elder: [500, 1000],
};
drops.push(new NpcDrop("Coins", coinRange[options.tier], "Always"));
// Tertiary drops
drops.push(new NpcDrop("Dragon Scale", [1, 3], DROP_RATES.FREQUENT_1_32), new NpcDrop("Dragon Tooth", [1, 2], DROP_RATES.UNCOMMON_1_256));
// Unique drops for certain dragons
if (options.hasUniqueDrops) {
drops.push(new NpcDrop("Dragon Dagger", 1, DROP_RATES.RARE_1_512));
}
return drops;
}
/**
* Build drops for demon types
* Most demons have similar drops with rarity variations
*/
export function createDemonDrops(options) {
const drops = [];
// All demons drop bones
drops.push(STANDARD_DROPS.bones);
// Coins based on tier
const coinAmounts = {
lesser: [0, 50],
greater: [25, 75],
black: [50, 150],
abyssal: [100, 250],
};
drops.push(new NpcDrop("Coins", coinAmounts[options.tier], "Always"));
// Tertiary drops - all demons share these
drops.push(new NpcDrop("Ashes", [1, 2], DROP_RATES.FREQUENT_1_32), new NpcDrop("Clue Scroll (Medium)", 1, DROP_RATES.UNCOMMON_1_128), new NpcDrop("Ancient Shard", 1, DROP_RATES.RARE_1_512));
return drops;
}
/**
* Build drops for various rats
* Rats have consistent patterns but vary in drop value
*/
export function createRatDrops(options) {
const drops = [];
// Rats drop bones
drops.push(STANDARD_DROPS.bones);
// Size determines coin drops
const coinDrops = {
small: [1, 10],
giant: [20, 50],
behemoth: [50, 100],
};
drops.push(new NpcDrop("Coins", coinDrops[options.size], "Always"));
// All rats can drop cheese and clue scrolls
drops.push(new NpcDrop("Cheese", [0, 1], DROP_RATES.FREQUENT_1_64), new NpcDrop("Clue Scroll (Easy)", 1, DROP_RATES.UNCOMMON_1_128));
// Crypt rats have special drops
if (options.area === "crypt") {
drops.push(new NpcDrop("Ashes", [1, 2], DROP_RATES.FREQUENT_1_32));
}
return drops;
}
/**
* ============================================================================
* SECTION 5: Shared Drop Table Registry
* ============================================================================
*/
/**
* Centralized location for shared drop tables
* Prevents duplication across similar NPCs
*/
export const SharedDropTables = {
// GWD Bosses share rare tables
gwd: {
rareTable: null,
initialize: function () {
if (this.rareTable)
return this.rareTable;
this.rareTable = new WeightedDropTable();
this.rareTable.addDrop(new NpcDrop("Bandos Godsword", 1, "Always"), 1);
this.rareTable.addDrop(new NpcDrop("Other GWD Unique", 1, "Always"), 1);
return this.rareTable;
},
},
// Boss unique table
bossUniqueSmallTable: null,
// Initialize shared tables on first use
initializeAll: function () {
this.gwd.initialize();
// Add more as needed
},
};
/**
* Validate a complete drop table
*/
export function validateNPCDrops(npcName, drops, options) {
const errors = [];
const warnings = [];
// Check for empty drops
if (drops.length === 0) {
warnings.push(`No drops defined for ${npcName}`);
}
// Check for duplicates
if (!options?.allowDuplicates) {
const itemIds = drops.map((d) => d.itemId);
const duplicates = itemIds.filter((id, index) => itemIds.indexOf(id) !== index);
if (duplicates.length > 0) {
errors.push(`Duplicate items found: ${[...new Set(duplicates)].join(", ")}`);
}
}
// Check that at least one guaranteed drop exists for combat NPCs
const hasGuaranteed = drops.some((d) => d.chance.isGuaranteed);
if (!hasGuaranteed && npcName !== "Passive") {
warnings.push(`No guaranteed drops for ${npcName} (combat NPC should have at least one)`);
}
// Basic sanity check on drop rates
for (const drop of drops) {
if (drop.chance.fraction > 1 || drop.chance.fraction < 0) {
errors.push(`Invalid drop rate for ${drop.itemId}: ${drop.chance.asRatioString}`);
}
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* ============================================================================
* SECTION 7: Implementation Helpers
* ============================================================================
*/
/**
* Get all NPCs that should have drops by category
*/
export function getNPCsByCategory(category) {
// This would be populated from actual NPC data
// Just a template for the concept
const categories = {
boss: ["Zulrah", "General Graardor", "K'ril Tsutsaroth", "Zilyana", "Kree'arra", "Kraken", "Cerberus", "Kalphite Queen", "King Black Dragon", "Vorkath"],
combat: ["Demon", "Dragon", "Giant", "Vampire", "Zombie", "Skeleton"],
slayer: ["Aberrant Spectre", "Abyssal Demon", "Blue Dragon", "Cave Kraken"],
animal: ["Chicken", "Duck", "Cow", "Sheep", "Penguin"],
other: ["Any specialized creatures"],
};
return categories[category] || [];
}
/**
* Create implementation checklist for a batch of NPCs
*/
export function createImplementationChecklist(npcs) {
let checklist = "# Implementation Checklist\n\n";
for (const npc of npcs) {
checklist += `- [ ] ${npc}\n`;
checklist += ` - [ ] Research wiki\n`;
checklist += ` - [ ] Define drops\n`;
checklist += ` - [ ] Validate\n`;
checklist += ` - [ ] Tests pass\n`;
}
return checklist;
}
/**
* ============================================================================
* SECTION 8: Quick Start Examples
* ============================================================================
*/
/**
* Example 1: Quick implementation of a simple combat NPC
*/
export const EXAMPLE_SimpleCombat = {
name: "Demon",
drops: createSimpleNPCDrops({
guaranteed: [STANDARD_DROPS.bones, new NpcDrop("Coins", [50, 100], "Always")],
tertiary: [new NpcDrop("Ashes", [1, 2], DROP_RATES.FREQUENT_1_32)],
}),
};
/**
* Example 2: Quick implementation of a dragon type
*/
export const EXAMPLE_Dragon = {
name: "Green Dragon",
drops: createDragonDrops({
tier: "chromatic",
hasUniqueDrops: false,
}),
};
/**
* Example 3: Quick implementation of a boss
*/
export function EXAMPLE_CreateBoss() {
return createBossNPCDrops({
primaryDrops: [new NpcDrop("Boss Scale", [20, 40], "Always"), new NpcDrop("Boss Hide", [10, 20], "Always")],
secondaryDrops: [new NpcDrop("Herb", [2, 5], "Always")],
rareDrops: [
{
item: new NpcDrop("Boss Unique 1", 1, "Always"),
weight: 1,
},
{
item: new NpcDrop("Boss Unique 2", 1, "Always"),
weight: 1,
},
],
uniqueRate: DROP_RATES.RARE_1_512,
});
}
/**
* ============================================================================
* USAGE GUIDE
* ============================================================================
*
* For Simple NPCs:
* ================
* const drops = createSimpleNPCDrops({
* guaranteed: [STANDARD_DROPS.bones, coinDrop],
* tertiary: [clueScroll],
* });
*
* For Dragons:
* ============
* const drops = createDragonDrops({ tier: 'baby', hasUniqueDrops: false });
*
* For Demons:
* ===========
* const drops = createDemonDrops({ tier: 'lesser' });
*
* For Bosses:
* ===========
* const table = createBossNPCDrops({
* primaryDrops: [...],
* rareDrops: [...],
* uniqueRate: DROP_RATES.RARE_1_512,
* });
*
* For Validation:
* ================
* const result = validateNPCDrops('Boss Name', drops);
* if (!result.valid) console.error(result.errors);
*/