Add quick actions and mass resources to level upgrade
This commit is contained in:
		
							
								
								
									
										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": [ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user