Add research tasks
This commit is contained in:
		| @@ -20,6 +20,10 @@ export function createActionHandler(name: string, scheduler: Scheduler): ActionC | |||||||
|     return new constructor(scheduler); |     return new constructor(scheduler); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function err(msg: string): never { | ||||||
|  |     throw new ActionError(msg); | ||||||
|  | } | ||||||
|  |  | ||||||
| export class ActionController { | export class ActionController { | ||||||
|     protected scheduler: Scheduler; |     protected scheduler: Scheduler; | ||||||
|     constructor(scheduler: Scheduler) { |     constructor(scheduler: Scheduler) { | ||||||
| @@ -29,11 +33,7 @@ export class ActionController { | |||||||
|     async run(args: Args, task: Task) {} |     async run(args: Args, task: Task) {} | ||||||
|  |  | ||||||
|     ensureSameVillage(args: Args, task: Task) { |     ensureSameVillage(args: Args, task: Task) { | ||||||
|         let villageId = args.villageId; |         let villageId = args.villageId || err('Undefined village id'); | ||||||
|         if (villageId === undefined) { |  | ||||||
|             throw new ActionError('Undefined village id'); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const activeVillageId = grabActiveVillageId(); |         const activeVillageId = grabActiveVillageId(); | ||||||
|         if (villageId !== activeVillageId) { |         if (villageId !== activeVillageId) { | ||||||
|             throw new TryLaterError(aroundMinutes(1), 'Not same village'); |             throw new TryLaterError(aroundMinutes(1), 'Not same village'); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { ActionController, registerAction } from './ActionController'; | import { ActionController, err, registerAction } from './ActionController'; | ||||||
| import { ActionError, GrabError, TryLaterError } from '../Errors'; | import { GrabError, TryLaterError } from '../Errors'; | ||||||
| import { clickBuildButton } from '../Page/BuildingPage'; | import { clickBuildButton } from '../Page/BuildingPage/BuildingPage'; | ||||||
| import { aroundMinutes } from '../utils'; | import { aroundMinutes } from '../utils'; | ||||||
| import { Args } from '../Queue/Args'; | import { Args } from '../Queue/Args'; | ||||||
| import { Task } from '../Queue/TaskProvider'; | import { Task } from '../Queue/TaskProvider'; | ||||||
| @@ -8,14 +8,9 @@ import { Task } from '../Queue/TaskProvider'; | |||||||
| @registerAction | @registerAction | ||||||
| export class BuildBuildingAction extends ActionController { | export class BuildBuildingAction extends ActionController { | ||||||
|     async run(args: Args, task: Task): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         this.ensureSameVillage(args, task); |  | ||||||
|  |  | ||||||
|         const buildTypeId = args.buildTypeId; |  | ||||||
|         if (buildTypeId === undefined) { |  | ||||||
|             throw new ActionError('Undefined build type id'); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|  |             this.ensureSameVillage(args, task); | ||||||
|  |             const buildTypeId = args.buildTypeId || err('Undefined build type id'); | ||||||
|             clickBuildButton(buildTypeId); |             clickBuildButton(buildTypeId); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             if (e instanceof GrabError) { |             if (e instanceof GrabError) { | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								src/Action/ResearchAction.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Action/ResearchAction.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import { ActionController, err, registerAction } from './ActionController'; | ||||||
|  | import { GrabError, TryLaterError } from '../Errors'; | ||||||
|  | import { aroundMinutes } from '../utils'; | ||||||
|  | import { Args } from '../Queue/Args'; | ||||||
|  | import { Task } from '../Queue/TaskProvider'; | ||||||
|  | import { clickResearchButton } from '../Page/BuildingPage/ForgePage'; | ||||||
|  |  | ||||||
|  | @registerAction | ||||||
|  | export class ResearchAction extends ActionController { | ||||||
|  |     async run(args: Args, task: Task): Promise<any> { | ||||||
|  |         try { | ||||||
|  |             this.ensureSameVillage(args, task); | ||||||
|  |             const unitId = args.unitId || err('No unitId in args'); | ||||||
|  |             clickResearchButton(unitId); | ||||||
|  |         } catch (e) { | ||||||
|  |             if (e instanceof GrabError) { | ||||||
|  |                 throw new TryLaterError(aroundMinutes(15), e.message); | ||||||
|  |             } | ||||||
|  |             throw e; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,8 +1,7 @@ | |||||||
| import { ActionController, registerAction } from './ActionController'; | import { ActionController, err, registerAction } from './ActionController'; | ||||||
| import { AbortTaskError, ActionError, TryLaterError } from '../Errors'; | import { AbortTaskError, TryLaterError } from '../Errors'; | ||||||
| import { Resources } from '../Core/Resources'; | import { Resources } from '../Core/Resources'; | ||||||
| import { Coordinates, Village } from '../Core/Village'; | import { Coordinates, Village } from '../Core/Village'; | ||||||
| import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage'; |  | ||||||
| import { grabVillageResources } from '../Page/ResourcesBlock'; | import { grabVillageResources } from '../Page/ResourcesBlock'; | ||||||
| import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock'; | import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock'; | ||||||
| import { SendResourcesTask } from '../Task/SendResourcesTask'; | import { SendResourcesTask } from '../Task/SendResourcesTask'; | ||||||
| @@ -10,10 +9,7 @@ import { aroundMinutes, timestamp } from '../utils'; | |||||||
| import { VillageStorage } from '../Storage/VillageStorage'; | import { VillageStorage } from '../Storage/VillageStorage'; | ||||||
| import { Args } from '../Queue/Args'; | import { Args } from '../Queue/Args'; | ||||||
| import { Task } from '../Queue/TaskProvider'; | import { Task } from '../Queue/TaskProvider'; | ||||||
|  | import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage'; | ||||||
| function err(msg: string): never { |  | ||||||
|     throw new ActionError(msg); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const TIMEOUT = 15; | const TIMEOUT = 15; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { ActionController, registerAction } from './ActionController'; | import { ActionController, registerAction } from './ActionController'; | ||||||
| import { grabContractResources } from '../Page/BuildingPage'; | import { grabContractResources } from '../Page/BuildingPage/BuildingPage'; | ||||||
| import { Args } from '../Queue/Args'; | import { Args } from '../Queue/Args'; | ||||||
| import { Task } from '../Queue/TaskProvider'; | import { Task } from '../Queue/TaskProvider'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { ActionController, registerAction } from './ActionController'; | import { ActionController, registerAction } from './ActionController'; | ||||||
| import { GrabError, TryLaterError } from '../Errors'; | import { GrabError, TryLaterError } from '../Errors'; | ||||||
| import { clickUpgradeButton } from '../Page/BuildingPage'; | import { clickUpgradeButton } from '../Page/BuildingPage/BuildingPage'; | ||||||
| import { aroundMinutes } from '../utils'; | import { aroundMinutes } from '../utils'; | ||||||
| import { Args } from '../Queue/Args'; | import { Args } from '../Queue/Args'; | ||||||
| import { Task } from '../Queue/TaskProvider'; | import { Task } from '../Queue/TaskProvider'; | ||||||
| @@ -8,9 +8,8 @@ import { Task } from '../Queue/TaskProvider'; | |||||||
| @registerAction | @registerAction | ||||||
| export class UpgradeBuildingAction extends ActionController { | export class UpgradeBuildingAction extends ActionController { | ||||||
|     async run(args: Args, task: Task): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         this.ensureSameVillage(args, task); |  | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|  |             this.ensureSameVillage(args, task); | ||||||
|             clickUpgradeButton(); |             clickUpgradeButton(); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             if (e instanceof GrabError) { |             if (e instanceof GrabError) { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| export const WAREHOUSE_ID = 10; | export const WAREHOUSE_ID = 10; | ||||||
| export const GARNER_ID = 11; | export const GARNER_ID = 11; | ||||||
|  | export const FORGE_ID = 13; | ||||||
| export const COLLECTION_POINT_ID = 16; | export const COLLECTION_POINT_ID = 16; | ||||||
| export const MARKET_ID = 17; | export const MARKET_ID = 17; | ||||||
| export const QUARTERS_ID = 19; | export const QUARTERS_ID = 19; | ||||||
|   | |||||||
| @@ -125,13 +125,13 @@ export class Executor { | |||||||
|         this.scheduler.clearActions(); |         this.scheduler.clearActions(); | ||||||
|  |  | ||||||
|         if (err instanceof AbortTaskError) { |         if (err instanceof AbortTaskError) { | ||||||
|             this.logger.warn('ABORT TASK', task.id); |             this.logger.warn('ABORT TASK', task.id, 'MSG', err.message); | ||||||
|             this.scheduler.removeTask(task.id); |             this.scheduler.removeTask(task.id); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (err instanceof TryLaterError) { |         if (err instanceof TryLaterError) { | ||||||
|             this.logger.warn('TRY', task.id, 'AFTER', err.seconds); |             this.logger.warn('TRY', task.id, 'AFTER', err.seconds, 'MSG', err.message); | ||||||
|             this.scheduler.postponeTask(task.id, err.seconds); |             this.scheduler.postponeTask(task.id, err.seconds); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| import { Grabber } from './Grabber'; | import { Grabber } from './Grabber'; | ||||||
| import { grabActiveVillageId } from '../Page/VillageBlock'; | import { grabActiveVillageId } from '../Page/VillageBlock'; | ||||||
| import { VillageStorage } from '../Storage/VillageStorage'; | import { VillageStorage } from '../Storage/VillageStorage'; | ||||||
| import { grabIncomingMerchants } from '../Page/BuildingPage'; |  | ||||||
| import { isMarketSendResourcesPage } from '../Page/PageDetectors'; | import { isMarketSendResourcesPage } from '../Page/PageDetectors'; | ||||||
|  | import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage'; | ||||||
|  |  | ||||||
| export class MarketPageGrabber extends Grabber { | export class MarketPageGrabber extends Grabber { | ||||||
|     grab(): void { |     grab(): void { | ||||||
|   | |||||||
| @@ -1,172 +0,0 @@ | |||||||
| import { GrabError } from '../Errors'; |  | ||||||
| import { elClassId, getNumber, trimPrefix, uniqId } from '../utils'; |  | ||||||
| import { Resources } from '../Core/Resources'; |  | ||||||
| import { Coordinates } from '../Core/Village'; |  | ||||||
| import { IncomingMerchant } from '../Core/Market'; |  | ||||||
|  |  | ||||||
| export function clickBuildButton(typeId: number) { |  | ||||||
|     const section = jQuery(`#contract_building${typeId}`); |  | ||||||
|     if (section.length !== 1) { |  | ||||||
|         throw new GrabError('No build section'); |  | ||||||
|     } |  | ||||||
|     const btn = section.find('.contractLink button.green.new'); |  | ||||||
|     if (btn.length !== 1) { |  | ||||||
|         throw new GrabError('No build button, try later'); |  | ||||||
|     } |  | ||||||
|     btn.trigger('click'); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function createBuildButton(onClickHandler: (buildTypeId: number, resources: Resources) => void) { |  | ||||||
|     const $els = jQuery('[id^=contract_building]'); |  | ||||||
|     $els.each((idx, el) => { |  | ||||||
|         const $el = jQuery(el); |  | ||||||
|         const buildTypeId = getNumber(trimPrefix($el.attr('id') || '', 'contract_building')); |  | ||||||
|         const btnId = uniqId(); |  | ||||||
|         const resElement = $el.find('.resourceWrapper .resource'); |  | ||||||
|         const resources = grabResourcesFromList(resElement); |  | ||||||
|         $el.append(`<div style="padding: 8px"><a id="${btnId}" href="#">Построить</a></div>`); |  | ||||||
|         jQuery(`#${btnId}`).on('click', evt => { |  | ||||||
|             evt.preventDefault(); |  | ||||||
|             onClickHandler(buildTypeId, resources); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function clickUpgradeButton() { |  | ||||||
|     const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); |  | ||||||
|     if (btn.length !== 1) { |  | ||||||
|         throw new GrabError('No upgrade button, try later'); |  | ||||||
|     } |  | ||||||
|     btn.trigger('click'); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function createUpgradeButton(onClickHandler: (resources: Resources) => void) { |  | ||||||
|     const upgradeContainer = jQuery('.upgradeButtonsContainer .section1'); |  | ||||||
|     if (upgradeContainer.length !== 1) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     const id = uniqId(); |  | ||||||
|     const btn = `<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>`; |  | ||||||
|     upgradeContainer.append(btn); |  | ||||||
|     const resources = grabContractResources(); |  | ||||||
|     jQuery(`#${id}`).on('click', evt => { |  | ||||||
|         evt.preventDefault(); |  | ||||||
|         onClickHandler(resources); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function grabResourcesFromList($els: JQuery) { |  | ||||||
|     const getText = (n: number) => |  | ||||||
|         jQuery($els.get(n)) |  | ||||||
|             .find('.value') |  | ||||||
|             .text(); |  | ||||||
|     const grab = (n: number) => getNumber(getText(n)); |  | ||||||
|     return new Resources(grab(0), grab(1), grab(2), grab(3)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function grabContractResources(): Resources { |  | ||||||
|     const $els = jQuery('#contract .resource'); |  | ||||||
|     if ($els.length === 0) { |  | ||||||
|         throw new GrabError('No resource contract element'); |  | ||||||
|     } |  | ||||||
|     return grabResourcesFromList($els); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function createTrainTroopButtons( |  | ||||||
|     onClickHandler: (troopId: number, resources: Resources, count: number) => void |  | ||||||
| ) { |  | ||||||
|     const troopBlocks = jQuery('.action.troop:not(.empty) .innerTroopWrapper'); |  | ||||||
|     if (troopBlocks.length === 0) { |  | ||||||
|         throw new GrabError('No troop blocks'); |  | ||||||
|     } |  | ||||||
|     troopBlocks.each((idx, el) => { |  | ||||||
|         const $el = jQuery(el); |  | ||||||
|         const troopId = elClassId($el.attr('class'), 'troop'); |  | ||||||
|         if (troopId === undefined) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         const id = uniqId(); |  | ||||||
|         $el.find('.details').append(`<div style="padding: 8px 0"><a id="${id}" href="#">Обучить</a></div>`); |  | ||||||
|         const resElement = $el.find('.resourceWrapper .resource'); |  | ||||||
|         const resources = grabResourcesFromList(resElement); |  | ||||||
|         jQuery(`#${id}`).on('click', evt => { |  | ||||||
|             evt.preventDefault(); |  | ||||||
|             const input = $el.find(`input[name="t${troopId}"]`); |  | ||||||
|             const count = getNumber(input.val()); |  | ||||||
|             if (count > 0) { |  | ||||||
|                 onClickHandler(troopId, resources, count); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface SendResourcesClickHandler { |  | ||||||
|     (resources: Resources, crd: Coordinates, scale: number): void; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function createSendResourcesButton(onClickHandler: SendResourcesClickHandler) { |  | ||||||
|     const id1 = uniqId(); |  | ||||||
|     const id10 = uniqId(); |  | ||||||
|     const id100 = uniqId(); |  | ||||||
|     const id1000 = uniqId(); |  | ||||||
|  |  | ||||||
|     jQuery('#button').before(`<div style="padding: 8px"> |  | ||||||
|         <a id="${id1}" href="#">Отправить</a> /  |  | ||||||
|         <a id="${id10}" href="#">x10</a> /  |  | ||||||
|         <a id="${id100}" href="#">x100</a> / |  | ||||||
|         <a id="${id1000}" href="#">x1000</a> |  | ||||||
|     </div>`); |  | ||||||
|  |  | ||||||
|     const createHandler = (handler: SendResourcesClickHandler, scale: number) => (evt: JQuery.Event) => { |  | ||||||
|         evt.preventDefault(); |  | ||||||
|         const sendSelect = jQuery('#send_select'); |  | ||||||
|         const resources = new Resources( |  | ||||||
|             getNumber(sendSelect.find('#r1').val()), |  | ||||||
|             getNumber(sendSelect.find('#r2').val()), |  | ||||||
|             getNumber(sendSelect.find('#r3').val()), |  | ||||||
|             getNumber(sendSelect.find('#r4').val()) |  | ||||||
|         ); |  | ||||||
|         const crd = new Coordinates(getNumber(jQuery('#xCoordInput').val()), getNumber(jQuery('#yCoordInput').val())); |  | ||||||
|         onClickHandler(resources, crd, scale); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     jQuery(`#${id1}`).on('click', createHandler(onClickHandler, 1)); |  | ||||||
|     jQuery(`#${id10}`).on('click', createHandler(onClickHandler, 10)); |  | ||||||
|     jQuery(`#${id100}`).on('click', createHandler(onClickHandler, 100)); |  | ||||||
|     jQuery(`#${id1000}`).on('click', createHandler(onClickHandler, 1000)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function grabMerchantsInfo() { |  | ||||||
|     const available = getNumber(jQuery('.merchantsAvailable').text()); |  | ||||||
|     const carry = getNumber(jQuery('.carry b').text()); |  | ||||||
|     return { available, carry }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function fillSendResourcesForm(resources: Resources, crd: Coordinates) { |  | ||||||
|     const sendSelect = jQuery('#send_select'); |  | ||||||
|     sendSelect.find('#r1').val(resources.lumber); |  | ||||||
|     sendSelect.find('#r2').val(resources.clay); |  | ||||||
|     sendSelect.find('#r3').val(resources.iron); |  | ||||||
|     sendSelect.find('#r4').val(resources.crop); |  | ||||||
|     jQuery('#xCoordInput').val(crd.x); |  | ||||||
|     jQuery('#yCoordInput').val(crd.y); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function clickSendButton() { |  | ||||||
|     jQuery('#enabledButton').trigger('click'); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function grabIncomingMerchants(): ReadonlyArray<IncomingMerchant> { |  | ||||||
|     const result: Array<IncomingMerchant> = []; |  | ||||||
|     const root = jQuery('#merchantsOnTheWay .ownMerchants'); |  | ||||||
|     root.find('.traders').each((idx, el) => { |  | ||||||
|         const $el = jQuery(el); |  | ||||||
|         result.push( |  | ||||||
|             new IncomingMerchant( |  | ||||||
|                 grabResourcesFromList($el.find('.resourceWrapper .resources')), |  | ||||||
|                 getNumber($el.find('.timer').attr('value')) |  | ||||||
|             ) |  | ||||||
|         ); |  | ||||||
|     }); |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
							
								
								
									
										71
									
								
								src/Page/BuildingPage/BuildingPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/Page/BuildingPage/BuildingPage.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | import { GrabError } from '../../Errors'; | ||||||
|  | import { getNumber, trimPrefix, uniqId } from '../../utils'; | ||||||
|  | import { Resources } from '../../Core/Resources'; | ||||||
|  |  | ||||||
|  | export function clickBuildButton(typeId: number) { | ||||||
|  |     const section = jQuery(`#contract_building${typeId}`); | ||||||
|  |     if (section.length !== 1) { | ||||||
|  |         throw new GrabError('No build section'); | ||||||
|  |     } | ||||||
|  |     const btn = section.find('.contractLink button.green.new'); | ||||||
|  |     if (btn.length !== 1) { | ||||||
|  |         throw new GrabError('No build button, try later'); | ||||||
|  |     } | ||||||
|  |     btn.trigger('click'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function createBuildButton(onClickHandler: (buildTypeId: number, resources: Resources) => void) { | ||||||
|  |     const $els = jQuery('[id^=contract_building]'); | ||||||
|  |     $els.each((idx, el) => { | ||||||
|  |         const $el = jQuery(el); | ||||||
|  |         const buildTypeId = getNumber(trimPrefix($el.attr('id') || '', 'contract_building')); | ||||||
|  |         const btnId = uniqId(); | ||||||
|  |         const resElement = $el.find('.resourceWrapper .resource'); | ||||||
|  |         const resources = grabResourcesFromList(resElement); | ||||||
|  |         $el.append(`<div style="padding: 8px"><a id="${btnId}" href="#">Построить</a></div>`); | ||||||
|  |         jQuery(`#${btnId}`).on('click', evt => { | ||||||
|  |             evt.preventDefault(); | ||||||
|  |             onClickHandler(buildTypeId, resources); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function clickUpgradeButton() { | ||||||
|  |     const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); | ||||||
|  |     if (btn.length !== 1) { | ||||||
|  |         throw new GrabError('No upgrade button, try later'); | ||||||
|  |     } | ||||||
|  |     btn.trigger('click'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function createUpgradeButton(onClickHandler: (resources: Resources) => void) { | ||||||
|  |     const upgradeContainer = jQuery('.upgradeButtonsContainer .section1'); | ||||||
|  |     if (upgradeContainer.length !== 1) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     const id = uniqId(); | ||||||
|  |     const btn = `<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>`; | ||||||
|  |     upgradeContainer.append(btn); | ||||||
|  |     const resources = grabContractResources(); | ||||||
|  |     jQuery(`#${id}`).on('click', evt => { | ||||||
|  |         evt.preventDefault(); | ||||||
|  |         onClickHandler(resources); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function grabResourcesFromList($els: JQuery) { | ||||||
|  |     const getText = (n: number) => | ||||||
|  |         jQuery($els.get(n)) | ||||||
|  |             .find('.value') | ||||||
|  |             .text(); | ||||||
|  |     const grab = (n: number) => getNumber(getText(n)); | ||||||
|  |     return new Resources(grab(0), grab(1), grab(2), grab(3)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function grabContractResources(): Resources { | ||||||
|  |     const $els = jQuery('#contract .resource'); | ||||||
|  |     if ($els.length === 0) { | ||||||
|  |         throw new GrabError('No resource contract element'); | ||||||
|  |     } | ||||||
|  |     return grabResourcesFromList($els); | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								src/Page/BuildingPage/ForgePage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/Page/BuildingPage/ForgePage.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | import { elClassId, getNumber, uniqId } from '../../utils'; | ||||||
|  | import { Resources } from '../../Core/Resources'; | ||||||
|  | import { grabResourcesFromList } from './BuildingPage'; | ||||||
|  | import { GrabError } from '../../Errors'; | ||||||
|  |  | ||||||
|  | interface ResearchClickHandler { | ||||||
|  |     (resources: Resources, unitId: number): void; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function createResearchButtons(onClickHandler: ResearchClickHandler) { | ||||||
|  |     const $els = jQuery('.research'); | ||||||
|  |     $els.each((index, $el) => createResearchButton(jQuery($el), onClickHandler)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function createResearchButton($el: JQuery, onClickHandler: ResearchClickHandler) { | ||||||
|  |     const unitId = grabUnitId($el); | ||||||
|  |     const resElement = $el.find('.resourceWrapper .resource'); | ||||||
|  |     const resources = grabResourcesFromList(resElement); | ||||||
|  |  | ||||||
|  |     const id = uniqId(); | ||||||
|  |     $el.find('.cta').after(`<div style="padding: 8px"> | ||||||
|  |         <a id="${id}" href="#">Исследовать</a> | ||||||
|  |     </div>`); | ||||||
|  |  | ||||||
|  |     jQuery(`#${id}`).on('click', evt => { | ||||||
|  |         evt.preventDefault(); | ||||||
|  |         onClickHandler(resources, unitId); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function grabUnitId($el: JQuery) { | ||||||
|  |     const unitImg = $el.find('img.unit'); | ||||||
|  |     return getNumber(elClassId(unitImg.attr('class'), 'u')); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function clickResearchButton(unitId: number) { | ||||||
|  |     const $blockEl = findResearchBlockByUnitId(unitId); | ||||||
|  |     if ($blockEl === undefined) { | ||||||
|  |         throw new GrabError(`No research block for unit ${unitId}`); | ||||||
|  |     } | ||||||
|  |     const $btn = $blockEl.find('button.green.contracting'); | ||||||
|  |     if ($btn.length !== 1) { | ||||||
|  |         throw new GrabError(`No research button for unit ${unitId}`); | ||||||
|  |     } | ||||||
|  |     $btn.trigger('click'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function findResearchBlockByUnitId(unitId: number): JQuery | undefined { | ||||||
|  |     const $els = jQuery('.research'); | ||||||
|  |     let $blockEl: JQuery | undefined = undefined; | ||||||
|  |     $els.each((index, el) => { | ||||||
|  |         const $el = jQuery(el); | ||||||
|  |         const blockUnitId = grabUnitId($el); | ||||||
|  |         if (blockUnitId === unitId && $blockEl === undefined) { | ||||||
|  |             $blockEl = $el; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |     return $blockEl; | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								src/Page/BuildingPage/MarketPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/Page/BuildingPage/MarketPage.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | import { getNumber, uniqId } from '../../utils'; | ||||||
|  | import { Resources } from '../../Core/Resources'; | ||||||
|  | import { Coordinates } from '../../Core/Village'; | ||||||
|  | import { IncomingMerchant } from '../../Core/Market'; | ||||||
|  | import { grabResourcesFromList } from './BuildingPage'; | ||||||
|  |  | ||||||
|  | interface SendResourcesClickHandler { | ||||||
|  |     (resources: Resources, crd: Coordinates, scale: number): void; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function createSendResourcesButton(onClickHandler: SendResourcesClickHandler) { | ||||||
|  |     const id1 = uniqId(); | ||||||
|  |     const id10 = uniqId(); | ||||||
|  |     const id100 = uniqId(); | ||||||
|  |     const id1000 = uniqId(); | ||||||
|  |  | ||||||
|  |     jQuery('#button').before(`<div style="padding: 8px"> | ||||||
|  |         <a id="${id1}" href="#">Отправить</a> /  | ||||||
|  |         <a id="${id10}" href="#">x10</a> /  | ||||||
|  |         <a id="${id100}" href="#">x100</a> / | ||||||
|  |         <a id="${id1000}" href="#">x1000</a> | ||||||
|  |     </div>`); | ||||||
|  |  | ||||||
|  |     const createHandler = (handler: SendResourcesClickHandler, scale: number) => (evt: JQuery.Event) => { | ||||||
|  |         evt.preventDefault(); | ||||||
|  |         const sendSelect = jQuery('#send_select'); | ||||||
|  |         const resources = new Resources( | ||||||
|  |             getNumber(sendSelect.find('#r1').val()), | ||||||
|  |             getNumber(sendSelect.find('#r2').val()), | ||||||
|  |             getNumber(sendSelect.find('#r3').val()), | ||||||
|  |             getNumber(sendSelect.find('#r4').val()) | ||||||
|  |         ); | ||||||
|  |         const crd = new Coordinates(getNumber(jQuery('#xCoordInput').val()), getNumber(jQuery('#yCoordInput').val())); | ||||||
|  |         onClickHandler(resources, crd, scale); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     jQuery(`#${id1}`).on('click', createHandler(onClickHandler, 1)); | ||||||
|  |     jQuery(`#${id10}`).on('click', createHandler(onClickHandler, 10)); | ||||||
|  |     jQuery(`#${id100}`).on('click', createHandler(onClickHandler, 100)); | ||||||
|  |     jQuery(`#${id1000}`).on('click', createHandler(onClickHandler, 1000)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function grabMerchantsInfo() { | ||||||
|  |     const available = getNumber(jQuery('.merchantsAvailable').text()); | ||||||
|  |     const carry = getNumber(jQuery('.carry b').text()); | ||||||
|  |     return { available, carry }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function fillSendResourcesForm(resources: Resources, crd: Coordinates) { | ||||||
|  |     const sendSelect = jQuery('#send_select'); | ||||||
|  |     sendSelect.find('#r1').val(resources.lumber); | ||||||
|  |     sendSelect.find('#r2').val(resources.clay); | ||||||
|  |     sendSelect.find('#r3').val(resources.iron); | ||||||
|  |     sendSelect.find('#r4').val(resources.crop); | ||||||
|  |     jQuery('#xCoordInput').val(crd.x); | ||||||
|  |     jQuery('#yCoordInput').val(crd.y); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function clickSendButton() { | ||||||
|  |     jQuery('#enabledButton').trigger('click'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function grabIncomingMerchants(): ReadonlyArray<IncomingMerchant> { | ||||||
|  |     const result: Array<IncomingMerchant> = []; | ||||||
|  |     const root = jQuery('#merchantsOnTheWay .ownMerchants'); | ||||||
|  |     root.find('.traders').each((idx, el) => { | ||||||
|  |         const $el = jQuery(el); | ||||||
|  |         result.push( | ||||||
|  |             new IncomingMerchant( | ||||||
|  |                 grabResourcesFromList($el.find('.resourceWrapper .resources')), | ||||||
|  |                 getNumber($el.find('.timer').attr('value')) | ||||||
|  |             ) | ||||||
|  |         ); | ||||||
|  |     }); | ||||||
|  |     return result; | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/Page/BuildingPage/TrooperPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/Page/BuildingPage/TrooperPage.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | import { Resources } from '../../Core/Resources'; | ||||||
|  | import { GrabError } from '../../Errors'; | ||||||
|  | import { elClassId, getNumber, uniqId } from '../../utils'; | ||||||
|  | import { grabResourcesFromList } from './BuildingPage'; | ||||||
|  |  | ||||||
|  | export function createTrainTroopButtons( | ||||||
|  |     onClickHandler: (troopId: number, resources: Resources, count: number) => void | ||||||
|  | ) { | ||||||
|  |     const troopBlocks = jQuery('.action.troop:not(.empty) .innerTroopWrapper'); | ||||||
|  |     if (troopBlocks.length === 0) { | ||||||
|  |         throw new GrabError('No troop blocks'); | ||||||
|  |     } | ||||||
|  |     troopBlocks.each((idx, el) => { | ||||||
|  |         const $el = jQuery(el); | ||||||
|  |         const troopId = elClassId($el.attr('class'), 'troop'); | ||||||
|  |         if (troopId === undefined) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         const id = uniqId(); | ||||||
|  |         $el.find('.details').append(`<div style="padding: 8px 0"><a id="${id}" href="#">Обучить</a></div>`); | ||||||
|  |         const resElement = $el.find('.resourceWrapper .resource'); | ||||||
|  |         const resources = grabResourcesFromList(resElement); | ||||||
|  |         jQuery(`#${id}`).on('click', evt => { | ||||||
|  |             evt.preventDefault(); | ||||||
|  |             const input = $el.find(`input[name="t${troopId}"]`); | ||||||
|  |             const count = getNumber(input.val()); | ||||||
|  |             if (count > 0) { | ||||||
|  |                 onClickHandler(troopId, resources, count); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
| @@ -4,18 +4,17 @@ import { Scheduler } from '../Scheduler'; | |||||||
| import { TrainTroopTask } from '../Task/TrainTroopTask'; | import { TrainTroopTask } from '../Task/TrainTroopTask'; | ||||||
| import { grabActiveVillageId, grabVillageList } from './VillageBlock'; | import { grabActiveVillageId, grabVillageList } from './VillageBlock'; | ||||||
| import { ConsoleLogger, Logger } from '../Logger'; | import { ConsoleLogger, Logger } from '../Logger'; | ||||||
| import { | import { createBuildButton, createUpgradeButton } from './BuildingPage/BuildingPage'; | ||||||
|     createBuildButton, |  | ||||||
|     createSendResourcesButton, |  | ||||||
|     createTrainTroopButtons, |  | ||||||
|     createUpgradeButton, |  | ||||||
| } from './BuildingPage'; |  | ||||||
| import { BuildBuildingTask } from '../Task/BuildBuildingTask'; | import { BuildBuildingTask } from '../Task/BuildBuildingTask'; | ||||||
| import { Resources } from '../Core/Resources'; | import { Resources } from '../Core/Resources'; | ||||||
| import { Coordinates } from '../Core/Village'; | import { Coordinates } from '../Core/Village'; | ||||||
| import { SendResourcesTask } from '../Task/SendResourcesTask'; | import { SendResourcesTask } from '../Task/SendResourcesTask'; | ||||||
| import { EMBASSY_ID, HORSE_STABLE_ID, QUARTERS_ID } from '../Core/Buildings'; | import { EMBASSY_ID, HORSE_STABLE_ID, QUARTERS_ID } from '../Core/Buildings'; | ||||||
| import { BuildingPageAttributes, isMarketSendResourcesPage } from './PageDetectors'; | import { BuildingPageAttributes, isForgePage, isMarketSendResourcesPage } from './PageDetectors'; | ||||||
|  | import { createTrainTroopButtons } from './BuildingPage/TrooperPage'; | ||||||
|  | import { createSendResourcesButton } from './BuildingPage/MarketPage'; | ||||||
|  | import { createResearchButtons } from './BuildingPage/ForgePage'; | ||||||
|  | import { ResearchTask } from '../Task/ResearchTask'; | ||||||
|  |  | ||||||
| export class BuildingPageController { | export class BuildingPageController { | ||||||
|     private scheduler: Scheduler; |     private scheduler: Scheduler; | ||||||
| @@ -53,6 +52,10 @@ export class BuildingPageController { | |||||||
|         if (isMarketSendResourcesPage()) { |         if (isMarketSendResourcesPage()) { | ||||||
|             createSendResourcesButton((res, crd, scale) => this.onSendResources(res, crd, scale)); |             createSendResourcesButton((res, crd, scale) => this.onSendResources(res, crd, scale)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (isForgePage()) { | ||||||
|  |             createResearchButtons((res, unitId) => this.onResearch(res, unitId)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onScheduleBuildBuilding(buildTypeId: number, resources: Resources) { |     private onScheduleBuildBuilding(buildTypeId: number, resources: Resources) { | ||||||
| @@ -101,4 +104,16 @@ export class BuildingPageController { | |||||||
|         }); |         }); | ||||||
|         notify(`Send resources ${JSON.stringify(resources)} from ${villageId} to ${JSON.stringify(coordinates)}`); |         notify(`Send resources ${JSON.stringify(resources)} from ${villageId} to ${JSON.stringify(coordinates)}`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private onResearch(resources: Resources, unitId: number) { | ||||||
|  |         const villageId = grabActiveVillageId(); | ||||||
|  |         this.scheduler.scheduleTask(ResearchTask.name, { | ||||||
|  |             villageId, | ||||||
|  |             buildTypeId: this.attributes.buildTypeId, | ||||||
|  |             buildId: this.attributes.buildId, | ||||||
|  |             unitId, | ||||||
|  |             resources, | ||||||
|  |         }); | ||||||
|  |         notify(`Researching ${unitId} scheduled`); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { elClassId, getNumber, parseLocation } from '../utils'; | import { elClassId, getNumber, parseLocation } from '../utils'; | ||||||
| import { MARKET_ID } from '../Core/Buildings'; | import { FORGE_ID, MARKET_ID } from '../Core/Buildings'; | ||||||
|  |  | ||||||
| export interface BuildingPageAttributes { | export interface BuildingPageAttributes { | ||||||
|     buildId: number; |     buildId: number; | ||||||
| @@ -40,3 +40,11 @@ export function isMarketSendResourcesPage(): boolean { | |||||||
|     const { buildTypeId, tabId } = getBuildingPageAttributes(); |     const { buildTypeId, tabId } = getBuildingPageAttributes(); | ||||||
|     return buildTypeId === MARKET_ID && tabId === 5; |     return buildTypeId === MARKET_ID && tabId === 5; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function isForgePage(): boolean { | ||||||
|  |     if (!isBuildingPage()) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     const { buildTypeId } = getBuildingPageAttributes(); | ||||||
|  |     return buildTypeId === FORGE_ID; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ export interface Args { | |||||||
|     trainCount?: number; |     trainCount?: number; | ||||||
|     resources?: ResourcesInterface; |     resources?: ResourcesInterface; | ||||||
|     coordinates?: CoordinatesInterface; |     coordinates?: CoordinatesInterface; | ||||||
|  |     unitId?: number; | ||||||
|     level?: number; |     level?: number; | ||||||
|     selector?: string; |     selector?: string; | ||||||
|     path?: string; |     path?: string; | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								src/Task/ResearchTask.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/Task/ResearchTask.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | import { TaskController, registerTask, ActionDefinition } from './TaskController'; | ||||||
|  | import { GoToPageAction } from '../Action/GoToPageAction'; | ||||||
|  | import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | ||||||
|  | import { TrainTrooperAction } from '../Action/TrainTrooperAction'; | ||||||
|  | import { path } from '../utils'; | ||||||
|  | import { Action } from '../Queue/ActionQueue'; | ||||||
|  | import { Args } from '../Queue/Args'; | ||||||
|  | import { Task } from '../Queue/TaskProvider'; | ||||||
|  | import { ResearchAction } from '../Action/ResearchAction'; | ||||||
|  |  | ||||||
|  | @registerTask | ||||||
|  | export class ResearchTask extends TaskController { | ||||||
|  |     defineActions(task: Task): Array<ActionDefinition> { | ||||||
|  |         const args = task.args; | ||||||
|  |  | ||||||
|  |         const pathArgs = { | ||||||
|  |             newdid: args.villageId, | ||||||
|  |             gid: args.buildTypeId || undefined, | ||||||
|  |             id: args.buildId || undefined, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return [[GoToPageAction.name, { ...args, path: path('/build.php', pathArgs) }], [ResearchAction.name]]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,10 +1,7 @@ | |||||||
| import { TaskController, registerTask, ActionDefinition } from './TaskController'; | import { TaskController, registerTask, ActionDefinition } from './TaskController'; | ||||||
| import { GoToPageAction } from '../Action/GoToPageAction'; | import { GoToPageAction } from '../Action/GoToPageAction'; | ||||||
| import { CompleteTaskAction } from '../Action/CompleteTaskAction'; |  | ||||||
| import { path } from '../utils'; | import { path } from '../utils'; | ||||||
| import { UpgradeResourceToLevel } from '../Action/UpgradeResourceToLevel'; | import { UpgradeResourceToLevel } from '../Action/UpgradeResourceToLevel'; | ||||||
| import { Action } from '../Queue/ActionQueue'; |  | ||||||
| import { Args } from '../Queue/Args'; |  | ||||||
| import { Task } from '../Queue/TaskProvider'; | import { Task } from '../Queue/TaskProvider'; | ||||||
|  |  | ||||||
| @registerTask | @registerTask | ||||||
| @@ -12,7 +9,7 @@ export class ResourcesToLevel extends TaskController { | |||||||
|     defineActions(task: Task): Array<ActionDefinition> { |     defineActions(task: Task): Array<ActionDefinition> { | ||||||
|         return [ |         return [ | ||||||
|             [GoToPageAction.name, { path: path('/dorf1.php', { newdid: task.args.villageId }) }], |             [GoToPageAction.name, { path: path('/dorf1.php', { newdid: task.args.villageId }) }], | ||||||
|             [UpgradeResourceToLevel.name, {}], |             [UpgradeResourceToLevel.name], | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,9 +26,9 @@ export class SendResourcesTask extends TaskController { | |||||||
|  |  | ||||||
|         return [ |         return [ | ||||||
|             [GoToPageAction.name, { path: pagePath }], |             [GoToPageAction.name, { path: pagePath }], | ||||||
|             [SendResourcesAction.name, {}], |             [SendResourcesAction.name], | ||||||
|             [ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }], |             [ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }], | ||||||
|             [CompleteTaskAction.name, {}], |             [CompleteTaskAction.name], | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ export function createTaskHandler(name: string, scheduler: Scheduler): TaskContr | |||||||
|     return new constructor(scheduler); |     return new constructor(scheduler); | ||||||
| } | } | ||||||
|  |  | ||||||
| export type ActionDefinition = [string, Args]; | export type ActionDefinition = [string] | [string, Args]; | ||||||
|  |  | ||||||
| export class TaskController { | export class TaskController { | ||||||
|     protected scheduler: Scheduler; |     protected scheduler: Scheduler; | ||||||
| @@ -41,8 +41,12 @@ export class TaskController { | |||||||
|         const args: Args = { ...task.args, taskId: task.id }; |         const args: Args = { ...task.args, taskId: task.id }; | ||||||
|         const commands: Array<Action> = []; |         const commands: Array<Action> = []; | ||||||
|         for (let def of this.defineActions(task)) { |         for (let def of this.defineActions(task)) { | ||||||
|  |             if (def.length === 1) { | ||||||
|  |                 commands.push(new Action(def[0], args)); | ||||||
|  |             } else { | ||||||
|                 commands.push(new Action(def[0], { ...args, ...def[1] })); |                 commands.push(new Action(def[0], { ...args, ...def[1] })); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|         commands.push(new Action(CompleteTaskAction.name, args)); |         commands.push(new Action(CompleteTaskAction.name, args)); | ||||||
|         return commands; |         return commands; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { TaskController, registerTask } from './TaskController'; | import { registerTask, TaskController } from './TaskController'; | ||||||
| import { GoToPageAction } from '../Action/GoToPageAction'; | import { GoToPageAction } from '../Action/GoToPageAction'; | ||||||
| import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | ||||||
| import { TrainTrooperAction } from '../Action/TrainTrooperAction'; | import { TrainTrooperAction } from '../Action/TrainTrooperAction'; | ||||||
| @@ -19,10 +19,8 @@ export class TrainTroopTask extends TaskController { | |||||||
|             s: args.sheetId, |             s: args.sheetId, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         const pagePath = path('/build.php', pathArgs); |  | ||||||
|  |  | ||||||
|         this.scheduler.scheduleActions([ |         this.scheduler.scheduleActions([ | ||||||
|             new Action(GoToPageAction.name, { ...args, path: pagePath }), |             new Action(GoToPageAction.name, { ...args, path: path('/build.php', pathArgs) }), | ||||||
|             new Action(TrainTrooperAction.name, args), |             new Action(TrainTrooperAction.name, args), | ||||||
|             new Action(CompleteTaskAction.name, args), |             new Action(CompleteTaskAction.name, args), | ||||||
|         ]); |         ]); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user