-
Notifications
You must be signed in to change notification settings - Fork 14
A Random Dungeon
So you want to create a random dungeon. This will involve spawning rooms, treasures, monsters, etc., and it will be useful to read creating objects on the fly first. That said, somethings are best not cloned. You just need a single instance of all the stuff the player cannot pick up and tell Quest to place it in the right rooms (whether the room itself is a clone or not).
One of the major problems with creating random rooms is having the items in the room match the description. If the description says there are magical glyphs on the walls, we want the player to be able to examine them. There is therefore more to this than just creating random descriptions; we need to generate both items and descriptions together.
As this is a tutorial, we will do this in a fancy way so you can see a useful technique. The reason to do this is that we are likely to have a lot of these items, and they are all pretty similar. So we set up lists of them that defines the differences:
prototypeItems = [
{ alias:'bone', type:'dungeon', t:'Just a bone, probably {random:human:orc:goblin:elf}. Junk.', tpNow:true},
{ alias:'rusty sword', type:'dungeon', template:WEAPON('d4'), t:'An old short sweord; not ay use as a weapon now.', desc:'A rusty sword lies in the corner.'},
{ alias:'ring', type:'dungeon', price:'d10 + 10', t:'Just a cheap ring, worth maybe {money:item:price}.'},
]
Now we need to go through the list and create each item. This is where all the fancy stuff is done, so this will get complicated. That said, you can just copy-and-paste much of it.
To keep stuff together, I am putting it all inside a dictionary called "dungeon". So far it just has three things in it, two functions and array.
The array, "clutter", will be important later - we will be filling it with objects that can be randomly added to rooms, and to facilitate that objects will be given an "apply" function that will add them (or a clone) to that room.
The "createDungeonItem" function creates a single item, based on values in the given dictionary (which is not the same as the normal dictionary used when creating an item). The "createDungeonStuff" function iterates through three lists, and uses "createDungeonItem" to create each item, having first added a few extra values to the data, depending on the type of item.
const dungeon = {
clutter:[],
createDungeonItem:function(data, addToClutter) {
let name = "r_" + verbify(data.alias)
if (data.type) name += '_' + data.type
if (data.flag) name += '_' + data.flag
name = util.findUniqueName(name)
const template = data.template ? data.template : TAKEABLE()
const merch_template = data.price ? MERCH(data.price) : {}
const item = createItem(name, template, merch_template, {
scenery:data.scenery,
flag:data.flag,
room_type:data.type,
room_flag:data.flag,
func:data.f,
alias:data.alias,
apply:data.apply,
desc:data.desc,
synonyms:data.synonyms,
text:data.t,
tpNow:data.tpNow,
examine:function(options) {
let s = this.text
if (currentLocation && currentLocation[this.alias + 'Addendum']) s += ' ' + currentLocation[this.alias + 'Addendum']
msg(s, options)
}
})
if (data.isLocatedAt) item.isLocatedAt = data.isLocatedAt
if (data.data) {
for (const key in data.data) item[key] = data.data[key]
}
if (data.apply) dungeon.clutter.push(item)
},
createDungeonStuff:function() {
for (const data of dungeon.backdropItems) {
data.template = {}
data.scenery = true
data.isLocatedAt = function(loc, situation) { return w[loc].room_type === this.room_type }
const item = dungeon.createDungeonItem(data)
}
for (const data of dungeon.roomDressingItems) {
data.template = {}
data.scenery = true
data.isLocatedAt = function(loc, situation) { return w[loc]['r_' + this.room_flag] }
data.apply = function(room) {
room['r_' + this.flag] = true
if (this.func) this.func(room)
if (this.desc) room.desc += ' ' + this.desc
}
const item = dungeon.createDungeonItem(data)
}
for (const data of dungeon.prototypeItems) {
data.template = data.template ? data.template : TAKEABLE()
data.scenery = data.desc ? true : false
data.apply = function(room) {
const obj = cloneObject(this, room.name)
if (typeof obj.price === 'string') obj.price = random.dice(obj.price)
if (this.desc) room.desc += '{if:' + obj.name + ':scenery: ' + this.desc + '}'
if (this.tpNow) {
obj.text = processText(obj.text)
}
}
const item = dungeon.createDungeonItem(data)
}
},
}
So now we need three lists of items.
The first list is for items that are everywhere, but cannot be picked up - walls, floors, etc. These are assumed to vary, depending on the nature of the room, and this is defined by the "room_type" attribute of the location. The examples below will only be present if the "room_type" is set to "dungeon"; you might want to have different entries with "room_type" set to "ice cave", "crypt", etc.
/*
alias The alias, as normal. The name will be generated from this
type The room type this item is found in
t The description to use for EXAMINE
data Optional. A dictionary of other values to be added to the item
*/
dungeon.backdropItems = [
{ alias:'walls', type:'dungeon', t:'The walls are stone blocks, roughly cut.'},
{ alias:'floor', type:'dungeon', t:'The floor is hard-packed earth.', synonyms:['ground']},
{ alias:'ceiling', type:'dungeon', t:'The walls arch up to form a vaulted ceiling above you.'},
]
The second list is for scenery - items that are present in occasional rooms, but cannot be picked up. These need to be mentioned in the room description, and you can do that either by giving a "desc" attribute - this will simply be appended - or an "f" function.
The function will be passed the room, and you can do what you want. See the glyphs example; the description for the walls will have a note added. You an do this for any item from the first list, just use the alias with "Addendum" appended as the room attribute name.
/*
alias The alias, as normal. The name will be generated from this
flag Any room with this flag (prepended with "r_" will have this item present
type Optional. The room type this item might be found in
t The description to use for EXAMINE
f Optional. If present, when the apply function is used, this will be called, and passed the room object.
desc Optional. If present, this will be added to the room description.
data Optional. A dictionary of other values to be added to the item
*/
dungeon.roomDressingItems = [
{
alias:'slime', flag:'slime',
t:'The slime is clear, but tinted green.',
desc:'There is slime running down the walls.',
},
{
alias:'glyphs', flag:'glyphs',
t:'The glyphs are written in blood....',
f:function(room) {
room.desc += ' There are arcane glyphs written on the walls.'
room.wallsAddendum = ' There are arcane glyphs written on them.'
},
data:{read:'You try to read the glyphs, but it just causes you to feel dizzy. Perhaps they are not meant for mortal minds...'},
},
]
The third list is items that can be picked up.
/*
alias The alias, as normal. The name will be generated from this
type The room type this item might be found in
t The description to use for EXAMINE
tpNow Optional. If true, the text processor will be run when this item is created. This can be useful for giving variety to clones.
desc Optional. If present, this item will be scenery and this text will be added to the room description.
It will be inserted into a text processor directive that will handle when the text should be seen.
data Optional. A dictionary of other values to be added to the item
price Optional. The value of the item. Note that this should be a string, and you can use stand RPG dice conventions to add variety (eg "2d6+5")
*/
dungeon.prototypeItems = [
{ alias:'bone', type:'dungeon', t:'Just a bone, probably {random:human:orc:goblin:elf}. Junk.', tpNow:true},
{ alias:'rusty sword', type:'dungeon', template:WEAPON('d4'), t:'An old short sword; not much use as a weapon now.', desc:'A rusty sword lies in the corner.'},
{ alias:'ring', type:'dungeon', price:'d10 + 10', t:'Just a cheap ring, worth maybe {money:item:price}.'},
]
We just need to call "createDungeonStuff" to get the lists converted to actual items.
dungeon.createDungeonStuff()
Now we need to see if it works. This code needs to go into settings.setup()
(or better, a function called by it), as Quest will throw an error if you try to clone something before that.
It adds an exit to the north of a room called "practice_room", and then generates five rooms heading north.
Each room is given something to test the features we just added. This is done by taking an item from the clutter array, and calling its "apply" function.
const number_of_clone_rooms = 5
w.practice_room.north = new Exit('clone_room1', {dir:'north', origin:w.practice_room})
endTurnUI(true)
for (let i = 1; i <= number_of_clone_rooms; i++) {
const clone = cloneObject(w.room_prototype, undefined, 'clone_room' + i)
if (i === 0) {
clone.exit_south = 'practice_room'
}
else {
clone.exit_south = 'clone_room' + (i - 1)
}
clone.exit_msg_south = 'Back to the practice room, you trot.'
if (i < number_of_clone_rooms) {
clone.exit_north = 'clone_room' + (i + 1)
clone.exit_msg_north = 'Further and further from the practice room, you venture.'
}
else {
clone.exit_north = '_'
clone.exit_func_north = 'paintedOnDoor'
}
clone.alias = dungeon.dungeonAdjectives[i] + ' ' + dungeon.dungeonNouns[i]
clone.headingAlias = (clone.alias.match(/^[aeiou]/) ? 'An ' : 'A ') + clone.alias
clone.desc = 'A dungeon room.')
clone.room_type = 'dungeon'
if (i <= dungeon.clutter.length) dungeon.clutter[i - 1].apply(clone)
}
Tutorial
- First steps
- Rooms and Exits
- Items
- Templates
- Items and rooms again
- More items
- Locks
- Commands
- Complex mechanisms
- Uploading
QuestJS Basics
- General
- Settings
- Attributes for items
- Attributes for rooms
- Attributes for exits
- Naming Items and Rooms
- Restrictions, Messages and Reactions
- Creating objects on the fly
- String Functions
- Random Functions
- Array/List Functions
- The
respond
function - Other Functions
The Text Processor
Commands
- Introduction
- Basic commands (from the tutorial)
- Complex commands
- Example of creating a command (implementing SHOOT GUN AT HENRY)
- More on commands
- Shortcut for commands
- Modifying existing commands
- Custom parser types
- Note on command results
- Meta-Commands
- Neutral language (including alternatives to "you")
- The parser
- Command matching
Templates for Items
- Introduction
- Takeable
- Openable
- Container and surface
- Locks and keys
- Wearable
- Furniture
- Button and Switch
- Readable
- Edible
- Vessel (handling liquids)
- Components
- Countable
- Consultable
- Rope
- Backscene (walls, etc.)
- Merchandise (including how to create a shop)
- Shiftable (can be pushed from one room to another)
See also:
- Custom templates (and alternatives)
Handing NPCs
- Introduction
- Attributes
- Allowing the player to give commands
- Conversations
- Simple TALK TO
- SAY
- ASK and TELL
- Dynamic conversations with TALK TO
- Following an agenda
- Reactions
- Giving
- Followers
- Changing the player point-of-view
The User Experience (UI)
The main screen
- Basics
- Printing Text Functions
- Special Text Effects
- Output effects (including pausing)
- Hyperlinks
- User Input
The Side Panes
Multi-media (sounds, images, maps, etc.)
- Images
- Sounds
- Youtube Video (Contribution by KV)
- Adding a map
- Node-based maps
- Image-based maps
- Hex maps
- Adding a playing board
- Roulette!... in a grid
Dialogue boxes
- Character Creation
- Other example dialogs [See also "User Input"]
Other Elements
- Toolbar (status bar across the top)
- Custom UI Elements
Role-playing Games
- Introduction
- Getting started
- Items
- Characters (and Monsters!)
- Attributes for characters
- Attacking and guarding
- Skills and Spells
- Limiting Magic
- Effects
- The Attack Object
- Quests for Quest
- User Interface
Web Basics
- HTML (the basic elements of a web page)
- CSS (how to style web pages)
- SVG (scalable vector graphics)
- Colours
- JavaScript
- Regular Expressions
How-to
Time
- Events (and Turnscripts)
- Date and Time (including custom calendars)
- Timed Events (i.e., real time, not game time)
Items
- Phone a Friend
- Using the USE verb
- Display Verbs
- Change Listeners
- Ensembles (grouping items)
Locations
- Large, open areas
- Region,s with sky, walls, etc.
- Dynamic Room Descriptions
- Transit system (lifts/elevators, buses, trains, simple vehicles)
- Rooms split into multiple locations
Exits
- Alternative Directions (eg, port and starboard)
- Destinations, Not Directions
Meta
- Customise Help
- Provide hints
- Include Achievements
- Add comments to your code
-
End The Game (
io.finish
)
Meta: About The Whole Game
- Translate from Quest 5
- Authoring Several Games at Once
- Chaining Several Games Together
- Competition Entry
- Walk-throughs
- Unit testing
- Debugging (trouble-shooting)
Releasing Your Game
Reference
- The Language File
- List of settings
- Scope
- The Output Queue
- Security
- Implementation notes (initialisation order, data structures)
- Files
- Code guidelines
- Save/load
- UNDO
- The editor
- The Cloak of Darkness
- Versions
- Quest 6 or QuestJS
- The other Folders
- Choose your own adventure