Add village resources view to dashboard

This commit is contained in:
Anton Vakhrushev 2020-04-16 13:07:26 +03:00
parent 29b55158d6
commit fc75b007c8
14 changed files with 180 additions and 56 deletions

View File

@ -1,6 +1,6 @@
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { GameState } from '../Storage/GameState'; import { DataStorage } from '../Storage/DataStorage';
import { Scheduler } from '../Scheduler'; import { Scheduler } from '../Scheduler';
const actionMap: { [name: string]: Function | undefined } = {}; const actionMap: { [name: string]: Function | undefined } = {};
@ -9,20 +9,18 @@ export function registerAction(constructor: Function) {
actionMap[constructor.name] = constructor; actionMap[constructor.name] = constructor;
} }
export function createAction(name: string, state: GameState, scheduler: Scheduler): ActionController | undefined { export function createAction(name: string, scheduler: Scheduler): ActionController | undefined {
const storedFunction = actionMap[name]; const storedFunction = actionMap[name];
if (storedFunction === undefined) { if (storedFunction === undefined) {
return undefined; return undefined;
} }
const constructor = (storedFunction as unknown) as typeof ActionController; const constructor = (storedFunction as unknown) as typeof ActionController;
return new constructor(state, scheduler); return new constructor(scheduler);
} }
export class ActionController { export class ActionController {
protected state: GameState;
protected scheduler: Scheduler; protected scheduler: Scheduler;
constructor(state: GameState, scheduler: Scheduler) { constructor(scheduler: Scheduler) {
this.state = state;
this.scheduler = scheduler; this.scheduler = scheduler;
} }

View File

@ -8,10 +8,10 @@ import { HeroAllResources } from '../Game';
@registerAction @registerAction
export class BalanceHeroResourcesAction extends ActionController { export class BalanceHeroResourcesAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const resources = grabResources().asList(); const resourcesAsList = grabResources().asList();
const currentType = grabCurrentHeroResource(); const currentType = grabCurrentHeroResource();
const sorted = resources.sort((x, y) => x.value - y.value); const sorted = resourcesAsList.sort((x, y) => x.value - y.value);
const min = sorted[0]; const min = sorted[0];
const max = sorted[sorted.length - 1]; const max = sorted[sorted.length - 1];
const delta = max.value - min.value; const delta = max.value - min.value;

View File

@ -16,6 +16,6 @@ export class GrabHeroAttributesAction extends ActionController {
let normalized = text.replace(/[^0-9]/g, ''); let normalized = text.replace(/[^0-9]/g, '');
const value = getNumber(normalized); const value = getNumber(normalized);
this.state.set('hero', { health: value }); // this.state.set('hero', { health: value });
} }
} }

View File

@ -28,7 +28,7 @@ export class SendOnAdventureAction extends ActionController {
adventures.sort((x, y) => x.level - y.level); adventures.sort((x, y) => x.level - y.level);
const easiest = adventures.shift(); const easiest = adventures.shift();
const hero = this.state.get('hero') || {}; const hero = { health: 0 };
console.log('EASIEST', easiest); console.log('EASIEST', easiest);
console.log('HERO', hero); console.log('HERO', hero);

View File

@ -0,0 +1,16 @@
import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue';
import { grabResources } from '../Page/ResourcesBlock';
import { grabActiveVillageId } from '../Page/VillageBlock';
import { VillageState } from '../Storage/VillageState';
@registerAction
export class StoreVillageState extends ActionController {
async run(args: Args, task: Task): Promise<any> {
const villageId = grabActiveVillageId();
const resources = grabResources();
const state = new VillageState(villageId);
state.storeResources(resources);
}
}

View File

@ -5,6 +5,8 @@
<task-list></task-list> <task-list></task-list>
<hr class="separator" /> <hr class="separator" />
<quick-actions></quick-actions> <quick-actions></quick-actions>
<hr class="separator" />
<village-state-list></village-state-list>
</div> </div>
</main> </main>
</template> </template>
@ -13,14 +15,18 @@
import Header from './Header'; import Header from './Header';
import TaskList from './TaskList'; import TaskList from './TaskList';
import QuickActions from './QuickActions'; import QuickActions from './QuickActions';
import VillageStateList from './VillageStateList';
export default { export default {
components: { components: {
hdr: Header, hdr: Header,
'task-list': TaskList, 'task-list': TaskList,
'quick-actions': QuickActions, 'quick-actions': QuickActions,
'village-state-list': VillageStateList,
}, },
data() { data() {
return {}; return {
shared: this.$root.$data,
};
}, },
}; };
</script> </script>

View File

@ -0,0 +1,40 @@
<template>
<section>
<table class="village-table">
<tr v-for="village in shared.villages" :key="village.id">
<td>{{ village.id }} - {{ village.name }}</td>
<td>Д: {{ resources(village.id).lumber }}</td>
<td>Г: {{ resources(village.id).clay }}</td>
<td>Ж: {{ resources(village.id).iron }}</td>
<td>З: {{ resources(village.id).crop }}</td>
</tr>
</table>
</section>
</template>
<script>
export default {
data() {
return {
shared: this.$root.$data,
};
},
methods: {
resources(id) {
return this.shared.getVillageResources(id);
},
},
};
</script>
<style scoped>
.village-table {
width: 100%;
border-collapse: collapse;
}
.village-table td {
border-top: 1px solid #ddd;
padding: 4px;
}
</style>

View File

@ -3,7 +3,7 @@ import { getNumber, toNumber, uniqId, waitForLoad } from '../utils';
import { Scheduler } from '../Scheduler'; import { Scheduler } from '../Scheduler';
import { BuildPage } from '../Page/BuildPage'; import { BuildPage } from '../Page/BuildPage';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { grabActiveVillage, grabActiveVillageId } from '../Page/VillageBlock'; import { grabActiveVillage, grabActiveVillageId, grabVillageList } from '../Page/VillageBlock';
import { import {
grabResourceDeposits, grabResourceDeposits,
onResourceSlotCtrlClick, onResourceSlotCtrlClick,
@ -14,6 +14,8 @@ import Vue from 'vue';
import DashboardApp from './Components/DashboardApp.vue'; import DashboardApp from './Components/DashboardApp.vue';
import { ResourcesToLevel } from '../Task/ResourcesToLevel'; import { ResourcesToLevel } from '../Task/ResourcesToLevel';
import { Logger } from '../Logger'; import { Logger } from '../Logger';
import { Resources } from '../Game';
import { VillageState } from '../Storage/VillageState';
interface QuickAction { interface QuickAction {
label: string; label: string;
@ -45,6 +47,7 @@ export class Dashboard {
const state = { const state = {
name: 'Dashboard', name: 'Dashboard',
village: grabActiveVillage(), village: grabActiveVillage(),
villages: grabVillageList(),
version: this.version, version: this.version,
taskList: this.scheduler.getTaskItems(), taskList: this.scheduler.getTaskItems(),
quickActions: quickActions, quickActions: quickActions,
@ -57,6 +60,11 @@ export class Dashboard {
scheduler.removeTask(taskId); scheduler.removeTask(taskId);
this.taskList = scheduler.getTaskItems(); this.taskList = scheduler.getTaskItems();
}, },
getVillageResources(villageId): Resources {
const state = new VillageState(villageId);
return state.getResources();
},
}; };
setInterval(() => state.refreshTasks(), 1000); setInterval(() => state.refreshTasks(), 1000);

View File

@ -2,6 +2,7 @@ import * as URLParse from 'url-parse';
const SESSION_KEY = 'travian_automation_mode'; const SESSION_KEY = 'travian_automation_mode';
const SESSION_VALUE = 'command_execution'; const SESSION_VALUE = 'command_execution';
const MODE_PARAMETER_NAME = 'auto-management';
export class ModeDetector { export class ModeDetector {
isAuto(): boolean { isAuto(): boolean {
@ -14,7 +15,7 @@ export class ModeDetector {
private isAutoByLocation(): boolean { private isAutoByLocation(): boolean {
const p = new URLParse(window.location.href, true); const p = new URLParse(window.location.href, true);
return p.query['auto-management'] !== undefined; return p.query[MODE_PARAMETER_NAME] !== undefined;
} }
private isAutoBySession(): boolean { private isAutoBySession(): boolean {

View File

@ -8,23 +8,21 @@ import { TaskQueueRenderer } from './TaskQueueRenderer';
import { createAction } from './Action/ActionController'; import { createAction } from './Action/ActionController';
import { createTask } from './Task/TaskController'; import { createTask } from './Task/TaskController';
import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
import { GameState } from './Storage/GameState';
import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
import { Logger } from './Logger'; import { Logger } from './Logger';
import { BuildBuildingTask } from './Task/BuildBuildingTask'; import { BuildBuildingTask } from './Task/BuildBuildingTask';
import { GrabVillageState } from './Task/GrabVillageState';
export class Scheduler { export class Scheduler {
private readonly version: string; private readonly version: string;
private taskQueue: TaskQueue; private taskQueue: TaskQueue;
private actionQueue: ActionQueue; private actionQueue: ActionQueue;
private gameState: GameState;
private logger: Logger; private logger: Logger;
constructor(version: string) { constructor(version: string) {
this.version = version; this.version = version;
this.taskQueue = new TaskQueue(); this.taskQueue = new TaskQueue();
this.actionQueue = new ActionQueue(); this.actionQueue = new ActionQueue();
this.gameState = new GameState();
this.logger = new Logger(this.constructor.name); this.logger = new Logger(this.constructor.name);
} }
@ -38,6 +36,7 @@ export class Scheduler {
this.scheduleUniqTask(3600, SendOnAdventureTask.name); this.scheduleUniqTask(3600, SendOnAdventureTask.name);
this.scheduleUniqTask(1200, BalanceHeroResourcesTask.name); this.scheduleUniqTask(1200, BalanceHeroResourcesTask.name);
this.scheduleUniqTask(300, GrabVillageState.name);
while (true) { while (true) {
await this.doTaskProcessingStep(); await this.doTaskProcessingStep();
@ -52,7 +51,7 @@ export class Scheduler {
private scheduleUniqTask(seconds: number, name: string, args: Args = {}) { private scheduleUniqTask(seconds: number, name: string, args: Args = {}) {
const taskScheduler = () => { const taskScheduler = () => {
if (!this.taskQueue.hasNamed(name)) { if (!this.taskQueue.hasNamed(name)) {
this.taskQueue.push(name, args, timestamp() + 10 * 60); this.taskQueue.push(name, args, timestamp() + 5 * 60);
} }
}; };
taskScheduler(); taskScheduler();
@ -90,7 +89,7 @@ export class Scheduler {
} }
private async processActionCommand(cmd: Command, task: Task) { private async processActionCommand(cmd: Command, task: Task) {
const actionController = createAction(cmd.name, this.gameState, this); const actionController = createAction(cmd.name, this);
this.logger.log('PROCESS ACTION', cmd.name, actionController); this.logger.log('PROCESS ACTION', cmd.name, actionController);
if (actionController) { if (actionController) {
await actionController.run(cmd.args, task); await actionController.run(cmd.args, task);

View File

@ -0,0 +1,43 @@
import { Logger } from '../Logger';
const NAMESPACE = 'travian:v1';
function join(...parts: Array<string>) {
return parts.map(p => p.replace(/[:]+$/g, '').replace(/^[:]+/g, '')).join(':');
}
export class DataStorage {
private readonly logger;
private readonly name: string;
constructor(name: string) {
this.name = name;
this.logger = new Logger(this.constructor.name);
}
get(key: string): any {
const fullKey = join(NAMESPACE, this.name, key);
this.logger.log('GET', key);
try {
const serialized = localStorage.getItem(fullKey);
return JSON.parse(serialized || '"null"');
} catch (e) {
if (e instanceof SyntaxError) {
return null;
}
throw e;
}
}
has(key: string): boolean {
const fullKey = join(NAMESPACE, this.name, key);
return localStorage.getItem(fullKey) !== null;
}
set(key: string, value: any) {
const fullKey = join(NAMESPACE, this.name, key);
let serialized = JSON.stringify(value);
this.logger.log('SET', fullKey, serialized);
localStorage.setItem(fullKey, serialized);
}
}

View File

@ -1,38 +0,0 @@
import { Logger } from '../Logger';
const NAMESPACE = 'game_state:v1';
function join(x: string, y: string) {
return x.replace(/[:]+$/g, '') + ':' + y.replace(/^[:]+/g, '');
}
export class GameState {
private readonly logger;
constructor() {
this.logger = new Logger(this.constructor.name);
}
get(key: string): any {
this.logger.log('GET', key);
try {
const serialized = localStorage.getItem(join(NAMESPACE, key));
return JSON.parse(serialized || 'null');
} catch (e) {
if (e instanceof SyntaxError) {
return null;
}
throw e;
}
}
has(key: string): boolean {
return localStorage.getItem(join(NAMESPACE, key)) === null;
}
set(key: string, value: any) {
let serialized = JSON.stringify(value);
this.logger.log('SET', key, serialized);
localStorage.setItem(join(NAMESPACE, key), serialized);
}
}

View File

@ -0,0 +1,19 @@
import { DataStorage } from './DataStorage';
import { Resources } from '../Game';
export class VillageState {
private storage: DataStorage;
constructor(villageId: number) {
this.storage = new DataStorage(`village.${villageId}`);
}
storeResources(resources: Resources) {
this.storage.set('res', resources);
}
getResources(): Resources {
let plain = this.storage.get('res');
let res = new Resources(0, 0, 0, 0, 0, 0);
return Object.assign(res, plain) as Resources;
}
}

View File

@ -0,0 +1,32 @@
import { Args, Command } from '../Common';
import { CompleteTaskAction } from '../Action/CompleteTaskAction';
import { GoToPageAction } from '../Action/GoToPageAction';
import { path } from '../utils';
import { Task } from '../Storage/TaskQueue';
import { TaskController, registerTask } from './TaskController';
import { grabVillageList } from '../Page/VillageBlock';
import { StoreVillageState } from '../Action/StoreVillageState';
@registerTask
export class GrabVillageState extends TaskController {
async run(task: Task) {
const args: Args = { ...task.args, taskId: task.id };
const actions: Array<Command> = [];
const villages = grabVillageList();
for (let village of villages) {
actions.push(
new Command(GoToPageAction.name, {
...args,
path: path('/dorf1.php', { newdid: village.id }),
})
);
actions.push(new Command(StoreVillageState.name, args));
}
actions.push(new Command(CompleteTaskAction.name, args));
this.scheduler.scheduleActions(actions);
}
}