rpd:modding_custom_mobs
Differences
This shows you the differences between two versions of the page.
| rpd:modding_custom_mobs [2025/12/25 21:04] – list fix Mikhael | rpd:modding_custom_mobs [2025/12/25 21:25] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Creating Custom Mobs with JSON and Lua ====== | ||
| + | This guide shows how to create completely new creatures in Remixed Dungeon without any Java coding. You'll learn to use JSON configuration and Lua scripting to define new enemies, NPCs, and other creatures. | ||
| + | |||
| + | ===== Mob Basics ===== | ||
| + | |||
| + | Every mob in Remixed Dungeon is defined by: | ||
| + | * A JSON file that describes its stats and properties | ||
| + | * Optional Lua scripts that define special behaviors | ||
| + | * An image file for its sprite | ||
| + | |||
| + | ===== Simple Mob: Custom Enemy ===== | ||
| + | |||
| + | ==== Step 1: Create the Mob JSON ==== | ||
| + | Create '' | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Step 2: Create the Lua Script ==== | ||
| + | Create '' | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | spawn = function(self, | ||
| + | -- Called when the mob is spawned | ||
| + | RPD.glog(" | ||
| + | end, | ||
| + | |||
| + | -- Called when the mob attacks | ||
| + | attackProc = function(self, | ||
| + | -- Calculate and deal damage | ||
| + | local damage = math.random(1, | ||
| + | enemy: | ||
| + | |||
| + | -- 30% chance to apply poison | ||
| + | if math.random() < 0.3 then | ||
| + | RPD.glog(" | ||
| + | RPD.affectBuff(enemy, | ||
| + | end | ||
| + | return damage | ||
| + | end, | ||
| + | |||
| + | die = function(self, | ||
| + | -- Called when the mob dies | ||
| + | -- Create a toxic gas cloud at the mob's position | ||
| + | local pos = self: | ||
| + | -- In practice, you would use game mechanics to create gas clouds | ||
| + | RPD.glog(" | ||
| + | |||
| + | -- Visual effect | ||
| + | RPD.topEffect(pos, | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Step 3: Add the Sprite ==== | ||
| + | Add your custom rat sprite to '' | ||
| + | |||
| + | ===== Complex Mob: Support Creature ===== | ||
| + | |||
| + | ==== JSON Definition ==== | ||
| + | Create '' | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Lua Script ==== | ||
| + | Create '' | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | spawn = function(self, | ||
| + | -- Called when the mob is spawned | ||
| + | RPD.glog(" | ||
| + | end, | ||
| + | |||
| + | act = function(self) | ||
| + | -- Called on each turn | ||
| + | local mobPos = self: | ||
| + | |||
| + | -- Find nearby allies (within 3 tiles) | ||
| + | local level = RPD.Dungeon.level() | ||
| + | local mobs = level: | ||
| + | local allies = {} | ||
| + | |||
| + | for i = 0, mobs: | ||
| + | local ally = mobs:get(i) | ||
| + | if ally ~= self and level: | ||
| + | local hpPercent = ally: | ||
| + | if hpPercent < 1.0 then -- Only consider injured allies | ||
| + | table.insert(allies, | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Find the most wounded ally | ||
| + | local mostWounded = nil | ||
| + | local lowestPercent = 1.0 | ||
| + | |||
| + | for _, entry in ipairs(allies) do | ||
| + | if entry.percent < lowestPercent then | ||
| + | lowestPercent = entry.percent | ||
| + | mostWounded = entry.ally | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Heal the most wounded ally if below 50% health | ||
| + | if mostWounded and lowestPercent < 0.5 then | ||
| + | mostWounded: | ||
| + | RPD.glog(" | ||
| + | RPD.topEffect(mostWounded: | ||
| + | end | ||
| + | |||
| + | -- Spend turn time | ||
| + | self: | ||
| + | end, | ||
| + | |||
| + | die = function(self, | ||
| + | -- Called when the mob dies | ||
| + | RPD.glog(" | ||
| + | RPD.topEffect(self: | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Special Behavior Mob: Mimic ===== | ||
| + | |||
| + | ==== JSON Definition ==== | ||
| + | Create '' | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Lua Script ==== | ||
| + | Create '' | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | spawn = function(self, | ||
| + | -- Called when the mimic appears | ||
| + | -- Mimics start in an inactive state | ||
| + | self.data = self.data or {} | ||
| + | self.data.inactive = true | ||
| + | RPD.glog(" | ||
| + | end, | ||
| + | |||
| + | act = function(self) | ||
| + | -- Called each turn - check for nearby player | ||
| + | local hero = RPD.Dungeon.hero | ||
| + | local distance = RPD.Dungeon.level(): | ||
| + | |||
| + | if self.data.inactive and distance <= 2 then | ||
| + | self.data.inactive = false | ||
| + | RPD.glog(" | ||
| + | -- Make the mob aggressive | ||
| + | self: | ||
| + | end | ||
| + | |||
| + | -- Spend turn time | ||
| + | self: | ||
| + | end, | ||
| + | |||
| + | -- Special attack | ||
| + | attackProc = function(self, | ||
| + | local calculatedDamage = math.random(8, | ||
| + | target: | ||
| + | RPD.glog(" | ||
| + | return calculatedDamage | ||
| + | end, | ||
| + | |||
| + | die = function(self, | ||
| + | -- Called when the mob dies | ||
| + | local pos = self: | ||
| + | -- Drop extra treasure when defeated | ||
| + | local gold = RPD.item(" | ||
| + | RPD.Dungeon.level(): | ||
| + | local healingPotion = RPD.item(" | ||
| + | RPD.Dungeon.level(): | ||
| + | RPD.glog(" | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Boss Example ===== | ||
| + | |||
| + | ==== JSON Definition ==== | ||
| + | Create '' | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Lua Script ==== | ||
| + | Create '' | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | act = function(self) | ||
| + | -- Called on each turn | ||
| + | local hero = RPD.Dungeon.hero | ||
| + | local distance = RPD.Dungeon.level(): | ||
| + | |||
| + | -- Summon a skeleton if there are fewer than 2 nearby | ||
| + | local nearbySkeletons = 0 | ||
| + | local allMobs = RPD.Dungeon.level(): | ||
| + | for i = 0, allMobs: | ||
| + | local other = allMobs: | ||
| + | if other: | ||
| + | | ||
| + | nearbySkeletons = nearbySkeletons + 1 | ||
| + | end | ||
| + | end | ||
| + | |||
| + | if nearbySkeletons < 2 and math.random() < 0.2 then | ||
| + | local mobPos = self: | ||
| + | local summonPos = nil | ||
| + | -- Find an empty adjacent cell | ||
| + | for direction = 0, 7 do | ||
| + | local adjCell = mobPos + RPD.PathFinder.CIRCLE8[direction + 1] | ||
| + | if RPD.Dungeon.level(): | ||
| + | | ||
| + | | ||
| + | summonPos = adjCell | ||
| + | break | ||
| + | end | ||
| + | end | ||
| + | |||
| + | if summonPos then | ||
| + | local skeleton = RPD.spawnMob(" | ||
| + | RPD.glog(" | ||
| + | RPD.topEffect(summonPos, | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Spend turn time | ||
| + | self: | ||
| + | end, | ||
| + | |||
| + | -- Called when attacking | ||
| + | attackProc = function(self, | ||
| + | -- 50% chance for magic missile, 50% for weaken | ||
| + | if math.random() < 0.5 then | ||
| + | local calculatedDamage = math.random(10, | ||
| + | target: | ||
| + | RPD.topEffect(target: | ||
| + | RPD.glog(" | ||
| + | return calculatedDamage | ||
| + | else | ||
| + | -- Weaken the target | ||
| + | RPD.affectBuff(target, | ||
| + | RPD.glog(" | ||
| + | return damage | ||
| + | end | ||
| + | end, | ||
| + | |||
| + | die = function(self, | ||
| + | -- Called when the boss dies | ||
| + | local hero = RPD.Dungeon.hero | ||
| + | RPD.glog(" | ||
| + | |||
| + | -- Apply a challenging debuff to the hero | ||
| + | RPD.affectBuff(hero, | ||
| + | |||
| + | -- Create a special item at the location | ||
| + | local trophy = RPD.item(" | ||
| + | RPD.Dungeon.level(): | ||
| + | |||
| + | RPD.topEffect(self: | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Advanced Mob Techniques ===== | ||
| + | |||
| + | ==== State Machine Mobs ==== | ||
| + | Create mobs with different behavior states: | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | act = function(self) | ||
| + | -- Get or initialize mob's state | ||
| + | self.data = self.data or {} | ||
| + | local state = self.data.state or " | ||
| + | |||
| + | if state == " | ||
| + | -- Patrol behavior | ||
| + | local hero = RPD.Dungeon.hero | ||
| + | if RPD.Dungeon.level(): | ||
| + | self.data.state = " | ||
| + | RPD.glog(self: | ||
| + | end | ||
| + | elseif state == " | ||
| + | -- Chase behavior | ||
| + | local hero = RPD.Dungeon.hero | ||
| + | if RPD.Dungeon.level(): | ||
| + | self.data.state = " | ||
| + | self.data.returnTo = self: | ||
| + | end | ||
| + | elseif state == " | ||
| + | -- Return to spawn point | ||
| + | if RPD.Dungeon.level(): | ||
| + | self.data.state = " | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Spend turn time | ||
| + | self: | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Environmental Interaction ==== | ||
| + | Mobs that interact with dungeon features: | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | act = function(self) | ||
| + | local pos = self: | ||
| + | local level = RPD.Dungeon.level() | ||
| + | |||
| + | -- Check for doors nearby | ||
| + | for dx = -1, 1 do | ||
| + | for dy = -1, 1 do | ||
| + | local checkCell = level: | ||
| + | if checkCell == RPD.Terrain.DOOR then | ||
| + | -- Open the door | ||
| + | level: | ||
| + | RPD.glog(self: | ||
| + | break | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Check if on flammable terrain | ||
| + | if level: | ||
| + | -- Set the grass on fire | ||
| + | level: | ||
| + | -- In practice would use fire mechanics | ||
| + | end | ||
| + | |||
| + | -- Spend turn time | ||
| + | self: | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Mob Groups and Coordinated Behavior ==== | ||
| + | Mobs that work together: | ||
| + | |||
| + | <code lua> | ||
| + | local RPD = require " | ||
| + | local mob = require " | ||
| + | |||
| + | return mob.init{ | ||
| + | spawn = function(self, | ||
| + | -- Find other nearby mobs of the same type to form a group | ||
| + | local nearbyMobs = {} | ||
| + | local allMobs = RPD.Dungeon.level(): | ||
| + | for i = 0, allMobs: | ||
| + | local other = allMobs: | ||
| + | if other ~= self and | ||
| + | | ||
| + | | ||
| + | | ||
| + | table.insert(nearbyMobs, | ||
| + | end | ||
| + | end | ||
| + | |||
| + | local groupId = nil | ||
| + | |||
| + | for _, other in ipairs(nearbyMobs) do | ||
| + | if other.data.groupId then | ||
| + | groupId = other.data.groupId | ||
| + | break | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- If no group found, create a new one | ||
| + | if not groupId then | ||
| + | groupId = math.random(1000000) | ||
| + | end | ||
| + | |||
| + | self.data = self.data or {} | ||
| + | self.data.groupId = groupId | ||
| + | end, | ||
| + | |||
| + | act = function(self) | ||
| + | if self.data.groupId then | ||
| + | local groupMobs = {} | ||
| + | |||
| + | -- Find all group members | ||
| + | local allMobs = RPD.Dungeon.level(): | ||
| + | for i = 0, allMobs: | ||
| + | local other = allMobs: | ||
| + | if other.data and other.data.groupId == self.data.groupId then | ||
| + | table.insert(groupMobs, | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- If this is the leader, make strategy decisions | ||
| + | if self.data.isLeader then | ||
| + | -- Move as a group toward the hero | ||
| + | local heroPos = RPD.Dungeon.hero: | ||
| + | for _, member in ipairs(groupMobs) do | ||
| + | -- Move each member toward the hero | ||
| + | member: | ||
| + | end | ||
| + | else | ||
| + | -- Check if we need a new leader | ||
| + | local hasLeader = false | ||
| + | for _, member in ipairs(groupMobs) do | ||
| + | if member.data and member.data.isLeader then | ||
| + | hasLeader = true | ||
| + | break | ||
| + | end | ||
| + | end | ||
| + | |||
| + | if not hasLeader and #groupMobs > 0 then | ||
| + | -- Make this mob the new leader | ||
| + | self.data.isLeader = true | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Spend turn time | ||
| + | self: | ||
| + | end | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Testing Your Mobs ===== | ||
| + | |||
| + | ==== Common Testing Steps ==== | ||
| + | * Enable your mod in-game | ||
| + | * Start a new game to see new mobs (they' | ||
| + | * Verify that sprites appear correctly | ||
| + | * Test all mob behaviors and Lua scripts | ||
| + | * Check that mob descriptions are properly localized | ||
| + | * Confirm that mob stats are balanced | ||
| + | |||
| + | ==== Bestiary Integration ==== | ||
| + | To make your mobs appear in the dungeon, add them to '' | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | {" | ||
| + | {" | ||
| + | ], | ||
| + | " | ||
| + | {" | ||
| + | {" | ||
| + | ] | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Testing Your Mobs ===== | ||
| + | |||
| + | ==== Common Testing Steps ==== | ||
| + | * Enable your mod in-game | ||
| + | * Start a new game to see new mobs (they' | ||
| + | * Verify that sprites appear correctly | ||
| + | * Test all mob behaviors and Lua scripts | ||
| + | * Check that mob descriptions are properly localized | ||
| + | * Confirm that mob stats are balanced | ||
| + | |||
| + | ==== Bestiary Integration ==== | ||
| + | To make your mobs appear in the dungeon, add them to '' | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | {" | ||
| + | {" | ||
| + | ], | ||
| + | " | ||
| + | {" | ||
| + | {" | ||
| + | ] | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Code References ===== | ||
| + | |||
| + | ==== Core Implementation ==== | ||
| + | - **Java Class**: '' | ||
| + | - **Lua Library**: '' | ||
| + | - **JSON Configuration**: | ||
| + | - **Lua Examples**: '' | ||
| + | |||
| + | ==== Key Methods in CustomMob.java ==== | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | |||
| + | ==== Key Methods in mob.lua ==== | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | |||
| + | ==== JSON Properties Supported ==== | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | |||
| + | Creating custom mobs without Java is quite powerful in Remixed Dungeon. With JSON and Lua, you can create complex behaviors, unique mechanics, and engaging new creatures for players to encounter! | ||
rpd/modding_custom_mobs.txt · Last modified: by 127.0.0.1
