/*
Copyright 2012 Jan Ove Saltvedt
This file is part of KBot.
KBot is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KBot is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KBot. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kbotpro.scriptsystem.runnable;
import com.kbotpro.bot.BotEnvironment;
import com.kbotpro.handlers.kbotscriptsystem.KBot2Script;
import com.kbotpro.scriptsystem.Methods;
import com.kbotpro.scriptsystem.interfaces.HTMLDescription;
import com.kbotpro.scriptsystem.interfaces.Looped;
import com.kbotpro.scriptsystem.interfaces.WorkerContainer;
import com.kbotpro.ui.ArgumentGetter;
import com.kbotpro.various.StaticStorage;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.List;
import java.util.ArrayList;
/**
* The basic class all scripts should extend.
* Provides pointers to all parts of the bot and some shortcuts to a selected few methods.
* Please refer to the Methods class for information about these methods.
*/
public abstract class Script extends Methods implements WorkerContainer {
private ExecutorService executorService;
private ThreadGroup threadGroup;
private List<Worker> startUpWorkers = new ArrayList<Worker>();
public List<Worker> allWorkers = new ArrayList<Worker>();
private boolean stopped = false;
protected Script() {
threadGroup = new ThreadGroup(getClass().getSimpleName()+" Worker ThreadGroup");
executorService = Executors.newCachedThreadPool(new ThreadFactory() {
public Thread newThread(Runnable r) {
return new Thread(threadGroup, r, "Worker@"+Script.this.getClass().getSimpleName());
}
});
}
/**
* Creates a worker to the workerContainer but does not start it.
* The created worker will start when the scripts starts, if you do not want this please use the
* createWorker(Looped looped, boolean startAutomaticallyOnScriptStart)
* method instead.
* @param looped the loop that shall be ran.
* @return The created worker which can be started using startWorker(Worker worker);
*/
public Worker createWorker(Looped looped){
return createWorker(looped, true);
}
/**
* Creates a worker to the workerContainer but does not start it.
* @param looped the loop that shall be ran.
* @param startAutomaticallyOnScriptStart Whether the worker shall start when the workerContainer starts
* @return The created worker which can be started using startWorker(Worker worker);
*/
public Worker createWorker(Looped looped, boolean startAutomaticallyOnScriptStart){
Worker worker = new Worker(this, looped);
if(startAutomaticallyOnScriptStart){
startUpWorkers.add(worker);
}
allWorkers.add(worker);
return worker;
}
/**
* Creates and starts a worker at once.
* @param looped the loop that shall be ran.
* @return The created worker
*/
public Worker startWorker(Looped looped){
Worker worker = createWorker(looped);
startWorker(worker);
return worker;
}
/**
* Starts a worker if its not alreaddy alive.
* @param worker
*/
public void startWorker(Worker worker){
if(worker.isAlive()){
return; // Already running
}
worker.future = executorService.submit(worker.runnable);
}
/**
* Starts all the workers
*/
private void startAllWorkers(){
for(Worker worker: startUpWorkers){
startWorker(worker);
}
}
private ServiceCallback.State state = ServiceCallback.State.DEAD;
private ServiceCallback serviceCallback;
private RunnableService service = new RunnableService() {
/**
* Is called before the service start to check if it can run.
*
* @return Returns a boolean indicating if the service can be started or not
*/
public boolean sCanStart() {
return canStart();
}
/**
* Is called to start the service
*/
public void sStart() {
if(state == ServiceCallback.State.PAUSED){
onResume();
return;
}
if(!canStart()){
return;
}
if (donatorsOnly() && !com.kbotpro.various.StaticStorage.userStorage.canUseCPUSaving()) {
JOptionPane.showMessageDialog(StaticStorage.mainForm,
"The author of this script has chosen to only" +
" allow KBot donators to run this script.",
"Insufficient privileges",
JOptionPane.ERROR_MESSAGE);
return;
}
if(Script.this instanceof HTMLDescription){
final HTMLDescription htmlDescription = (HTMLDescription) Script.this;
if(!(Script.this instanceof KBot2Script) || ((KBot2Script)Script.this).isKBot2HTMLDesc()){
final ArgumentGetter[] argumentGetter = {null};
EventQueue.invokeLater(new Runnable() {
public void run() {
argumentGetter[0] = new ArgumentGetter(htmlDescription, getBotEnv());
}
});
while(argumentGetter[0] == null){
sleep(100);
}
argumentGetter[0].waitFor();
}
}
registerWorkers();
onStart();
startAllWorkers();
state = ServiceCallback.State.ACTIVE;
serviceCallback.setState(state, Script.this);
}
/**
* Is called to pause the service
*/
public void sPause() {
onPause();
state = ServiceCallback.State.PAUSED;
serviceCallback.setState(state, Script.this);
}
/**
* Is called to stop the service
*/
public void sStop() {
stopped = true;
stop();
for(Worker worker: allWorkers){
if(worker.isAlive()){
worker.cancel();
}
}
new Timer(10000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (threadGroup.activeCount() == 0) {
state = ServiceCallback.State.DEAD;
serviceCallback.setState(state, Script.this);
return;
}
try {
threadGroup.destroy();
} catch (ThreadDeath td) {
getLogger().logImportant("Force killed " + Script.this.toString());
}
state = ServiceCallback.State.DEAD;
serviceCallback.setState(state, Script.this);
}
});
}
/**
* Is called before start to set the callback.
* This is later used by the service to send information back.
*
* @param serviceCallback the callback to be set
*/
public void setCallback(ServiceCallback serviceCallback) {
Script.this.serviceCallback = serviceCallback;
}
/**
* Gets the current state.
*
* @return state
*/
public ServiceCallback.State getState() {
return state;
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p/>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
public void run() {
for(Worker worker: startUpWorkers){
worker.start();
}
}
};
public final void notifyWorkerDone(final Worker worker){
botEnv.executorService.submit(new Runnable() {
public void run() {
sleep(10);
boolean foundOneAlive = false;
for(Worker worker2: allWorkers){
if(worker == worker2){
continue;
}
if(worker2.isAlive()){
foundOneAlive = true;
break;
}
}
if(!foundOneAlive){
stopped = true;
stop();
state = ServiceCallback.State.DEAD;
serviceCallback.setState(state, Script.this);
}
}
});
}
/**
* Gets the scripts name
*
* @return String containing name
*/
public abstract String getName();
/**
* Is called before the workerContainer starts to check if it can run.
*
* @return Returns a boolean indicating if the service can be started or not
*/
public boolean canStart(){
return true;
}
public boolean donatorsOnly() {
return false;
}
/**
* Is called right before the run() gets called
*/
public abstract void onStart();
/**
* This is called right before onStart() and you should create all the main workers in this method.
*/
public abstract void registerWorkers();
/**
* Is called when the workerContainer is paused.
*/
public void onPause(){ }
/**
* Is called when the workerContainer is paused.
*/
public void onResume(){ }
/**
* Is called when an anti-random needs to deposit items to make space.
* @return An int array containing items that are vital for the script to continue running. By default will not deposit items that stack.
*/
public int[] doNotDeposit() {
return new int[] {
1271, 1265, 15259, 1267, 1273, 1275, 1269, //pickaxes
1359, 1357, 1361, 1351, 6739, 1349, 1355, 1353, //hatchets
301, 13431, 305, 303, 307, 309, 10129, 311, 11323, //fishing equipment
946, 2347, 590, 10008, 10006, 8011, 8010, 8009, 8008, 8013, 8007, 995 //misc common items
};
}
/**
* Is called to stop the workerContainer.
* The workerContainer is than added to the cleanup queue and thread will be force killed if not stopped within 10 seconds.
*/
public abstract void stop();
/**
* Used internally by the bot. (This is called by reflection)
* Please ignore this...
* @return
*/
private final Service getService(){
return service;
}
public boolean isStopped() {
return stopped;
}
public BotEnvironment getBotEnv() {
return botEnv;
}
}