Village settings

This commit is contained in:
Anton Vakhrushev 2020-05-17 19:16:08 +03:00
parent 950345ffdb
commit 70f804e8d3
9 changed files with 225 additions and 30 deletions

View File

@ -8,9 +8,6 @@ import { Task } from '../Queue/TaskProvider';
import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage'; import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage';
import { VillageState } from '../VillageState'; import { VillageState } from '../VillageState';
const TIMEOUT = 15;
const AMOUNT_THRESHOLD = 100;
@registerAction @registerAction
export class SendResourcesAction extends ActionController { export class SendResourcesAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
@ -29,17 +26,18 @@ export class SendResourcesAction extends ActionController {
console.log('To transfer res', readyToTransfer); console.log('To transfer res', readyToTransfer);
// Schedule recurrent task // Schedule recurrent task
this.scheduler.scheduleTask(task.name, task.args, timestamp() + aroundMinutes(TIMEOUT)); const timeout = senderVillage.settings.sendResourcesTimeout;
this.scheduler.scheduleTask(task.name, task.args, timestamp() + aroundMinutes(timeout));
fillSendResourcesForm(readyToTransfer, coordinates); fillSendResourcesForm(readyToTransfer, coordinates);
clickSendButton(); clickSendButton();
} }
private getMerchantsCapacity(): number { private getMerchantsCapacity(timeout: number): number {
const merchants = grabMerchantsInfo(); const merchants = grabMerchantsInfo();
const capacity = merchants.available * merchants.carry; const capacity = merchants.available * merchants.carry;
if (!capacity) { if (!capacity) {
throw new TryLaterError(aroundMinutes(TIMEOUT), 'No merchants'); throw new TryLaterError(aroundMinutes(timeout), 'No merchants');
} }
return capacity; return capacity;
} }
@ -53,13 +51,21 @@ export class SendResourcesAction extends ActionController {
{ name: 'Sender free', ...free }, { name: 'Sender free', ...free },
]); ]);
if (free.amount() < AMOUNT_THRESHOLD) { const amount = free.amount();
throw new TryLaterError(aroundMinutes(TIMEOUT), 'Little free resources'); const threshold = senderState.settings.sendResourcesThreshold;
const timeout = senderState.settings.sendResourcesTimeout;
if (amount < threshold) {
throw new TryLaterError(
aroundMinutes(timeout),
`No free resources (amount ${amount} < threshold ${threshold})`
);
} }
return free; return free;
} }
private getRecipientRequirements(recipientState: VillageState): Resources { private getRecipientRequirements(recipientState: VillageState, timeout: number): Resources {
const maxPossibleToStore = recipientState.storage.capacity.sub(recipientState.performance); const maxPossibleToStore = recipientState.storage.capacity.sub(recipientState.performance);
const currentResources = recipientState.resources; const currentResources = recipientState.resources;
const incomingResources = recipientState.incomingResources; const incomingResources = recipientState.incomingResources;
@ -79,17 +85,18 @@ export class SendResourcesAction extends ActionController {
]); ]);
if (missingResources.empty()) { if (missingResources.empty()) {
throw new TryLaterError(aroundMinutes(TIMEOUT), 'No missing resources'); throw new TryLaterError(aroundMinutes(timeout), 'No missing resources');
} }
return missingResources; return missingResources;
} }
private getResourcesForTransfer(senderState: VillageState, recipientState: VillageState): Resources { private getResourcesForTransfer(senderState: VillageState, recipientState: VillageState): Resources {
const timeout = senderState.settings.sendResourcesTimeout;
const senderReadySendResources = this.getSenderAvailableResources(senderState); const senderReadySendResources = this.getSenderAvailableResources(senderState);
const recipientNeedsResources = this.getRecipientRequirements(recipientState); const recipientNeedsResources = this.getRecipientRequirements(recipientState, timeout);
const contractResources = senderReadySendResources.min(recipientNeedsResources); const contractResources = senderReadySendResources.min(recipientNeedsResources);
const merchantsCapacity = this.getMerchantsCapacity(); const merchantsCapacity = this.getMerchantsCapacity(timeout);
let readyToTransfer = contractResources; let readyToTransfer = contractResources;
if (contractResources.amount() > merchantsCapacity) { if (contractResources.amount() > merchantsCapacity) {

View File

@ -155,16 +155,16 @@ export class ControlPanel {
buildPage.run(); buildPage.run();
} }
this.createControlPanel(state); this.createControlPanel(state, villageStateRepository);
} }
private createControlPanel(gameState: GameState) { private createControlPanel(gameState: GameState, villageStateRepository: VillageStateRepository) {
const appId = `app-${uniqId()}`; const appId = `app-${uniqId()}`;
jQuery('body').prepend(`<div id="${appId}"></div>`); jQuery('body').prepend(`<div id="${appId}"></div>`);
new Vue({ new Vue({
el: `#${appId}`, el: `#${appId}`,
data: gameState, data: gameState,
store: createStore(), store: createStore(villageStateRepository),
render: h => h(DashboardApp), render: h => h(DashboardApp),
}); });
} }

View File

@ -37,7 +37,12 @@ export class Village {
export type VillageList = Array<Village>; export type VillageList = Array<Village>;
// export interface VillageSettings { export interface VillageSettings {
// id: number; sendResourcesThreshold: number;
// sendResourcesTimeout: number;
// } }
export const VillageSettingsDefaults: VillageSettings = {
sendResourcesTimeout: 15,
sendResourcesThreshold: 100,
} as const;

View File

@ -8,8 +8,9 @@
<hr class="separator" /> <hr class="separator" />
<task-list /> <task-list />
</section> </section>
<section id="dashboard-secondary"> <section id="dashboard-secondary" v-if="isSecondaryDashboardVisible">
<log-list v-if="isLogsVisible" /> <log-list v-if="isLogsVisible" />
<village-editor v-if="isVillageEditorVisible" />
</section> </section>
</main> </main>
</template> </template>
@ -22,8 +23,10 @@ import VillageStateList from './VillageStateList';
import LogList from './LogList'; import LogList from './LogList';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import { Mutations } from './Store'; import { Mutations } from './Store';
import VillageEditor from './VillageEditor';
export default { export default {
components: { components: {
'village-editor': VillageEditor,
'hdr': Header, 'hdr': Header,
'task-list': TaskList, 'task-list': TaskList,
'quick-actions': QuickActions, 'quick-actions': QuickActions,
@ -35,9 +38,15 @@ export default {
shared: this.$root.$data, shared: this.$root.$data,
}; };
}, },
computed: mapState({ computed: {
isLogsVisible: state => state.views.logs, ...mapState({
}), isLogsVisible: state => state.views.logs,
isVillageEditorVisible: state => state.views.villageEditor,
}),
isSecondaryDashboardVisible() {
return this.isLogsVisible || this.isVillageEditorVisible;
},
},
methods: { methods: {
toggleLogs() { toggleLogs() {
this.$store.commit(Mutations.toggleLogs); this.$store.commit(Mutations.toggleLogs);

View File

@ -1,21 +1,45 @@
import Vuex from 'vuex'; import Vuex from 'vuex';
import { StorageLogRecord } from '../Logger';
import { LogStorage } from '../Storage/LogStorage'; import { LogStorage } from '../Storage/LogStorage';
import { VillageStateRepository } from '../VillageState';
import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
import { getNumber, notify } from '../utils';
import { VillageStorage } from '../Storage/VillageStorage';
export enum Mutations { export enum Mutations {
showLogs = 'showLogs', showLogs = 'showLogs',
hideLogs = 'hideLogs', hideLogs = 'hideLogs',
toggleLogs = 'toggleLogs', toggleLogs = 'toggleLogs',
updateLogs = 'updateLogs', updateLogs = 'updateLogs',
ToggleVillageEditor = 'toggle_village_editor',
SetVillageSettings = 'set_village_settings',
UpdateVillageSendResourceThreshold = 'UpdateVillageSendResourceThreshold',
UpdateVillageSendResourceTimeout = 'UpdateVillageSendResourceTimeout',
} }
export function createStore() { export enum Actions {
OpenVillageEditor = 'open_village_editor',
SaveVillageSettings = 'save_village_settings',
}
export function createStore(villageStateRepository: VillageStateRepository) {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
views: { views: {
villageEditor: false,
logs: false, logs: false,
}, },
logs: [], logs: [],
villageSettings: {
villageId: 0,
villageName: '',
sendResourcesThreshold: 0,
sendResourcesTimeout: 0,
},
},
getters: {
reverseLogs: state => {
return state.logs.slice().reverse();
},
}, },
mutations: { mutations: {
[Mutations.showLogs](state) { [Mutations.showLogs](state) {
@ -30,10 +54,43 @@ export function createStore() {
[Mutations.updateLogs](state, { logs }) { [Mutations.updateLogs](state, { logs }) {
state.logs = logs; state.logs = logs;
}, },
[Mutations.ToggleVillageEditor](state, visible?: any) {
state.views.villageEditor = visible === undefined ? !state.views.villageEditor : !!visible;
},
[Mutations.SetVillageSettings](state, settings) {
state.villageSettings = settings;
},
[Mutations.UpdateVillageSendResourceThreshold](state, value) {
state.villageSettings.sendResourcesThreshold = getNumber(value);
},
[Mutations.UpdateVillageSendResourceTimeout](state, value) {
state.villageSettings.sendResourcesTimeout = getNumber(value);
},
}, },
getters: { actions: {
reverseLogs: state => { [Actions.OpenVillageEditor]({ commit }, { villageId }) {
return state.logs.slice().reverse(); const state = villageStateRepository.getVillageState(villageId);
const settings = state.settings;
commit(Mutations.SetVillageSettings, {
villageId: state.id,
villageName: state.village.name,
sendResourcesThreshold: settings.sendResourcesThreshold,
sendResourcesTimeout: settings.sendResourcesTimeout,
});
commit(Mutations.ToggleVillageEditor, true);
},
[Actions.SaveVillageSettings]({ state }) {
const villageId = state.villageSettings.villageId;
const villageName = state.villageSettings.villageName;
const newSettings: VillageSettings = {
sendResourcesThreshold:
state.villageSettings.sendResourcesThreshold || VillageSettingsDefaults.sendResourcesThreshold,
sendResourcesTimeout:
state.villageSettings.sendResourcesTimeout || VillageSettingsDefaults.sendResourcesTimeout,
};
const storage = new VillageStorage(villageId);
storage.storeSettings(newSettings);
notify(`Настройки для ${villageName} сохранены`);
}, },
}, },
}); });

View File

@ -0,0 +1,91 @@
<template>
<section class="village-editor">
<p class="summary">
Village Editor: {{ villageName }},
<a href="#" v-on:click.prevent="close">close</a>
</p>
<form class="form" action="" v-on:submit.prevent="save">
<div class="form-input">
<label class="label" title="Порог отправки (сумма)">Порог отправки (сумма)</label>
<input class="input" type="text" v-model="sendResourcesThreshold" />
</div>
<div class="form-input">
<label class="label" title="Таймаут отправки (мин)">Таймаут отправки (мин)</label>
<input class="input" type="text" v-model="sendResourcesTimeout" />
</div>
<div class="form-actions">
<button class="btn">Сохранить</button>
</div>
</form>
</section>
</template>
<script>
import { Actions, Mutations } from './Store';
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
villageName: state => state.villageSettings.villageName,
}),
sendResourcesThreshold: {
get() {
return this.$store.state.villageSettings.sendResourcesThreshold;
},
set(value) {
this.$store.commit(Mutations.UpdateVillageSendResourceThreshold, value);
},
},
sendResourcesTimeout: {
get() {
return this.$store.state.villageSettings.sendResourcesTimeout;
},
set(value) {
this.$store.commit(Mutations.UpdateVillageSendResourceTimeout, value);
},
},
},
methods: {
close() {
this.$store.commit(Mutations.ToggleVillageEditor, false);
},
save() {
this.$store.dispatch(Actions.SaveVillageSettings);
},
},
};
</script>
<style scoped lang="scss">
@import 'style';
.village-editor {
background-color: white;
}
.summary {
@include with-padding;
}
.form {
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
}
.form-input {
margin-bottom: 5px;
}
.label {
display: inline-block;
width: 200px;
text-align: right;
}
.input {
margin-left: 5px;
}
.form-actions {
margin-top: 10px;
}
.btn {
border: 1px solid #555;
padding: 2px 4px;
}
</style>

View File

@ -14,8 +14,9 @@
<tbody> <tbody>
<template v-for="villageState in shared.villageStates"> <template v-for="villageState in shared.villageStates">
<tr class="normal-line top-line"> <tr class="normal-line top-line">
<td :class="{ active: villageState.village.active }" :title="villageState.id"> <td :class="{ active: villageState.village.active }" :title="villageHint(villageState)">
{{ villageState.village.name }} {{ villageState.village.name }}
(<a href="#" v-on:click.prevent="openEditor(villageState.id)">ред</a>)
</td> </td>
<td class="right"> <td class="right">
<filling <filling
@ -199,6 +200,7 @@ import ResourceBalance from './ResourceBalance';
import VillageResource from './VillageResource'; import VillageResource from './VillageResource';
import { COLLECTION_POINT_ID, HORSE_STABLE_ID, MARKET_ID, QUARTERS_ID } from '../Core/Buildings'; import { COLLECTION_POINT_ID, HORSE_STABLE_ID, MARKET_ID, QUARTERS_ID } from '../Core/Buildings';
import { path } from '../Helpers/Path'; import { path } from '../Helpers/Path';
import { Actions } from './Store';
function secondsToTime(value) { function secondsToTime(value) {
if (value === 0) { if (value === 0) {
@ -221,6 +223,13 @@ export default {
}; };
}, },
methods: { methods: {
villageHint(villageState) {
const id = villageState.id;
const name = villageState.village.name;
const timeout = villageState.settings.sendResourcesTimeout;
const threshold = villageState.settings.sendResourcesThreshold;
return `${name}, ${id}, отправка ${timeout} мин, порог ${threshold}`;
},
path(name, args) { path(name, args) {
return path(name, args); return path(name, args);
}, },
@ -274,6 +283,9 @@ export default {
dropResourceTransferTasks(fromVillageId, toVillageId) { dropResourceTransferTasks(fromVillageId, toVillageId) {
this.shared.dropResourceTransferTasks(fromVillageId, toVillageId); this.shared.dropResourceTransferTasks(fromVillageId, toVillageId);
}, },
openEditor(villageId) {
this.$store.dispatch(Actions.OpenVillageEditor, { villageId });
},
}, },
}; };
</script> </script>

View File

@ -3,12 +3,14 @@ import { BuildingQueueInfo } from '../Game';
import { Resources, ResourcesInterface } from '../Core/Resources'; import { Resources, ResourcesInterface } from '../Core/Resources';
import { ResourceStorage } from '../Core/ResourceStorage'; import { ResourceStorage } from '../Core/ResourceStorage';
import { IncomingMerchant } from '../Core/Market'; import { IncomingMerchant } from '../Core/Market';
import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
const RESOURCES_KEY = 'res'; const RESOURCES_KEY = 'res';
const CAPACITY_KEY = 'cap'; const CAPACITY_KEY = 'cap';
const PERFORMANCE_KEY = 'perf'; const PERFORMANCE_KEY = 'perf';
const BUILDING_QUEUE_KEY = 'bq'; const BUILDING_QUEUE_KEY = 'bq';
const INCOMING_MERCHANTS_KEY = 'im'; const INCOMING_MERCHANTS_KEY = 'im';
const SETTINGS_KEY = 'settings';
const ResourceOptions = { const ResourceOptions = {
factory: () => new Resources(0, 0, 0, 0), factory: () => new Resources(0, 0, 0, 0),
@ -74,4 +76,14 @@ export class VillageStorage {
return new IncomingMerchant(Resources.fromObject(norm), Number(norm.ts || 0)); return new IncomingMerchant(Resources.fromObject(norm), Number(norm.ts || 0));
}); });
} }
getSettings(): VillageSettings {
return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, {
factory: () => Object.assign({}, VillageSettingsDefaults),
});
}
storeSettings(settings: VillageSettings) {
this.storage.set(SETTINGS_KEY, settings);
}
} }

View File

@ -1,4 +1,4 @@
import { Village } from './Core/Village'; import { Village, VillageSettings } from './Core/Village';
import { Scheduler } from './Scheduler'; import { Scheduler } from './Scheduler';
import { Resources } from './Core/Resources'; import { Resources } from './Core/Resources';
import { VillageStorage } from './Storage/VillageStorage'; import { VillageStorage } from './Storage/VillageStorage';
@ -55,6 +55,7 @@ interface VillageOwnState {
totalRequired: RequiredResources; totalRequired: RequiredResources;
incomingResources: Resources; incomingResources: Resources;
buildRemainingSeconds: number; buildRemainingSeconds: number;
settings: VillageSettings;
} }
interface VillageOwnStateDictionary { interface VillageOwnStateDictionary {
@ -124,6 +125,7 @@ function createVillageOwnState(village: Village, scheduler: Scheduler): VillageO
totalRequired: calcResourceBalance(totalRequiredResources, resources, performance), totalRequired: calcResourceBalance(totalRequiredResources, resources, performance),
buildRemainingSeconds: buildQueueInfo.seconds, buildRemainingSeconds: buildQueueInfo.seconds,
incomingResources: calcIncomingResources(storage), incomingResources: calcIncomingResources(storage),
settings: storage.getSettings(),
}; };
} }