Skip to content

Commit 2586832

Browse files
author
Spencer Carlson
committed
Fix:
A bug where books with long sections wouldn't display all pages. A typo in a mining message Add: A method to find BookData from book ID. Book data for the Construction guide book Book data for the pie recipe book Modify: The book system to load all books from the books directory The book system to be more generic, and much simpler in certain areas. Removed lots of unnecessary duplicate calls. Rename: stronghold-of-security-book.plugin to books.plugin loadStrongholdOfSecurityBook() to loadBookData()
1 parent cfdbfc6 commit 2586832

File tree

6 files changed

+214
-141
lines changed

6 files changed

+214
-141
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
bookId: 8463,
3+
bookTitle: "Guide to Construction",
4+
showTableOfContents: true,
5+
bookSections: [
6+
{
7+
header: "How to build in your house",
8+
text: "In order to build you will need to turn building mode on. This can be done on entering the house or using a button on the options interface. If you have a bank PIN you must enter it when entering building mode.\n\nIn building mode the ghostly shapes of furniture and doorways you have not built yet will appear in your house. These are called hotspots. You can use these to build furniture and new rooms.\n\nTo build a piece of furniture, right-click the hotspot and select Build. You will then be able to select the piece of furniture you want to build from the menu. Below each furniture icon is a list of materials; to build the furniture you will need to have all these materials in your intentory. You will also need to have a hammer, a saw and have the correct Construction level.\n\nNails work slightly differently to other materials. You will sometimes find you break nails, especially if you have a low Construction level, so you may need to bring more nails than the furniture requires. Nails made of stronger metals will break less often.\n\nYou can remove a piece of furniture if you wish to build something else in the same space. To do this, right-click on it and select Remove. You will not get any of the materials back. Some pieces of furniture can be upgraded to better pieces of furniture without having to remove them first.\n\nTo build a new room, you must use one of the door hotspots at the edges of rooms or garden squares. Right-click on it and select Build. This will bring up a list of rooms. Different rooms cost different amounts of gold and have different Construction level requirements.\n\nIf you select Build on a door hotspot that already leads to a room, you will be asked whether you want to delete that room."
9+
},
10+
{
11+
header: "Raw Materials",
12+
text: "The main raw material you will use to make furniture is planks. The sawmill operator north east of Varrock will turn logs into planks for you, for a fee. The useful planks are wood, oak, teak and mahogany. The sawmill operator also sells saws, cloth and nails.\n\nFor higher level furniture you may also need limestone, marble, gold leaf and magic building crystals. These are sold by the stonemason who lives in Keldagrim.\n\nSome pieces of furniture also require materials that are not specific to construction, such as steel bars and soft clay.",
13+
},
14+
{
15+
header: "Room types",
16+
text: "Parlour: This is the lowest-level room and provides space for three people to sit around a fire\nGarden: The garden is largely decorative but it also contains the exit portal.\nKitchen: This room can be used for preparing food. As you build better furniture in it you will find yourself able to prepare better meals in it.\nDining room: Eight people can sit around the tables you build in this room.\nBedroom: Some of the furniture in this room can be used to change your hair and clothes. You will also need to have two of these rooms in order to hire a servant.\nHalls: These are primarily used to connect other rooms, but they also provide space to show off the owner's skill and quest achievements.\nGames room: Various games can be built in this room to allow friends to play and train together.\nCombat Room: With this room you can challenge your friends in a personal duelling ring.\nWorkshop: This room allows you to train Construction without modifying your own house, by making furniture that can be sold to other players. It also provides space for you to train Crafting and Smithing.\nChapel: This room can be dedicated to any of RuneScape's major gods, and the altar can be used to offer bones.\nMenagerie: You can keep your pets in this room.\nStudy: You can use the lectern in this room to create clay tablets recording magic spells. Eagle lecterns make teleport spells and demon lecterns make enchantment spells. The elemental sphere in this room can be used to change the element of an elemental staff.\nPortal chamber: In this room you can build portals to various places around the world.\nFormal garden: The formal garden can contain various plants and ornaments to beautify the grounds of your house.\nThrone Room: This room can be used to hold audiences with large numbers of friends. It also contains the lever that turns on challenge mode.\nOubliette: If you build an oubliette below your throne room you can drop people from there into a cage which you can fill with various horrors.\nDungeon: Dungeon corridors and junctions can be built to create an underground maze full of monsters, traps and doors.\nTreasure room: You can place a prize in this room for visitors to your dungeon to try to reach."
17+
},
18+
{
19+
header: "Servants",
20+
text: "Once you have two bedrooms, you can hire a servant by going to the Servants' Guild in Ardougne. You will have to pay when you hire them, and the servant will then periodically demand wages. Servants can take items to and from the bank for you, and can also greet guests and serve food and drinks."
21+
}
22+
]
23+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
bookId: 7162,
3+
bookTitle: "Pie recipe book",
4+
showTableOfContents: true,
5+
bookSections: [
6+
{
7+
header: "Redberry pie",
8+
text: "Pour a hand full of Redberries into an empty Pie Shell, bake until the berries are soft and serve warm."
9+
},
10+
{
11+
header: "Meat pie",
12+
text: "Line a fresh Pie Shell with Cooked Meat and heat until the pastry starts to bronze, serve with a selection of sauces."
13+
},
14+
{
15+
header: "Mud pie",
16+
text: "Start with a Pie Shell and add a Bucket of Compost, then pour in a Bucket of Water to keep the consistency gooey. To finish, cover with Clay and bake until a good shell forms. Serve at maximum speed a good over-arm throw!"
17+
},
18+
{
19+
header: "Apple pie",
20+
text: "Take a Pie Shell and layer in Apple, cook until the juices start to bubble and leave to cool before serving."
21+
},
22+
{
23+
header: "Garden pie",
24+
text: "Fill a Pie Shell with Tomato, then add Onion and top with Cabbage. Bake golden brown and serve with a steak."
25+
},
26+
{
27+
header: "Fish pie",
28+
text: "Take one Pie Shell and fill with trout, add a Cod for flavour, and then top with Potato for texture. Cook well until the potato turns golden and serve."
29+
},
30+
{
31+
header: "Admiral pie",
32+
text: "For a more upperclass fish pie, fill your Pie Shell with Salmon and then add Tuna for colour. Top with Potato and cook until golden."
33+
},
34+
{
35+
header: "Wild pie",
36+
text: "Line a Pie Shell with raw Bear Meat, then add Raw Chompy for substance, and top with fresh Rabbit Meat. Bake until the juices start to bubble and serve."
37+
},
38+
{
39+
header: "Summer pie",
40+
text: "Into a Pie Shell, put Strawberry, then a layer of Watermelon, and top with Apple. Cook well and leave to cool before serving."
41+
}
42+
]
43+
}

src/game-engine/config/index.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import {
2525
StrongholdOfSecurityQuiz,
2626
StrongholdOfSecurityQuizQuestion
2727
} from '@engine/config/stronghold-of-security-quiz-config';
28-
import { BookData, loadStrongholdOfSecurityBookData } from '@engine/config/sectioned-book-config';
29-
import { LandscapeObject, loadXteaRegionFiles, ObjectConfig, XteaRegion } from '@runejs/filestore';
28+
import { BookData, loadBookData } from '@engine/config/sectioned-book-config';
29+
import { loadXteaRegionFiles, ObjectConfig, XteaRegion } from '@runejs/filestore';
3030

3131
require('json5/lib/register');
3232

@@ -45,7 +45,7 @@ export let shopMap: { [key: string]: Shop };
4545
export let skillGuides: SkillGuide[] = [];
4646
export let xteaRegions: { [key: number]: XteaRegion };
4747
export let strongholdOfSecurityQuizData: StrongholdOfSecurityQuiz;
48-
export let strongholdOfSecurityBookData: BookData;
48+
export let bookData: BookData[];
4949

5050
export const musicRegionMap = new Map<number, number>();
5151
export const widgets: { [key: string]: any } = require('../../../data/config/widgets.json5');
@@ -68,7 +68,7 @@ export async function loadGameConfigurations(): Promise<void> {
6868
npcPresetMap = npcPresets;
6969

7070
strongholdOfSecurityQuizData = await loadStrongholdOfSecurityQuizData(`data/config/stronghold-of-security-quiz.json5`);
71-
strongholdOfSecurityBookData = await loadStrongholdOfSecurityBookData(`data/config/books/security-book.json5`);
71+
bookData = await loadBookData(`data/config/books/`);
7272
npcSpawns = await loadNpcSpawnConfigurations('data/config/npc-spawns/');
7373
musicRegions = await loadMusicRegionConfigurations();
7474
musicRegions.forEach(song => song.regionIds.forEach(region => musicRegionMap.set(region, song.songId)));
@@ -83,7 +83,6 @@ export async function loadGameConfigurations(): Promise<void> {
8383
`${Object.keys(npcMap).length} npcs, ${npcSpawns.length} npc spawns, ${Object.keys(shopMap).length} shops and ${skillGuides.length} skill guides.`);
8484
}
8585

86-
8786
export const findItem = (itemKey: number | string): ItemDetails | null => {
8887
if (!itemKey) {
8988
return null;
@@ -228,3 +227,16 @@ export function getRandomStrongholdOfSecurityQuizQuestion(): StrongholdOfSecurit
228227
const randomIndex = Math.floor(Math.random() * strongholdOfSecurityQuizData.questions.length);
229228
return strongholdOfSecurityQuizData.questions[randomIndex];
230229
}
230+
231+
export function getBookFromId(bookId: number): BookData | null {
232+
const bookExists = bookData.some(book => book.bookContents.bookId === bookId);
233+
if(bookExists) {
234+
for (const book of bookData) {
235+
if(book.bookContents.bookId === bookId) {
236+
return book;
237+
}
238+
}
239+
} else {
240+
return null;
241+
}
242+
}

src/game-engine/config/sectioned-book-config.ts

Lines changed: 53 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { logger } from '@runejs/core';
44
import { filestore } from '@engine/game-server';
55
import { TextWidget } from '@runejs/filestore';
66
import { wrapText } from '@engine/util/strings';
7+
import { loadConfigurationFiles } from '@runejs/core/fs';
8+
import * as fs from 'fs';
79

810

911
export interface BookData {
@@ -36,113 +38,82 @@ export interface BookSections {
3638
export interface BookPage {
3739
header?: string;
3840
lines: string[];
41+
pageNumber: number;
3942
}
4043

44+
/**
45+
* Returns a boolean for whether or not the specified section header exists in the book.
46+
* @param bookContents The book to find the header in.
47+
* @param bookSectionHeader The header to search for.
48+
*/
4149
export function bookSectionHeaderExists(bookContents: BookContents, bookSectionHeader: string): boolean {
42-
for (const bookSection of bookContents.bookSections) {
43-
if (bookSection.header === bookSectionHeader) {
44-
return true;
45-
}
46-
}
47-
return false;
50+
return bookContents.bookSections.some(section => section.header === bookSectionHeader);
4851
}
4952

50-
function getPageDataFromPageNumber(bookContents: BookContents, page: number): BookPage {
53+
function getBookDataForBookContents(bookContents: BookContents): BookData {
5154
const textWidget = filestore.widgetStore.decodeWidget(215) as TextWidget;
5255

5356
const output = [];
54-
if (page === 1 && bookContents.showTableOfContents) {
57+
let pageNumber = 1;
58+
59+
const bookPages: { [key: number]: BookPage } = {};
60+
if (bookContents.showTableOfContents) {
5561
output.push(``);
5662
bookContents.bookSections.forEach(section => output.push(section.header));
57-
return { header: `Chapters`, lines: output };
63+
bookPages[pageNumber] = { header: `Chapters`, lines: output, pageNumber: pageNumber };
64+
pageNumber++;
5865
}
5966

60-
const outputPages: BookPage[] = [];
67+
const sectionLocations: { [key: string]: number } = {};
68+
6169
bookContents.bookSections.forEach(section => {
6270
const wrappedText = wrapText(section.text, 202, textWidget.fontId);
63-
let pageLineAmount = 14;
64-
65-
for (let line = 0; line < wrappedText.length; line += pageLineAmount) {
66-
if ((pageLineAmount + line) > (wrappedText.length - line)) {
67-
pageLineAmount = wrappedText.length - line;
68-
}
69-
if (line === 0) {
70-
outputPages.push({ header: section.header, lines: wrappedText.slice(line, line + pageLineAmount) });
71-
} else {
72-
outputPages.push({ lines: wrappedText.slice(line, line + pageLineAmount) });
71+
const pageLineAmount = 14;
72+
73+
let pageContainsHeader = true;
74+
while (wrappedText.length) {
75+
const pageLines = wrappedText.splice(0, pageLineAmount);
76+
bookPages[pageNumber] = {
77+
header: (pageContainsHeader ? section.header : undefined),
78+
lines: pageLines,
79+
pageNumber: pageNumber
80+
};
81+
if (pageContainsHeader) {
82+
sectionLocations[section.header] = pageNumber;
7383
}
84+
pageContainsHeader = false;
85+
pageNumber++;
7486
}
7587
});
7688

77-
if(outputPages[page - 2] === undefined) {
78-
return undefined;
79-
}
80-
return outputPages[page - 2];
89+
logger.info(`Book: ` + bookContents.bookTitle + ` has ` + Object.keys(sectionLocations).length + ` sections.`)
90+
return { sectionLocations: sectionLocations, bookPages: bookPages, bookContents: bookContents };
8191
}
8292

83-
export const pageExists = (book: BookContents, page: number): boolean => {
84-
return book.bookSections[page - 1] !== undefined;
93+
/**
94+
* An enum that represents either the left, or the right page in a book.
95+
*/
96+
export enum PageSide {
97+
LEFT_SIDE = 'LEFT',
98+
RIGHT_SIDE = 'RIGHT'
8599
}
86-
function getBookPagesFromBookContents(bookContents: BookContents): { [key: number]: BookPage } {
87-
const bookPages: { [key: number]: BookPage } = {};
88100

89-
let pageNumber = 1;
90-
let bookPage = getPageDataFromPageNumber(bookContents, pageNumber);
91-
while (bookPage !== undefined) {
92-
bookPages[pageNumber] = bookPage;
93-
pageNumber++;
94-
bookPage = getPageDataFromPageNumber(bookContents, pageNumber);
95-
}
96-
return bookPages;
101+
export const pageExists = (book: BookData, page: number): boolean => {
102+
return (book.bookPages[page] !== undefined);
97103
}
98104

105+
export function loadBookData(path: string): BookData[] | null {
106+
const books: BookData[] = [];
99107

100-
function getSectionLocationsFromBookContents(bookContents: BookContents): { [key: string]: number } {
101-
const sectionLocations: { [key: string]: number } = {};
102-
// const bookPages: { [key: number]: BookPage } = {};
103-
let pageNumber = 1;
104-
let leftPageData = getPageDataFromPageNumber(bookContents, (2 * pageNumber) - 1);
105-
let rightPageData = getPageDataFromPageNumber(bookContents, (2 * pageNumber));
106-
107-
while (leftPageData.lines !== undefined && rightPageData.lines !== undefined) {
108-
109-
const leftBookPage = (2 * pageNumber) - 1;
110-
const rightBookPage = (2 * pageNumber) - 1;
111-
leftPageData = getPageDataFromPageNumber(bookContents, leftBookPage);
112-
rightPageData = getPageDataFromPageNumber(bookContents, rightBookPage);
113-
114-
if(leftPageData === undefined) {
115-
return;
116-
}
117-
if(rightPageData === undefined) {
118-
return;
119-
}
120-
if (leftPageData.header) {
121-
sectionLocations[leftPageData.header] = leftBookPage;
122-
}
123-
if (rightPageData.header) {
124-
sectionLocations[rightPageData.header] = rightBookPage;
125-
}
126-
pageNumber += 1;
127-
128-
129-
}
130-
return sectionLocations;
108+
fs.readdir(path, function(error, filenames) {
109+
filenames.forEach(function(filename) {
110+
const bookContents = safeLoad(readFileSync(path + filename, 'utf8'),
111+
{ schema: JSON_SCHEMA }) as BookContents;
112+
const bookData = getBookDataForBookContents(bookContents);
113+
books.push(bookData);
114+
});
115+
});
116+
return books;
131117
}
132118

133-
export function loadStrongholdOfSecurityBookData(path: string): BookData | null {
134-
try {
135-
const book = safeLoad(readFileSync(path, 'utf8'),
136-
{ schema: JSON_SCHEMA }) as BookContents;
137119

138-
if (!book) {
139-
throw new Error('Unable to read book data!');
140-
}
141-
142-
const sectionLocations = getSectionLocationsFromBookContents(book);
143-
const bookPages = getBookPagesFromBookContents(book);
144-
return { bookPages: bookPages, bookContents: book, sectionLocations: sectionLocations };
145-
} catch (error) {
146-
logger.error('Error parsing book data: ' + error);
147-
}
148-
}

src/game-engine/world/skill-util/harvest-skill.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
1616
if (!target) {
1717
switch (skill) {
1818
case Skill.MINING:
19-
player.sendMessage('There is current no ore available in this rock.');
19+
player.sendMessage('There is currently no ore available in this rock.');
2020
break;
2121
default:
2222
player.sendMessage(colorText('HARVEST SKILL ERROR, PLEASE CONTACT DEVELOPERS', colors.red));

0 commit comments

Comments
 (0)