Last active
July 2, 2024 16:18
-
-
Save its-jackson/67e1b5b61e6687b4a38fe36be1b020d4 to your computer and use it in GitHub Desktop.
Employs state based delays through its state tree, ensuring specific tasks are completed in a controlled manner. It also has anticipation models by preparing for the next action (like hovering the next tree, spec orb) and adjusting behavior based on local time.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package scripts | |
import org.tribot.script.sdk.* | |
import org.tribot.script.sdk.Combat.* | |
import org.tribot.script.sdk.Waiting.wait | |
import org.tribot.script.sdk.antiban.Antiban | |
import org.tribot.script.sdk.frameworks.behaviortree.* | |
import org.tribot.script.sdk.painting.Painting | |
import org.tribot.script.sdk.script.TribotScript | |
import org.tribot.script.sdk.script.TribotScriptManifest | |
import scripts.nexus.sdk.control | |
import scripts.nexus.sdk.mouse.MousePaintThread | |
import scripts.nexus.sdk.routine.api.Logger | |
import scripts.nexus.sdk.routine.api.managers.AntibanManager | |
import scripts.nexus.sdk.routine.api.managers.BankManager | |
import scripts.nexus.sdk.routine.api.managers.TribotDefaultGameSettingManager | |
import scripts.nexus.sdk.routine.api.managers.checkAllCameraTasks | |
import scripts.nexus.sdk.routine.api.woodcutting.Axe | |
import scripts.nexus.sdk.routine.api.woodcutting.TreeManager | |
import scripts.nexus.sdk.routine.api.woodcutting.TreeType | |
import scripts.nexus.sdk.statetree.* | |
import java.awt.Color | |
import java.lang.System.currentTimeMillis | |
import java.time.Duration | |
import java.time.LocalTime | |
import java.time.format.DateTimeFormatter | |
import kotlin.jvm.optionals.getOrNull | |
private const val KEY_CURRENT_TREE = "currentTree" | |
private const val KEY_NEXT_TREE = "nextTree" | |
private const val KEY_SHOULD_HOVER_NEXT_TREE = "shouldHoverNextTree" | |
private const val KEY_SHOULD_HOVER_SPEC_ORB = "shouldHoverSpecOrb" | |
data class AIConfig( | |
val axe: Axe = Axe.DRAGON, | |
val tree: TreeType = TreeType.MAGIC_TREE_AT_WOODCUTTING_GUILD | |
) | |
class AIContextProvider( | |
var config: AIConfig, | |
var logger: Logger, | |
var treeManager: TreeManager, | |
var bankManager: BankManager, | |
var antibanManager: AntibanManager, | |
var gameSettingManager: TribotDefaultGameSettingManager, | |
var localTime: LocalTime = getCurrentTime(), | |
var lastBehaviorUpdateTime: LocalTime = localTime, | |
var idleTickCounter: Int = 0, | |
var treesChoppedCounter: Int = 0, | |
var logsChoppedCounter: Int = 0, | |
var initialLogCount: Int = 0, | |
var finalLogCount: Int = 0, | |
) : IContextProvider | |
@TribotScriptManifest( | |
name = "iChopper", | |
author = "Polymorphic", | |
category = "Woodcutting", | |
description = "Local" | |
) | |
class StateTreeWoodcutter : TribotScript { | |
private val paintThread = MousePaintThread() | |
private var stateTree: StateTree<AIContextProvider>? = null | |
private var lastUpdateTimeMs: Long = currentTimeMillis() | |
private val defaultWaitTime: Int = 25 | |
private var waitTime: Int = defaultWaitTime | |
override fun execute(p0: String) { | |
val ctxProvider = AIContextProvider( | |
config = AIConfig(), | |
logger = Logger("Woodcutting AI"), | |
antibanManager = AntibanManager(), | |
gameSettingManager = TribotDefaultGameSettingManager(), | |
bankManager = BankManager(BankManager.BankType.DEPOSIT_BOX), | |
treeManager = TreeManager() | |
) | |
val ctxData = ContextData(ctxProvider) | |
val parameters = StateTreeParameters().apply { | |
this[KEY_CURRENT_TREE] = null | |
this[KEY_NEXT_TREE] = null | |
this[KEY_SHOULD_HOVER_NEXT_TREE] = false | |
this[KEY_SHOULD_HOVER_SPEC_ORB] = false | |
} | |
// Run the global tasks on tick frequency before the state tree runs | |
val globalTasks = listOf( | |
buildTask<AIContextProvider>(5) { | |
behaviorTree { | |
sequence { | |
perform { | |
val currentTime = it.data.context.localTime | |
val lastUpdateTime = it.data.context.lastBehaviorUpdateTime | |
val logger = it.data.context.logger | |
// Check if an hour has passed since the last behavior update | |
if (Duration.between(lastUpdateTime, currentTime).toHours() >= 1) { | |
if (currentTime.hour in 6..18) { | |
logger.info("It's day time. Adjusting behavior accordingly") | |
// | |
} else { | |
logger.info("It's night time. Adjusting behavior accordingly") | |
// | |
} | |
// Update the last behavior update time | |
it.data.context.lastBehaviorUpdateTime = currentTime | |
} | |
} | |
} | |
} | |
} | |
) | |
// Meant to expose data to the tree where the context data and tree params are out of scope | |
val evaluators = listOf( | |
buildEvaluator<AIContextProvider>( | |
onStart = { | |
behaviorTree { | |
sequence { | |
perform { | |
it.data.context.treeManager.tree = it.data.context.config.tree.tree | |
} | |
perform { | |
Antiban.setScriptAiAntibanEnabled(false) | |
} | |
perform { | |
paintThread.start() | |
} | |
perform { | |
val currentTime = it.data.context.localTime | |
val logger = it.data.context.logger | |
if (currentTime.hour in 6..18) { | |
logger.info("Evaluator onStart: it's day time. Adjusting behavior accordingly") | |
// | |
} else { | |
logger.info("Evaluator onStart: it's night time. Adjusting behavior accordingly") | |
// | |
} | |
it.data.context.lastBehaviorUpdateTime = currentTime | |
} | |
perform { | |
Painting.addPaint { g2 -> | |
g2.color = Color.WHITE | |
g2.drawString("iChopper", 15, 60) | |
g2.drawString("State tree: ${stateTree?.currentStateName}", 15, 80) | |
g2.drawString("Local clock: ${it.data.context.localTime.let { getTimeFormatted(it) }}", 15, 100) | |
g2.drawString("Trees chopped: ${it.data.context.treesChoppedCounter}", 15, 120) | |
g2.drawString("Logs chopped: ${it.data.context.logsChoppedCounter}", 15, 140) | |
} | |
} | |
} | |
} | |
}, | |
onTick = { | |
behaviorTree { | |
sequence { | |
perform { | |
it.data.context.localTime = getCurrentTime() | |
} | |
} | |
} | |
}, | |
onStop = { | |
behaviorTree { | |
sequence { | |
perform { | |
it.data.context.localTime = getCurrentTime() | |
} | |
} | |
} | |
} | |
) | |
) | |
val rootState = buildRootState() | |
stateTree = buildStateTree( | |
rootState, | |
parameters, | |
ctxData, | |
globalTasks, | |
evaluators | |
) | |
while (true) { | |
wait(waitTime) | |
val currentTimeMs = currentTimeMillis() | |
val deltaTimeMs = currentTimeMs - lastUpdateTimeMs | |
val fps = if (deltaTimeMs > 0) 1000 / deltaTimeMs else 0 | |
when (val tickStatus = stateTree?.tick(deltaTimeMs, fps)) { | |
null -> { | |
break | |
} | |
BehaviorTreeStatus.SUCCESS -> { | |
// | |
} | |
BehaviorTreeStatus.FAILURE -> { | |
// | |
} | |
BehaviorTreeStatus.KILL -> { | |
stateTree?.stop() | |
break | |
} | |
} | |
lastUpdateTimeMs = currentTimeMs | |
} | |
} | |
} | |
fun buildRootState() = buildSelectorState( | |
name = "RootSelectorState", | |
childStates = listOf( | |
// Ensure player is logged in -> goto root | |
buildLoginGameState(), | |
// Banking state (inventory is full) -> goto root | |
buildBankingState(), | |
// Move to the area to check for trees if not in area -> goto root | |
buildCheckingAreaState(), | |
// Move to area, select and validate optimal tree to chop down if in area and no tree current tree -> goto root | |
buildVerifyingTreeState(), | |
// Move to tree state -> goto root | |
buildMovingToTreeState(), | |
// Use special attack state -> goto root | |
buildUseSpecialAttackState(), | |
// Is chopping tree state -> goto banking state, special attack state, verifying tree state, root | |
buildChoppingTreeState(), | |
// Chop down the tree state -> goto chopping state, root | |
buildChopDownTreeState() | |
) | |
) | |
fun buildLoginGameState( | |
transitions: List<Transition<AIContextProvider>> = listOf( | |
Transition(null) { Login.isLoggedIn() } | |
) | |
) = buildState( | |
name = "LoginGameState", | |
enterConditions = listOf { !Login.isLoggedIn() }, | |
tasks = listOf(buildLoginGameTask()), | |
transitions = transitions | |
) | |
fun buildLoginGameTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
control { | |
condition { | |
Login.login() | |
} | |
} | |
} | |
} | |
} | |
fun buildMovingToBankState() = buildState( | |
name = "MovingToBankState", | |
tasks = listOf(buildMoveToBankTask()), | |
enterConditions = listOf { | |
!it.data.context.treeManager.isNearBank() | |
}, | |
transitions = listOf( | |
Transition(null) { | |
it.data.context.treeManager.isNearBank() | |
} | |
) | |
) | |
fun buildMoveToBankTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
condition { | |
it.data.context.treeManager.moveToBank() | |
} | |
} | |
} | |
} | |
fun buildBankingState() = buildState( | |
name = "BankingState", | |
tasks = listOf(buildBankLogsTask()), | |
childStates = listOf(buildMovingToBankState()), | |
enterConditions = listOf { | |
it.data.context.treeManager.shouldClearInv() | |
}, | |
transitions = listOf( | |
Transition(null) { | |
!it.data.context.treeManager.shouldClearInv() | |
}, | |
) | |
) | |
fun buildBankLogsTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
condition { | |
it.data.context.bankManager.open() | |
} | |
condition { | |
it.data.context.bankManager.depositInventory() | |
} | |
perform { | |
it.data.context.bankManager.close() | |
} | |
} | |
} | |
} | |
fun buildCheckingAreaState() = buildState( | |
name = "CheckingAreaState", | |
tasks = listOf(buildCheckAreaTask()), | |
enterConditions = listOf { | |
!it.data.context.treeManager.isInCurrentArea() | |
}, | |
transitions = listOf( | |
Transition(null) { | |
it.data.context.treeManager.isInCurrentArea() | |
} | |
) | |
) | |
fun buildCheckAreaTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
perform { | |
it.data.context.treeManager.moveToCurrentArea() | |
} | |
} | |
} | |
} | |
fun buildVerifyingTreeState() = buildState( | |
name = "VerifyingTreeState", | |
tasks = listOf(buildVerifyTreeTask()), | |
enterConditions = listOf { | |
it.parameters[KEY_CURRENT_TREE] === null | |
}, | |
transitions = listOf( | |
Transition(null) { | |
it.parameters[KEY_CURRENT_TREE] !== null | |
} | |
) | |
) | |
fun buildVerifyTreeTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
perform { | |
it.parameters[KEY_CURRENT_TREE] = it.data.context.treeManager.getBestInteractableTreeObject() | |
} | |
} | |
} | |
} | |
fun buildMovingToTreeState() = buildState( | |
name = "MovingToTreeState", | |
enterConditions = listOf { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { obj -> obj.distance() > 7 } ?: false | |
}, | |
tasks = listOf(buildMoveToTreeTask()), | |
transitions = listOf( | |
Transition(null) { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { obj -> obj.distance() <= 7 } ?: false | |
}, | |
Transition(null) { | |
it.parameters.getGameObject(KEY_CURRENT_TREE) === null | |
} | |
) | |
) | |
fun buildMoveToTreeTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.data.context.logger.info("Identified tree died") | |
} | |
perform { | |
it.parameters[KEY_CURRENT_TREE] = null | |
} | |
} | |
} | |
condition { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.moveToTree(tree) { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
!it.data.context.treeManager.isTreeValid(tree) | |
} ?: false | |
} | |
} | |
} | |
} | |
} | |
} | |
fun buildChoppingTreeState() = buildState( | |
name = "ChoppingTreeState", | |
tasks = listOf(buildChoppingTreeTask(), buildAntibanTask()), | |
enterConditions = listOf( | |
{ it.data.context.config.axe.matchesPlayerAnimation() }, | |
{ | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
&& MyPlayer.get().getOrNull()?.isInteractingWithObject(tree) == true | |
} ?: false | |
} | |
), | |
transitions = listOf( | |
Transition(null) { | |
it.parameters.getGameObject(KEY_CURRENT_TREE) === null | |
}, | |
Transition(null) { | |
!it.data.context.config.axe.matchesPlayerAnimation() | |
&& !it.data.context.config.axe.matchesSpecialAttackAnimation() | |
&& it.data.context.idleTickCounter >= 4 // Check for idle time | |
}, | |
Transition(buildBankingState()) { | |
it.data.context.treeManager.shouldClearInv() | |
}, | |
Transition(buildUseSpecialAttackState()) { | |
shouldUseSpecialAttack() | |
} | |
), | |
onEnter = { | |
behaviorTree { | |
sequence { | |
// Reset the idle tick counter on entering the state | |
perform { | |
it.data.context.idleTickCounter = 0 | |
} | |
// Take a snapshot of the initial log count | |
perform { | |
it.data.context.initialLogCount = it.data.context.treeManager.getLogCount() | |
it.data.context.logger.info("onEnter initial log count: ${it.data.context.initialLogCount}") | |
} | |
// Anticipate and set the next tree to hover | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_NEXT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
perform { | |
val currentTree = it.parameters.getGameObject(KEY_CURRENT_TREE) | |
val logger = it.data.context.logger | |
it.data.context.treeManager.findNextTreeToHover(currentTree)?.let { tree -> | |
logger.info("Found next tree during onEnter -> $tree") | |
it.parameters[KEY_NEXT_TREE] = tree | |
} | |
} | |
} | |
} | |
} | |
}, | |
onExit = { | |
behaviorTree { | |
sequence { | |
// Reset the idle tick counter on entering the state | |
perform { | |
it.data.context.idleTickCounter = 0 | |
} | |
// Take a snapshot of the final log count and update chopped counter | |
perform { | |
it.data.context.finalLogCount = it.data.context.treeManager.getLogCount() | |
val logsChopped = (it.data.context.finalLogCount - it.data.context.initialLogCount).coerceAtLeast(0) | |
it.data.context.logsChoppedCounter += logsChopped | |
it.data.context.logger.info("onExit final log count: ${it.data.context.finalLogCount}") | |
it.data.context.logger.info("onExit logs chopped: $logsChopped") | |
} | |
// Check and change state if current tree chopped down | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.data.context.logger.info("Current tree chopped down during onExit") | |
} | |
perform { | |
it.parameters[KEY_CURRENT_TREE] = null | |
} | |
} | |
} | |
// Check and change state if next tree chopped down | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_NEXT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.data.context.logger.info("Next tree chopped down during onExit") | |
} | |
perform { | |
it.parameters[KEY_NEXT_TREE] = null | |
} | |
} | |
} | |
// Set the currentTree if currentTree not valid and if nextTree was found during task execution and is valid still | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.data.context.logger.info("Assign nextTree to currentTree during onExit") | |
} | |
perform { | |
it.parameters[KEY_CURRENT_TREE] = it.parameters[KEY_NEXT_TREE] | |
} | |
perform { | |
it.parameters[KEY_NEXT_TREE] = null | |
} | |
} | |
} | |
} | |
} | |
} | |
) | |
fun buildChoppingTreeTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
// Check and update if current tree chopped down | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.data.context.logger.info("Tree chopped down during chopping task...") | |
} | |
perform { | |
it.data.context.treesChoppedCounter++ | |
} | |
perform { | |
it.parameters[KEY_CURRENT_TREE] = null | |
} | |
} | |
} | |
// Check and update if next tree chopped down | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_NEXT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.parameters[KEY_NEXT_TREE] = null | |
} | |
perform { | |
val currentTree = it.parameters.getGameObject(KEY_CURRENT_TREE) | |
it.data.context.treeManager.findNextTreeToHover(currentTree)?.let { tree -> | |
it.data.context.logger.info("Found next tree during chopping task -> $tree") | |
it.parameters[KEY_NEXT_TREE] = tree | |
} | |
} | |
} | |
} | |
// Hover the spec orb or the next tree | |
selector { | |
sequence { | |
condition { | |
shouldHoverSpecialAttackOrb() | |
} | |
perform { | |
hoverSpecialAttackOrb() | |
} | |
} | |
sequence { | |
condition { | |
it.parameters.getGameObject(KEY_NEXT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
perform { | |
it.parameters.getGameObject(KEY_NEXT_TREE)?.let { tree -> | |
tree.hover() | |
} | |
} | |
} | |
} | |
// Increment or reset the idle tick counter to ensure transition when not chopping | |
perform { | |
if (it.data.context.config.axe.matchesPlayerAnimation() | |
|| it.data.context.config.axe.matchesSpecialAttackAnimation() | |
) { | |
it.data.context.idleTickCounter = 0 | |
} else { | |
it.data.context.idleTickCounter++ | |
} | |
} | |
} | |
} | |
} | |
fun buildChopDownTreeState() = buildState( | |
name = "ChopDownTreeState", | |
tasks = listOf(buildChopDownTreeTask()), | |
enterConditions = listOf { | |
!it.data.context.config.axe.matchesPlayerAnimation() | |
}, | |
transitions = listOf( | |
Transition(buildChoppingTreeState()) { | |
it.data.context.config.axe.matchesPlayerAnimation() | |
}, | |
Transition(null) { | |
it.parameters.getGameObject(KEY_CURRENT_TREE) === null | |
} | |
) | |
) | |
fun buildChopDownTreeTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
selector { | |
condition { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} | |
} | |
sequence { | |
perform { | |
it.data.context.logger.info("Current tree chopped down...") | |
} | |
perform { | |
it.parameters[KEY_CURRENT_TREE] = it.parameters[KEY_NEXT_TREE] | |
} | |
} | |
} | |
selector { | |
condition { | |
MyPlayer.isMoving() && it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.isTreeValid(tree) | |
} ?: false | |
} | |
perform { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
it.data.context.treeManager.interactTreeObject(tree, it.data.context.config.axe) { | |
it.parameters.getGameObject(KEY_CURRENT_TREE)?.let { tree -> | |
!it.data.context.treeManager.isTreeValid(tree) | |
} ?: false | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
fun buildUseSpecialAttackState() = buildState( | |
name = "UseSpecialAttackState", | |
tasks = listOf(buildUseSpecialAttackTask()), | |
enterConditions = listOf { | |
shouldUseSpecialAttack() | |
}, | |
transitions = listOf( | |
Transition(null) { | |
!shouldUseSpecialAttack() | |
} | |
) | |
) | |
fun buildUseSpecialAttackTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
condition { | |
useSpecialAttack() | |
} | |
} | |
} | |
} | |
fun buildAntibanTask() = buildTask<AIContextProvider> { | |
behaviorTree { | |
sequence { | |
perform { | |
it.data.context.gameSettingManager.checkAllCameraTasks() | |
} | |
perform { | |
it.data.context.antibanManager.executeSkillCheckTask(Skill.WOODCUTTING) | |
} | |
perform { | |
it.data.context.antibanManager.executeEntityHoverTask() | |
} | |
perform { | |
it.data.context.antibanManager.executeRandomMouseMoveTask() | |
} | |
} | |
} | |
} | |
private const val SPECIAL_ATTACK_ORB_ROOT_IDX = 160 | |
private val useSpecialAttackOrbAddress: WidgetAddress = WidgetAddress.create( | |
SPECIAL_ATTACK_ORB_ROOT_IDX | |
) { | |
it.actions.any { a -> | |
a.equals("Use", ignoreCase = true) | |
} | |
} | |
private fun shouldUseSpecialAttack(): Boolean { | |
return getWeaponType() == WeaponType.AXE | |
&& canUseSpecialAttack() | |
&& !isSpecialAttackEnabled() | |
} | |
private fun useSpecialAttack(): Boolean { | |
return activateSpecialAttack() | |
} | |
private fun shouldHoverSpecialAttackOrb(): Boolean { | |
return getSpecialAttackPercent() in 80..90 | |
} | |
private fun hoverSpecialAttackOrb(): Boolean { | |
return useSpecialAttackOrbAddress.lookup() | |
.getOrNull() | |
?.let { it.isVisible && it.hover() } | |
?: false | |
} | |
private val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") | |
private fun getCurrentTime(): LocalTime { | |
return LocalTime.now() | |
} | |
private fun getTimeFormatted(currentTime: LocalTime): String { | |
return currentTime.format(formatter) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment