Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/config.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type AppConfig = {
dpeFolder: string;
cfruFolder: string;
assetsFolder: string;
};
1 change: 1 addition & 0 deletions src/common/convert-to-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function populateCoords(
return {
...coords,
species,
size: 0,
};
}
return undefined;
Expand Down
2 changes: 2 additions & 0 deletions src/common/ipc.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export type IPCConfigs = {
'set-dpe-location': () => string;
'locate-cfru': () => void;
'set-cfru-location': () => string;
'locate-assets': () => void;
'set-assets-location': () => string;
'load-files': () => void;
'pokemon-source-data': (data: AllPokemonData) => AllPokemonData;
'data-saved': () => boolean;
Expand Down
22 changes: 21 additions & 1 deletion src/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint global-require: off, no-console: off, promise/always-return: off */
import 'core-js/stable';
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron';
import { app, BrowserWindow, dialog, ipcMain, protocol, shell } from 'electron';
import log from 'electron-log';
import ElectronStore from 'electron-store';
import { autoUpdater } from 'electron-updater';
Expand Down Expand Up @@ -146,6 +146,20 @@ ipcMain.on('locate-cfru', async (event) => {
}
});

ipcMain.on('locate-assets', async (event) => {
const result = dialog.showOpenDialogSync(mainWindow!, {
properties: ['openDirectory'],
message: 'Select Assets Location',
title: 'Select Assets Folder',
defaultPath: store.get('assetsFolder'),
});
if (result && result.length > 0) {
store.set('assetsFolder', result[0]);
event.reply('set-assets-location', result[0]);
await loadFiles();
}
});

if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
Expand Down Expand Up @@ -184,6 +198,12 @@ const createWindow = async () => {
return path.join(RESOURCES_PATH, ...paths);
};

protocol.registerFileProtocol('asset', (request, callback) => {
const relativePath = request.url.replace('asset://', '');
const absolutePath = path.join(store.get('assetsFolder'), relativePath);
callback(absolutePath);
});

mainWindow = new BrowserWindow({
show: false,
width: 1024,
Expand Down
22 changes: 22 additions & 0 deletions src/renderer/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ const folderInputStyle: SxProps<Theme> = {
export const SettingsDialog = ({ open, onClose }: SettingsDialogProps) => {
const [dpeFolder, setDPEFolder] = React.useState(Config.get('dpeFolder'));
const [cfruFolder, setCFRUFolder] = React.useState(Config.get('cfruFolder'));
const [assetsFolder, setAssetsFolder] = React.useState(
Config.get('assetsFolder')
);

const handleDPESelect = () => {
window.electron.ipcRenderer.send('locate-dpe');
};
const handleCFRUSelect = () => {
window.electron.ipcRenderer.send('locate-cfru');
};
const handleAssetsSelect = () => {
window.electron.ipcRenderer.send('locate-assets');
};

useEffect(() => {
const clearDpeListener = window.electron.ipcRenderer.on(
Expand All @@ -54,9 +60,15 @@ export const SettingsDialog = ({ open, onClose }: SettingsDialogProps) => {
'set-cfru-location',
setCFRUFolder
);
const clearAssetsListener = window.electron.ipcRenderer.on(
'set-assets-location',
setAssetsFolder
);

return () => {
clearDpeListener();
clearCfruListener();
clearAssetsListener();
};
}, [setDPEFolder, setCFRUFolder]);

Expand Down Expand Up @@ -101,6 +113,16 @@ export const SettingsDialog = ({ open, onClose }: SettingsDialogProps) => {
/>
<Button onClick={handleCFRUSelect}>Select</Button>
</FormControl>

<FormControl variant="outlined" sx={folderInputStyle}>
<TextField
disabled
label="Assets Folder"
value={assetsFolder}
sx={{ flexGrow: 1 }}
/>
<Button onClick={handleAssetsSelect}>Select</Button>
</FormControl>
</Box>
</Box>
</Modal>
Expand Down
Binary file added src/renderer/images/battle_bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/renderer/images/battle_bg_shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions src/renderer/pokemon-editor/tabs/graphics/BattlePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { observer } from 'mobx-react-lite';
import { useEffect, useRef } from 'react';
import BgImage from '../../../images/battle_bg.png';
import BgImageWithShadow from '../../../images/battle_bg_shadow.png';
import { usePokemonStoreContext } from '../../pokemon.store';

export const BattlePreview = observer(() => {
const pokemonStore = usePokemonStoreContext();
const species = pokemonStore.selectedSpecies;
const battleScreenCanvas = useRef<HTMLCanvasElement>(null);

if (species) {
useEffect(() => {
function getSprite() {
return `asset://${species!.name.toLowerCase()}/sprites.png`;
}

function getCroppedSpriteCanvas(
xOffset: number,
callback: (data: HTMLCanvasElement) => void
) {
const spritesheet = new Image();
spritesheet.src = getSprite();
spritesheet.addEventListener('load', () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
canvas.width = spritesheet.width;
canvas.height = spritesheet.height;
context.drawImage(spritesheet, xOffset, 0, 64, 64, 0, 0, 64, 64);
const imageData = context.getImageData(0, 0, 64, 64);
const bgRed = imageData.data[0];
const bgGreen = imageData.data[1];
const bgBlue = imageData.data[2];
const bgAlpha = imageData.data[3];
for (let i = 0; i < imageData.data.length; i += 4) {
// is this pixel tbg colored
if (
imageData.data[i] === bgRed &&
imageData.data[i + 1] === bgGreen &&
imageData.data[i + 2] === bgBlue &&
imageData.data[i + 3] === bgAlpha
) {
// change to transparent
imageData.data[i] = 0;
imageData.data[i + 1] = 0;
imageData.data[i + 2] = 0;
imageData.data[i + 3] = 0;
}
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.putImageData(imageData, 0, 0);
callback(canvas);
});
}

function getFrontSpriteData(callback: (data: HTMLCanvasElement) => void) {
return getCroppedSpriteCanvas(0, callback);
}

function getBackSpriteData(callback: (data: HTMLCanvasElement) => void) {
return getCroppedSpriteCanvas(192, callback);
}

const frontCanvas = battleScreenCanvas.current!;
const frontContext = frontCanvas.getContext('2d')!;

const bgImage = new Image();
bgImage.src = species.enemyElevation > 0 ? BgImageWithShadow : BgImage;
bgImage.addEventListener('load', () => {
frontContext.drawImage(bgImage, 0, 0);
getBackSpriteData((data) => {
frontContext.drawImage(data, 40, 48 + species.backCoords.y_offset);
});
getFrontSpriteData((data) => {
frontContext.drawImage(
data,
144,
8 + species.frontCoords.y_offset - species.enemyElevation
);
});
});
}, [
species,
species.frontCoords.y_offset,
species.backCoords.y_offset,
species.enemyElevation,
]);
return (
<canvas
ref={battleScreenCanvas}
width="240"
height="160"
style={{
imageRendering: 'pixelated',
height: 320,
marginTop: 20,
}}
/>
);
}
return null;
});
72 changes: 39 additions & 33 deletions src/renderer/pokemon-editor/tabs/graphics/GraphicsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,51 @@ import { ObservableNumberField } from '../../../common/forms/ObservableNumberFie
import { ObservableSwitch } from '../../../common/forms/ObservableSwitch';
import { ObservableTextField } from '../../../common/forms/ObservableTextField';
import { usePokemonStoreContext } from '../../pokemon.store';
import { BattlePreview } from './BattlePreview';

export const GraphicsTab = observer(() => {
const pokemonStore = usePokemonStoreContext();
const species = pokemonStore.selectedSpecies;

if (species) {
return (
<Box id="graphics" className="common-form">
<ObservableSwitch
label="Override Constant"
store={species}
path={['manualSpriteConst']}
/>
<ObservableTextField
label="Sprite Constant"
store={species}
path={['spriteConst']}
setter="setSpriteConst"
disabled={!species.manualSpriteConst}
/>
<ObservableNumberField
label="Front Y-Offset"
store={species}
path={['frontCoords', 'y_offset']}
/>
<ObservableNumberField
label="Front Elevation"
store={species}
path={['enemyElevation']}
/>
<ObservableNumberField
label="Back Y-Offset"
store={species}
path={['backCoords', 'y_offset']}
/>
<ObservableNumberField
label="Icon Palette"
store={species}
path={['iconPalette']}
/>
<Box id="graphics">
<Box id="graphics-form" className="common-form">
<ObservableSwitch
label="Override Constant"
store={species}
path={['manualSpriteConst']}
/>
<ObservableTextField
label="Sprite Constant"
store={species}
path={['spriteConst']}
setter="setSpriteConst"
disabled={!species.manualSpriteConst}
/>
<ObservableNumberField
label="Front Y-Offset"
store={species}
path={['frontCoords', 'y_offset']}
/>
<ObservableNumberField
label="Front Elevation"
store={species}
path={['enemyElevation']}
/>
<ObservableNumberField
label="Back Y-Offset"
store={species}
path={['backCoords', 'y_offset']}
/>
<ObservableNumberField
label="Icon Palette"
store={species}
path={['iconPalette']}
/>
</Box>

<BattlePreview />
</Box>
);
}
Expand Down