Add quick actions and mass resources to level upgrade
This commit is contained in:
parent
9d37697b13
commit
ed66b0a308
51
src/Action/UpgradeResourceToLevel.ts
Normal file
51
src/Action/UpgradeResourceToLevel.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { ActionController, registerAction } from './ActionController';
|
||||
import { Args } from '../Common';
|
||||
import { ActionError, GrabError, TryLaterError } from '../Errors';
|
||||
import { Task } from '../Storage/TaskQueue';
|
||||
import { clickUpgradeButton } from '../Page/BuildingPage';
|
||||
import { grabResourceDeposits } from '../Page/SlotBlock';
|
||||
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
|
||||
|
||||
@registerAction
|
||||
export class UpgradeResourceToLevel extends ActionController {
|
||||
async run(args: Args, task: Task): Promise<any> {
|
||||
const deposits = grabResourceDeposits();
|
||||
if (deposits.length === 0) {
|
||||
throw new ActionError(task.id, 'No deposits');
|
||||
}
|
||||
|
||||
const villageId = args.villageId;
|
||||
const requiredLevel = args.level;
|
||||
const tasks = this.scheduler.getTaskItems();
|
||||
|
||||
const allUpgraded = deposits.reduce((memo, dep) => memo && dep.level >= requiredLevel, true);
|
||||
|
||||
if (allUpgraded) {
|
||||
this.scheduler.completeTask(task.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const available = deposits
|
||||
.sort((x, y) => x.level - y.level)
|
||||
.filter(dep => dep.ready)
|
||||
.filter(
|
||||
dep =>
|
||||
tasks.find(
|
||||
t =>
|
||||
t.name === UpgradeBuildingTask.name &&
|
||||
t.args.villageId === villageId &&
|
||||
t.args.buildId === dep.buildId
|
||||
) === undefined
|
||||
);
|
||||
|
||||
if (available.length === 0) {
|
||||
throw new TryLaterError(task.id, 10 * 60, 'No available deposits');
|
||||
}
|
||||
|
||||
const targetDep = available[0];
|
||||
|
||||
this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId: targetDep.buildId });
|
||||
|
||||
throw new TryLaterError(task.id, 20 * 60, 'Sleep for next round');
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
export interface Args {
|
||||
villageId?: number;
|
||||
buildId?: number;
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
<div id="dashboard-inner">
|
||||
<hdr></hdr>
|
||||
<task-list></task-list>
|
||||
<hr class="separator" />
|
||||
<quick-actions></quick-actions>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
@ -10,10 +12,12 @@
|
||||
<script>
|
||||
import Header from './Header';
|
||||
import TaskList from './TaskList';
|
||||
import QuickActions from './QuickActions';
|
||||
export default {
|
||||
components: {
|
||||
hdr: Header,
|
||||
'task-list': TaskList,
|
||||
'quick-actions': QuickActions,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
@ -38,5 +42,9 @@ export default {
|
||||
#dashboard-inner {
|
||||
background-color: white;
|
||||
margin: 4px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.separator {
|
||||
margin: 10px auto;
|
||||
}
|
||||
</style>
|
||||
|
34
src/Dashboard/Components/QuickActions.vue
Normal file
34
src/Dashboard/Components/QuickActions.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<ul class="actions">
|
||||
<li v-for="action in actions">
|
||||
<a href="#" v-on:click.prevent="onAction(action.cb)">{{ action.label }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
shared: this.$root.$data,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
actions() {
|
||||
return this.shared.quickActions;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onAction(cb) {
|
||||
cb();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actions {
|
||||
margin: 10px auto;
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
</style>
|
@ -3,9 +3,12 @@
|
||||
<p class="summary">Task count: {{ shared.taskList.length }}</p>
|
||||
<div class="container">
|
||||
<table class="task-table">
|
||||
<tr class="task-item" v-for="task in shared.taskList">
|
||||
<tr v-for="task in shared.taskList" class="task-item" :class="{ 'this-village': isThisVillageTask(task) }">
|
||||
<td :title="formatDate(task.ts)">{{ formatDate(task.ts) }}</td>
|
||||
<td :title="task.id">{{ task.id }}</td>
|
||||
<td>
|
||||
<a href="#" title="Remove task" class="remove-action" v-on:click.prevent="onRemove(task.id)">×</a>
|
||||
</td>
|
||||
<td :title="task.name">{{ task.name }}</td>
|
||||
<td :title="JSON.stringify(task.args)">{{ JSON.stringify(task.args) }}</td>
|
||||
</tr>
|
||||
@ -23,11 +26,21 @@ export default {
|
||||
shared: this.$root.$data,
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
formatDate(ts) {
|
||||
const d = new Date(ts * 1000);
|
||||
return dateFormat(d, 'HH:MM:ss');
|
||||
},
|
||||
isThisVillageTask(task) {
|
||||
const taskVillageId = (task.args || {}).villageId;
|
||||
const currentVillageId = (this.shared.village || {}).id;
|
||||
return taskVillageId !== undefined && taskVillageId === currentVillageId;
|
||||
},
|
||||
onRemove(taskId) {
|
||||
console.log('ON REMOVE TASK', taskId);
|
||||
this.shared.removeTask(taskId);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -54,4 +67,11 @@ export default {
|
||||
padding: 2px 4px;
|
||||
max-width: 25%;
|
||||
}
|
||||
.this-village {
|
||||
color: blue;
|
||||
}
|
||||
.remove-action {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,14 +1,18 @@
|
||||
import * as URLParse from 'url-parse';
|
||||
import { markPage, uniqId, waitForLoad } from '../utils';
|
||||
import { uniqId, waitForLoad } from '../utils';
|
||||
import { Scheduler } from '../Scheduler';
|
||||
import { TaskQueueRenderer } from '../TaskQueueRenderer';
|
||||
import { BuildPage } from '../Page/BuildPage';
|
||||
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
|
||||
import { grabResources } from '../Page/ResourcesBlock';
|
||||
import { grabActiveVillage, grabActiveVillageId, grabVillageList } from '../Page/VillageBlock';
|
||||
import { onResourceSlotCtrlClick, showBuildingSlotIds, showResourceSlotIds } from '../Page/SlotBlock';
|
||||
import { grabActiveVillage, grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import {
|
||||
grabResourceDeposits,
|
||||
onResourceSlotCtrlClick,
|
||||
showBuildingSlotIds,
|
||||
showResourceSlotIds,
|
||||
} from '../Page/SlotBlock';
|
||||
import Vue from 'vue';
|
||||
import DashboardApp from './Components/DashboardApp.vue';
|
||||
import { ResourcesToLevel } from '../Task/ResourcesToLevel';
|
||||
|
||||
export class Dashboard {
|
||||
private readonly version: string;
|
||||
@ -25,45 +29,54 @@ export class Dashboard {
|
||||
const p = new URLParse(window.location.href, true);
|
||||
this.log('PARSED LOCATION', p);
|
||||
|
||||
const res = grabResources();
|
||||
this.log('RES', res);
|
||||
|
||||
const villages = grabVillageList();
|
||||
this.log('VILL', villages);
|
||||
|
||||
const villageId = grabActiveVillageId();
|
||||
|
||||
const scheduler = this.scheduler;
|
||||
const quickActions: any[] = [];
|
||||
|
||||
const state = {
|
||||
name: 'Dashboard',
|
||||
village: grabActiveVillage(),
|
||||
version: this.version,
|
||||
taskList: this.scheduler.getTaskItems(),
|
||||
quickActions: quickActions,
|
||||
|
||||
refreshTasks() {
|
||||
this.taskList = scheduler.getTaskItems();
|
||||
},
|
||||
|
||||
removeTask(taskId: string) {
|
||||
scheduler.removeTask(taskId);
|
||||
this.taskList = scheduler.getTaskItems();
|
||||
},
|
||||
};
|
||||
|
||||
const appId = `app-${uniqId()}`;
|
||||
jQuery('body').prepend(`<div id="${appId}"></div>`);
|
||||
new Vue({
|
||||
el: `#${appId}`,
|
||||
data: state,
|
||||
render: h => h(DashboardApp),
|
||||
});
|
||||
setInterval(() => state.refreshTasks(), 1000);
|
||||
|
||||
// markPage('Dashboard', this.version);
|
||||
// this.renderTaskQueue();
|
||||
// setInterval(() => this.renderTaskQueue(), 5000);
|
||||
const deposits = grabResourceDeposits();
|
||||
if (deposits.length) {
|
||||
const sorted = deposits.sort((x, y) => x.level - y.level);
|
||||
const minLevel = sorted[0].level;
|
||||
for (let i = minLevel + 1; i < minLevel + 4; ++i) {
|
||||
quickActions.push({
|
||||
label: `Ресурсы до уровня ${i}`,
|
||||
cb: () => {
|
||||
scheduler.scheduleTask(ResourcesToLevel.name, { villageId, level: i });
|
||||
state.refreshTasks();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const tasks = this.scheduler.getTaskItems();
|
||||
const buildingsInQueue = tasks
|
||||
.filter(t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId)
|
||||
.map(t => t.args.buildId);
|
||||
.map(t => t.args.buildId || 0);
|
||||
|
||||
if (p.pathname === '/dorf1.php') {
|
||||
showResourceSlotIds(buildingsInQueue);
|
||||
onResourceSlotCtrlClick(buildId => {
|
||||
this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId });
|
||||
const n = new Notification(`Building ${buildId} scheduled`);
|
||||
setTimeout(() => n && n.close(), 4000);
|
||||
});
|
||||
onResourceSlotCtrlClick(buildId => this.onResourceSlotCtrlClick(villageId, buildId, state));
|
||||
console.log(grabResourceDeposits());
|
||||
}
|
||||
|
||||
if (p.pathname === '/dorf2.php') {
|
||||
@ -73,11 +86,25 @@ export class Dashboard {
|
||||
if (p.pathname === '/build.php') {
|
||||
new BuildPage(this.scheduler, Number(p.query.id)).run();
|
||||
}
|
||||
|
||||
this.createControlPanel(state);
|
||||
}
|
||||
|
||||
private renderTaskQueue() {
|
||||
this.log('RENDER TASK QUEUE');
|
||||
new TaskQueueRenderer().render(this.scheduler.getTaskItems());
|
||||
private createControlPanel(state) {
|
||||
const appId = `app-${uniqId()}`;
|
||||
jQuery('body').prepend(`<div id="${appId}"></div>`);
|
||||
new Vue({
|
||||
el: `#${appId}`,
|
||||
data: state,
|
||||
render: h => h(DashboardApp),
|
||||
});
|
||||
}
|
||||
|
||||
private onResourceSlotCtrlClick(villageId: number, buildId: number, state) {
|
||||
this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId });
|
||||
state.refreshTasks();
|
||||
const n = new Notification(`Building ${buildId} scheduled`);
|
||||
setTimeout(() => n && n.close(), 4000);
|
||||
}
|
||||
|
||||
private log(...args) {
|
||||
|
22
src/Game.ts
22
src/Game.ts
@ -12,6 +12,15 @@ export const ResourceMapping: ReadonlyArray<{ num: number; type: ResourceType }>
|
||||
{ num: 4, type: ResourceType.Crop },
|
||||
];
|
||||
|
||||
export function numberToResourceType(typeAsNumber: number): ResourceType {
|
||||
for (let mp of ResourceMapping) {
|
||||
if (typeAsNumber === mp.num) {
|
||||
return mp.type;
|
||||
}
|
||||
}
|
||||
throw new Error(`Type ${typeAsNumber} in not valid`);
|
||||
}
|
||||
|
||||
export type ResourceList = Array<{ num: number; type: ResourceType; value: number }>;
|
||||
|
||||
export class Resources {
|
||||
@ -80,3 +89,16 @@ export type HeroAllResourcesType = 'all';
|
||||
export const HeroAllResources: HeroAllResourcesType = 'all';
|
||||
|
||||
export type HeroResourceType = ResourceType | HeroAllResourcesType;
|
||||
|
||||
export class ResourceDeposit {
|
||||
readonly buildId: number;
|
||||
readonly type: ResourceType;
|
||||
readonly level: number;
|
||||
readonly ready: boolean;
|
||||
constructor(buildId: number, type: ResourceType, level: number, ready: boolean) {
|
||||
this.buildId = buildId;
|
||||
this.type = type;
|
||||
this.level = level;
|
||||
this.ready = ready;
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ export class BuildPage {
|
||||
});
|
||||
}
|
||||
|
||||
private onTrainTroopClick(buildId: Number, troopId: Number, el: HTMLElement) {
|
||||
private onTrainTroopClick(buildId: number, troopId: number, el: HTMLElement) {
|
||||
console.log('TRAIN TROOPERS', 'TROOP ID', troopId, 'BUILDING ID', buildId);
|
||||
const villageId = grabActiveVillageId();
|
||||
const input = jQuery(el).find(`input[name="t${troopId}"]`);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { elClassId, getNumber } from '../utils';
|
||||
import { numberToResourceType, ResourceDeposit } from '../Game';
|
||||
|
||||
interface Slot {
|
||||
el: HTMLElement;
|
||||
@ -14,7 +15,7 @@ function slotElements(prefix: string): Array<Slot> {
|
||||
return result;
|
||||
}
|
||||
|
||||
function showSlotIds(prefix: string, buildingIds: number[]): void {
|
||||
function showSlotIds(prefix: string, buildingIds: Array<number>): void {
|
||||
const slots = slotElements(prefix);
|
||||
slots.forEach(slot => {
|
||||
const oldLabel = jQuery(slot.el)
|
||||
@ -54,3 +55,17 @@ export function onResourceSlotCtrlClick(cb: (buildId: number) => void): void {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function slotToDepositMapper(slot: Slot): ResourceDeposit {
|
||||
const el = slot.el;
|
||||
const classes = jQuery(el).attr('class') || '';
|
||||
const buildId = getNumber(elClassId(classes, 'buildingSlot'));
|
||||
const level = getNumber(elClassId(classes, 'level'));
|
||||
const type = getNumber(elClassId(classes, 'gid'));
|
||||
const ready = !jQuery(el).hasClass('notNow');
|
||||
return new ResourceDeposit(buildId, numberToResourceType(type), level, ready);
|
||||
}
|
||||
|
||||
export function grabResourceDeposits(): Array<ResourceDeposit> {
|
||||
return slotElements('buildingSlot').map(slotToDepositMapper);
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ export class Scheduler {
|
||||
|
||||
completeTask(id: TaskId) {
|
||||
this.taskQueue.complete(id);
|
||||
this.actionQueue.clear();
|
||||
}
|
||||
|
||||
scheduleTask(name: string, args: Args): void {
|
||||
@ -152,6 +153,11 @@ export class Scheduler {
|
||||
this.taskQueue.push(name, args, timestamp());
|
||||
}
|
||||
|
||||
removeTask(id: TaskId) {
|
||||
this.taskQueue.remove(id);
|
||||
this.actionQueue.clear();
|
||||
}
|
||||
|
||||
scheduleActions(actions: Array<Command>): void {
|
||||
this.actionQueue.assign(actions);
|
||||
}
|
||||
|
@ -76,6 +76,11 @@ export class TaskQueue {
|
||||
this.flushItems(items);
|
||||
}
|
||||
|
||||
remove(id: TaskId) {
|
||||
const [_, items] = this.shiftTask(id);
|
||||
this.flushItems(items);
|
||||
}
|
||||
|
||||
seeItems(): TaskList {
|
||||
return this.getItems();
|
||||
}
|
||||
|
22
src/Task/ResourcesToLevel.ts
Normal file
22
src/Task/ResourcesToLevel.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Args, Command } from '../Common';
|
||||
import { Task } from '../Storage/TaskQueue';
|
||||
import { TaskController, registerTask } from './TaskController';
|
||||
import { GoToPageAction } from '../Action/GoToPageAction';
|
||||
import { CompleteTaskAction } from '../Action/CompleteTaskAction';
|
||||
import { path } from '../utils';
|
||||
import { UpgradeResourceToLevel } from '../Action/UpgradeResourceToLevel';
|
||||
|
||||
@registerTask
|
||||
export class ResourcesToLevel extends TaskController {
|
||||
async run(task: Task) {
|
||||
const args: Args = { ...task.args, taskId: task.id };
|
||||
this.scheduler.scheduleActions([
|
||||
new Command(GoToPageAction.name, {
|
||||
...args,
|
||||
path: path('/dorf1.php', { newdid: args.villageId }),
|
||||
}),
|
||||
new Command(UpgradeResourceToLevel.name, args),
|
||||
new Command(CompleteTaskAction.name, args),
|
||||
]);
|
||||
}
|
||||
}
|
@ -85,10 +85,12 @@ export function getNumber(value: any, def: number = 0): number {
|
||||
return converted === undefined ? def : converted;
|
||||
}
|
||||
|
||||
export function path(p: string, query: { [key: string]: string | number } = {}) {
|
||||
export function path(p: string, query: { [key: string]: string | number | undefined } = {}) {
|
||||
let parts: string[] = [];
|
||||
for (let k in query) {
|
||||
parts.push(`${k}=${query[k]}`);
|
||||
if (query[k] !== undefined) {
|
||||
parts.push(`${k}=${query[k]}`);
|
||||
}
|
||||
}
|
||||
return p + (parts.length ? '?' + parts.join('&') : '');
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"outDir": "./dist",
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"target": "es2015",
|
||||
"target": "es2018",
|
||||
"types": ["node", "url-parse", "jquery", "mocha", "chai"]
|
||||
},
|
||||
"include": [
|
||||
|
Loading…
Reference in New Issue
Block a user