Treecapitator Help

Discussion in 'CommandHelper' started by Chloe Sanders, Dec 1, 2017.

  1. Chloe Sanders

    Chloe Sanders New Member

    This one is going to be a little more complicated >.< I think I know how I'm going to do this the main problem is I dont know how to search for blocks near a location.

    How do I search for blocks of a specific type in a 2 block radius?
  2. PseudoKnight

    PseudoKnight Well-Known Member

    Ya, recursion can be tricky. (And if you do it wrong you'll end up freezing your server.) It's hard to explain any parts of this without doing the whole thing. There's multiple ways of doing this, but I'll give you one. (i haven't actually done this before, so I might have not thought of something important) Essentially you'll want to save a starting location, put it in a new array of locations. Then create a loop using the array of locations. Check adjacent blocks to the side and upwards (not downwards!) using get_block_at(x, y, z, world) format by increasing and/or decreasing one those values by 1 until you've checked all appropriate sides. Before you check the block type you have to make sure that the location you're checking is not already in your array of locations. If one of those blocks matches, then you break_block() it and push that location to your array. You might also want a distance check or an array size check to make sure you're not going too far (multiple trees intersecting each other). Also, you might want to save the first log type and leaf type you encounter so that you only remove those blocks in the future (multiple tree types intersecting). You could also put the block breaks in an execution queue so that it animates upwards.
  3. Chloe Sanders

    Chloe Sanders New Member

    I see how I can do this with what you have told me but it feels really messy like how I would need to manually code the log radius for each tree seperatly due to certain types of trees need special treatment. For example an acacia tree would need to be checked for logs in a radius of 2 since it can have diagonal logs that wont be picked up by blocks north, east, south, west and up. Do you think it would be possible to have some code that would automatically check all blocks in a radius based off a command without having to manually do it? For example I made this command:
    register_command('radius', array(
      'description': 'Radius Check',
      'usage': '/radius <block radius>',
      'permission': 'radius.test',
      'noPermMsg': colorize('&4You do not have permission to use this command'),
      'executor': closure(@alias, @sender, @args) {
        if(array_index_exists(@args, 0)) {
          if(@args[0] >= 10) {
            msg (colorize('&cA radius of&f ' . @args[0] . ' &cis too large!'))
          } else {
            msg (colorize('&6All blocks in a radius of ' . @args[0] . ' at x:&a ' . pcursor()[0] . ' &6y: &a' . pcursor()[1] . ' &6z: &a' . pcursor()[2] . '&6:'))
            //code to search all blocks in a <block radius>
        } else {
          msg (colorize('&cCommand Usage: &f/radius <block radius>'))
    If I could get the code checking for blocks accuratly doing /radius 1, /radius 2, /radius 3 etc... it would make adding a treecapitator much easier
  4. PseudoKnight

    PseudoKnight Well-Known Member

    If the first log is acacia, use 4 more checks for diagonal-up blocks.
  5. Chloe Sanders

    Chloe Sanders New Member

    So there is no easier way of doing something like this?
    proc(_treecheck, @location, @block) {
      @location['x'] += -1
      if (get_block_at(@location) == @block['type'] . ":" . @block['data']) {
      @location['x'] += +2
      if (get_block_at(@location) == @block['type'] . ":" . @block['data']) {
      @location['x'] += -1
      @location['z'] += -1
      if (get_block_at(@location) == @block['type'] . ":" . @block['data']) {
      @location['z'] += +2
      if (get_block_at(@location) == @block['type'] . ":" . @block['data']) {
      @location['x'] += -1
      if (get_block_at(@location) == @block['type'] . ":" . @block['data']) {
    bind(block_break, null, null, @tree_drop) {
      array @trees = array('162:1': null);
      if(array_index_exists(@trees, @tree_drop['block']['type'] . ":" . @tree_drop['block']['data'])) {
    its hurting my head just thinking about it :confused: this currently only does north, east, south, west and 1 diaganal direction not done it for blocks upwards and such yet as its slowly making me go insane :p
  6. PseudoKnight

    PseudoKnight Well-Known Member

    Ya, I don't blame you. This is not an easy thing to code. You're currently not checking if you've already checked a location, so this would loop forever, freezing your server. And yes, there are ways of making this simpler (easier? easy can sometimes just be brute forcing, which is not always good). There's a lot of redundancy there you can wrap in a proc. Also you don't have to modify the location reference, but if you do, it will be helpful to create a copy of it for the array of locations. (eg. @location = @location[])
  7. Chloe Sanders

    Chloe Sanders New Member

    Actually I have as it will only continue to loop if the original block is found "if (get_block_at(@location) == @block['type'] . ":" . @block['data']) {"

    I actually made a treecapitator ages ago using a plugin called "Skript" but I wasn't a huge fan of the plugin and you always had to download loads of addon plugins for basic features. It was much easier for me in skript however as there is a feature where you loop all blocks at a certain location in a given radius - just to showcase that here was the code
    script load:
      set {csk_trees::oak_tree::trunk} to "17:0|17:4|17:8"
      set {csk_trees::oak_tree::leaves} to "18:4|18:8"
      set {csk_trees::spruce_tree::trunk} to "17:1|17:5|17:9"
      set {csk_trees::spruce_tree::leaves} to "18:1|18:9"
      set {csk_trees::birch_tree::trunk} to "17:2|17:6|17:10"
      set {csk_trees::birch_tree::leaves} to "18:2|18:10"
      set {csk_trees::jungle_tree::trunk} to "17:3|17:7|17:11"
      set {csk_trees::jungle_tree::leaves} to "18:3|18:11"
      set {csk_trees::acacia_tree::trunk} to "162:0|162:4|162:8"
      set {csk_trees::acacia_tree::leaves} to "161:0|161:8"
      set {csk_trees::dark_oak_tree::trunk} to "162:1|162:5|162:9"
      set {csk_trees::dark_oak_tree::leaves} to "161:1|161:9"
    function treechop(location:location, tree:text, y:number, tool:item, tree_radius:number=1, leave_radius:number=2):
      loop blocks in radius {_tree_radius} of {_location}:
        if {csk_trees::%{_tree}%::trunk} contains "%id of loop-block%:%data value of loop-block%":
          location of loop-block is not {_location}
          y coord of loop-block is greater than or equal to {_y}
          break loop-block using {_tool}
          treechop(location of loop-block, "%{_tree}%", {_y}, {_tool}, {_tree_radius}, {_leave_radius})
      loop blocks in radius {_leave_radius} of {_location}:
        if {csk_trees::%{_tree}%::leaves} contains "%id of loop-block%:%data value of loop-block%":
          break loop-block using {_tool}
    break of any log:
      tool of player is any axe
      set {_tree_radius} to 1
      set {_leave_radius} to 1
      if {csk_trees::oak_tree::trunk} contains "%id of block%:%data value of block%":
        set {_tree} to "oak_tree"
      else if {csk_trees::spruce_tree::trunk} contains "%id of block%:%data value of block%":
        set {_tree} to "spruce_tree"
      else if {csk_trees::birch_tree::trunk} contains "%id of block%:%data value of block%":
        set {_tree} to "birch_tree"
      else if {csk_trees::jungle_tree::trunk} contains "%id of block%:%data value of block%":
        set {_tree} to "jungle_tree"
      else if {csk_trees::acacia::trunk} contains "%id of block%:%data value of block%":
        set {_tree} to "acacia_tree"
      else if {csk_trees::dark_oak_tree::trunk} contains "%id of block%:%data value of block%":
        set {_tree} to "dark_oak_tree"
        set {_tree_radius} to 2
        set {_leave_radius} to 2
      treechop(location of block, "%{_tree}%", y coord of block, tool of player, {_tree_radius}, {_leave_radius})
    It managed to support all tree types and it is what I have planned for this script I was just wondering if there is a loop all blocks at a location feature in command helper. I'll try and do it your way but I may need help
  8. PseudoKnight

    PseudoKnight Well-Known Member

    A cuboid iterator is a commonly used procedure for that (3 nested for() loops for x, y, z). It's a little more efficient than a radius if you need to define your shape more specifically. In this case a 3x2x3 cuboid. It's less efficient than my 5 block check (9 with acacia) because it checks 17 blocks, but it'd work.

    I see how you're avoiding infinite recursion. I can work with that.

    This actually got me interested in trying some things.
    proc _try_break(@x, @y, @z, @blocks) {
        @block = get_block_at(@x, @y, @z);
        if(array_contains(@blocks, @block)) {
            @loc = array(@x, @y, @z, pworld());
            @sound = if(@block == @blocks['log'], 'BLOCK_WOOD_BREAK', 'BLOCK_GRASS_BREAK');
            play_sound(@loc, array('sound': @sound, 'volume': 0.1));
            _treecheck(@x, @y, @z, @blocks);

    proc _treecheck(@x, @y, @z, @blocks, @checkDiagonal = true) {
        // Add a delay to animate the breaking. It's pretty and distributes the load.
        set_timeout(100, closure() {
            _try_break(@x, @y + 1, @z, @blocks);
            _try_break(@x - 1, @y, @z, @blocks);
            _try_break(@x + 1, @y, @z, @blocks);
            _try_break(@x, @y, @z - 1, @blocks);
            _try_break(@x, @y, @z + 1, @blocks);
            if(@checkDiagonal && @blocks['log'] == '162:0') {
                // check diagonal for acacia trunk once
                _treecheck(@x, @y + 1, @z, @blocks, false);

    bind(block_break, null, null, @event) {
        @type = @event['block']['type'];
        if(@type == 17 || @type == 162) {
            @data = @event['block']['data'];
            @blocks = array(
                'log': @type.':'[email protected]data,
                'leaves': if(@type == 17, 18, 161).':'.(@data + 8),
            @loc = @event['location'];
            _treecheck(@loc['x'], @loc['y'], @loc['z'], @blocks);
    There's still the problem of this breaking merged trees, but while you can solve that a little bit by passing a source location and checking a radius based on the tree type, it's impossible to solve completely. It's one of those reasons I never really liked those plugins. (Plus punching trees is fun too. XD)
    Last edited: Dec 3, 2017
    Chloe Sanders likes this.
  9. Chloe Sanders

    Chloe Sanders New Member

    oooh this looks helpful :) going to have to test it out tomorrow and see how far I can get with this ;)
  10. Chloe Sanders

    Chloe Sanders New Member

    How would I save information to a file and grab it in the future? Like how would I keep variables for say making an economy plugin, home plugin, etc... I didn't keep wanting to create new threads I've sort of got the treecapitator working now but I'd like to take a break from it as I dont think I'm good enough with command helper yet.

    Wasn't sure if it was 'res_create_resource()' as in the description it says it will be deprecated in the future
  11. PseudoKnight

    PseudoKnight Well-Known Member

    No, res_create_resource() is an advanced tool for string building and seeding random numbers. The easiest solution is to use persistence functions like get_value() and store_value(). It stores in an sqlite database by default, but that can be configured to something else like Serialized, for example, which is what I recommend if you want something fast but not thread safe or manually editable.
  12. Oboist

    Oboist Member

    Some tool i wrote for my server (wher player breaks a log, it tries to find the whole tree. If it finds "non-natural" blocks or no leaves found around, it stops (because clearly it's some building, not a tree)).
    It is part of bigger cleaning tool, so i just write all blocks that needs cleaning to one file. (so _reblock() replaces all stashed blocks)

    But, of course, it is not for quick tree breaking, its for cleaning floating tree pieces leaved by players. Really makes server prettier.


    bind(block_break, null, null, @event){
    @loc = @event[location]
    if(@loc[world] == 'world' && sk_regions_at(@loc) == array() && (@event[block][type] == '17' || @event[block][type] == '162')){
    @treeid = time()
    @tree = _treesearch(@treeid, array(), @loc)
    if(import(leaves[email protected]treeid) == true && import(notatree[email protected]treeid) != true){
    foreach(@block in @tree){
    chd_write('../values/'.reblock.'.txt', '{|}'.integer(@block[0]).'.'.integer(@block[1]).'.'.integer(@block[2]).'{|}'.0, 'APPEND')

    proc _treesearch(@treeid, @tree, @loc){
    if(import(notatree[email protected]treeid) == true, return())
    array_push(@tree, @loc)
    for(@x = @loc['x'] - 1, @x <= @loc['x'] + 1, @x++){
    for(@y = @loc['y'] - 1, @y <= @loc['y'] + 1, @y++){
    for(@z = @loc['z'] - 1, @z <= @loc['z'] + 1, @z++){
        @newloc = array(0: @x, 1: @y, 2: @z, 3: 'world', x: @x, y: @y, z: @z, world: 'world')
        @block = split(':', get_block_at(@newloc))[0]
        if(!array_contains(array(0, 1, 2, 3, 6, 8, 9, 10, 11, 12, 13, 17, 18, 31, 32, 37, 38, 39, 40, 51, 60, 78, 79, 81, 82, 86, 99, 100, 103, 106, 110, 111, 119, 127, 141, 142, 159, 161, 162, 172, 174, 175), @block)){
            export(notatree[email protected]treeid, true)
        } else {
            if(@block == 18 || @block == 161){
                export(leaves[email protected]treeid, true)
            } else if(@block == 17 || @block == 162){
                if(!array_contains_ic(@tree, @newloc), @tree = _treesearch(@treeid, @tree, @newloc))

    proc _reblock(){
    @arr = split('{|}', read('../values/reblock.txt'))
    for(@x = array_size(@arr) - 1, @x > 0 , @x -= 2){
    @trgt = split('.', @arr[@x - 1])
    if(sk_regions_at(array(1: @trgt[0], 2: @trgt[1], 3: @trgt[2], 4: 'world', x: @trgt[0], y: @trgt[1], z: @trgt[2], world: 'world')) == array()){set_block_at(@trgt[0], @trgt[1], @trgt[2], @arr[@x], 'world')}}
    chd_write('../values/'.reblock.'.txt', '0.1.0{|}1')
    console('[REBLOCK] DONE!', false)

    register_command('reblock', array(executor: closure(@alias, @pl, @args){_reblock()}))
    and, in my case, i use
    set_cron('0 4 * * *', closure(_reblock()))
    for daily cleaning.

    Maybe i only should add execution queue in _reblock...
    Last edited: Jan 7, 2018