I wanted to code Python stuff in Minecraft, so added a Python Block and movable Python Hand which can be programmed with Python. The code may be edited in-game using standard writeable books, which suck, so I also created an extended book which provides more space, cursor and mouse control, syntax checking and some other features.
Install using the download and follow the standard Forge mod installation instructions.
Copyright (c) 2017 Richard Jones <richard@mechanicalcat.net> All Rights Reserved
Once you've created a Python Block or Hand you can write some Python code in a Writeable Book (or the superior Python Code Book), right click it on a the Python Hand or Block and have that code be run.
If you want to delay the running of your code you can put all the code in a run() function. That function will then be run only when you right click the Python Wand on the hand or block.
Also, Python Blocks may have other events that they handle:
- Redstone Power
- If you define a powerOn() or powerOff() function these will be called when the block receives, or loses redstone power (eg from redstone wiring, or just a button placed on the block.)
- Time Passsing
- If you define a tick() function this will be called about 20 times per second.
Python Wands are used to create Python Code Books and interact with Python Blocks and Python Hands:
Python Blocks sit in the world and can do stuff:
Python Hands can move around in the world and can do stuff.
Python Code Book is a superior editable book that will also check your Python code for syntax errors:
The Hand and Block share a bunch of functionality. For example, to make a message appear in the in-game chat, you would use:
chat("hello, world!")
From this point on, I will refer to the block name, but you can also use the "hand" name here too:
runner
- This is the game "thing" that is running the code. If a player runs the code using a wand, then the runner will be that player. If the code is installed in a Python Block and triggered by redstone or any event other than the player using the block then the runner will be the Python Block (if the player uses the block then the runner is the player again). If the code is installed in a Python Hand then the same rules apply as with Python Blocks, except of course that the runner will be the Python Hand.
pos
The block-space position of the block or hand. Block space uses only integer (whole) numbers to locate things in the world using X (East/West), Y (Up/Down) and Z (North/South) coordinates. You can get a new pos by adding or moving the block's pos around, like:
pos.up() pos.east() pos.add(1, 0, 4) # East/X 1, Up/Y 0 and North/Z 4
colors
- A list of all the standard Minecraft dye color names.
facings
- A list of all the standard Minecraft facing names.
players
- Access to the players in the game, more information below.
Choose colors and facings with random.choice() for extra creativity!
Both Python Blocks and Python Hands may define a run()
function that
will be invoked when the block or hand is activated (right-clicked) by the
player. For example, on a block:
def run(): block.firework()
Or you can do something more fancy:
def run(): all = ", ".join(p.name for p in players.all()) runner.chat("hello", all)
Doc TBD:
block.isPowered() # returns boolean block.firework() block.spawn('Sheep') # see REFERENCE.txt for a list
The spawn() method also returns the entity so you can do additional things:
sheep = block.spawn('Sheep') sheep.potion('glowing')
Warning: you are strongly advised to put all code inside a function like run() or powerOn() - code not in such a function will be executed every time the block is loaded!
Python Blocks may define additional event handler functions related to world interaction with the block:
def powerOn(): # invoked when a redstone signal powers block def powerOff(): # invoked when redstone signal stops powering block def tick(): # invoked every server tick (20 times a second?) def onPlayerWalk(player): # invoked when the player walks over the block def onEntityWalk(entity): # invoked when a non-player entity walks over the block
For example:
def powerOn(): block.firework()
or:
def powerOn(): block.spawn('Zombie')
Players and Entities passed into run / onPlayerWalk / onEntityWalk have the following methods:
player.move(x, y, z) # move by that amount player.tp(x, y, z) # teleport to those coordinates player.tp(pos) # or to a location given by a position object
Living entities have the following methods:
player.potion("jump") # affect with a potion effect name - only living entities # the REFERENCE.txt file lists potion names
Set 'em on fire:
player.ignite() player.ignite(10) # default is 4 seconds, this is 10 seconds
Say hello:
player.chat("hello, world!")
Players also have a name:
player.chat("hello, %s!" % player.name)
Chat can also take a comma-separated list of things to put in the chat message, just like Python's print() function can:
player.chat("hello", player)
This would display something like:
hello Richard
Players have achievements. Achievements are listed in REFERENCE.txt and the standard Minecraft achievements have IDs starting with "achievement." which may be omitted for convenience. You may give and take achievements:
player.giveAchievement('achievement.overkill') player.takeAchievement('overkill') # note the "achievement." prefix is optional
You also have access to all of the current players through the players
global:
players.all() players.random() players.get('Richard') # may throw an error, of course players.closest(pos) # closest player to the position, within 10 # blocks, may return None
There's also a few type-checking methods on entities:
player.isPlayer() # returns true for just players mob.isMob() # returns true for all living entities except the player player.isMob() # returns false mob.isEntity() # returns true for all entities entity.isEntity() # including minecarts and other non-living entities
Give the player walking over the block a speed buff or slowness nerf depending on whether the block has redstone power or not:
def onPlayerWalk(player): if block.isPowered(): player.potion("speed") else: player.potion("slowness")
More complete docs TBD:
hand.forward() hand.forward(10) hand.back(5) hand.sidle(5) # move sideways left hand.reverse() hand.left() hand.right() hand.face(‘north’) # ('south', 'east', 'west') hand.move(x, y, z) # remember where the hand is and restore it after we do some things with hand.remember() as pos: hand.left() hand.forward(10) # hand is now back at pos, and has the same facing
Warning: you are strongly advised to put all code inside a function like run() or powerOn() - code not in such a function will be executed every time the block is loaded!
Python Blocks and Hands may place blocks in the world. They both have the same methos listed below, the only difference is that blocks have a "pos" first argument, so for example, placing a ladder:
hand.ladder(8, 'ladder') # place a bunch of the block in a vertical line block.ladder(pos.up(), 8, 'ladder') # place the ladder on top of this block
Note that it is possible to place a block in the same position as the Python Block which will remove it from the game!
The block placement functions are:
hand.put('cobblestone') # the REFERENCE.txt file lists block names hand.clear() # set to air - hand.put('air') hand.line(5, 'stone') hand.ladder(8, 'ladder') # place a bunch of the block in a vertical line hand.wall(5, 3, 'planks') # depth, height hand.floor(5, 5, 'stonebrick') # width, depth hand.circle(5, 'stone') # unfilled, centered on hand hand.circle(5, 'stone', fill=True) # filled hand.ellipse(5, 10, 'stone') # also accepts fill=True # beds and door special double blocks are handled hand.put('wooden_door') hand.put('bed') # more complex constructions hand.cube(5, 5, 4, 'stonebrick') # width, height, depth; is hollow hand.roof(9, 9, 'oak') # oak "hip" roof
Roof materials are oak, stone, brick, stone_brick, nether_brick, sandstone, spruce, birch, jungle, acacia, dark_oak, quartz, red_sandstone and purpur. Technically anything else registered with a block named "blockname" stairs name "blockname_stairs" and "blockname_slab" will suffice, with some workarounds the mod does for wood and stone variants of things. Actually, any old block will work, but if you want nice aesthetically pleasing angles, you'll want to use one of the stair compatible ones. Also, the stairs and slabs keep the monsters from spawning, but this will work:
hand.roof(5, 5, 'wool', color='red')
Block variations are handled through keyword arguments. All of the above block- placing functions accept the following keywords:
# different plank types hand.put('planks', type='acacia') # if you use an invalid keyword, it will fail hand.put('planks', color='red') # different stone slab types hand.put('stone_slab', type='quartz', half='top') # if a block has orientation, it is taken from the hand's direction # but if there's a surface in the way we'll try to mount the thing # on that surface hand.put(8, 'torch') # colored blocks hand.put('wool', color='red') # or 'stained_glass', 'stained_hardened_clay' import random hand.put('wool', color=random.choice(colors)) # stairs - non 'straight' shapes are tricky to get right - they must # join up with another stair piece to retain their non-straight shape # or they will revert to straight! hand.put('oak_stairs', facing='left', # or right, back, and cardinals half='top', shape='outer_right')
You may alter block variations after they've been put down:
hand.put('wool', color='red') hand.alter(color='yellow') # any of the keywords above are acceptable
Roof styles include "hip", "gable", "box-gable" (flat ended gable), "shed" (sloped one direction) and "box-shed". To get a box gable with overhang you could:
hand.roof(7, 5, 'oak', style='box-gable') hand.sidle(1) hand.roof(9, 5, 'dark_oak', style='gable')
By default roofs are filled to prevent spawning, but you can turn it off:
hand.roof(7, 5, 'oak', fill=False)
Note that these examples put all the code inside functions so they're not executed every time the hand is loaded!
An example making a little house:
def run(): hand.down(1) hand.cube(7, 7, 5, 'planks', type='oak') hand.up(1) with hand.remember(): hand.up(4); hand.back(2); hand.sidle(1); hand.roof(9, 9, 'dark_oak', style='gable') hand.sidle(-3) hand.put('wooden_door') hand.forward(3) hand.put('torch') hand.forward() hand.put('bed') hand.left(); hand.forward(1) hand.put('crafting_table') hand.sidle(1) hand.put('chest') hand.sidle(1) hand.put('furnace')
A more complete example which creates a little two-storey tower with a door, bed and ladder from ground up to the roof. Put each of these functions on a different page of the book:
# page 1: the basic tower structure def tower(): hand.down() hand.circle(5, 'cobblestone', fill=True) for i in range(9): hand.up() if i in (3, 7): hand.circle(5, 'planks', fill=True) hand.circle(5, 'stone') if i in (0, 4, 8): hand.put('torch') # page 2: door and ladder access def access(): hand.back(6) for i in range(3): hand.clear() hand.up() hand.down() hand.forward() hand.put('planks') hand.back() hand.put('torch') hand.forward() hand.down(2) hand.put('wooden_door') hand.forward(8) hand.ladder(8, 'ladder') # page 3: ground floor furnishings def furnish(): hand.left() hand.forward(2) hand.put('bed') hand.sidle(1) hand.put('crafting_table') hand.sidle(1) hand.put('chest') hand.sidle(1) hand.put('furnace') # page 4: the complete tower def run(): with hand.remember(): tower() with hand.remember(): access() with hand.remember(): furnish() with hand.remember(): hand.back(6) hand.sidle(5) hand.up(9) hand.roof(11, 11, 'dark_oak', fill=False)
Roof demo:
def run(): STYLES = ["hip", "gable", "shed", "box-gable", "box-shed"] def roofs(fill): for style in STYLES: for i in range(4): hand.forward(2) hand.roof(7, 5, 'oak', style=style, fill=fill) hand.left() hand.forward(2) hand.forward(20) with hand.remember(): roofs(False) hand.sidle(20) roofs(True)
When held in the main hand with a book in the off hand, the wand
will execute the code in the book. If the book defines an invoke
function, it can do extra things:
def invoke(): runner.chat('hello')
The runner variable is always the player holding the wand.
By adding a target
argument to your invoke function you can
cast wither on mobs:
def invoke(target): if target and target.isMob(): target.potion('wither')
Noting that the target may be None, if you're not pointing at anything! Or, teleport somewhere:
def invoke(target): if target and target.isBlock(): runner.tp(target.pos.up())
A full set of standard Minecraft commands (think command blocks) are available for calling as functions. The arguments to the command functions are the same as the standard Minecraft commands themselves, so for example the Minecraft command block command:
time set day
becomes:
def onPlayerWalk(player): time('set', 'day')
Other examples - noting that all command arguments are to be provided as strings:
time('set', 'day') toggledownfall() clear('minecraft:golden_sword', '-1', '-1', '{ench:[{id:16s,lvl:1s}]')
Some commands have slightly nicer options. The following are equivalent:
tp('~3', '~10', '~5', '@r') players.random().move(3, 10, 5) # fewer target selectors here though # assuming we're at pos (-609, 4, 1045) setblock('-609', '4', '1045', 'stone', .... and on) hand.put('stone') achievement('give', 'achievement.overkill', 'Richard') player.giveAchievement('overkill')
The command will be invoked by the runner active for the code. This mostly just affects whether the player will see a chat message of the command result; but it also sets the default target of the action for commands like "achievement", "tp", and so on.
- 1.9 (unreleased)
- Bug fix in Python code exception handling (thanks suguby!)
- 1.8
- Completed the list of entities spawnable
- Many bugfixes
- 1.7
- All block placement methods can now specify block variation keywords
- Added handling of plank types in block variations
- Python Blocks can now do all the block placement things
- Added roof() with various styles
- Added half and type keyword argument handling for put()ing of slabs and stone slabs in particular
- Added standard Minecraft command invocation
- Added alter() to change variations of existing blocks
- Altered wand so it invokes code in an off-hand book
- player.ignite() ... because (also works as creeper.ignite())
- 1.6
- Altered the hand store/restore position methods to be a context manager
- Added facing, half and shape and color keyword argument handling for put() which allows placing colored blocks (wool, glass, ...) and stairs. Also allows facing to be different to that of the hand when placed.
- Added top-level "colors" list of the standard Minecraft color names
- Hand no longer put()s things in its current position, always puts in faced position
- Code is now saved with block/hand and kept with harvested items for restoration when placed in world again
- The run() function may now optionally accept a player argument
- 1.5
- Add player/entity walk event
- Initialise Python on startup, rather than on first object use in game
- 1.4
- Added floor(), wall() and cube()
- Added sidle() for moving sideways
- Correct some put() attachment oddities, is more consistent now
- 1.3
- Replaced blocks, items and entities with string inputs.
- 1.2
- Moved chat/lava/water/clear to be top-level functions
- Lots of documentation
- 1.1
- Packaging fixes (removed the .exe files from the jython redist)
- 1.0
- Initial release! Had the Python Code Book, Hand, Block and Wand.
This mod is open source and contributors are welcomed! The project is hosted on github. If you need help with git, please let me know!
This code follows the Minecraft Forge installation methodology. It will apply some small patches to the vanilla MCP source code, giving you and it access to some of the data and functions you need to build a successful mod.
Note also that the patches are built against "unrenamed" MCP source code (aka srgnames) - this means that you will not be able to read them directly against normal code.
Three steps are needed to build this mod:
Check this git repository out:
git clone git@github.com:r1chardj0n3s/pycode-minecraft.git
You should probably fork your own copy of the repository on github and clone that rather than clone my repository directly.
Run the gradle setup:
Windows: "gradlew setupDecompWorkspace"
Linux/Mac OS: "./gradlew setupDecompWorkspace"
After all that finished, you're left with a choice:
For eclipse, run "gradlew eclipse" (./gradlew eclipse if you are on Mac/Linux) then open Eclipse and switch your workspace to /eclipse/
If you preffer to use IntelliJ, steps are a little different:
If at any point you are missing libraries in your IDE, or you've run into problems you can run "gradlew --refresh-dependencies" to refresh the local cache.
"gradlew clean" to reset everything (this does not affect your code) and then start the processs again.
You should now be able to compile and run minecraft with this mod.
For more details update more often refer to the Forge Forums: http://www.minecraftforge.net/forum/index.php/topic,14048.0.html
Update the version string in:
build.gradle
Then run:
./gradlew build
And upload the .jar file from build/libs/
.
- figure out what BlockStoneSlab "seamless" does, and how isDouble works?
- consider renaming the put argument "type" to "variant"?
- clear book title field on click
This is not an exhaustive list, and should probably be put into github issues.
- editing
- replace vertical field cursor with area field one for consistency
- selection-based copy / cut / paste
- scrolling rather than paging?
- add help button (describe key controls, mouse control)
- blocks / items / entities listing somehow
- blocks and hands
- spawn error report with traceback on error
- model replacement (OBJ)
- inventory - with survival mode only able to put blocks from inventory
- copy book name to hand/block
- wand
- bring up a REPL when activated against air?
- REPL would want to have auto-complete
- blocks
- pull from inventory, push out
- generate redstone power
- texture map replacement
- replace more vanilla commands with nicer API
- hand
- more roof generation styles
- tick() handling
- code
- consider allowing event handlers to be generators, to hold state?