Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static class usage #175

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
470ba92
Moved dependencies
GenerelSchwerz Feb 13, 2023
dde8bbb
Update package.json
GenerelSchwerz Feb 13, 2023
f29f6a2
update to prismarine-registry
GenerelSchwerz Feb 13, 2023
def88d0
remove unused imports
GenerelSchwerz Feb 13, 2023
e525fa4
fixed last mcDataLoader dependency
GenerelSchwerz Feb 13, 2023
4418f3d
big update
GenerelSchwerz Mar 4, 2023
02c7b7b
update again
GenerelSchwerz Mar 4, 2023
f4c3d3b
update again
GenerelSchwerz Mar 4, 2023
8ea7c93
add ts-standard dependency
GenerelSchwerz Mar 5, 2023
5cbe0b6
update
GenerelSchwerz Mar 5, 2023
faeeeac
apply ts-standard fixes
GenerelSchwerz Mar 5, 2023
8d13e02
another update, fully functional nested classes and utility methods t…
GenerelSchwerz Mar 6, 2023
8471b98
updates yet again, restructured examples
GenerelSchwerz Mar 6, 2023
221596e
fix ts-standard issues
GenerelSchwerz Mar 6, 2023
af2e07c
add cloning of states to allow multiple of same behavior, yet unrelat…
GenerelSchwerz Mar 6, 2023
e6c73d8
add cloning of of nested machines as well
GenerelSchwerz Mar 6, 2023
a556e64
fix ts-standard issues
GenerelSchwerz Mar 6, 2023
89e50f0
update to examples
GenerelSchwerz Mar 6, 2023
e895057
update webserver example
GenerelSchwerz Mar 6, 2023
07f4dd0
update to stateExiting not matching cloned types of class
GenerelSchwerz Mar 6, 2023
44a35e9
update to stateExiting not matching cloned types of class
GenerelSchwerz Mar 6, 2023
89c6375
cloning state machines now supported.
GenerelSchwerz Mar 6, 2023
0750526
statically setting positions for states is now supported via the Webs…
GenerelSchwerz Mar 6, 2023
8366e87
updates to be more faithful to original
GenerelSchwerz Mar 6, 2023
4c1fbd6
update to readme
GenerelSchwerz Mar 6, 2023
c1b97ca
update readme
GenerelSchwerz Mar 6, 2023
6ace632
update readme
GenerelSchwerz Mar 6, 2023
388a5af
update readme
GenerelSchwerz Mar 6, 2023
3a292ba
throwaway commit
GenerelSchwerz Mar 9, 2023
ba391f7
fix failing ts-standard
GenerelSchwerz Mar 9, 2023
24be427
many different updates included, check personal repo for commit log
GenerelSchwerz Mar 9, 2023
458b2a4
improved external typings
GenerelSchwerz Mar 10, 2023
e070fd9
update
GenerelSchwerz Mar 10, 2023
7dbc605
Big improvements, builders for both transitions and nested state mach…
GenerelSchwerz Mar 10, 2023
be777ab
fix failing ts-standard
GenerelSchwerz Mar 10, 2023
e56bcd5
deprecate old builders
GenerelSchwerz Mar 10, 2023
0f83c2f
fixed fail ts-standard
GenerelSchwerz Mar 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# specifically shouldn't be published to npm
.gitignore
.github/
src/
examples/
tsconfig.json
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
93 changes: 40 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,62 +47,49 @@ const bot = mineflayer.createBot({ username: "Player" });
// Load your dependency plugins.
bot.loadPlugin(require('mineflayer-pathfinder').pathfinder);

// Import required structures.
const { BotStateMachine, buildTransition, buildNestedMachineArgs } = require('mineflayer-statemachine')

// Import required behaviors.
// Note: Rename behaviors by import schema here.
const {
StateTransition,
BotStateMachine,
EntityFilters,
BehaviorFollowEntity,
BehaviorLookAtEntity,
BehaviorGetClosestEntity,
NestedStateMachine } = require("mineflayer-statemachine");

// Wait for our bot to login.
bot.once("spawn", () =>
{
// This targets object is used to pass data between different states. It can be left empty.
const targets = {};

// Create our states
const getClosestPlayer = new BehaviorGetClosestEntity(bot, targets, EntityFilters().PlayersOnly);
const followPlayer = new BehaviorFollowEntity(bot, targets);
const lookAtPlayer = new BehaviorLookAtEntity(bot, targets);

// Create our transitions
const transitions = [

// We want to start following the player immediately after finding them.
// Since getClosestPlayer finishes instantly, shouldTransition() should always return true.
new StateTransition({
parent: getClosestPlayer,
child: followPlayer,
shouldTransition: () => true,
}),

// If the distance to the player is less than two blocks, switch from the followPlayer
// state to the lookAtPlayer state.
new StateTransition({
parent: followPlayer,
child: lookAtPlayer,
shouldTransition: () => followPlayer.distanceToTarget() < 2,
}),

// If the distance to the player is more than two blocks, switch from the lookAtPlayer
// state to the followPlayer state.
new StateTransition({
parent: lookAtPlayer,
child: followPlayer,
shouldTransition: () => lookAtPlayer.distanceToTarget() >= 2,
}),
];

// Now we just wrap our transition list in a nested state machine layer. We want the bot
// to start on the getClosestPlayer state, so we'll specify that here.
const rootLayer = new NestedStateMachine(transitions, getClosestPlayer);
BehaviorFindEntity: FindEntity,
BehaviorFollowEntity: FollowTarget,
BehaviorLookAtEntity: LookAtTarget
} = require('mineflayer-statemachine/lib/behaviors')

// Util function to find the nearest player.
const nearestPlayer = (e) => e.type === 'player'

const transitions = [

// We want to start following the player immediately after finding them.
// Since BehaviorFindEntity finishes instantly, we will transition almost immediately.
buildTransition('findToFollow', FindEntity, FollowTarget)
.setShouldTransition(state => state.foundEntity()),

// We can start our state machine simply by creating a new instance.
new BotStateMachine(bot, rootLayer);
});
// If the distance to the player is less than two blocks, switch from the followPlayer
// state to the lookAtPlayer state.
buildTransition('followToLook', FollowTarget, LookAtTarget)
.setShouldTransition(state => state.distanceToTarget() < 2),

// If the distance to the player is more than two blocks, switch from the lookAtPlayer
// state to the followPlayer state.
buildTransition('lookToFollow', LookAtTarget, FollowTarget)
.setShouldTransition(state => state.distanceToTarget() >= 2)
]

// Now we just wrap our transition list in a nested state machine layer. We want the bot
// to start on the getClosestPlayer state, so we'll specify that here.
// We can specify entry arguments to our entry class here as well.
const root = buildNestedMachineArgs('rootLayer', transitions, FindEntity, [nearestPlayer])

// We can start our state machine simply by creating a new instance.
// We can delay the start of our machine by using autoStart: false
const machine = new BotStateMachine({bot, root, autoStart: false});

// Start the machine anytime using BotStateMachine.start()
bot.once('spawn', () => machine.start())
```

### Documentation
Expand Down
210 changes: 210 additions & 0 deletions examples/advanced.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// check for correct usage at startup
if (process.argv.length < 4 || process.argv.length > 6) {
console.log("Usage : node lookatplayers.js <host> <port> [<name>] [<password>]");
process.exit(1);
}

// Create your bot
const mineflayer = require('mineflayer');
const bot = mineflayer.createBot({
host: process.argv[2],
port: parseInt(process.argv[3]),
username: process.argv[4] ? process.argv[4] : "statemachine_bot",
password: process.argv[5],
});

// Load your dependency plugins.
bot.loadPlugin(require("mineflayer-pathfinder").pathfinder);

// Import required structures.
const {
BotStateMachine,
buildTransition,
buildNestedMachine,
StateMachineWebserver,
WebserverBehaviorPositions
} = require("../");

// Import required behaviors.
// Note: Behavior renaming happens here.
const {
BehaviorWildcard: Wildcard,
BehaviorIdle : Idle,
BehaviorExit : Exit,
BehaviorFindEntity : FindEntity,
BehaviorLookAtEntity : LookAtTarget,
BehaviorFollowEntity : FollowEntity,
} = require("../lib/behaviors")

// Have a class that requires arguments and you're too lazy to provide them every time?
// No worries, now you can transform this class into a new one using these provided arguments!
// Yes, it's strongly typed. Don't ask how, it took a while.
// For those interested, you can see what remaining arguments you need to pass by hovering the variable.
const FindPlayer = FindEntity.transform("FindPlayer", [e=>e.type === "player"])
const CustomFollowEntity = FollowEntity.transform("FollowPlayer", [{followDistance: 2}])

// create an array storing all of the transitions we will build.
const comeToMeTransitions = [

// Transitions occur in order inside of this array.
// We first want to check whether or not we have found an entity.
// If we do not find an entity, go to exit class of this machine.
// We should also inform our user that there was no nearby player.
buildTransition("findToExit", FindPlayer, Exit)
.setShouldTransition(state => !state.foundEntity())
.setOnTransition(() => bot.chat('Failed to find entity!')),

// This transition will only run if the previous one was not met.
// So, if we find an entity, transition into our follow entity method!
// We should also tell our user that we found an entity.
buildTransition("findToFollow", FindPlayer, CustomFollowEntity)
.setShouldTransition(state => state.foundEntity())
.setOnTransition(() => bot.chat('Found entity!')),

// We have entered the follow state IFF we have found an entity.
// We will stay in this state until it completes, then transition to exit.
// We should tell our user that we reach our goal, when we do.
buildTransition('followToExit', CustomFollowEntity, Exit)
.setShouldTransition(state => state.isFinished())
.setOnTransition(() => bot.chat('Reached goal, finishing!'))
]

// This machine will store all of of the transitions and provide usecases for them.
// This machine will start on the Enter state (provided)
// and will signal being finished IFF an Exit state is provided.
const comeMachine = buildNestedMachine('comeToMe', comeToMeTransitions, FindPlayer, Exit)


// Same logic as above except for a new machine.
const followAndLookTransitions = [

// Here is our first newly added feature: the wildcard.
// The Wildcard class is provided by mineflayer-statemachine, you have to use it.
// This transition will always be checked, regardless of what is currently running.
// In this example, this is used to immediately terminate this statemachine regardless of current state.
buildTransition('wildcardExit', Wildcard, Exit),

// We have entered this machine, looked for a player, and didn't find one.
// We will exit this statemachine.
// We should report to user that we did not find an entity.
buildTransition("findToExit", FindPlayer, Exit)
.setShouldTransition(state => !state.foundEntity())
.setOnTransition(() => bot.chat('Failed to find entity!')),

// We're looking for a player, and now we found one!
// We should move to the LookAtTarget state.
buildTransition("findToLook", FindPlayer, LookAtTarget)
.setShouldTransition(state => state.foundEntity()),

// We are looking at a player, but he's moving away!
// We should start following them if they get too far away (2 blocks)
buildTransition("lookToFollow", LookAtTarget, CustomFollowEntity)
.setShouldTransition(state => state.distanceToTarget() > 2),

// We were following a player, but now they're really close!
// We should stop following and simply look at them if they're so close.
buildTransition("followToLook", CustomFollowEntity, LookAtTarget)
.setShouldTransition(state => state.distanceToTarget() <= 2),

// Another new feature! Multiple parents for transitions.
// In this example, both of these classes have the method `distanceToTarget`
// So it's safe to call this method! (in typescript, this is strongly checked)
// If this state is in either of these parent states, this transition will trigger
//
// In this example, we will exit the program if the player gets too far away.
buildTransition('followingTooFar', [CustomFollowEntity, LookAtTarget], Exit)
.setShouldTransition(state => state.distanceToTarget() > 32)
]

// Another creation of the machine.
// However, notice the Array of Behaviors for exit.
// That's right, we can have multiple exit states now! Why? I have no idea yet. But you can!
// NOTE: I *may* make it so machines have easy access to one another's active states. Maybe.
const followMachine = buildNestedMachine('followAndLook', followAndLookTransitions, FindPlayer, [Exit])


// Yet more transitions, except these encompass the previous state machines!
const rootTransitions = [

// wildcard!
// In this example, why bother writing "return to idle" statements for every machine?
// Use one wildcard to handle them all! :grin:
buildTransition('wildcardRevert', Wildcard, Idle)
.setShouldTransition(state => state.isFinished()),

// Switch to comeMachine
buildTransition('come', Idle, comeMachine)
.setOnTransition(() => bot.chat('coming')),

// Switch to followMachine
buildTransition('follow', Idle, followMachine)
.setOnTransition(() => bot.chat('Following'))
]

// We've made it!
// Build the root machine and we're done (mostly)
const root = buildNestedMachine("rootLayer", rootTransitions, Idle);


// simply make a new machine!
// NOTE: autoStart: false is used because I don't want to wrap everything in bot.spawn
// up to you, default is true.
// Congrats! All statemachine functionality is done.
const machine = new BotStateMachine({ bot, root, autoStart: false });

// might as well provide some functionality to commands.
bot.once("spawn", () => {
machine.start()

// todo: add better manual transition switching.
bot.on('chat', (user, message) => {
const [cmd] = message.trim().split(' ')
if (cmd === 'come') root.transitions[1].trigger()
if (cmd === 'follow') root.transitions[2].trigger()
if (cmd === 'stop') {
if (machine.activeMachine === followMachine) {
followMachine.transitions[0].trigger();
} else {
bot.chat('We are currently not following!')
}
}
})
});

// EVERYTHING AFTER THIS IS OPTIONAL.

// We can set up static state offsets (x, y) based on their parent machine
// Usage is obvious, so have fun reading.
const offsets = new WebserverBehaviorPositions()
// comeMachine
.set(FindPlayer, 100, 350, comeMachine)
.set(CustomFollowEntity, 350, 350, comeMachine)
.set(Exit, 600, 350, comeMachine)
// followMachine
.set(Wildcard, 350, 650, followMachine)
.set(Exit, 350, 350, followMachine)
.set(FindPlayer, 350, 50, followMachine)
.set(LookAtTarget, 100, 200, followMachine)
.set(CustomFollowEntity, 100, 500, followMachine)
// rootMachine
.set(Idle, 350, 350, root)
.set(Wildcard, 350, 50, root)
.set(followMachine, 100, 600, root)
.set(comeMachine, 600, 600, root)


// create a debug server using the root machine and its presetPositions.
const webserver = new StateMachineWebserver({stateMachine: machine, presetPositions: offsets})

// this can be started whenever, so have at it!
webserver.startServer();


// debug, displaying 0ms switching of states.
let time = performance.now()
machine.on('stateEntered', (type, nested, state) => {
const now = performance.now();
console.log(type.stateName, state.stateName, now - time);

time = now;
})
62 changes: 62 additions & 0 deletions examples/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// check for correct usage at startup
if (process.argv.length < 4 || process.argv.length > 6) {
console.log("Usage : node lookatplayers.js <host> <port> [<name>] [<password>]");
process.exit(1);
}

// Create your bot
const mineflayer = require('mineflayer');
const bot = mineflayer.createBot({
host: process.argv[2],
port: parseInt(process.argv[3]),
username: process.argv[4] ? process.argv[4] : "statemachine_bot",
password: process.argv[5],
});

// Load your dependency plugins.
bot.loadPlugin(require("mineflayer-pathfinder").pathfinder);


// Import required structures.
const { BotStateMachine, buildTransition, buildNestedMachineArgs } = require('@nxg-org/mineflayer-statemachine')

// Import required behaviors.
// Note: Rename behaviors by import schema here.
const {
BehaviorFindEntity: FindEntity,
BehaviorFollowEntity: FollowTarget,
BehaviorLookAtEntity: LookAtTarget
} = require('@nxg-org/mineflayer-statemachine/lib/behaviors')

// Util function to find the nearest player.
const nearestPlayer = (e) => e.type === 'player'

const transitions = [

// We want to start following the player immediately after finding them.
// Since BehaviorFindEntity finishes instantly, we will transition almost immediately.
buildTransition('findToFollow', FindEntity, FollowTarget)
.setShouldTransition(state => state.foundEntity()),

// If the distance to the player is less than two blocks, switch from the followPlayer
// state to the lookAtPlayer state.
buildTransition('followToLook', FollowTarget, LookAtTarget)
.setShouldTransition(state => state.distanceToTarget() < 2),

// If the distance to the player is more than two blocks, switch from the lookAtPlayer
// state to the followPlayer state.
buildTransition('lookToFollow', LookAtTarget, FollowTarget)
.setShouldTransition(state => state.distanceToTarget() >= 2)
]

// Now we just wrap our transition list in a nested state machine layer. We want the bot
// to start on the getClosestPlayer state, so we'll specify that here.
// We can specify entry arguments to our entry class here as well.
const root = buildNestedMachineArgs('rootLayer', transitions, FindEntity, [nearestPlayer])

// We can start our state machine simply by creating a new instance.
// We can delay the start of our machine by using autoStart: false
const machine = new BotStateMachine({bot, root, autoStart: false});

// Start the machine anytime using BotStateMachine.start()
bot.once('spawn', () => machine.start())
Loading