Skip to content

Commit d75bbb0

Browse files
authored
Merge pull request #33 from oli414/barrels
Add mine theme decorations and build process.
2 parents 055e522 + 268a914 commit d75bbb0

30 files changed

+366
-7
lines changed

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: CI
2+
on: [push, pull_request]
3+
jobs:
4+
build:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v2
8+
- name: Download gxc
9+
run: |
10+
curl -Lo tools.tar.gz https://github.com/IntelOrca/libsawyer/releases/download/v1.1.0/libsawyer-tools-musl-x64.tar.gz
11+
tar -C /usr/bin -xf tools.tar.gz
12+
- name: Create graphics asset pack
13+
run: node build.mjs --verbose
14+
- name: Upload artifacts
15+
uses: actions/upload-artifact@v2
16+
with:
17+
name: "OpenRCT2 Open Graphics Asset Pack"
18+
path: out/**/*

.gitignore

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
temp/
2+
out/
13
*.parkobj
2-
3-
#Hidden folders/files
4-
.*/
5-
.*
6-
!.gitignore
4+
*.png
5+
/rct2/**/sprites/
6+
sprites.json
7+
/rct2/**/object.json
78

89
# Blender Autosave files
910
*.blend1
1011

11-
1212
#OS special files
1313
*~
1414
.fuse_hidden*
@@ -41,4 +41,4 @@ Icon
4141
.AppleDesktop
4242
Network Trash Folder
4343
Temporary Items
44-
.apdisk
44+
.apdisk

build.mjs

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#!/usr/bin/env node
2+
import fs from 'fs';
3+
import path from 'path';
4+
import { spawn } from 'child_process';
5+
import { platform } from 'os';
6+
7+
const verbose = process.argv.indexOf('--verbose') != -1;
8+
9+
async function main() {
10+
const objects = [];
11+
for (const dir of ['rct2', 'openrct2']) {
12+
const moreObjects = await getObjects(dir);
13+
objects.push(...moreObjects);
14+
}
15+
16+
// Process all objects with gx files first before those with .png files
17+
objects.sort((a, b) => {
18+
if (typeof a.images === typeof b.images) return 0;
19+
if (typeof a.images === 'string') return -1;
20+
return 1;
21+
});
22+
23+
await mkdir('out');
24+
await mkdir('temp');
25+
26+
const manifest = await readJsonFile('manifest.json');
27+
manifest.objects = [];
28+
const singleSpriteManifest = [];
29+
const gxMergeList = [];
30+
let imageIndex = 0;
31+
for (const obj of objects) {
32+
console.log(`Processing ${obj.id}...`);
33+
34+
let numImages;
35+
const images = obj.images;
36+
37+
if (typeof images === 'string') {
38+
const result = images.match(/^\$LGX:(.+)\[([0-9]+)\.\.([0-9]+)\]$/);
39+
if (result) {
40+
const fileName = result[1];
41+
const index = parseInt(result[2]);
42+
const length = parseInt(result[3]);
43+
gxMergeList.push(path.join(obj.cwd, fileName));
44+
numImages = length - index + 1;
45+
} else {
46+
const result = images.match(/^\$LGX:(.+)$/);
47+
if (result) {
48+
const fileName = result[1];
49+
const fullPath = path.join(obj.cwd, fileName);
50+
gxMergeList.push(fullPath);
51+
numImages = await getGxImageCount('out', fullPath);
52+
} else {
53+
throw new Error(`Unsupported image format: ${images}`);
54+
}
55+
}
56+
} else {
57+
for (const image of images) {
58+
image.path = path.join(obj.cwd, image.path);
59+
}
60+
singleSpriteManifest.push(...images);
61+
numImages = images.length;
62+
}
63+
64+
obj.images = `$LGX:images.dat[${imageIndex}..${imageIndex + numImages - 1}]`;
65+
obj.cwd = undefined;
66+
manifest.objects.push(obj);
67+
imageIndex += numImages;
68+
}
69+
gxMergeList.push('images.dat');
70+
71+
console.log(`Building asset pack...`);
72+
await writeJsonFile('temp/manifest.json', manifest);
73+
await writeJsonFile('temp/images.json', singleSpriteManifest);
74+
await compileGx('temp', 'images.json', 'images.dat');
75+
if (gxMergeList.length >= 2) {
76+
await mergeGx('temp', gxMergeList, 'images.dat');
77+
}
78+
79+
const outFilename = `${manifest.id}.parkap`
80+
const outPath = path.join('../out/', outFilename);
81+
await zip('temp', outPath, ['manifest.json', 'images.dat']);
82+
rm('temp');
83+
console.log(`${outFilename} created successfully`);
84+
}
85+
86+
async function getObjects(dir) {
87+
const result = [];
88+
const files = await getAllFiles(dir);
89+
for (const file of files) {
90+
const jsonRegex = /^.+\..+\.json$/;
91+
if (jsonRegex.test(file)) {
92+
const cwd = path.join('..', path.dirname(file));
93+
const obj = await readJsonFile(file);
94+
obj.cwd = cwd;
95+
result.push(obj);
96+
}
97+
}
98+
return result;
99+
}
100+
101+
function compileGx(cwd, manifest, outputFile) {
102+
return startProcess('gxc', ['build', outputFile, manifest], cwd);
103+
}
104+
105+
async function mergeGx(cwd, inputFiles, outputFile) {
106+
await startProcess('gxc', ['merge', outputFile, inputFiles[inputFiles.length - 2], inputFiles[inputFiles.length - 1]], cwd);
107+
for (let i = inputFiles.length - 3; i >= 0; i--) {
108+
await startProcess('gxc', ['merge', outputFile, inputFiles[i], outputFile], cwd);
109+
}
110+
}
111+
112+
async function getGxImageCount(cwd, inputFile) {
113+
const stdout = await startProcess('gxc', ['details', inputFile], cwd);
114+
const result = stdout.match(/numEntries: ([0-9]+)/);
115+
if (result) {
116+
return parseInt(result[1]);
117+
} else {
118+
throw new Error(`Unable to get number of images for gx file: ${inputFile}`);
119+
}
120+
}
121+
122+
function readJsonFile(path) {
123+
return new Promise((resolve, reject) => {
124+
fs.readFile(path, 'utf8', (err, data) => {
125+
if (err) {
126+
reject(err);
127+
} else {
128+
resolve(JSON.parse(data));
129+
}
130+
});
131+
});
132+
}
133+
134+
function writeJsonFile(path, data) {
135+
return new Promise((resolve, reject) => {
136+
const json = JSON.stringify(data, null, 4) + '\n';
137+
fs.writeFile(path, json, 'utf8', err => {
138+
if (err) {
139+
reject(err);
140+
} else {
141+
resolve();
142+
}
143+
});
144+
});
145+
}
146+
147+
function zip(cwd, outputFile, paths) {
148+
if (platform() == 'win32') {
149+
return startProcess('7z', ['a', '-tzip', outputFile, ...paths], cwd);
150+
} else {
151+
return startProcess('zip', [outputFile, ...paths], cwd);
152+
}
153+
}
154+
155+
function startProcess(name, args, cwd) {
156+
return new Promise((resolve, reject) => {
157+
const options = {};
158+
if (cwd) options.cwd = cwd;
159+
if (verbose) {
160+
console.log(`Launching \"${name} ${args.join(' ')}\"`);
161+
}
162+
const child = spawn(name, args, options);
163+
let stdout = '';
164+
child.stdout.on('data', data => {
165+
stdout += data;
166+
});
167+
child.stderr.on('data', data => {
168+
stdout += data;
169+
});
170+
child.on('error', err => {
171+
if (err.code == 'ENOENT') {
172+
reject(new Error(`${name} was not found`));
173+
} else {
174+
reject(err);
175+
}
176+
});
177+
child.on('close', code => {
178+
if (code !== 0) {
179+
reject(new Error(`${name} failed:\n${stdout}`));
180+
} else {
181+
resolve(stdout);
182+
}
183+
});
184+
});
185+
}
186+
187+
function mkdir(path) {
188+
return new Promise((resolve, reject) => {
189+
fs.access(path, (error) => {
190+
if (error) {
191+
fs.mkdir(path, err => {
192+
if (err) {
193+
reject(err);
194+
} else {
195+
resolve();
196+
}
197+
});
198+
} else {
199+
resolve();
200+
}
201+
});
202+
});
203+
}
204+
205+
function getAllFiles(root) {
206+
return new Promise((resolve, reject) => {
207+
const results = [];
208+
let pending = 0;
209+
const find = (root) => {
210+
pending++;
211+
fs.readdir(root, (err, fileNames) => {
212+
// if (err) {
213+
// reject(err);
214+
// }
215+
for (const fileName of fileNames) {
216+
const fullPath = path.join(root, fileName);
217+
pending++;
218+
fs.stat(fullPath, (err, stat) => {
219+
// if (err) {
220+
// reject(err);
221+
// }
222+
if (stat) {
223+
if (stat.isDirectory()) {
224+
find(fullPath);
225+
} else {
226+
results.push(fullPath);
227+
}
228+
}
229+
pending--;
230+
if (pending === 0) {
231+
resolve(results);
232+
}
233+
});
234+
}
235+
pending--;
236+
if (pending === 0) {
237+
resolve(results.sort());
238+
}
239+
});
240+
};
241+
find(root);
242+
});
243+
}
244+
245+
function rm(filename) {
246+
if (verbose) {
247+
console.log(`Deleting ${filename}`)
248+
}
249+
return new Promise((resolve, reject) => {
250+
fs.stat(filename, (err, stat) => {
251+
if (err) {
252+
if (err.code == 'ENOENT') {
253+
resolve();
254+
} else {
255+
reject();
256+
}
257+
} else {
258+
if (stat.isDirectory()) {
259+
fs.rm(filename, { recursive: true }, err => {
260+
if (err) {
261+
reject(err);
262+
}
263+
resolve();
264+
});
265+
} else {
266+
fs.unlink(filename, err => {
267+
if (err) {
268+
reject(err);
269+
}
270+
resolve();
271+
});
272+
}
273+
}
274+
});
275+
});
276+
}
277+
278+
try {
279+
await main();
280+
} catch (err) {
281+
console.log(err.message);
282+
process.exitCode = 1;
283+
}

manifest.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"id": "openrct2.graphics.opengraphics",
3+
"authors": [
4+
"OpenRCT2"
5+
],
6+
"version": "0.1",
7+
"strings": {
8+
"name": {
9+
"en-GB": "Open Graphics"
10+
},
11+
"description": {
12+
"en-GB": "The official OpenRCT2 asset pack that replicates the nostalgic graphical style",
13+
"nl-NL": "Het officiële OpenRCT2 assetpakket dat de nostalgische grafische stijl repliceert"
14+
}
15+
}
16+
}
1.1 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "rct2.scenery_small.tbr1",
3+
"authors": [
4+
"Oli414"
5+
],
6+
"images": "$LGX:output/images.dat"
7+
}
704 KB
Binary file not shown.
1.2 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "rct2.scenery_small.tbr2",
3+
"authors": [
4+
"Oli414"
5+
],
6+
"images": "$LGX:output/images.dat"
7+
}
703 KB
Binary file not shown.

0 commit comments

Comments
 (0)