diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b08e1ce..d25837ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# 1.0.2 + +- Fixed issue with rendering list items of advantages/disadvantages/special abilities. +- Fixed shortcuts being global. +- Reloading a hero with `Stigma (Albino)` won't change selected eye color anymore. + +## macOS + +- App now quits automatically on close. + +## English Specific + +- Fixed list of professions. + +## German Specific + +- Fixed typo in failed check description of skill "Driving". +- Fixed attribute adjustment in attributes tab. +- Fixed levels of required instances of `Principles` and `Obligations` of `Blessed One of Kor`. + +# 1.0.1 + +- Fixed special abilities tab. + # 1.0.0 ### New Features / Breaking Changes @@ -39,7 +63,7 @@ - The AP cost for profession variants are now considered when calculating AP spent. - Activating spells is now disabled the limits of EL are reached. - Fixed showing separators when sorting spells or liturgical chants by group. -- Fxied text fields in animal section of the character sheet breaking layout or showing up twice. +- Fixed text fields in animal section of the character sheet breaking layout or showing up twice. - Fixed AP used for dis/advantages shown in window used for adding them. Remaining parts of the old AP system have been removed. - Fixed saving heroes (previously saved heroes are not currupted). - Fixed filters for list of races and list of professions. diff --git a/package.json b/package.json index 6a66a852..7e652559 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "optolyth", "productName": "Optolyth", - "version": "1.0.1", + "version": "1.0.2", "description": "A generator for heroes for the P&P-RPG 'The Dark Eye'.", "author": { "name": "Lukas Obermann", @@ -13,6 +13,7 @@ "dependencies": { "@std/esm": "^0.9.2", "classnames": "^2.2.5", + "electron-localshortcut": "^3.1.0", "electron-log": "^2.2.12", "electron-updater": "^2.17.3", "electron-window-state": "^4.1.1", diff --git a/src/entry.tsx b/src/entry.tsx index 2f41e9d5..4e8f9b03 100644 --- a/src/entry.tsx +++ b/src/entry.tsx @@ -23,14 +23,17 @@ import { addErrorAlert } from './actions/AlertActions'; import { requestClose, requestInitialData, setUpdateDownloadProgress, updateAvailable, updateNotAvailable } from './actions/IOActions'; import { showAbout } from './actions/LocationActions'; import { AppContainer } from './containers/App'; -import { app, AppState } from './reducers/app'; +import { app } from './reducers/app'; import { getLocaleMessages } from './selectors/stateSelectors'; import { _translate } from './utils/I18n'; import { isDialogOpen } from './utils/SubwindowsUtils'; +import localShortcut = require('electron-localshortcut'); -const store = createStore(app, applyMiddleware(ReduxThunk)); +const store = createStore(app, applyMiddleware(ReduxThunk)); store.dispatch(requestInitialData()).then(() => { + const currentWindow = remote.getCurrentWindow(); + if (remote.process.platform === 'darwin') { const { dispatch, getState } = store; const locale = getLocaleMessages(getState())!; @@ -91,27 +94,27 @@ store.dispatch(requestInitialData()).then(() => { remote.Menu.setApplicationMenu(menu); }); - remote.globalShortcut.register('Cmd+Q', () => { + localShortcut.register(currentWindow, 'Cmd+Q', () => { store.dispatch(quitAccelerator()); }); } - remote.globalShortcut.register('CmdOrCtrl+Z', () => { + localShortcut.register(currentWindow, 'CmdOrCtrl+Z', () => { store.dispatch(undoAccelerator()); }); - remote.globalShortcut.register('CmdOrCtrl+Y', () => { + localShortcut.register(currentWindow, 'CmdOrCtrl+Y', () => { store.dispatch(redoAccelerator()); }); - remote.globalShortcut.register('CmdOrCtrl+Shift+Z', () => { + localShortcut.register(currentWindow, 'CmdOrCtrl+Shift+Z', () => { store.dispatch(redoAccelerator()); }); - remote.globalShortcut.register('CmdOrCtrl+S', () => { + localShortcut.register(currentWindow, 'CmdOrCtrl+S', () => { store.dispatch(saveHeroAccelerator()); }); - remote.globalShortcut.register('CmdOrCtrl+W', () => { + localShortcut.register(currentWindow, 'CmdOrCtrl+W', () => { store.dispatch(backAccelerator()); }); - remote.globalShortcut.register('CmdOrCtrl+O', () => { + localShortcut.register(currentWindow, 'CmdOrCtrl+O', () => { store.dispatch(openSettingsAccelerator()); }); ipcRenderer.send('loading-done'); diff --git a/src/main.ts b/src/main.ts index 9243722a..89941db0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -135,19 +135,11 @@ async function main() { createWindow(); app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - } - else { - globalShortcut.register('Cmd+Q', () => { - app.quit(); - }); - } + app.quit(); }); app.on('activate', () => { if (mainWindow === null) { - globalShortcut.unregister('Cmd+Q'); createWindow(); } }); diff --git a/src/reducers/app.ts b/src/reducers/app.ts index 71bbf124..9d0bf5bc 100644 --- a/src/reducers/app.ts +++ b/src/reducers/app.ts @@ -6,6 +6,7 @@ import { herolist, HerolistState } from './herolist'; import { locale, LocaleState } from './locale'; import { ui, UIState } from './ui'; import { wiki, WikiState } from './wikiReducer'; +import { Action } from 'redux'; export interface AppState { currentHero: CurrentHeroState; @@ -23,4 +24,4 @@ const appSlices = combineReducers({ wiki }); -export const app = reduceReducers(appSlices, appPost); +export const app = reduceReducers(appSlices, appPost); diff --git a/src/reducers/appPost.ts b/src/reducers/appPost.ts index 201c7771..e400192f 100644 --- a/src/reducers/appPost.ts +++ b/src/reducers/appPost.ts @@ -13,7 +13,7 @@ type Action = ReceiveInitialDataAction | RedoAction | UndoAction; export function appPost( state: AppState, action: Action, - previousState: AppState, + previousState: AppState | undefined, ): AppState { switch (action.type) { case ActionTypes.RECEIVE_INITIAL_DATA: { @@ -71,7 +71,7 @@ export function appPost( }, }; } - else if ((areAllRuleBooksEnabled(previousState) && !areAllRuleBooksEnabled(state) && !getEnabledRuleBooks(state).has('US25208') || getEnabledRuleBooks(previousState).has('US25208') && !getEnabledRuleBooks(state).has('US25208')) && getCurrentTab(state) === 'zoneArmor') { + else if (previousState && (areAllRuleBooksEnabled(previousState) && !areAllRuleBooksEnabled(state) && !getEnabledRuleBooks(state).has('US25208') || getEnabledRuleBooks(previousState).has('US25208') && !getEnabledRuleBooks(state).has('US25208')) && getCurrentTab(state) === 'zoneArmor') { return { ...state, ui: { @@ -87,7 +87,7 @@ export function appPost( } case ActionTypes.REDO: { - if (getPhase(previousState) === 2 && getPhase(state) === 3 && ['advantages', 'disadvantages'].includes(getCurrentTab(state))) { + if (previousState && getPhase(previousState) === 2 && getPhase(state) === 3 && ['advantages', 'disadvantages'].includes(getCurrentTab(state))) { return { ...state, ui: { diff --git a/src/reducers/dependentInstances.ts b/src/reducers/dependentInstances.ts index 58e1bf97..0df318ad 100644 --- a/src/reducers/dependentInstances.ts +++ b/src/reducers/dependentInstances.ts @@ -144,14 +144,14 @@ export function dependentInstances(state = initialState, action: Action) { const currentAttribute = state.attributes.get(current)!; const nextAttribute = state.attributes.get(next)!; - const setItem = (attribute: Data.AttributeInstance) => { + const setItem = (attribute: Data.AttributeInstance, remove?: boolean) => { return (state: DependentInstancesState) => { return ListUtils.setStateItem( state, attribute.id, { ...attribute, - mod: attribute.mod - value + mod: attribute.mod + (remove === true ? -value : value) } ); } @@ -159,7 +159,7 @@ export function dependentInstances(state = initialState, action: Action) { return ListUtils.mergeReducedOptionalState( state, - setItem(currentAttribute), + setItem(currentAttribute, true), setItem(nextAttribute), ); } diff --git a/src/selectors/attributeSelectors.ts b/src/selectors/attributeSelectors.ts index 5b64617d..bd52ebcd 100644 --- a/src/selectors/attributeSelectors.ts +++ b/src/selectors/attributeSelectors.ts @@ -203,23 +203,59 @@ export const getAdjustmentValue = createSelector( } ); +export const getCurrentAdjustmentAttribute = createSelector( + getAdjustmentValue, + getAttributes, + getForView, + (adjustmentValue, attributes, attributesCalculated) => { + const currentAttribute = Array.from(attributes.values()).find(e => { + return e.mod === adjustmentValue; + }); + + return currentAttribute && + attributesCalculated.find(e => { + return e.id === currentAttribute.id; + }); + } +); + +export const getCurrentAdjustmentId = createSelector( + getCurrentAdjustmentAttribute, + attribute => attribute && attribute.id +); + export const getAvailableAdjustmentIds = createSelector( getCurrentRace, getAdjustmentValue, getAttributes, getForView, - (race, adjustmentValue, attributes, attributesCalculated) => { + getCurrentAdjustmentAttribute, + (race, adjustmentValue, attributes, attributesCalculated, currentAttribute) => { const arr = race && race.attributeAdjustmentsSelection[1]; + if (arr) { + if ( + currentAttribute && + typeof currentAttribute.max === 'number' && + typeof adjustmentValue === 'number' && + currentAttribute.value > currentAttribute.max - adjustmentValue + ) { + return [currentAttribute.id]; + } + return arr.filter(id => { const attribute = attributes.get(id); const attributeCalculated = attributesCalculated.find(e => e.id === id); + if (attribute && attributeCalculated) { - if (attribute.mod === adjustmentValue) { - return !attributeCalculated.max || attributeCalculated.max - adjustmentValue >= attribute.value; + if ( + attributeCalculated.max === undefined || + currentAttribute && currentAttribute.id === id + ) { + return true; } else if (typeof adjustmentValue === 'number') { - return !attributeCalculated.max || attributeCalculated.max + adjustmentValue >= attribute.value; + return attributeCalculated.max + adjustmentValue >= attribute.value; } } return false; @@ -228,16 +264,3 @@ export const getAvailableAdjustmentIds = createSelector( return []; } ); - -export const getCurrentAdjustmentId = createSelector( - getAvailableAdjustmentIds, - getAdjustmentValue, - getAttributes, - (availableAdjustmentIds, adjustmentValue, attributes) => { - const currentAttribute = [...attributes.values()].find(e => availableAdjustmentIds.includes(e.id) && e.mod === adjustmentValue); - if (currentAttribute) { - return currentAttribute.id; - } - return; - } -); diff --git a/src/types/external.d.ts b/src/types/external.d.ts index 2dab3b71..c8c2ad76 100644 --- a/src/types/external.d.ts +++ b/src/types/external.d.ts @@ -155,3 +155,37 @@ declare module 'remark-breaks' { export = ph; } + +declare module 'electron-localshortcut' { + import { Accelerator, BrowserWindow } from 'electron'; + + namespace LocalShortcut { + + // Docs: http://electron.atom.io/docs/api/global-shortcut + + /** + * When the accelerator is already taken by other applications, this call will + * still return false. This behavior is intended by operating systems, since they + * don't want applications to fight for global shortcuts. + */ + export function isRegistered(window: BrowserWindow, accelerator: Accelerator): boolean; + /** + * Registers a global shortcut of accelerator. The callback is called when the + * registered shortcut is pressed by the user. When the accelerator is already + * taken by other applications, this call will silently fail. This behavior is + * intended by operating systems, since they don't want applications to fight for + * global shortcuts. + */ + export function register(window: BrowserWindow, accelerator: Accelerator, callback: Function): void; + /** + * Unregisters the global shortcut of accelerator. + */ + export function unregister(window: BrowserWindow, accelerator: Accelerator): void; + /** + * Unregisters all of the global shortcuts. + */ + export function unregisterAll(window: BrowserWindow): void; + } + + export = LocalShortcut; +} diff --git a/src/utils/ListUtils.ts b/src/utils/ListUtils.ts index c56cd750..24056d70 100644 --- a/src/utils/ListUtils.ts +++ b/src/utils/ListUtils.ts @@ -115,16 +115,27 @@ export function mergeReducedOptionalState( ): DependentInstancesState; export function mergeReducedOptionalState( oldState: DependentInstancesState, - instance?: I, + instance?: I | BothInstancesStateReducer, ...reducers: BothInstancesStateReducer[] ): DependentInstancesState { - return reducers.reduce( + const allReducers = [...reducers]; + + let secondArg: I | undefined; + + if (typeof instance === 'function') { + allReducers.unshift(instance); + } + else { + secondArg = instance; + } + + return allReducers.reduce( (oldState, reducer) => { const oldStateCopy = { ...oldState }; - const newState = reducer(oldStateCopy, instance); + const newState = reducer(oldStateCopy, secondArg); const keys = Object.keys(newState) as (keyof DependentInstancesState)[]; diff --git a/src/utils/VersionUtils.ts b/src/utils/VersionUtils.ts index 2e0d0d34..56530ad7 100644 --- a/src/utils/VersionUtils.ts +++ b/src/utils/VersionUtils.ts @@ -699,6 +699,7 @@ export function convertHero(hero: Hero) { } entry.clientVersion = '0.51.4'; } + if (satisfies(entry.clientVersion, '< 1.0.0')) { if (entry.activatable.hasOwnProperty('DISADV_45') && entry.activatable.DISADV_45.some(e => e.sid === 1)) { entry.pers.haircolor = 24; @@ -706,5 +707,34 @@ export function convertHero(hero: Hero) { } entry.clientVersion = '1.0.0'; } + + if (satisfies(entry.clientVersion, '< 1.0.2')) { + let adjValue = 0; + + if (entry.r === 'R_1' || entry.r === 'R_3') { + adjValue = 1; + } + else if (entry.r === 'R_2') { + adjValue = -2; + } + else if (entry.r === 'R_4') { + adjValue = -2; + } + + let index = entry.attr.values.findIndex(e => e[2] === adjValue); + + if (index === -1) { + index = entry.attr.values.findIndex(e => e[2] !== 0); + } + + entry.attr.values = entry.attr.values.map((e, i) => { + const inter = [...e] as [string, number, number]; + inter[2] = i === index ? adjValue : 0; + return inter; + }); + + entry.clientVersion = '1.0.2'; + } + return entry; } diff --git a/src/views/attributes/AttributesAdjustment.tsx b/src/views/attributes/AttributesAdjustment.tsx index e33ffd58..bfcab46e 100644 --- a/src/views/attributes/AttributesAdjustment.tsx +++ b/src/views/attributes/AttributesAdjustment.tsx @@ -26,7 +26,7 @@ export function AttributesAdjustment(props: AttributesAdjustmentProps) { }))} value={currentAttributeId} onChange={setAdjustmentId} - disabled={currentAttributeId === undefined} + disabled={currentAttributeId === undefined || availableAttributeIds.length === 1} />} );