Changes from 1.2.1 - 1.5.1

Contains slightly modified commits from between 1.2.1 and 1.5.1.
This commit is contained in:
Thoronium
2024-10-30 13:18:07 -06:00
parent 31c60755af
commit f6f5eae31c
46 changed files with 2619 additions and 1669 deletions

View File

@@ -8,19 +8,23 @@ import { dataDir } from '@tauri-apps/api/path'
import './Downloads.css'
import Divider from './Divider'
import { getConfigOption } from '../../../utils/configuration'
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event'
import HelpButton from '../common/HelpButton'
import { ask } from '@tauri-apps/api/dialog'
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip'
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip' // Change to link that can be updated without modifying here
const FULL_QUEST_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterQuests.zip'
const FULL_50_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterLunaGC50.zip' // https://github.com/Kei-Luna/LunaGC_5.0.0
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
const UNSTABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/unstable/Grasscutter.zip'
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
const RESOURCES_DOWNLOAD = 'https://gitlab.com/api/v4/projects/35984297/repository/archive.zip' // Use Yuuki res as grasscutter crepe res are broken
const MIGOTO_DOWNLOAD =
'https://github.com/SilentNightSound/GI-Model-Importer/releases/download/V7.0/3dmigoto-GIMI-for-playing-mods.zip'
'https://github.com/SilentNightSound/GI-Model-Importer/releases/download/v7.0/3dmigoto-GIMI-for-playing-mods.zip'
const MIGOTO_FALLBACK = 'https://cdn.discordapp.com/attachments/615655311960965130/1177724469847003268/GIMI7.zip' // Since main dl fails for a few too many users
interface IProps {
closeFn: () => void
@@ -56,8 +60,10 @@ export default class Downloads extends React.Component<IProps, IState> {
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
this.downloadGrasscutterFullBuild = this.downloadGrasscutterFullBuild.bind(this)
this.downloadGrasscutterFullQuest = this.downloadGrasscutterFullQuest.bind(this)
this.downloadGrasscutterFull50 = this.downloadGrasscutterFull50.bind(this)
this.downloadGrasscutterStableRepo = this.downloadGrasscutterStableRepo.bind(this)
this.downloadGrasscutterDevRepo = this.downloadGrasscutterDevRepo.bind(this)
this.downloadGrasscutterUnstable = this.downloadGrasscutterUnstable.bind(this)
this.downloadGrasscutterLatest = this.downloadGrasscutterLatest.bind(this)
this.downloadResources = this.downloadResources.bind(this)
this.downloadMigoto = this.downloadMigoto.bind(this)
@@ -76,6 +82,19 @@ export default class Downloads extends React.Component<IProps, IState> {
this.setState({ grasscutter_set: true }, this.forceUpdate)
})
// Listen for GIMI failure to initiate fallback
listen('download_error', ({ payload }) => {
// @ts-expect-error shut up typescript
const errorData: {
path: string
error: string
} = payload
if (errorData.path.includes('GIMI.zip')) {
this.downloadMigotoFallback()
}
})
if (!gc_path || gc_path === '') {
this.setState({
grasscutter_set: false,
@@ -85,15 +104,15 @@ export default class Downloads extends React.Component<IProps, IState> {
return
}
const path = gc_path.substring(0, gc_path.lastIndexOf('/'))
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
if (gc_path) {
const resources_exist: boolean =
((await invoke('dir_exists', {
path: path + '/resources',
path: path + '\\resources',
})) as boolean) &&
(!(await invoke('dir_is_empty', {
path: path + '/resources',
path: path + '\\resources',
})) as boolean)
this.setState({
@@ -110,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
// Set to default if not set
if (!path || path === '') {
const appdata = await dataDir()
folderPath = appdata + 'cultivation/grasscutter'
folderPath = appdata + 'cultivation\\grasscutter'
// Early return since its formatted properly
return folderPath
@@ -133,8 +152,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterFullBuild() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '/GrasscutterCulti.zip', async () => {
await unzip(folder + '/GrasscutterCulti.zip', folder + '/', true)
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '\\GrasscutterCulti.zip', async () => {
await unzip(folder + '\\GrasscutterCulti.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -143,8 +162,18 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterFullQuest() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '/GrasscutterQuests.zip', async () => {
await unzip(folder + '/GrasscutterQuests.zip', folder + '/', true)
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '\\GrasscutterQuests.zip', async () => {
await unzip(folder + '\\GrasscutterQuests.zip', folder + '\\', true)
this.toggleButtons()
})
this.toggleButtons()
}
async downloadGrasscutterFull50() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_50_DOWNLOAD, folder + '\\Grasscutter50.zip', async () => {
await unzip(folder + '\\Grasscutter50.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -153,8 +182,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStableRepo() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => {
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -163,18 +192,28 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterDevRepo() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => {
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
this.toggleButtons()
})
this.toggleButtons()
}
async downloadGrasscutterUnstable() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(UNSTABLE_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
this.toggleButtons
})
this.toggleButtons()
}
async downloadGrasscutterLatest() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '/grasscutter.zip', async () => {
await unzip(folder + '/grasscutter.zip', folder + '/', true)
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -185,27 +224,38 @@ export default class Downloads extends React.Component<IProps, IState> {
}
async downloadResources() {
// Tell the user this is not needed in most cases
if (
!(await ask(
'These are not needed if you have already downloaded the All-in-One!! \nAre you sure you want to continue this download?'
))
) {
// If refusing confirmation
return
}
// Tell the user this takes some time
alert(
'Extracting resources can take time! If your resources appear to be "stuck" extracting for less than 15-20 mins, they likely still are extracting.'
)
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '/resources.zip', async () => {
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
// Delete the existing folder if it exists
if (
await invoke('dir_exists', {
path: folder + '/resources',
path: folder + '\\resources',
})
) {
await invoke('dir_delete', {
path: folder + '/resources',
path: folder + '\\resources',
})
}
await unzip(folder + '/resources.zip', folder + '/', true)
await unzip(folder + '\\resources.zip', folder + '\\', true)
// Rename folder to resources
invoke('rename', {
path: folder + '/Resources',
path: folder + '\\Resources',
newName: 'resources',
})
@@ -216,13 +266,27 @@ export default class Downloads extends React.Component<IProps, IState> {
}
async downloadMigoto() {
const folder = (await this.getCultivationFolder()) + '/3dmigoto'
await invoke('dir_create', {
path: folder,
if (!this.state.swag) {
await setConfigOption('swag_mode', true)
this.setState({ swag: true })
await setConfigOption('last_extras', { migoto: true, akebi: false, reshade: false })
}
const folder = await this.getCultivationFolder()
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '\\GIMI.zip', async () => {
await unzip(folder + '\\GIMI.zip', folder + '\\', true, true)
this.toggleButtons()
})
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '/GIMI-3dmigoto.zip', async () => {
await unzip(folder + '/GIMI-3dmigoto.zip', folder + '/', true)
this.toggleButtons()
}
async downloadMigotoFallback() {
const folder = await this.getCultivationFolder()
this.props.downloadManager.addDownload(MIGOTO_FALLBACK, folder + '\\GIMI7.zip', async () => {
await unzip(folder + '\\GIMI7.zip', folder + '\\', true, true)
this.toggleButtons()
})
@@ -256,7 +320,6 @@ export default class Downloads extends React.Component<IProps, IState> {
<Tr text={'downloads.grasscutter_fullbuild'} />
<HelpButton contents="help.gc_fullbuild" />
</div>
<div className="DownloadValue" id="downloadMenuButtonGCFullBuild">
<BigButton
disabled={this.state.grasscutter_downloading}
@@ -267,7 +330,6 @@ export default class Downloads extends React.Component<IProps, IState> {
</BigButton>
</div>
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerGCFullQuest">
<div className="DownloadLabel" id="downloadMenuLabelGCFullQuest">
<Tr text={'downloads.grasscutter_fullquest'} />
@@ -276,18 +338,38 @@ export default class Downloads extends React.Component<IProps, IState> {
<div className="DownloadValue" id="downloadMenuButtonGCFullQuest">
<BigButton
disabled={this.state.grasscutter_downloading}
onClick={this.downloadGrasscutterFullQuest}
onClick={this.downloadGrasscutterFull50}
id="grasscutterFullBuildBtn"
>
<Tr text="components.download" />
</BigButton>
</div>
</div>
<Divider />
<div className="HeaderText" id="downloadMenuIndividualHeader">
<Tr text="downloads.individual_header" />
</div>
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCUnstable">
<div className="DownloadLabel" id="downloadMenuLabelGCUnstable">
<Tr
text={
this.state.grasscutter_set ? 'downloads.grasscutter_unstable' : 'downloads.grasscutter_unstable_update'
}
/>
<HelpButton contents="help.gc_unstable_jar" />
</div>
<div className="DownloadValue" id="downloadMenuButtonGCUnstable">
<BigButton
disabled={this.state.grasscutter_downloading}
onClick={this.downloadGrasscutterUnstable}
id="grasscutterUnstableBtn"
>
<Tr text="components.download" />
</BigButton>
</div>
</div> */}
<div className="DownloadMenuSection" id="downloadMenuContainerGCDev">
<div className="DownloadLabel" id="downloadMenuLabelGCDev">
<Tr
@@ -306,7 +388,28 @@ export default class Downloads extends React.Component<IProps, IState> {
</div>
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCStableData">
<div className="DownloadLabel" id="downloadMenuLabelGCStableData">
<Tr
text={
this.state.grasscutter_set
? 'downloads.grasscutter_stable_data'
: 'downloads.grasscutter_stable_data_update'
}
/>
<HelpButton contents="help.gc_stable_data" />
</div>
<div className="DownloadValue" id="downloadMenuButtonGCStableData">
<BigButton
disabled={this.state.repo_downloading}
onClick={this.downloadGrasscutterStableRepo}
id="grasscutterStableRepo"
>
<Tr text="components.download" />
</BigButton>
</div>
</div> */}
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
<div className="DownloadLabel" id="downloadMenuLabelGCDevData">
<Tr
text={
@@ -326,7 +429,7 @@ export default class Downloads extends React.Component<IProps, IState> {
<Tr text="components.download" />
</BigButton>
</div>
</div>
</div> */}
<div className="DownloadMenuSection" id="downloadMenuContainerResources">
<div className="DownloadLabel" id="downloadMenuLabelResources">
@@ -344,25 +447,23 @@ export default class Downloads extends React.Component<IProps, IState> {
</div>
</div>
{this.state.swag && (
<>
<Divider />
<div className="HeaderText" id="downloadMenuModsHeader">
<Tr text="downloads.mods_header" />
<>
<Divider />
<div className="HeaderText" id="downloadMenuModsHeader">
<Tr text="downloads.mods_header" />
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto">
<div className="DownloadLabel" id="downloadMenuLabelMigoto">
<Tr text={'downloads.migoto'} />
<HelpButton contents="help.migoto" />
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto">
<div className="DownloadLabel" id="downloadMenuLabelMigoto">
<Tr text={'downloads.migoto'} />
<HelpButton contents="help.migoto" />
</div>
<div className="DownloadValue" id="downloadMenuButtonMigoto">
<BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
<Tr text="components.download" />
</BigButton>
</div>
<div className="DownloadValue" id="downloadMenuButtonMigoto">
<BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
<Tr text="components.download" />
</BigButton>
</div>
</>
)}
</div>
</>
</Menu>
)
}

View File

@@ -45,8 +45,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGame() {
const folder = this.state.gameDownloadFolder
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '/game.zip', async () => {
await unzip(folder + '/game.zip', folder + '/', true)
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', async () => {
await unzip(folder + '\\game.zip', folder + '\\', true)
this.setState({
gameDownloading: false,
})

View File

@@ -16,8 +16,11 @@ import DownloadHandler from '../../../utils/download'
import * as meta from '../../../utils/rsa'
import HelpButton from '../common/HelpButton'
import SmallButton from '../common/SmallButton'
import { confirm } from '@tauri-apps/api/dialog'
import { ask, confirm } from '@tauri-apps/api/dialog'
import TextInput from '../common/TextInput'
import { unzip } from '../../../utils/zipUtils'
import { getGameExecutable } from '../../../utils/game'
import { emit } from '@tauri-apps/api/event'
export enum GrasscutterElevation {
None = 'None',
@@ -52,6 +55,8 @@ interface IState {
un_elevated: boolean
redirect_more: boolean
launch_args: string
offline_mode: boolean
newer_game: boolean
// Linux stuff
grasscutter_elevation: string
@@ -88,6 +93,8 @@ export default class Options extends React.Component<IProps, IState> {
un_elevated: false,
redirect_more: false,
launch_args: '',
offline_mode: false,
newer_game: false,
// Linux stuff
grasscutter_elevation: GrasscutterElevation.None,
@@ -118,13 +125,10 @@ export default class Options extends React.Component<IProps, IState> {
const languages = await getLanguages()
const platform: string = await invoke('get_platform')
let encEnabled
if (config.grasscutter_path) {
// Remove jar from path
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
}
// Remove jar from path
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
console.log(platform)
@@ -150,6 +154,8 @@ export default class Options extends React.Component<IProps, IState> {
un_elevated: config.un_elevated || false,
redirect_more: config.redirect_more || false,
launch_args: config.launch_args,
offline_mode: config.offline_mode || false,
newer_game: config.newer_game || false,
// Linux stuff
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
@@ -163,22 +169,28 @@ export default class Options extends React.Component<IProps, IState> {
this.forceUpdate()
}
setGameExecutable(value: string) {
setConfigOption('game_install_path', value)
async setGameExecutable(value: string) {
await setConfigOption('game_install_path', value)
// I hope this stops people setting launcher.exe because oml it's annoying
if (value.endsWith('launcher.exe')) {
if (value.endsWith('launcher.exe') || value.endsWith('.lnk')) {
const pathArr = value.replace(/\\/g, '/').split('/')
pathArr.pop()
const path = pathArr.join('/') + '/Genshin Impact Game/'
alert(
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}`
)
if (value.endsWith('.lnk')) {
alert(
'You have set your game executable to a shortcut. You should not do this. Your patching will not work, and your proxy may shut off unexpectedly.'
)
} else {
alert(
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}`
)
}
}
// If setting any other game, automatically set to redirect more
if (!value.toLowerCase().includes('genshin' || 'yuanshen')) {
if (!value.toLowerCase().includes('genshin') || !value.toLowerCase().includes('yuanshen')) {
if (!this.state.redirect_more) {
this.toggleOption('redirect_more')
}
@@ -187,26 +199,24 @@ export default class Options extends React.Component<IProps, IState> {
this.setState({
game_install_path: value,
})
emit('set_game', { game_path: value })
}
async setGrasscutterJar(value: string) {
setConfigOption('grasscutter_path', value)
this.setState({
grasscutter_path: value,
})
const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
// Update encryption button when setting new jar
this.setState({
grasscutter_path: value,
encryption: encEnabled,
})
window.location.reload()
// Encryption refuses to re-render w/o reload unless updated twice
this.forceUpdateEncyption()
}
setJavaPath(value: string) {
@@ -267,7 +277,7 @@ export default class Options extends React.Component<IProps, IState> {
if (!isUrl) {
const filename = value.replace(/\\/g, '/').split('/').pop()
const localBgPath = (await dataDir()).replace(/\\/g, '/')
const localBgPath = ((await dataDir()) as string).replace(/\\/g, '/')
await setConfigOption('custom_background', `${localBgPath}/cultivation/bg/${filename}`)
@@ -284,6 +294,16 @@ export default class Options extends React.Component<IProps, IState> {
}
}
async forceUpdateEncyption() {
const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
this.setState({
encryption: await server.encryptionEnabled(folderPath + '/config.json'),
})
}
async toggleEncryption() {
const config = await getConfig()
@@ -343,6 +363,15 @@ export default class Options extends React.Component<IProps, IState> {
}
async addMigotoDelay() {
if (
!(await ask(
'Set delay for 3dmigoto loader? This is specifically made for GIMI v6 and earlier. Using it on latest GIMI or SRMI will cause issues!!! \n\nWould you like to continue?',
{ title: 'GIMI Delay', type: 'warning' }
))
) {
return
}
invoke('set_migoto_delay', {
migotoPath: this.state.migoto_path,
})
@@ -355,6 +384,15 @@ export default class Options extends React.Component<IProps, IState> {
}
async deleteWebCache() {
if (await ask('Would you like to clear login cache? Yes to clear login cache. No to clear web cache.')) {
await invoke('wipe_registry', {
// The exe is always PascalCase so we can get the dir using regex
execName: (await getGameExecutable())?.split('.exe')[0].replace(/([a-z\d])([A-Z])/g, '$1 $2'),
})
alert('Cleared login cache!')
return
}
alert('Cultivation may freeze for a moment while this occurs!')
// Get webCaches folder path
@@ -364,8 +402,70 @@ export default class Options extends React.Component<IProps, IState> {
const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches'
// Delete the folder
await invoke('dir_delete', { path: path })
await invoke('dir_delete', { path: path2 })
if (await invoke('dir_exists', { path: path })) {
await invoke('dir_delete', { path: path })
}
if (await invoke('dir_exists', { path: path2 })) {
await invoke('dir_delete', { path: path2 })
}
}
async fixRes() {
const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/')
let folderPath = path.substring(0, path.lastIndexOf('/'))
// Set to default if not set
if (!path || path === '') {
const appdata = await dataDir()
folderPath = appdata + 'cultivation\\grasscutter'
}
if (path.includes('/')) {
folderPath = path.substring(0, path.lastIndexOf('/'))
} else {
folderPath = path.substring(0, path.lastIndexOf('\\'))
}
// Check if Grasscutter path exists
if (folderPath.length < 1) {
alert('Grasscutter not installed or not set! This option can only work when it is installed.')
return
}
// Check if resources zip exists
if (
!(await invoke('dir_exists', {
path: folderPath + '\\GC-Resources-4.0.zip',
}))
) {
alert('Resources are already unzipped or do not exist! Ensure your resources zip is named "GC-Resources-4.0.zip"')
return
}
alert(
'This may fix white screen issues on login! Please be patient while extraction occurs, it may take some time (5-10 minutes). \n\n !! You will be alerted when it is done !!'
)
// Unzip resources
await unzip(folderPath + '\\GC-Resources-4.0.zip', folderPath + '\\', true)
// Rename folder to resources
invoke('rename', {
path: folderPath + '\\Resources',
newName: 'resources',
})
// Update config.json to read from folder
await server.changeResourcePath(folderPath + '/config.json')
// Check if Grasscutter is running, and restart if so to apply changes
if (await invoke('is_grasscutter_running')) {
alert('Automatically restarting Grasscutter for changes to apply!')
await invoke('restart_grasscutter')
}
alert('Resource fixing finished! Please launch the server again and try playing.')
}
async toggleOption(opt: keyof Configuration) {
@@ -542,7 +642,7 @@ export default class Options extends React.Component<IProps, IState> {
<Tr text="swag.migoto" />
</div>
<div className="OptionValue" id="menuOptionsDirMigoto">
<SmallButton onClick={this.addMigotoDelay} id="migotoDelay" contents="help.add_delay"></SmallButton>
<SmallButton onClick={this.addMigotoDelay} id="migotoDelay"></SmallButton>
<DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
</div>
</div>
@@ -599,6 +699,31 @@ export default class Options extends React.Component<IProps, IState> {
</div>
</div>
) : null}
<div className="OptionSection" id="menuOptionsContainerOffline">
<div className="OptionLabel" id="menuOptionsLabelOffline">
<Tr text="options.offline_mode" />
</div>
<div className="OptionValue" id="menuOptionsCheckboxOffline">
<Checkbox
onChange={() => this.toggleOption('offline_mode')}
checked={this.state?.offline_mode}
id="offlineMode"
/>
</div>
</div>
<div className="OptionSection" id="menuOptionsContainerNewerGame">
<div className="OptionLabel" id="menuOptionsLabelNewerGame">
<Tr text="Patch Mihoyonet" />
</div>
<div className="OptionValue" id="menuOptionsCheckboxNewerGame">
<Checkbox
onChange={() => this.toggleOption('newer_game')}
checked={this.state?.newer_game}
id="newerGame"
/>
</div>
</div>
<Divider />
@@ -709,6 +834,15 @@ export default class Options extends React.Component<IProps, IState> {
value={this.state.launch_args}
/>
</div>
<div className="OptionLabel" id="menuOptionsLabelFixRes">
<Tr text="options.fix_res" />
</div>
<div className="OptionValue" id="menuOptionsButtonfixRes">
<BigButton onClick={this.fixRes} id="fixRes">
<Tr text="components.fix" />
</BigButton>
</div>
</Menu>
)
}