Compare commits

...

253 Commits

Author SHA1 Message Date
songoda-plugins-overview[bot]
bbdcdfbc36
Updates contents of README.md (#11)
Co-authored-by: songoda-plugins-overview[bot] <111250264+songoda-plugins-overview[bot]@users.noreply.github.com>
2024-04-18 19:19:13 +02:00
craftaro-plugins-overview[bot]
412d96fe18
Updates contents of README.md 2024-03-07 20:52:54 +00:00
Christian Koop
4b1fa2b37c
Adds plugin logo at docs/Logo.png 2024-03-07 21:39:36 +01:00
ceze88
ee570e4719 Release v3.0.0 2024-01-11 11:59:02 +01:00
ceze88
6a82c260b5 Migrate to dynamic dependecy loading 2024-01-11 11:53:50 +01:00
ceze88
f65872827d Release v3.0.0-b3-SNAPSHOT 2023-10-24 13:05:21 +02:00
ceze88
139b15cc8d Fix negative durability 2023-10-24 13:04:23 +02:00
Christian Koop
b533be2c26
Release v3.0.0-b2-SNAPSHOT 2023-10-23 18:24:30 +02:00
Christian Koop
3455ad87e3
Merge pull request #10 from General-Zimmer/development
First draft of TreeDamageEvent implementation
2023-08-21 11:34:04 +02:00
GZimmer
7553a3c897 First draft of TreeDamageEvent implementation 2023-07-29 14:01:26 +02:00
ceze88
3847ff9dc5 Release v3.0.0-SNAPSHOT 2023-07-26 17:57:16 +02:00
ceze88
01355d3e13 Refactors package name from com.songoda to com.craftaro 2023-07-26 17:56:09 +02:00
Christian Koop
963875d5bf
Adds MANGROVE_PROPAGULE as leaves for mangrove in the default config
They sometimes kept floating in-air
2023-07-01 11:01:25 +02:00
Christian Koop
12c5c78a1d
Adds VINE as leaves for all the trees in the default config
Some times vines just kept floating in-air
2023-07-01 11:00:51 +02:00
Christian Koop
2523487543
Improve mangrove root detection in the default config 2023-07-01 10:30:16 +02:00
Christian Koop
5c84ee51e8
Unifies formatting across the default config 2023-07-01 10:18:33 +02:00
Christian Koop
490f55388d
Adds cherry trees to the default config 2023-07-01 10:18:14 +02:00
Christian Koop
096a1f25b4
Release v2.4.0-SNAPSHOT 2023-06-29 14:38:44 +02:00
Christian Koop
2858427d86
Migrate from SongodaCore to CraftaroCore v3.0.0-SNAPSHOT 2023-06-29 14:38:44 +02:00
Christian Koop
b1cb59b7e8
Heavy code style changes and slight refactoring 2023-06-29 14:31:32 +02:00
Christian Koop
ea940d573d
Simplify project setup to single-module and modernize pom.xml+plugin.yml 2023-06-29 13:48:34 +02:00
Christian Koop
3e9f7d2eec
Delete .DS_Store files 2023-06-29 13:38:04 +02:00
craftaro-plugins-overview[bot]
d243aba6af
Updates contents of README.md (#9)
Co-authored-by: craftaro-plugins-overview[bot] <111250264+craftaro-plugins-overview[bot]@users.noreply.github.com>
2023-06-29 13:32:36 +02:00
Christian Koop
16091dcb59
Create README.md 2023-06-29 13:26:58 +02:00
ceze88
e04a4af615 Release 2.3.7 2023-04-28 14:38:20 +02:00
ceze88
3bb28b8d2d Fix negative durability and protect-tool option 2023-04-28 14:34:57 +02:00
ceze88
ca8d830ee5 Release 2.3.6 2023-04-13 14:41:00 +02:00
ceze88
5cb3e2e56d Updates SongodaCore to v2.6.19 2023-04-13 14:40:28 +02:00
ceze88
79276c0eba Release 2.3.6-BETA 2023-03-29 21:54:19 +02:00
ceze88
101b783c36 Updates SongodaCore to v2.6.19-DEV 2023-03-29 21:52:45 +02:00
Christian Koop
84e8fc8eb6
Switch LICENSE to CC BY-NC-ND 4.0
Co-authored-by: Eli Rickard <38917063+EliRickard@users.noreply.github.com>
2023-03-29 21:29:51 +02:00
ceze88
5637a0d85e Release v2.3.5 2023-01-25 16:12:22 +01:00
Christian Koop
75202b8007
Release v2.3.5-DEV 2023-01-12 20:15:53 +01:00
Christian Koop
f4bd38f794
Updates SongodaCore to v2.6.18-DEV 2023-01-12 20:15:38 +01:00
ceze88
228deb9f17 Bump SongodaCore version to 2.6.17 2022-12-29 18:11:50 +01:00
Christian Koop
94d8a27e4a
Merge branch 'development' 2022-09-05 22:29:41 +02:00
Christian Koop
b3f11ff819
Release v2.3.4 2022-09-05 22:29:32 +02:00
songoda-projects-overview[bot]
04ebcb4a52 Bump SongodaCore version to 2.6.16 2022-09-05 22:19:55 +02:00
songoda-projects-overview[bot]
48a4cf416b Bump SongodaCore version to 2.6.15-DEV 2022-08-15 00:48:23 +02:00
Christian Koop
59ef36fe1c
Update maven-shade-plugin to v3.3.0 2022-07-10 13:08:32 +02:00
Christian Koop
0f3f8f0e7b
Merge branch 'development' 2022-07-07 20:11:24 +02:00
Christian Koop
1c4146abff
Adds mangrove trees to default config 2022-07-07 20:11:08 +02:00
Christian Koop
c39f3cc1bd
Merge branch 'development' 2022-07-07 20:09:52 +02:00
Christian Koop
ad153088eb
Release v2.3.3 2022-07-07 20:09:16 +02:00
Christian Koop
7479ed36b7
Fixes Console-Spam when using axes 2022-07-07 20:09:16 +02:00
Christian Koop
fc5e102e0c
Updates SongodaCore to v2.6.13 2022-07-07 20:09:16 +02:00
Christian Koop
7f1c9ab3a3
Merge branch 'development' 2022-03-18 21:11:18 +01:00
Christian Koop
7cff210782
Release v2.3.2 2022-03-18 21:11:11 +01:00
Christian Koop
5c5f938f3c
Replaces deprecated NBTItem calls 2022-03-18 21:11:11 +01:00
Christian Koop
07c6afdad2
Updates Core to v2.6.12 2022-03-18 21:11:11 +01:00
Christian Koop
f4f8540aec
Code cleanup 2022-03-18 21:11:11 +01:00
Christian Koop
30923352a5
Merge branch 'development' 2022-01-30 12:15:19 +01:00
Christian Koop
23bd964898
Version 2.3.1 2022-01-30 12:14:36 +01:00
Christian Koop
a97feabe74
Updates SongodaCore to v2.6.11 2022-01-30 12:09:42 +01:00
Christian Koop
bc405fb3fa
Merge pull request #4 from ItsAZZA/development
Add azalea trees | Add rooted dirt as global plantable soil
2021-12-13 19:12:30 +01:00
Niko
7d39c8d967 Add azalea trees | Add rooted dirt as global plantable soil 2021-12-13 19:59:11 +02:00
Christian Koop
9011160798
Merge branch 'development' 2021-12-10 18:26:27 +01:00
Christian Koop
e2a32dfdee
Version 2.3.0 2021-12-10 18:26:08 +01:00
Christian Koop
7fc7c11808
Update SongodaCore to v2.6.4 2021-12-10 18:25:46 +01:00
Christian Koop
b5bb178569
Update dependency spigot to spigot-api v1.18 2021-12-10 18:05:39 +01:00
Christian Koop
d9eb494a6f
pom.xml: Minor cleanup 2021-12-10 18:05:39 +01:00
Christian Koop
564bfa6e8c
pom.xml: Use https for songoda maven repo 2021-12-10 17:50:15 +01:00
Christian Koop
ef7c7ac4fa
Merge branch 'development' 2021-08-30 21:58:26 +02:00
Christian Koop
5d8600df12
Version 2.2.6 2021-08-29 15:46:41 +02:00
Christian Koop
5cc632f625
Fixes mcMMO Treefeller-Check [SD-8327] 2021-08-29 15:26:28 +02:00
Christian Koop
1e2a41fd90
Take Unbreaking-Enchantment into consideration [SD-8182] 2021-08-29 14:28:47 +02:00
Brianna
24a556eacf Merge branch 'development' 2021-06-16 00:30:46 -05:00
Brianna
c94ec4c055 version 2.2.5
s
2021-06-16 00:29:59 -05:00
Brianna
3e1fa6f937 1.17 support.
d
2021-06-15 18:03:30 -05:00
Brianna
49c9a1a42e Fix for unbreaking. 2021-06-13 17:25:46 -05:00
underscore11code
daa4e8dcc2 Respect Unbreaking 2021-04-26 20:01:28 -07:00
Brianna
b600ca6bac Merge branch 'development' 2021-04-05 09:07:50 -05:00
Brianna
fb9d299eee version 2.2.4 2021-04-05 09:07:41 -05:00
Brianna
c234f202bc Added fragile blocks. 2021-04-05 09:07:09 -05:00
Brianna
cfc53d5b72 Merge branch 'development' 2021-02-19 17:31:07 -06:00
Brianna
5833234da4 version 2.2.3 2021-02-19 17:30:53 -06:00
Brianna
4d78edcb45 Fixed and modified the way the max tree block setting worked. 2021-02-19 17:29:12 -06:00
Brianna
76e3b1fc3b Removed old data. 2021-02-19 12:59:50 -06:00
Brianna
2da515f31f Merge branch 'development' 2021-01-21 13:07:26 -06:00
Brianna
c706ce3e6c version 2.2.2 2021-01-21 13:07:00 -06:00
Brianna
5355e227ab Use the updated SongodaCore item damage method to fix axes not breaking. 2021-01-21 10:19:02 -06:00
Brianna
6096047bae Merge branch 'development' 2021-01-15 15:00:22 -06:00
Brianna
ac8d2a9714 version 2.2.1b 2021-01-15 15:00:10 -06:00
Brianna
7817beb831 Merge branch 'development' 2020-12-23 11:46:53 -06:00
Brianna
e174ebc5bf version 2.2.1 2020-12-23 11:46:46 -06:00
Brianna
3585ce75bb Added adherence to the hook config options. 2020-12-23 09:35:21 -06:00
Brianna
fdd4286195 Merge branch 'development' 2020-11-24 15:51:04 -06:00
Brianna
c445e132e2 version 2.2 2020-11-24 15:50:50 -06:00
Brianna
ae1d967628 Fixed some issues with the Jobs hook. 2020-11-24 15:50:45 -06:00
Wertik
99af64c422 Remove amount from arguments. 2020-11-24 08:42:03 -06:00
Wertik
6fe148a667 Add required-axe option to default config.yml 2020-11-24 08:41:51 -06:00
Wertik
33b6c14811 Apply NBT after ItemMeta. 2020-11-24 08:41:42 -06:00
Wertik
8be488173f Add the ability to limit tools to a custom axe with NBT. 2020-11-24 08:41:38 -06:00
Brianna
4f079a384e Merge branch 'development' 2020-11-03 18:19:02 -06:00
Brianna
5dcb528eea version 2.1.1 2020-11-03 18:18:47 -06:00
Brianna
9d58e513b4 This is backwards. 2020-11-03 16:37:30 -06:00
Brianna
a6926de3a2 Merge branch 'development' 2020-10-30 15:01:03 -05:00
Brianna
ffb7657a78 version 2.1 2020-10-30 15:00:42 -05:00
Brianna
065a5b809b Removed proprietary compatibility support and switched to SongodaCore. 2020-10-30 15:00:27 -05:00
Brianna
488bb6b7b4 Merge branch 'development' 2020-09-23 16:31:00 -05:00
Brianna
ee6439fe65 version 2.0.9b 2020-09-23 16:30:18 -05:00
MysteriousKyle
f59cc05d9d Update pom.xml 2020-09-23 16:17:06 -05:00
Brianna
ff862c56d4 Merge branch 'development' 2020-08-28 18:09:58 -05:00
Brianna
23e7643eb9 version 2.0.9 2020-08-28 18:09:38 -05:00
MattAKAFred
9feb8752a7 Adds PlayerItemBreakEvent to tool breaking, fixes issue preventing plugins like AutomaticInventory from recognizing tool breaks. Tested working in 1.16.1. Fix should also work in Legacy Adapter, though that is untested and thus not part of this PR (would also require import org.bukkit.Bukkit in legacy). 2020-08-28 17:55:31 -05:00
Brianna
151b443a80 Lets use SongodaCore hooks. 2020-08-28 17:54:47 -05:00
Brianna
d3e2d27913 Wrong seed. 2020-08-28 08:41:15 -05:00
Brianna
7256b314f6 Merge branch 'development' 2020-06-28 19:18:56 -05:00
Brianna
04b23f5e1d version 2.0.8 2020-06-28 19:18:36 -05:00
Brianna
e020a96488 Added 1.16 support. 2020-06-28 19:18:30 -05:00
Brianna
b18a8cf469 Merge branch 'development' 2020-06-11 11:41:01 -05:00
Brianna
5e1a913ac4 version 2.0.7 2020-06-11 11:40:47 -05:00
Brianna
ef31edb1ee Wrong plugin ID. 2020-06-11 11:32:19 -05:00
Brianna
3464574b74 Added Public repo 2020-03-16 15:32:18 -04:00
Brianna
06e741536f Merge branch 'development' 2020-02-03 17:31:02 -05:00
Brianna
6922ed36d3 version 2.0.6 2020-02-03 17:30:36 -05:00
Brianna
43cd0be611 Fixed locale issue. 2020-02-03 17:29:57 -05:00
Brianna
7f9ee381f6 Fix for config issues. 2020-02-03 17:29:19 -05:00
Brianna
1e493e8f00 Fix for versioning issue. 2020-02-03 17:28:53 -05:00
Brianna
8cf9161abf Updated language file. 2020-02-01 07:03:55 -05:00
Brianna
41d2f5ff29 Merge branch 'development' 2020-01-27 08:19:19 -05:00
Brianna
46d1f7b01d version 2.0.5 2020-01-27 08:19:11 -05:00
Brianna
7b45e94d59 Removed CI, Converted to Maven, Added Songoda Core, Redid Locale 2020-01-27 08:16:29 -05:00
Steven Conley
60aa7a3157 Fixed McMMO hook not registering properly. 2020-01-12 18:35:43 -06:00
Brianna
4a121e924e Merge branch 'development' 2019-11-09 17:12:15 -05:00
Brianna
daabc4f70e version 2.0.4 2019-11-09 16:32:34 -05:00
Brianna
9903170d00 Fix tree detection with destroyleaves disabled. 2019-11-09 16:32:34 -05:00
Brianna
0bf1b4c263 Merge branch 'development' 2019-11-09 15:46:37 -05:00
Brianna
bcde04159d version 2.0.3 2019-11-09 15:46:24 -05:00
Brianna
588f210698 Removed unused imports 2019-11-09 15:46:17 -05:00
Esophose
e8ef575d2a Actually register the hook 2019-11-09 15:46:06 -05:00
Esophose
712cfeb998 CoreProtect logging 2019-11-09 15:46:02 -05:00
Brianna O'Keefe
d38ed65eed Merge branch 'development' into 'master'
Version 2.0.2

See merge request Songoda/ultimatetimber!20
2019-08-13 14:25:06 +00:00
jascotty2
281423031f version 2.0.2 2019-08-13 09:10:20 -05:00
jascotty2
0bd7c3f22d fix legacy mcmmo hook error when player isn't set up 2019-08-13 09:01:26 -05:00
Esophose
8d7a4bb675 Leaf detection efficiency, handle tool unbreaking tags in 1.8 2019-08-03 02:30:12 -06:00
Esophose
2524442911 Handle tool unbreaking tags in 1.9+ 2019-06-15 16:27:04 -06:00
Esophose
0b57057b4d Fix default spruce tree config 2019-05-08 16:10:54 -06:00
Esophose
8988679f2a Misc bug fixes 2019-05-04 20:09:20 -06:00
Esophose
119e54ad28 Remove debug messages 2019-05-02 20:15:16 -06:00
Esophose
cac222813d Fix log stripping issue 2019-05-02 19:27:08 -06:00
Esophose
20b124a990 Jungle range 2019-05-02 18:38:29 -06:00
Esophose
c98377bb93 Max log distance from trunk setting, bug fixes 2019-05-02 18:33:26 -06:00
Esophose
a7787caa84 Songoda Updater and Locale system 2019-05-01 20:17:06 -06:00
Esophose
03cb32cf5c Durability issue fix, cooldown message fix 2019-05-01 15:04:11 -06:00
Esophose
cffdf696c3 Silk touch settings, loot double drops fix, 2019-05-01 04:01:34 -06:00
Esophose
7d716fe62b Entire tree loot, topple cooldown, sneaking change 2019-05-01 01:33:21 -06:00
Esophose
90ba6bbf02 Disintegrate/Crumble block integrity checks 2019-04-30 00:18:45 -06:00
Esophose
53ba40fb65 Bug fixes 2019-04-28 22:43:13 -06:00
Esophose
4b35f0ee4e Hook tree loot double drops fix, placed block detection 2019-04-28 18:58:38 -06:00
Esophose
a5b8fab267 Hooks fixes 2019-04-27 21:45:57 -06:00
Esophose
0e1afcef84 Invalid tree animation in config handling, fix typo 2019-04-27 20:17:40 -06:00
Esophose
5ce176841e Hook system rewrite 2019-04-27 20:04:45 -06:00
Esophose
a52ba616fe Gradle project 2019-04-22 18:34:31 -06:00
Esophose
31923fa0b2 Minor changes 2019-04-22 18:33:53 -06:00
Esophose
8ff4e315eb Added animation type CRUMBLE 2019-04-17 02:11:58 -06:00
Esophose
6e34b1cb6b Remove .iml files 2019-04-15 14:36:42 -06:00
Esophose
0db463378b Hook stuff, event priority stuff 2019-04-15 14:33:31 -06:00
Esophose
314d6fcb2b Merge remote-tracking branch 'origin/ultimate' into ultimate 2019-04-11 19:19:17 -06:00
Esophose
ae7c7e425d Priority and location 2019-04-11 19:19:10 -06:00
Esophose
bf0078e794 Update .gitlab-ci.yml 2019-04-11 08:50:43 +00:00
Esophose
bd3097422e Placeholder % addition, added setting, bug fixes 2019-04-05 00:05:43 -06:00
Esophose
1c17042835 1.12- compatibility, fix sapling replant bug 2019-04-01 19:07:02 -06:00
Esophose
2114d395e0 Fancy tree animation done 2019-04-01 13:50:19 -06:00
Esophose
7b2ddb1562 Clean up imports, start work on fancy tree animation 2019-04-01 04:22:08 -06:00
Esophose
26c567bb56 Silk touch fix 2019-03-31 13:48:03 -06:00
Esophose
d47ebbb64b Better support for material data, fix default legacy config typos 2019-03-31 03:08:41 -06:00
Esophose
86dbb57ac2 1.8-1.12 support 2019-03-31 01:50:42 -06:00
Esophose
e94a0c6259 Fix tool damage not being applied 2019-03-30 17:22:45 -06:00
Esophose
120cb29c3b Fixed tree setting issues. Improved the tree audio experience 2019-03-30 16:25:04 -06:00
Esophose
d00668bfbb Tree animation disintegrate added 2019-03-30 15:56:22 -06:00
Esophose
fdac6ef040 Added setting to prevent destroying leaves 2019-03-30 14:50:42 -06:00
Esophose
f6a22966da Bug fixing, global soil code 2019-03-30 03:34:27 -06:00
Esophose
06b570c8c5 It did build, just couldn't move the file 2019-03-30 01:09:58 -06:00
Esophose
5d83a2bd27 Does it build? 2019-03-30 00:46:13 -06:00
Esophose
d8bd19a08a Refactor into maven modules 2019-03-29 21:59:10 -06:00
Esophose
8026babcdf Tree detector, file formatting, this., new settings, other changes 2019-03-29 16:52:17 -06:00
Esophose
94ffdcc5f5 Tree detection entire tree base check 2019-03-28 16:19:52 -06:00
Esophose
e3bbc814f3 IBlockData and soil changes 2019-03-28 14:34:17 -06:00
Esophose
9329cc1fb6 Tree detection 2019-03-28 11:38:37 -06:00
Esophose
671236f57d Tree definition loading & some other methods 2019-03-28 02:30:33 -06:00
Esophose
a267bd745c Additional boilerplate, actually start implementing some stuff 2019-03-28 00:42:22 -06:00
Esophose
4192e5ba74 Even more boilerplate code 2019-03-27 22:22:13 -06:00
Esophose
665ece06da Added lots of boilerplate code 2019-03-27 18:56:39 -06:00
Esophose
7bd1157f07 WIP 2019-03-26 14:35:17 -06:00
Esophose
25a524c230 Configuration files 2019-03-25 19:44:29 -06:00
Esophose
cece22e247 The beginning 2019-03-25 13:40:56 -06:00
Esophose
0aad72bc2c Version number 2019-03-05 21:31:14 -07:00
Esophose
0ed8bef901 Add metrics 2019-03-05 21:06:38 -07:00
Esophose
b9389137cf Fix bug with entire tree base setting 2019-02-17 16:13:19 -07:00
Esophose
f9cac11d7a Add config setting to require entire tree base to be broken 2019-02-17 01:37:31 -07:00
Esophose
5c3ac55fde Add setting to scatter tree blocks on the ground 2019-02-16 01:08:34 -07:00
Esophose
82e8c064f4 Refactor hooking system 2019-02-16 00:16:54 -07:00
Esophose
9610cebb39 Account for unbreaking enchantment 2019-02-15 23:29:58 -07:00
Esophose
319298968f Add Jobs Reborn hook, add delete log setting, fix some sapling replants 2019-02-15 22:32:12 -07:00
Esophose
d9f7c60836 Update pom.xml with mcMMO dependency 2019-02-13 18:05:11 -07:00
Esophose
1b370a73dd Bump version to 1.0.12, mcMMO Classic support, fix axe durability 2019-02-11 19:11:08 -07:00
Esophose
69d7a665f4 Added mcMMO woodcutting XP support 2019-02-10 21:50:07 -07:00
Esophose
6c788c216e Fix permission spam on tab complete, make wood materials drop logs 2019-02-09 14:27:43 -07:00
Esophose
bcfdeabb2c Complete rewrite of tree detection algorithm 2019-02-09 04:14:35 -07:00
Esophose
d0cedf4ced Fix trying to sometimes drop air 2019-02-08 20:28:27 -07:00
Esophose
a766b780cb Bump version to 1.0.11, minor fix 2019-02-07 01:28:16 -07:00
Esophose
00b1bfc82a Simplify .gitignore 2019-02-06 22:35:05 -07:00
Esophose
c9fdb9fe5c Fix falling block detection reliability 2019-02-06 22:19:04 -07:00
Esophose
78e7b4cd7c Item drop system rewrite 2019-02-06 20:38:12 -07:00
Esophose
83ceca627c Tab complete, valid world reloading, onDisable message 2019-02-06 20:37:16 -07:00
Esophose
5cad56e436 Minor clean up 2019-02-06 20:25:59 -07:00
Brianna O'Keefe
2b27db5805 Clean 2019-02-06 01:57:34 -05:00
electro2560
9ccd0e31f6 Change events package 2019-02-06 01:39:32 -05:00
electro2560
8f11177280 Add tree fell event and make fall event cancellable 2019-02-06 01:39:27 -05:00
electro2560
407fbdccc0 Add tree events 2019-02-06 01:39:12 -05:00
Brianna O'Keefe
041c265024 Chop toggle. 2019-02-06 01:38:37 -05:00
Brianna O'Keefe
08d304444e cleaned 2018-12-19 12:58:05 -05:00
Lars Dormans
2ff0745a32 Merge branch 'OOP-778-master' into 'master'
Resolve OOP-778 "Master"

Closes OOP-778

See merge request Songoda/ultimatetimber!8
2018-12-18 14:23:17 +00:00
Brian
5ea8dfe9e6 Bug Fixes 2018-12-18 16:12:26 +02:00
Brian
cbf4f1ba0e Revert "Bug Fixes"
This reverts commit bab00ae
2018-12-18 14:53:27 +02:00
Brian
bab00aed4d Bug Fixes
# Conflicts:
#	src/main/java/com/songoda/ultimatetimber/treefall/NoAnimationTreeDestroyer.java
#	src/main/java/com/songoda/ultimatetimber/treefall/TreeFallEvent.java
#	src/main/java/com/songoda/ultimatetimber/treefall/TreeLoot.java
2018-12-18 14:52:10 +02:00
Brianna O'Keefe
b84a5e39fe Deleted target/classes/com/songoda/ultimatetimber/treefall/TreeReplant$3.class, target/maven-status/maven-compiler-plugin/compile/java-compile/createdFiles.lst, target/maven-status/maven-compiler-plugin/compile/java-compile/inputFiles.lst, target/UltimateTimber-Legacy-maven-version-number.jar, target/UltimateTimber-Legacy.jar files 2018-12-17 23:44:17 +00:00
Brianna O'Keefe
d32bbb4a9e Update .gitlab-ci.yml 2018-12-17 23:39:24 +00:00
Lars Dormans
9c8229a9dd Merge branch 'OOP-778-master' into 'master'
Resolve OOP-778 "Master"

Closes OOP-778, SD-683, SD-810, SD-757, and SD-809

See merge request Songoda/ultimatetimber!5
2018-12-17 21:56:32 +00:00
Brian
92d4899cd9 Bug Fixes 2018-12-17 17:22:07 +02:00
Brian
4813fc9032 Bug Fixes 2018-12-17 16:10:43 +02:00
Brian
14753fab86 Bug fixes 2018-12-17 15:58:57 +02:00
Brian
e2593d3422 reload command permission check 2018-12-16 15:20:59 +02:00
Brian
1d93f15491 Update NoAnimationTreeDestroyer.java 2018-12-15 13:37:50 +02:00
Brian
9495d15d67 TreeChecker update
Made so Mushrooms replant,
Increased accuracy of TreeChecker
2018-12-15 13:32:50 +02:00
Brian
5af145615b Update NoAnimationTreeDestroyer.java 2018-12-15 12:53:31 +02:00
Brian
e485388b3d TreeCheck & NoAnimationTreeDestroye update
Increased accuracy of TreeCheck
Fixed that when animation is disabled, it doesn't replant.
2018-12-15 12:53:26 +02:00
Brian
17cb5b6b76 Update TreeFallAnimation.java
Makes the animation in the middle of block.
2018-12-14 16:38:09 +02:00
Brian
624211fe0b
Fixes 5 bugs.
Fixes SD-683
Fixes SD-810
Fixes SD-757
Added Mooshroom replant
Fixes SD-809
2018-12-14 15:07:44 +01:00
Brianna O'Keefe
765d3c41b8 version 2018-12-10 09:54:12 -05:00
Lars Dormans
a044d6971c Merge branch 'master' into 'master'
updated plugin.yml

See merge request Songoda/ultimatetimber!3
2018-12-06 15:30:02 +00:00
Brianna O'Keefe
269d7567f0 version 2018-12-05 05:29:50 -05:00
Brianna O'Keefe
6123a7465b five seconds before breaking saplings. 2018-12-05 05:26:10 -05:00
Coastal
a845d30f7b Add README.md 2018-12-02 07:25:21 +00:00
Coastal
c9ed661469 updated plugin.yml to support multiple worlds 2018-11-26 23:59:38 +00:00
Brianna O'Keefe
3a0b05c987 fixed reload for custom items. 2018-11-23 11:48:07 -05:00
Brianna O'Keefe
a52c2ad0be version 2018-11-23 11:23:30 -05:00
Brianna O'Keefe
743df4d385 Axes will now be forced broken if damage goes negative. 2018-11-23 11:23:04 -05:00
Brianna O'Keefe
fcb5d84b5b Update .gitlab-ci.yml 2018-11-20 00:03:07 +00:00
Brianna O'Keefe
be223b7fee Update .gitlab-ci.yml 2018-11-20 00:00:22 +00:00
Brianna O'Keefe
84e7c34097 Update pom.xml 2018-11-19 23:53:08 +00:00
Brianna O'Keefe
1873945a40 Update DefaultConfig.java 2018-11-19 20:49:40 +00:00
Brianna O'Keefe
6c6ded977f Update .gitlab-ci.yml 2018-11-19 20:45:13 +00:00
Brianna O'Keefe
dcd9b46a5c Update src/main/java/com/songoda/ultimatetimber/configurations/DefaultConfig.java, src/main/java/com/songoda/ultimatetimber/treefall/EventFilter.java files 2018-11-19 20:44:44 +00:00
Brianna O'Keefe
bca27ee82e Update pom.xml 2018-11-19 02:06:47 +00:00
Brianna O'Keefe
3acf7f0984 Update .gitlab-ci.yml 2018-11-19 01:57:01 +00:00
Brianna O'Keefe
68dab68ede Update .gitlab-ci.yml 2018-11-19 01:55:46 +00:00
Brianna O'Keefe
10cf409818 Update .gitlab-ci.yml 2018-11-19 01:53:16 +00:00
Brianna O'Keefe
1d6809936e Update .gitlab-ci.yml, src/main/resources/plugin.yml, pom.xml, LICENSE files
Deleted target/classes/plugin.yml, src/LICENSE files
2018-11-19 01:49:30 +00:00
Lars Dormans
bfdf2a2dca Update .gitlab-ci.yml 2018-11-17 10:43:16 +00:00
Brianna O'Keefe
0b1aa35a61 Update .gitlab-ci.yml 2018-11-17 10:42:42 +00:00
Brianna O'Keefe
2ab7c56de3 license 2018-11-17 05:39:29 -05:00
63 changed files with 4802 additions and 1499 deletions

80
.gitignore vendored
View File

@ -1,74 +1,10 @@
## JetBrains IDEs
/.idea/
*.iml
\.idea/libraries/Maven__com_google_code_gson_gson_2_8_0\.xml
## Maven
/**/target/
/dependency-reduced-pom.xml
\.idea/libraries/Maven__com_google_guava_guava_21_0\.xml
\.idea/libraries/Maven__com_googlecode_json_simple_json_simple_1_1_1\.xml
\.idea/libraries/Maven__commons_lang_commons_lang_2_6\.xml
\.idea/libraries/Maven__junit_junit_4_10\.xml
\.idea/libraries/Maven__net_md_5_bungeecord_chat_1_13_SNAPSHOT\.xml
\.idea/libraries/Maven__org_bukkit_bukkit_1_13_R0_1_SNAPSHOT\.xml
\.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_1\.xml
\.idea/libraries/Maven__org_spigotmc_spigot_api_1_13_R0_1_SNAPSHOT\.xml
\.idea/libraries/Maven__org_yaml_snakeyaml_1_21\.xml
\.idea/misc\.xml
\.idea/modules\.xml
\.idea/workspace\.xml
target/classes/com/songoda/ultimatetimber/commands/CommandHandler\.class
target/classes/com/songoda/ultimatetimber/commands/ReloadCommand\.class
target/classes/com/songoda/ultimatetimber/configurations/DefaultConfig\.class
target/classes/com/songoda/ultimatetimber/treefall/AxeDurability\.class
target/classes/com/songoda/ultimatetimber/treefall/EventFilter\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeChecker\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeEntityDamage\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeFallAnimation\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeFallAnimation\$1\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeFallAnimation\$2\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeFallEvent\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeLoot\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeLoot\$1\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeReplant\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeReplant\$1\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeReplant\$2\.class
target/classes/com/songoda/ultimatetimber/treefall/TreeSounds\.class
target/classes/com/songoda/ultimatetimber/UltimateTimber\.class
target/classes/plugin\.yml
target/maven-archiver/pom\.properties
target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles\.lst
target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles\.lst
target/UltimateTimber\.jar
UltimateTimber\.iml
## Misc.
.DS_Store

View File

@ -1,12 +0,0 @@
stages:
- build
build:
stage: build
image: maven:3.3.9-jdk-8
script: "mvn clean package"
artifacts:
name: "UltimateTimber"
paths:
- "/builds/Songoda/UltimateTimber/target/*.jar"
- "/builds/Songoda/UltimateTimber/Read_this_before_your_first_use.txt"

327
LICENSE Normal file
View File

@ -0,0 +1,327 @@
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-NoDerivatives 4.0 International Public
License ("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
c. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
d. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
e. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
f. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
g. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
h. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce and reproduce, but not Share, Adapted Material
for NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material, You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
For the avoidance of doubt, You do not have permission under
this Public License to Share Adapted Material.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only and provided You do not Share Adapted Material;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================

44
README.md Normal file
View File

@ -0,0 +1,44 @@
<!--suppress HtmlDeprecatedAttribute -->
<div align="center">
<img src="docs/Logo.png" width="128px">
# UltimateTimber
**Give your players a new and exciting way to chop down trees.**
**Includes animated block falling, excellent tree detection, custom drops, realistic sounds, and more.**
[![Discord][Discord shield]][Discord invite]
<br>
[![Latest version][Latest version shield]][Plugin page]
[![bStats Servers][bStats shield]][bStats page]
</div>
## Download (Marketplace)
You can visit [our marketplace][Plugin page] to download UltimateTimber as well as take a
look at many other fantastic plugins which are sure to catch your eye.
## Documentation
You can find all the information about UltimateTimber, including dependencies, commands, permissions and incompatible
plugins on [our wiki][Plugin wiki].
Feel free to also contribute to the wiki as a way to help others in the community with using the plugin.
## Support
If you encounter any issues while using the plugin, feel free to contact us on
[our Discord server][Discord invite].
## Suggestions
For suggestions about features you think should be added to the plugin to increase its functionality, feel free to
create a thread over on [our Discord server][Discord invite].
[Plugin page]: https://songoda.com/product/2
[Plugin wiki]: https://songoda.notion.site/UltimateTimber-95103806d7f84353bb308335bd8ce37e
[Discord invite]: https://discord.gg/7TXM8xr2Ng
[Discord shield]: https://img.shields.io/discord/1214289374506917889?color=5865F2&label=Discord&logo=discord&logoColor=5865F2
[Latest version shield]: https://img.shields.io/badge/dynamic/xml?style=flat&color=blue&logo=github&logoColor=white&label=Latest&url=https%3A%2F%2Fraw.githubusercontent.com%2Fcraftaro%2FUltimateTimber%2Fmaster%2Fpom.xml&query=%2F*%5Blocal-name()%3D'project'%5D%2F*%5Blocal-name()%3D'version'%5D
[bStats page]: https://bstats.org/plugin/bukkit/UltimateTimber/4184
[bStats shield]: https://img.shields.io/bstats/servers/4184?label=Servers

BIN
docs/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

123
pom.xml
View File

@ -4,51 +4,130 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ultimatetimber</groupId>
<groupId>com.craftaro</groupId>
<artifactId>UltimateTimber</artifactId>
<version>0.0.10</version>
<version>3.0.0</version>
<name>UltimateTimber</name>
<description>Give your players a new and exciting way to chop down trees</description>
<url>https://craftaro.com/marketplace/product/18</url>
<properties>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<issueManagement>
<url>https://discord.gg/craftaro</url>
<system>Discord server</system>
</issueManagement>
<scm>
<url>https://github.com/craftaro/UltimateTimber</url>
<connection>scm:git:git://github.com/craftaro/UltimateTimber.git</connection>
</scm>
<build>
<finalName>${project.artifactId}</finalName>
<defaultGoal>clean resources:resources package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${project.name}-${project.version}</finalName>
<shadedArtifactAttached>false</shadedArtifactAttached>
<useDependencyReducedPomInJar>true</useDependencyReducedPomInJar>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>com.craftaro.core</pattern>
<shadedPattern>com.craftaro.ultimatetimber.core</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/**</exclude>
<exclude>LICENSE</exclude>
<exclude>LICENSE.**</exclude>
</excludes>
</filter>
<filter>
<artifact>com.craftaro:CraftaroCore</artifact>
<excludeDefaults>false</excludeDefaults>
<includes>
<include>**/nms/v*/**</include>
</includes>
<excludes>
<exclude>**/third_party/org/apache/**</exclude>
<exclude>**/third_party/net/kyori/**</exclude>
<exclude>**/third_party/com/zaxxer/**</exclude>
<exclude>**/third_party/org/jooq/**</exclude>
<exclude>**/third_party/org/mariadb/**</exclude>
<exclude>**/third_party/com/h2database/**</exclude>
<exclude>**/third_party/org/h2/**</exclude>
<exclude>**/third_party/com/cryptomorin/**</exclude>
<exclude>**/third_party/org/reactivestreams/**</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spigot-repo</id>
<id>craftaro-minecraft-plugins</id>
<url>https://repo.craftaro.com/repository/minecraft-plugins/</url>
</repository>
<repository>
<id>SpigotMC</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<!--Spigot API-->
<dependency>
<groupId>com.craftaro</groupId>
<artifactId>CraftaroCore</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- TODO: Check if spigot-api can be downgraded to 1.8 -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.13-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!--Bukkit API-->
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.13-R0.1-SNAPSHOT</version>
<version>1.18-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
</project>

View File

@ -0,0 +1,158 @@
package com.craftaro.ultimatetimber;
import com.craftaro.core.SongodaCore;
import com.craftaro.core.SongodaPlugin;
import com.craftaro.core.commands.CommandManager;
import com.craftaro.core.configuration.Config;
import com.craftaro.core.dependency.Dependency;
import com.craftaro.core.hooks.LogManager;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatetimber.commands.CommandGiveAxe;
import com.craftaro.ultimatetimber.commands.CommandReload;
import com.craftaro.ultimatetimber.commands.CommandToggle;
import com.craftaro.ultimatetimber.manager.PlacedBlockManager;
import com.craftaro.ultimatetimber.manager.TreeDetectionManager;
import com.craftaro.ultimatetimber.manager.ChoppingManager;
import com.craftaro.ultimatetimber.manager.ConfigurationManager;
import com.craftaro.ultimatetimber.manager.Manager;
import com.craftaro.ultimatetimber.manager.SaplingManager;
import com.craftaro.ultimatetimber.manager.TreeAnimationManager;
import com.craftaro.ultimatetimber.manager.TreeDefinitionManager;
import com.craftaro.ultimatetimber.manager.TreeFallManager;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UltimateTimber extends SongodaPlugin {
private final Set<Manager> managers = new HashSet<>();
private ChoppingManager choppingManager;
private ConfigurationManager configurationManager;
private PlacedBlockManager placedBlockManager;
private SaplingManager saplingManager;
private TreeAnimationManager treeAnimationManager;
private TreeDefinitionManager treeDefinitionManager;
private TreeDetectionManager treeDetectionManager;
private TreeFallManager treeFallManager;
/**
* @deprecated Use {@link #getPlugin(Class)} instead
*/
@Deprecated
public static UltimateTimber getInstance() {
return getPlugin(UltimateTimber.class);
}
@Override
protected Set<Dependency> getDependencies() {
return new HashSet<>();
}
@Override
public void onPluginLoad() {
}
@Override
public void onPluginEnable() {
// Run Songoda Updater
SongodaCore.registerPlugin(this, 18, XMaterial.IRON_AXE);
// Load hooks
LogManager.load();
// Setup plugin commands
CommandManager commandManager = new CommandManager(this);
commandManager.addMainCommand("ut")
.addSubCommands(
new CommandReload(this),
new CommandToggle(this),
new CommandGiveAxe(this)
);
// Register managers
this.choppingManager = this.registerManager(ChoppingManager.class);
this.configurationManager = new ConfigurationManager(this);
this.placedBlockManager = this.registerManager(PlacedBlockManager.class);
this.saplingManager = this.registerManager(SaplingManager.class);
this.treeAnimationManager = this.registerManager(TreeAnimationManager.class);
this.treeDefinitionManager = this.registerManager(TreeDefinitionManager.class);
this.treeDetectionManager = this.registerManager(TreeDetectionManager.class);
this.treeFallManager = this.registerManager(TreeFallManager.class);
this.reloadConfig();
}
@Override
public void onPluginDisable() {
this.configurationManager.disable();
this.managers.forEach(Manager::disable);
}
@Override
public void onDataLoad() {
}
@Override
public void onConfigReload() {
this.configurationManager.reload();
this.managers.forEach(Manager::reload);
this.setLocale(getConfig().getString("locale"), true);
}
@Override
public List<Config> getExtraConfig() {
return Collections.emptyList();
}
/**
* Registers a manager
*
* @param managerClass The class of the manager to create a new instance of
* @param <T> extends Manager
* @return A new instance of the given manager class
*/
private <T extends Manager> T registerManager(Class<T> managerClass) {
try {
T newManager = managerClass.getConstructor(UltimateTimber.class).newInstance(this);
this.managers.add(newManager);
return newManager;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
public ChoppingManager getChoppingManager() {
return this.choppingManager;
}
public ConfigurationManager getConfigurationManager() {
return this.configurationManager;
}
public PlacedBlockManager getPlacedBlockManager() {
return this.placedBlockManager;
}
public SaplingManager getSaplingManager() {
return this.saplingManager;
}
public TreeAnimationManager getTreeAnimationManager() {
return this.treeAnimationManager;
}
public TreeDefinitionManager getTreeDefinitionManager() {
return this.treeDefinitionManager;
}
public TreeDetectionManager getTreeDetectionManager() {
return this.treeDetectionManager;
}
public TreeFallManager getTreeFallManager() {
return this.treeFallManager;
}
}

View File

@ -0,0 +1,139 @@
package com.craftaro.ultimatetimber.animation;
import com.craftaro.core.compatibility.CompatibleHand;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.FallingTreeBlock;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockSet;
import com.craftaro.ultimatetimber.utils.BlockUtils;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public abstract class TreeAnimation {
protected final TreeAnimationType treeAnimationType;
protected final DetectedTree detectedTree;
protected final Player player;
protected final boolean hasSilkTouch;
protected TreeBlockSet<FallingBlock> fallingTreeBlocks;
TreeAnimation(TreeAnimationType treeAnimationType, DetectedTree detectedTree, Player player) {
this.treeAnimationType = treeAnimationType;
this.detectedTree = detectedTree;
this.player = player;
ItemStack itemInHand = CompatibleHand.getHand(CompatibleHand.MAIN_HAND).getItem(player);
this.hasSilkTouch = itemInHand != null && itemInHand.hasItemMeta() && itemInHand.getItemMeta().hasEnchant(Enchantment.SILK_TOUCH);
this.fallingTreeBlocks = new TreeBlockSet<>(); // Should be overridden in any subclasses that need to use it
}
/**
* Plays this tree topple animation
*
* @param whenFinished The runnable to run when the animation is done
*/
public abstract void playAnimation(Runnable whenFinished);
/**
* Gets the type of tree animation that this is
*
* @return The TreeAnimationType
*/
public TreeAnimationType getTreeAnimationType() {
return this.treeAnimationType;
}
/**
* Gets the detected tree
*
* @return The detected tree
*/
public DetectedTree getDetectedTree() {
return this.detectedTree;
}
/**
* Gets the player who started this tree animation
*
* @return The player who started this tree animation
*/
public Player getPlayer() {
return this.player;
}
/**
* Checks if this tree animation has silk touch
*
* @return True if this animation has silk touch, otherwise false
*/
public boolean hasSilkTouch() {
return this.hasSilkTouch;
}
/**
* Gets a TreeBlockSet of the active falling tree blocks
* May return null if the animation type does not use falling blocks
*
* @return A tree block set
*/
public TreeBlockSet<FallingBlock> getFallingTreeBlocks() {
return this.fallingTreeBlocks;
}
/**
* Converts a TreeBlock into a FallingTreeBlock
*
* @param treeBlock The TreeBlock to convert
* @return A FallingTreeBlock that has been converted from a TreeBlock
*/
protected FallingTreeBlock convertToFallingBlock(TreeBlock treeBlock) {
Location location = treeBlock.getLocation().clone().add(0.5, 0, 0.5);
Block block = treeBlock.getBlock();
XMaterial material = CompatibleMaterial.getMaterial(block.getType()).get();
if (CompatibleMaterial.isAir(material)) {
this.replaceBlock(treeBlock);
return null;
}
FallingBlock fallingBlock = BlockUtils.spawnFallingBlock(location, material);
BlockUtils.configureFallingBlock(fallingBlock);
FallingTreeBlock fallingTreeBlock = new FallingTreeBlock(fallingBlock, treeBlock.getTreeBlockType());
this.replaceBlock(treeBlock);
return fallingTreeBlock;
}
/**
* Replaces a given block with a new one
*
* @param treeBlock The tree block to replace
*/
public void replaceBlock(TreeBlock treeBlock) {
treeBlock.getBlock().setType(Material.AIR);
UltimateTimber.getInstance().getSaplingManager().replantSapling(this.detectedTree.getTreeDefinition(), treeBlock);
}
/**
* Removes a falling block from the animation
*
* @param fallingBlock The FallingBlock to remove
*/
public void removeFallingBlock(FallingBlock fallingBlock) {
for (ITreeBlock<FallingBlock> fallingTreeBlock : this.fallingTreeBlocks.getAllTreeBlocks()) {
if (fallingTreeBlock.getBlock().equals(fallingBlock)) {
this.fallingTreeBlocks.remove(fallingTreeBlock);
return;
}
}
}
}

View File

@ -0,0 +1,109 @@
package com.craftaro.ultimatetimber.animation;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.manager.ConfigurationManager;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.FallingTreeBlock;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockSet;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import com.craftaro.ultimatetimber.utils.BlockUtils;
import com.craftaro.ultimatetimber.utils.ParticleUtils;
import com.craftaro.ultimatetimber.utils.SoundUtils;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TreeAnimationCrumble extends TreeAnimation {
public TreeAnimationCrumble(DetectedTree detectedTree, Player player) {
super(TreeAnimationType.CRUMBLE, detectedTree, player);
}
@Override
public void playAnimation(Runnable whenFinished) {
UltimateTimber ultimateTimber = UltimateTimber.getInstance();
boolean useCustomSound = ConfigurationManager.Setting.USE_CUSTOM_SOUNDS.getBoolean();
boolean useCustomParticles = ConfigurationManager.Setting.USE_CUSTOM_PARTICLES.getBoolean();
// Order blocks by y-axis, lowest first, but shuffled randomly
int currentY = -1;
List<List<ITreeBlock<Block>>> treeBlocks = new ArrayList<>();
List<ITreeBlock<Block>> currentPartition = new ArrayList<>();
List<ITreeBlock<Block>> orderedDetectedTreeBlocks = new ArrayList<>(this.detectedTree.getDetectedTreeBlocks().getAllTreeBlocks());
orderedDetectedTreeBlocks.sort(Comparator.comparingInt(x -> x.getLocation().getBlockY()));
for (ITreeBlock<Block> treeBlock : orderedDetectedTreeBlocks) {
if (currentY != treeBlock.getLocation().getBlockY()) {
Collections.shuffle(currentPartition);
treeBlocks.add(new ArrayList<>(currentPartition));
currentPartition.clear();
currentY = treeBlock.getLocation().getBlockY();
}
currentPartition.add(treeBlock);
}
Collections.shuffle(currentPartition);
treeBlocks.add(new ArrayList<>(currentPartition));
TreeDefinition td = this.detectedTree.getTreeDefinition();
new BukkitRunnable() {
@Override
public void run() {
if (!treeBlocks.isEmpty()) {
List<ITreeBlock<Block>> partition = treeBlocks.get(0);
for (int i = 0; i < 3 && !partition.isEmpty(); i++) {
ITreeBlock<Block> treeBlock = partition.remove(0);
if (treeBlock.getTreeBlockType() == TreeBlockType.LOG) {
if (td.getLogMaterial().stream().noneMatch(x -> x == CompatibleMaterial.getMaterial(treeBlock.getBlock().getType()).orElse(null))) {
continue;
}
} else if (treeBlock.getTreeBlockType() == TreeBlockType.LEAF) {
if (td.getLeafMaterial().stream().noneMatch(x -> x == CompatibleMaterial.getMaterial(treeBlock.getBlock().getType()).orElse(null))) {
continue;
}
}
FallingTreeBlock fallingTreeBlock = TreeAnimationCrumble.this.convertToFallingBlock((TreeBlock) treeBlock);
if (fallingTreeBlock == null) {
continue;
}
BlockUtils.toggleGravityFallingBlock(fallingTreeBlock.getBlock(), true);
fallingTreeBlock.getBlock().setVelocity(Vector.getRandom().setY(0).subtract(new Vector(0.5, 0, 0.5)).multiply(0.15));
TreeAnimationCrumble.this.fallingTreeBlocks.add(fallingTreeBlock);
if (TreeAnimationCrumble.this.fallingTreeBlocks == null) {
TreeAnimationCrumble.this.fallingTreeBlocks = new TreeBlockSet<>(fallingTreeBlock);
}
if (useCustomSound) {
SoundUtils.playLandingSound(treeBlock);
}
if (useCustomParticles) {
ParticleUtils.playFallingParticles(treeBlock);
}
}
if (partition.isEmpty()) {
treeBlocks.remove(0);
}
}
if (treeBlocks.isEmpty() && TreeAnimationCrumble.this.fallingTreeBlocks.getAllTreeBlocks().isEmpty()) {
whenFinished.run();
this.cancel();
}
}
}.runTaskTimer(ultimateTimber, 0, 1);
}
}

View File

@ -0,0 +1,101 @@
package com.craftaro.ultimatetimber.animation;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.manager.TreeDefinitionManager;
import com.craftaro.ultimatetimber.manager.ConfigurationManager;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import com.craftaro.ultimatetimber.utils.ParticleUtils;
import com.craftaro.ultimatetimber.utils.SoundUtils;
import org.bukkit.block.Block;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TreeAnimationDisintegrate extends TreeAnimation {
public TreeAnimationDisintegrate(DetectedTree detectedTree, Player player) {
super(TreeAnimationType.DISINTEGRATE, detectedTree, player);
}
@Override
public void playAnimation(Runnable whenFinished) {
UltimateTimber ultimateTimber = UltimateTimber.getInstance();
TreeDefinitionManager treeDefinitionManager = ultimateTimber.getTreeDefinitionManager();
boolean useCustomSound = ConfigurationManager.Setting.USE_CUSTOM_SOUNDS.getBoolean();
boolean useCustomParticles = ConfigurationManager.Setting.USE_CUSTOM_PARTICLES.getBoolean();
List<ITreeBlock<Block>> orderedLogBlocks = new ArrayList<>(this.detectedTree.getDetectedTreeBlocks().getLogBlocks());
orderedLogBlocks.sort(Comparator.comparingInt(x -> x.getLocation().getBlockY()));
List<ITreeBlock<Block>> leafBlocks = new ArrayList<>(this.detectedTree.getDetectedTreeBlocks().getLeafBlocks());
Collections.shuffle(leafBlocks);
Player p = this.player;
TreeDefinition td = this.detectedTree.getTreeDefinition();
boolean hst = this.hasSilkTouch;
new BukkitRunnable() {
@Override
public void run() {
List<ITreeBlock<Block>> toDestroy = new ArrayList<>();
if (!orderedLogBlocks.isEmpty()) {
ITreeBlock<Block> treeBlock = orderedLogBlocks.remove(0);
toDestroy.add(treeBlock);
} else if (!leafBlocks.isEmpty()) {
ITreeBlock<Block> treeBlock = leafBlocks.remove(0);
toDestroy.add(treeBlock);
if (!leafBlocks.isEmpty()) {
treeBlock = leafBlocks.remove(0);
toDestroy.add(treeBlock);
}
}
for (ITreeBlock<FallingBlock> fallingTreeBlock : TreeAnimationDisintegrate.this.fallingTreeBlocks.getAllTreeBlocks()) {
FallingBlock fallingBlock = fallingTreeBlock.getBlock();
fallingBlock.setVelocity(fallingBlock.getVelocity().clone().subtract(new Vector(0, 0.05, 0)));
}
if (!toDestroy.isEmpty()) {
ITreeBlock<Block> first = toDestroy.get(0);
if (useCustomSound) {
SoundUtils.playLandingSound(first);
}
for (ITreeBlock<Block> treeBlock : toDestroy) {
if (treeBlock.getTreeBlockType() == TreeBlockType.LOG) {
if (td.getLogMaterial().stream().noneMatch(x -> x == CompatibleMaterial.getMaterial(treeBlock.getBlock().getType()).orElse(null))) {
continue;
}
} else if (treeBlock.getTreeBlockType() == TreeBlockType.LEAF) {
if (td.getLeafMaterial().stream().noneMatch(x -> x == CompatibleMaterial.getMaterial(treeBlock.getBlock().getType()).orElse(null))) {
continue;
}
}
if (useCustomParticles) {
ParticleUtils.playFallingParticles(treeBlock);
}
treeDefinitionManager.dropTreeLoot(td, treeBlock, p, hst, false);
TreeAnimationDisintegrate.this.replaceBlock((TreeBlock) treeBlock);
}
} else {
this.cancel();
whenFinished.run();
}
}
}.runTaskTimer(ultimateTimber, 0, 1);
}
}

View File

@ -0,0 +1,97 @@
package com.craftaro.ultimatetimber.animation;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.manager.ConfigurationManager;
import com.craftaro.ultimatetimber.manager.TreeAnimationManager;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.FallingTreeBlock;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockSet;
import com.craftaro.ultimatetimber.utils.BlockUtils;
import com.craftaro.ultimatetimber.utils.ParticleUtils;
import com.craftaro.ultimatetimber.utils.SoundUtils;
import org.bukkit.block.Block;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
public class TreeAnimationFancy extends TreeAnimation {
public TreeAnimationFancy(DetectedTree detectedTree, Player player) {
super(TreeAnimationType.FANCY, detectedTree, player);
}
@Override
public void playAnimation(Runnable whenFinished) {
UltimateTimber ultimateTimber = UltimateTimber.getInstance();
boolean useCustomSound = ConfigurationManager.Setting.USE_CUSTOM_SOUNDS.getBoolean();
boolean useCustomParticles = ConfigurationManager.Setting.USE_CUSTOM_PARTICLES.getBoolean();
ITreeBlock<Block> initialTreeBlock = this.detectedTree.getDetectedTreeBlocks().getInitialLogBlock();
FallingTreeBlock initialFallingBlock = this.convertToFallingBlock((TreeBlock) this.detectedTree.getDetectedTreeBlocks().getInitialLogBlock());
if (useCustomSound) {
SoundUtils.playFallingSound(initialTreeBlock);
}
Vector velocityVector = initialTreeBlock.getLocation().clone().subtract(this.player.getLocation().clone()).toVector().normalize().setY(0);
this.fallingTreeBlocks = new TreeBlockSet<>(initialFallingBlock);
for (ITreeBlock<Block> treeBlock : this.detectedTree.getDetectedTreeBlocks().getAllTreeBlocks()) {
FallingTreeBlock fallingTreeBlock = this.convertToFallingBlock((TreeBlock) treeBlock);
if (fallingTreeBlock == null) {
continue;
}
FallingBlock fallingBlock = fallingTreeBlock.getBlock();
this.fallingTreeBlocks.add(fallingTreeBlock);
if (useCustomParticles) {
ParticleUtils.playFallingParticles(treeBlock);
}
double multiplier = (treeBlock.getLocation().getY() - this.player.getLocation().getY()) * 0.05;
fallingBlock.setVelocity(velocityVector.clone().multiply(multiplier));
fallingBlock.setVelocity(fallingBlock.getVelocity().multiply(0.3));
}
new BukkitRunnable() {
int timer = 0;
@Override
public void run() {
if (this.timer == 0) {
for (ITreeBlock<FallingBlock> fallingTreeBlock : TreeAnimationFancy.this.fallingTreeBlocks.getAllTreeBlocks()) {
FallingBlock fallingBlock = fallingTreeBlock.getBlock();
BlockUtils.toggleGravityFallingBlock(fallingBlock, true);
fallingBlock.setVelocity(fallingBlock.getVelocity().multiply(1.5));
}
}
if (TreeAnimationFancy.this.fallingTreeBlocks.getAllTreeBlocks().isEmpty()) {
whenFinished.run();
this.cancel();
return;
}
for (ITreeBlock<FallingBlock> fallingTreeBlock : TreeAnimationFancy.this.fallingTreeBlocks.getAllTreeBlocks()) {
FallingBlock fallingBlock = fallingTreeBlock.getBlock();
fallingBlock.setVelocity(fallingBlock.getVelocity().clone().subtract(new Vector(0, 0.05, 0)));
}
this.timer++;
if (this.timer > 4 * 20) {
TreeAnimationManager treeAnimationManager = ultimateTimber.getTreeAnimationManager();
for (ITreeBlock<FallingBlock> fallingTreeBlock : TreeAnimationFancy.this.fallingTreeBlocks.getAllTreeBlocks()) {
treeAnimationManager.runFallingBlockImpact(TreeAnimationFancy.this, fallingTreeBlock);
}
whenFinished.run();
this.cancel();
}
}
}.runTaskTimer(ultimateTimber, 20L, 1L);
}
}

View File

@ -0,0 +1,37 @@
package com.craftaro.ultimatetimber.animation;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.manager.TreeDefinitionManager;
import com.craftaro.ultimatetimber.manager.ConfigurationManager;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.utils.ParticleUtils;
import com.craftaro.ultimatetimber.utils.SoundUtils;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
public class TreeAnimationNone extends TreeAnimation {
public TreeAnimationNone(DetectedTree detectedTree, Player player) {
super(TreeAnimationType.NONE, detectedTree, player);
}
@Override
public void playAnimation(Runnable whenFinished) {
TreeDefinitionManager treeDefinitionManager = UltimateTimber.getInstance().getTreeDefinitionManager();
if (ConfigurationManager.Setting.USE_CUSTOM_SOUNDS.getBoolean())
SoundUtils.playFallingSound(this.detectedTree.getDetectedTreeBlocks().getInitialLogBlock());
if (ConfigurationManager.Setting.USE_CUSTOM_PARTICLES.getBoolean())
for (ITreeBlock<Block> treeBlock : this.detectedTree.getDetectedTreeBlocks().getAllTreeBlocks())
ParticleUtils.playFallingParticles(treeBlock);
for (ITreeBlock<Block> treeBlock : this.detectedTree.getDetectedTreeBlocks().getAllTreeBlocks()) {
treeDefinitionManager.dropTreeLoot(this.detectedTree.getTreeDefinition(), treeBlock, this.player, this.hasSilkTouch, false);
this.replaceBlock((TreeBlock) treeBlock);
}
whenFinished.run();
}
}

View File

@ -0,0 +1,24 @@
package com.craftaro.ultimatetimber.animation;
/**
* The types of tree animations that are available
*/
public enum TreeAnimationType {
FANCY, DISINTEGRATE, CRUMBLE, NONE;
/**
* Gets a TreeAnimationType from a given string
*
* @param string The string
* @return The TreeAnimationType, returns FANCY if the string is an invalid type
*/
public static TreeAnimationType fromString(String string) {
for (TreeAnimationType value : values()) {
if (value.name().equalsIgnoreCase(string)) {
return value;
}
}
return TreeAnimationType.FANCY;
}
}

View File

@ -0,0 +1,80 @@
package com.craftaro.ultimatetimber.commands;
import com.craftaro.core.commands.AbstractCommand;
import com.craftaro.core.utils.PlayerUtils;
import com.craftaro.ultimatetimber.UltimateTimber;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class CommandGiveAxe extends AbstractCommand {
private final UltimateTimber plugin;
public CommandGiveAxe(UltimateTimber plugin) {
super(CommandType.CONSOLE_OK, true, "give");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if (args.length < 1) {
return ReturnType.SYNTAX_ERROR;
}
Player player = Bukkit.getPlayer(args[0]);
if (player == null) {
if (args[0].trim().equalsIgnoreCase("me")) {
if (!(sender instanceof Player)) {
return ReturnType.NEEDS_PLAYER;
}
player = (Player) sender;
} else {
this.plugin.getLocale().getMessageOrDefault("command.give.not-a-player", "&cNot a player.")
.sendPrefixedMessage(sender);
return ReturnType.FAILURE;
}
}
ItemStack axe = this.plugin.getTreeDefinitionManager().getRequiredAxe();
if (axe == null) {
this.plugin.getLocale().getMessageOrDefault("command.give.no-axe", "&cThe axe could not be loaded.")
.sendPrefixedMessage(sender);
return ReturnType.FAILURE;
}
player.getInventory().addItem(axe);
this.plugin.getLocale().getMessageOrDefault("command.give.given", "&fAxe given to &a%player%")
.processPlaceholder("player", player.getName())
.sendPrefixedMessage(sender);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
List<String> suggestions = null;
if (args.length == 1) {
suggestions = PlayerUtils.getVisiblePlayerNames(sender, args[0]);
suggestions.add("me");
}
return suggestions;
}
@Override
public String getPermissionNode() {
return "ultimatetimber.give";
}
@Override
public String getSyntax() {
return "give <player/me>";
}
@Override
public String getDescription() {
return "Give a required axe.";
}
}

View File

@ -0,0 +1,43 @@
package com.craftaro.ultimatetimber.commands;
import com.craftaro.core.commands.AbstractCommand;
import com.craftaro.ultimatetimber.UltimateTimber;
import org.bukkit.command.CommandSender;
import java.util.List;
public class CommandReload extends AbstractCommand {
private final UltimateTimber plugin;
public CommandReload(UltimateTimber plugin) {
super(CommandType.CONSOLE_OK, "reload");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
this.plugin.reloadConfig();
this.plugin.getLocale().getMessage("command.reload.reloaded").sendPrefixedMessage(sender);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}
@Override
public String getPermissionNode() {
return "ultimatetimber.reload";
}
@Override
public String getSyntax() {
return "reload";
}
@Override
public String getDescription() {
return this.plugin.getLocale().getMessage("command.reload.description").getMessage();
}
}

View File

@ -0,0 +1,48 @@
package com.craftaro.ultimatetimber.commands;
import com.craftaro.core.commands.AbstractCommand;
import com.craftaro.ultimatetimber.UltimateTimber;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
public class CommandToggle extends AbstractCommand {
private final UltimateTimber plugin;
public CommandToggle(UltimateTimber plugin) {
super(CommandType.CONSOLE_OK, "toggle");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if (this.plugin.getChoppingManager().togglePlayer((Player) sender)) {
this.plugin.getLocale().getMessage("command.toggle.enabled").sendPrefixedMessage(sender);
} else {
this.plugin.getLocale().getMessage("command.toggle.disabled").sendPrefixedMessage(sender);
}
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}
@Override
public String getPermissionNode() {
return "ultimatetimber.toggle";
}
@Override
public String getSyntax() {
return "toggle";
}
@Override
public String getDescription() {
return this.plugin.getLocale().getMessage("command.toggle.description").getMessage();
}
}

View File

@ -0,0 +1,67 @@
package com.craftaro.ultimatetimber.events;
import org.bukkit.entity.Entity;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
public class TreeDamageEvent extends PlayerEvent implements Cancellable {
private boolean cancelled = false;
private FallingBlock blockAttacker = null;
private Player playerAttacker = null;
private static final HandlerList handlers = new HandlerList();
/**
* Represents a TreeDamage event.
*/
public TreeDamageEvent(FallingBlock attacker, Player victim) {
super(victim);
this.blockAttacker = attacker;
}
// Hoping this one is used whenever possible
/**
* Represents a TreeDamage event.
*/
public TreeDamageEvent(Player attacker, Player victim) {
super(victim);
this.playerAttacker = attacker;
}
/**
* @return the attacker as either FallingBlock or Player
*/
public Entity getAttacker() {
if (this.playerAttacker != null)
return this.playerAttacker;
if (this.blockAttacker != null)
return this.blockAttacker;
return null;
}
/**
* Get Player damaged by this event. This method is only here for clarification
*/
public Player getVictim() {
return this.getPlayer();
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {return handlers;}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelState) {this.cancelled = cancelState;}
}

View File

@ -0,0 +1,26 @@
package com.craftaro.ultimatetimber.events;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerEvent;
/**
* Abstract tree event containing tree's blocks and broke block
*/
public abstract class TreeEvent extends PlayerEvent {
protected final DetectedTree detectedTree;
public TreeEvent(Player who, DetectedTree detectedTree) {
super(who);
this.detectedTree = detectedTree;
}
/**
* Get the tree blocks
*
* @return The blocks that are part of the tree
*/
public DetectedTree getDetectedTree() {
return this.detectedTree;
}
}

View File

@ -0,0 +1,39 @@
package com.craftaro.ultimatetimber.events;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
/**
* Called when a tree will fall
*/
public class TreeFallEvent extends TreeEvent implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private boolean cancelled = false;
public TreeFallEvent(Player who, DetectedTree detectedTree) {
super(who, detectedTree);
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@ -0,0 +1,25 @@
package com.craftaro.ultimatetimber.events;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
/**
* Called when a tree fell
*/
public class TreeFellEvent extends TreeEvent {
private static final HandlerList HANDLERS = new HandlerList();
public TreeFellEvent(Player who, DetectedTree detectedTree) {
super(who, detectedTree);
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@ -0,0 +1,91 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.ultimatetimber.UltimateTimber;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class ChoppingManager extends Manager {
private final Set<UUID> disabledPlayers;
private final Map<UUID, Boolean> cooldownedPlayers;
private boolean useCooldown;
private int cooldownAmount;
public ChoppingManager(UltimateTimber plugin) {
super(plugin);
this.disabledPlayers = new HashSet<>();
this.cooldownedPlayers = new HashMap<>();
}
@Override
public void reload() {
this.useCooldown = ConfigurationManager.Setting.PLAYER_TREE_TOPPLE_COOLDOWN.getBoolean();
this.cooldownAmount = ConfigurationManager.Setting.PLAYER_TREE_TOPPLE_COOLDOWN_LENGTH.getInt();
}
@Override
public void disable() {
this.disabledPlayers.clear();
this.cooldownedPlayers.clear();
}
/**
* Toggles a player's chopping status
*
* @param player The player to toggle
* @return True if the player has chopping enabled, or false if they have it disabled
*/
public boolean togglePlayer(Player player) {
if (this.disabledPlayers.contains(player.getUniqueId())) {
this.disabledPlayers.remove(player.getUniqueId());
return true;
} else {
this.disabledPlayers.add(player.getUniqueId());
return false;
}
}
/**
* Checks if a player has chopping enabled
*
* @param player The player to check
* @return True if the player has chopping enabled, or false if they have it disabled
*/
public boolean isChopping(Player player) {
return !this.disabledPlayers.contains(player.getUniqueId());
}
/**
* Sets a player into cooldown
*
* @param player The player to cooldown
*/
public void cooldownPlayer(Player player) {
if (!this.useCooldown || player.hasPermission("ultimatetimber.bypasscooldown"))
return;
this.cooldownedPlayers.put(player.getUniqueId(), false);
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> this.cooldownedPlayers.remove(player.getUniqueId()), this.cooldownAmount * 20L);
}
/**
* Checks if a player is in cooldown
*
* @param player The player to check
* @return True if the player can topple trees, otherwise false
*/
public boolean isInCooldown(Player player) {
boolean cooldowned = this.useCooldown && this.cooldownedPlayers.containsKey(player.getUniqueId());
if (cooldowned && !this.cooldownedPlayers.get(player.getUniqueId())) {
this.plugin.getLocale().getMessage("event.on.cooldown").sendPrefixedMessage(player);
this.cooldownedPlayers.replace(player.getUniqueId(), true);
}
return cooldowned;
}
}

View File

@ -0,0 +1,211 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.ultimatetimber.UltimateTimber;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.List;
public class ConfigurationManager extends Manager {
public enum Setting {
SERVER_TYPE(SettingType.STRING),
LOCALE(SettingType.STRING),
DISABLED_WORLDS(SettingType.STRING_LIST),
MAX_LOGS_PER_CHOP(SettingType.INT),
DESTROY_LEAVES(SettingType.BOOLEAN),
LEAVES_REQUIRED_FOR_TREE(SettingType.INT),
REALISTIC_TOOL_DAMAGE(SettingType.BOOLEAN),
PROTECT_TOOL(SettingType.BOOLEAN),
APPLY_SILK_TOUCH(SettingType.BOOLEAN),
APPLY_SILK_TOUCH_TOOL_DAMAGE(SettingType.BOOLEAN),
ALWAYS_REPLANT_SAPLING(SettingType.BOOLEAN),
BREAK_ENTIRE_TREE_BASE(SettingType.BOOLEAN),
DESTROY_INITIATED_BLOCK(SettingType.BOOLEAN),
ONLY_DETECT_LOGS_UPWARDS(SettingType.BOOLEAN),
ONLY_TOPPLE_WHILE(SettingType.STRING),
ALLOW_CREATIVE_MODE(SettingType.BOOLEAN),
REQUIRE_CHOP_PERMISSION(SettingType.BOOLEAN),
PLAYER_TREE_TOPPLE_COOLDOWN(SettingType.BOOLEAN),
PLAYER_TREE_TOPPLE_COOLDOWN_LENGTH(SettingType.INT),
IGNORE_REQUIRED_TOOLS(SettingType.BOOLEAN),
REPLANT_SAPLINGS(SettingType.BOOLEAN),
REPLANT_SAPLINGS_COOLDOWN(SettingType.INT),
FALLING_BLOCKS_REPLANT_SAPLINGS(SettingType.BOOLEAN),
FALLING_BLOCKS_REPLANT_SAPLINGS_CHANCE(SettingType.DOUBLE),
FALLING_BLOCKS_DEAL_DAMAGE(SettingType.BOOLEAN),
FALLING_BLOCK_DAMAGE(SettingType.INT),
ADD_ITEMS_TO_INVENTORY(SettingType.BOOLEAN),
USE_CUSTOM_SOUNDS(SettingType.BOOLEAN),
USE_CUSTOM_PARTICLES(SettingType.BOOLEAN),
BONUS_LOOT_MULTIPLIER(SettingType.DOUBLE),
IGNORE_PLACED_BLOCKS(SettingType.BOOLEAN),
IGNORE_PLACED_BLOCKS_MEMORY_SIZE(SettingType.INT),
HOOKS_APPLY_EXPERIENCE(SettingType.BOOLEAN),
HOOKS_APPLY_EXTRA_DROPS(SettingType.BOOLEAN),
HOOKS_REQUIRE_ABILITY_ACTIVE(SettingType.BOOLEAN),
TREE_ANIMATION_TYPE(SettingType.STRING),
SCATTER_TREE_BLOCKS_ON_GROUND(SettingType.BOOLEAN),
FRAGILE_BLOCKS(SettingType.STRING_LIST);
private final SettingType settingType;
private Object value = null;
Setting(SettingType settingType) {
this.settingType = settingType;
}
/**
* Gets the setting as a boolean
*
* @return The setting as a boolean
*/
public boolean getBoolean() {
this.loadValue();
return (boolean) this.value;
}
/**
* Gets the setting as an int
*
* @return The setting as an int
*/
public int getInt() {
this.loadValue();
return (int) this.value;
}
/**
* Gets the setting as a double
*
* @return The setting a double
*/
public double getDouble() {
this.loadValue();
return (double) this.value;
}
/**
* Gets the setting as a String
*
* @return The setting a String
*/
public String getString() {
this.loadValue();
return (String) this.value;
}
/**
* Gets the setting as a string list
*
* @return The setting as a string list
*/
@SuppressWarnings("unchecked")
public List<String> getStringList() {
this.loadValue();
return (List<String>) this.value;
}
/**
* Resets the cached value
*/
public void reset() {
this.value = null;
}
/**
* Loads the value from the config and caches it if it isn't set yet
*/
private void loadValue() {
if (this.value != null) {
return;
}
FileConfiguration config = UltimateTimber.getPlugin(UltimateTimber.class).getConfigurationManager().getConfig();
switch (this.settingType) {
case BOOLEAN:
this.value = config.getBoolean(this.getNameAsKey());
break;
case INT:
this.value = config.getInt(this.getNameAsKey());
break;
case DOUBLE:
this.value = config.getDouble(this.getNameAsKey());
break;
case STRING:
this.value = config.getString(this.getNameAsKey());
break;
case STRING_LIST:
this.value = config.getStringList(this.getNameAsKey());
break;
}
}
/**
* Gets the name of this Setting as a FileConfiguration-compatible key
*
* @return The key for a FileConfiguration
*/
private String getNameAsKey() {
return this.name().replace("_", "-").toLowerCase();
}
}
private enum SettingType {
BOOLEAN,
INT,
DOUBLE,
STRING,
STRING_LIST
}
private YamlConfiguration configuration;
public ConfigurationManager(UltimateTimber plugin) {
super(plugin);
}
@Override
public void reload() {
this.plugin.getCoreConfig().load();
File configFile = new File(this.plugin.getDataFolder() + "/config.yml");
// If an old config still exists, rename it, so it doesn't interfere
if (configFile.exists() && this.plugin.getConfig().get("server-type") == null) {
File renameConfigTo = new File(this.plugin.getDataFolder() + "/config-old.yml");
configFile.renameTo(renameConfigTo);
configFile = new File(this.plugin.getDataFolder() + "/config.yml");
}
// Create the new config if it doesn't exist
if (!configFile.exists()) {
String newConfigName = "config.yml";
File newConfigFile = new File(this.plugin.getDataFolder() + "/" + newConfigName);
this.plugin.saveResource(newConfigName, false);
newConfigFile.renameTo(configFile);
}
this.configuration = YamlConfiguration.loadConfiguration(configFile);
for (Setting setting : Setting.values()) {
setting.reset();
}
}
@Override
public void disable() {
for (Setting setting : Setting.values()) {
setting.reset();
}
}
/**
* Gets the config.yml as a YamlConfiguration
*
* @return The YamlConfiguration of the config.yml
*/
public YamlConfiguration getConfig() {
return this.configuration;
}
}

View File

@ -0,0 +1,21 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.ultimatetimber.UltimateTimber;
public abstract class Manager {
protected UltimateTimber plugin;
Manager(UltimateTimber plugin) {
this.plugin = plugin;
}
/**
* Reloads the Manager's settings
*/
public abstract void reload();
/**
* Cleans up the Manager's resources
*/
public abstract void disable();
}

View File

@ -0,0 +1,131 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.events.TreeFellEvent;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.event.world.StructureGrowEvent;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class PlacedBlockManager extends Manager implements Listener {
private Set<Location> placedBlocks;
private boolean ignorePlacedBlocks;
private int maxPlacedBlockMemorySize;
public PlacedBlockManager(UltimateTimber plugin) {
super(plugin);
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void reload() {
this.ignorePlacedBlocks = ConfigurationManager.Setting.IGNORE_PLACED_BLOCKS.getBoolean();
this.maxPlacedBlockMemorySize = ConfigurationManager.Setting.IGNORE_PLACED_BLOCKS_MEMORY_SIZE.getInt();
// Ensures the oldest entry is removed if it exceeds the limit
this.placedBlocks = Collections.newSetFromMap(new LinkedHashMap<Location, Boolean>() {
@Override
protected boolean removeEldestEntry(Map.Entry<Location, Boolean> eldest) {
return this.size() > PlacedBlockManager.this.maxPlacedBlockMemorySize;
}
});
}
@Override
public void disable() {
this.placedBlocks.clear();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlaced(BlockPlaceEvent event) {
if (!this.ignorePlacedBlocks) {
return;
}
// Ignore stripping logs
if (event.getBlockPlaced().getType().name().contains("STRIPPED") && !CompatibleMaterial.isAir(CompatibleMaterial.getMaterial(event.getBlockReplacedState().getType()).get())) {
return;
}
this.internalProtect(event.getBlock(), true);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
if (!this.ignorePlacedBlocks) {
return;
}
this.internalProtect(event.getBlock(), false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onLeafDecay(LeavesDecayEvent event) {
if (!this.ignorePlacedBlocks) {
return;
}
this.internalProtect(event.getBlock(), false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onStructureGrow(StructureGrowEvent event) {
if (!this.ignorePlacedBlocks) {
return;
}
for (BlockState blockState : event.getBlocks()) {
this.internalProtect(blockState.getBlock(), false);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTreeFell(TreeFellEvent event) {
if (!this.ignorePlacedBlocks) {
return;
}
for (ITreeBlock<Block> treeBlock : event.getDetectedTree().getDetectedTreeBlocks().getAllTreeBlocks()) {
this.internalProtect(treeBlock.getBlock(), false);
}
}
/**
* Handles when a block is placed/broken
*/
private void internalProtect(Block block, boolean isPlaced) {
if (isPlaced) {
if (this.isBlockPlaced(block)) {
return;
}
this.placedBlocks.add(block.getLocation());
} else {
this.placedBlocks.remove(block.getLocation());
}
}
/**
* Gets if a block is placed
*
* @param block The Block to check
* @return True if the block is placed, otherwise false
*/
public boolean isBlockPlaced(Block block) {
return this.placedBlocks.contains(block.getLocation());
}
}

View File

@ -0,0 +1,120 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.third_party.com.cryptomorin.xseries.XBlock;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class SaplingManager extends Manager {
private final Random random;
private final Set<Location> protectedSaplings;
public SaplingManager(UltimateTimber plugin) {
super(plugin);
this.random = new Random();
this.protectedSaplings = new HashSet<>();
}
@Override
public void reload() {
}
@Override
public void disable() {
}
/**
* Replants a sapling given a TreeDefinition and Location
* Takes into account config settings
*
* @param treeDefinition The TreeDefinition of the sapling
* @param treeBlock The ITreeBlock to replant for
*/
public void replantSapling(TreeDefinition treeDefinition, ITreeBlock treeBlock) {
if (!ConfigurationManager.Setting.REPLANT_SAPLINGS.getBoolean()) {
return;
}
Block block = treeBlock.getLocation().getBlock();
if (block.getType() != Material.AIR || treeBlock.getTreeBlockType() == TreeBlockType.LEAF) {
return;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> this.internalReplant(treeDefinition, treeBlock), 1L);
}
/**
* Randomly replants a sapling given a TreeDefinition and Location
* Takes into account config settings
*
* @param treeDefinition The TreeDefinition of the sapling
* @param treeBlock The ITreeBlock to replant for
*/
public void replantSaplingWithChance(TreeDefinition treeDefinition, ITreeBlock treeBlock) {
if (!ConfigurationManager.Setting.FALLING_BLOCKS_REPLANT_SAPLINGS.getBoolean() || !CompatibleMaterial.isAir(CompatibleMaterial.getMaterial(treeBlock.getLocation().getBlock().getType()).get())) {
return;
}
double chance = ConfigurationManager.Setting.FALLING_BLOCKS_REPLANT_SAPLINGS_CHANCE.getDouble();
if (this.random.nextDouble() > chance / 100) {
return;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> this.internalReplant(treeDefinition, treeBlock), 1L);
}
/**
* Replants a sapling given a TreeDefinition and Location
*
* @param treeDefinition The TreeDefinition of the sapling
* @param treeBlock The ITreeBlock to replant for
*/
private void internalReplant(TreeDefinition treeDefinition, ITreeBlock treeBlock) {
TreeDefinitionManager treeDefinitionManager = this.plugin.getTreeDefinitionManager();
Block block = treeBlock.getLocation().getBlock();
Block blockBelow = block.getRelative(BlockFace.DOWN);
boolean isValidSoil = false;
for (XMaterial soilMaterial : treeDefinitionManager.getPlantableSoilMaterial(treeDefinition)) {
if (soilMaterial == CompatibleMaterial.getMaterial(blockBelow.getType()).orElse(null)) {
isValidSoil = true;
break;
}
}
if (!isValidSoil) {
return;
}
XMaterial material = treeDefinition.getSaplingMaterial();
XBlock.setType(block, material);
int cooldown = ConfigurationManager.Setting.REPLANT_SAPLINGS_COOLDOWN.getInt();
if (cooldown != 0) {
this.protectedSaplings.add(block.getLocation());
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> this.protectedSaplings.remove(block.getLocation()), cooldown * 20L);
}
}
/**
* Gets if a sapling is protected
*
* @param block The Block to check
* @return True if the sapling is protected, otherwise false
*/
public boolean isSaplingProtected(Block block) {
return this.protectedSaplings.contains(block.getLocation());
}
}

View File

@ -0,0 +1,224 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.animation.TreeAnimation;
import com.craftaro.ultimatetimber.animation.TreeAnimationCrumble;
import com.craftaro.ultimatetimber.animation.TreeAnimationDisintegrate;
import com.craftaro.ultimatetimber.animation.TreeAnimationFancy;
import com.craftaro.ultimatetimber.animation.TreeAnimationNone;
import com.craftaro.ultimatetimber.animation.TreeAnimationType;
import com.craftaro.ultimatetimber.events.TreeDamageEvent;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import com.craftaro.ultimatetimber.utils.ParticleUtils;
import com.craftaro.ultimatetimber.utils.SoundUtils;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import java.util.HashSet;
import java.util.Set;
public class TreeAnimationManager extends Manager implements Listener, Runnable {
private final Set<TreeAnimation> activeAnimations;
private final int taskId;
public TreeAnimationManager(UltimateTimber plugin) {
super(plugin);
this.activeAnimations = new HashSet<>();
this.taskId = -1;
Bukkit.getPluginManager().registerEvents(this, plugin);
Bukkit.getScheduler().runTaskTimer(this.plugin, this, 0, 1L);
}
@Override
public void reload() {
this.activeAnimations.clear();
}
@Override
public void disable() {
this.activeAnimations.clear();
Bukkit.getScheduler().cancelTask(this.taskId);
}
@Override
public void run() {
for (TreeAnimation treeAnimation : this.activeAnimations) {
Set<ITreeBlock<FallingBlock>> groundedBlocks = new HashSet<>();
for (ITreeBlock<FallingBlock> fallingTreeBlock : treeAnimation.getFallingTreeBlocks().getAllTreeBlocks()) {
FallingBlock fallingBlock = fallingTreeBlock.getBlock();
if (fallingBlock.isDead() || ServerVersion.isServerVersionAtLeast(ServerVersion.V1_17) && fallingBlock.isOnGround()) {
groundedBlocks.add(fallingTreeBlock);
}
}
for (ITreeBlock<FallingBlock> fallingBlock : groundedBlocks) {
this.runFallingBlockImpact(treeAnimation, fallingBlock);
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_17)) {
fallingBlock.getBlock().remove();
}
treeAnimation.getFallingTreeBlocks().remove(fallingBlock);
}
}
}
/**
* Plays an animation for toppling a tree
*
* @param detectedTree The DetectedTree
* @param player The Player who toppled the tree
*/
public void runAnimation(DetectedTree detectedTree, Player player) {
switch (TreeAnimationType.fromString(ConfigurationManager.Setting.TREE_ANIMATION_TYPE.getString())) {
case FANCY:
this.registerTreeAnimation(new TreeAnimationFancy(detectedTree, player));
break;
case DISINTEGRATE:
this.registerTreeAnimation(new TreeAnimationDisintegrate(detectedTree, player));
break;
case CRUMBLE:
this.registerTreeAnimation(new TreeAnimationCrumble(detectedTree, player));
break;
case NONE:
this.registerTreeAnimation(new TreeAnimationNone(detectedTree, player));
break;
}
}
/**
* Checks if the given block is in an animation
*
* @param block The block to check
*/
public boolean isBlockInAnimation(Block block) {
for (TreeAnimation treeAnimation : this.activeAnimations) {
for (ITreeBlock<Block> treeBlock : treeAnimation.getDetectedTree().getDetectedTreeBlocks().getAllTreeBlocks()) {
if (treeBlock.getBlock().equals(block)) {
return true;
}
}
}
return false;
}
/**
* Checks if the given falling block is in an animation
*
* @param fallingBlock The falling block to check
*/
public boolean isBlockInAnimation(FallingBlock fallingBlock) {
for (TreeAnimation treeAnimation : this.activeAnimations) {
for (ITreeBlock<FallingBlock> treeBlock : treeAnimation.getFallingTreeBlocks().getAllTreeBlocks()) {
if (treeBlock.getBlock().equals(fallingBlock)) {
return true;
}
}
}
return false;
}
/**
* Gets a TreeAnimation that a given falling block is in
*
* @return A TreeAnimation
*/
private TreeAnimation getAnimationForBlock(FallingBlock fallingBlock) {
for (TreeAnimation treeAnimation : this.activeAnimations) {
for (ITreeBlock<FallingBlock> treeBlock : treeAnimation.getFallingTreeBlocks().getAllTreeBlocks()) {
if (treeBlock.getBlock().equals(fallingBlock)) {
return treeAnimation;
}
}
}
return null;
}
/**
* Registers and runs a tree animation
*/
private void registerTreeAnimation(TreeAnimation treeAnimation) {
this.activeAnimations.add(treeAnimation);
treeAnimation.playAnimation(() -> this.activeAnimations.remove(treeAnimation));
}
/**
* Reacts to a falling block hitting the ground
*
* @param treeAnimation The tree animation for the falling block
* @param treeBlock The tree block to impact
*/
public void runFallingBlockImpact(TreeAnimation treeAnimation, ITreeBlock<FallingBlock> treeBlock) {
TreeDefinitionManager treeDefinitionManager = this.plugin.getTreeDefinitionManager();
boolean useCustomSound = ConfigurationManager.Setting.USE_CUSTOM_SOUNDS.getBoolean();
boolean useCustomParticles = ConfigurationManager.Setting.USE_CUSTOM_PARTICLES.getBoolean();
TreeDefinition treeDefinition = treeAnimation.getDetectedTree().getTreeDefinition();
if (useCustomParticles) {
ParticleUtils.playLandingParticles(treeBlock);
}
if (useCustomSound) {
SoundUtils.playLandingSound(treeBlock);
}
Block block = treeBlock.getLocation().subtract(0, 1, 0).getBlock();
if (ConfigurationManager.Setting.FRAGILE_BLOCKS.getStringList().contains(block.getType().toString())) {
block.getWorld().dropItemNaturally(block.getLocation(), CompatibleMaterial.getMaterial(block.getType()).get().parseItem());
block.breakNaturally();
}
treeDefinitionManager.dropTreeLoot(treeDefinition, treeBlock, treeAnimation.getPlayer(), treeAnimation.hasSilkTouch(), false);
this.plugin.getSaplingManager().replantSaplingWithChance(treeDefinition, treeBlock);
treeAnimation.getFallingTreeBlocks().remove(treeBlock);
}
@EventHandler(priority = EventPriority.HIGH)
public void onFallingBlockLand(EntityChangeBlockEvent event) {
if (event.getEntityType() != EntityType.FALLING_BLOCK) {
return;
}
FallingBlock fallingBlock = (FallingBlock) event.getEntity();
if (!this.isBlockInAnimation(fallingBlock)) {
return;
}
if (ConfigurationManager.Setting.FALLING_BLOCKS_DEAL_DAMAGE.getBoolean()) {
int damage = ConfigurationManager.Setting.FALLING_BLOCK_DAMAGE.getInt();
for (Entity entity : fallingBlock.getNearbyEntities(0.5, 0.5, 0.5)) {
if (!(entity instanceof LivingEntity)) {
continue;
}
if (entity instanceof Player) {
Player p = ((Player) entity).getPlayer();
TreeDamageEvent treeDamageEvent = new TreeDamageEvent(fallingBlock, p);
Bukkit.getServer().getPluginManager().callEvent(treeDamageEvent);
if (!treeDamageEvent.isCancelled())
((LivingEntity) entity).damage(damage, fallingBlock);
} else
((LivingEntity) entity).damage(damage, fallingBlock);
}
}
if (ConfigurationManager.Setting.SCATTER_TREE_BLOCKS_ON_GROUND.getBoolean()) {
TreeAnimation treeAnimation = this.getAnimationForBlock(fallingBlock);
if (treeAnimation != null) {
treeAnimation.removeFallingBlock(fallingBlock);
return;
}
}
event.setCancelled(true);
}
}

View File

@ -0,0 +1,543 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.core.hooks.McMMOHook;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.core.third_party.de.tr7zw.nbtapi.NBTItem;
import com.craftaro.core.utils.TextUtils;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.google.common.base.Strings;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import com.craftaro.ultimatetimber.tree.TreeLoot;
import com.craftaro.ultimatetimber.utils.BlockUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
public class TreeDefinitionManager extends Manager {
private final Random random;
private final Set<TreeDefinition> treeDefinitions;
private final Set<XMaterial> globalPlantableSoil;
private final Set<TreeLoot> globalLogLoot, globalLeafLoot, globalEntireTreeLoot;
private final Set<ItemStack> globalRequiredTools;
private boolean globalAxeRequired;
private ItemStack requiredAxe;
private String requiredAxeKey;
public TreeDefinitionManager(UltimateTimber plugin) {
super(plugin);
this.random = new Random();
this.treeDefinitions = new HashSet<>();
this.globalPlantableSoil = new HashSet<>();
this.globalLogLoot = new HashSet<>();
this.globalLeafLoot = new HashSet<>();
this.globalEntireTreeLoot = new HashSet<>();
this.globalRequiredTools = new HashSet<>();
}
@Override
public void reload() {
this.treeDefinitions.clear();
this.globalPlantableSoil.clear();
this.globalLogLoot.clear();
this.globalLeafLoot.clear();
this.globalEntireTreeLoot.clear();
this.globalRequiredTools.clear();
ConfigurationManager configurationManager = this.plugin.getConfigurationManager();
YamlConfiguration config = configurationManager.getConfig();
// Load tree settings
ConfigurationSection treeSection = config.getConfigurationSection("trees");
top:
for (String key : treeSection.getKeys(false)) {
ConfigurationSection tree = treeSection.getConfigurationSection(key);
Set<XMaterial> logMaterials = new HashSet<>();
Set<XMaterial> leafMaterials = new HashSet<>();
XMaterial saplingMaterial;
Set<XMaterial> plantableSoilMaterial = new HashSet<>();
double maxLogDistanceFromTrunk;
int maxLeafDistanceFromLog;
boolean detectLeavesDiagonally;
boolean dropOriginalLog;
boolean dropOriginalLeaf;
Set<TreeLoot> logLoot = new HashSet<>();
Set<TreeLoot> leafLoot = new HashSet<>();
Set<TreeLoot> entireTreeLoot = new HashSet<>();
Set<ItemStack> requiredTools = new HashSet<>();
boolean requiredAxe;
for (String materialString : tree.getStringList("logs")) {
Optional<XMaterial> material = CompatibleMaterial.getMaterial(materialString);
if (!material.isPresent() || !material.get().isSupported()) {
continue top;
}
logMaterials.add(material.get());
}
for (String materialString : tree.getStringList("leaves")) {
Optional<XMaterial> material = CompatibleMaterial.getMaterial(materialString);
if (!material.isPresent() || !material.get().isSupported()) {
continue top;
}
leafMaterials.add(material.get());
}
saplingMaterial = CompatibleMaterial.getMaterial(tree.getString("sapling")).get();
for (String materialString : tree.getStringList("plantable-soil")) {
Optional<XMaterial> material = CompatibleMaterial.getMaterial(materialString);
if (!material.isPresent() || !material.get().isSupported()) {
continue top;
}
plantableSoilMaterial.add(material.get());
}
maxLogDistanceFromTrunk = tree.getDouble("max-log-distance-from-trunk");
maxLeafDistanceFromLog = tree.getInt("max-leaf-distance-from-log");
detectLeavesDiagonally = tree.getBoolean("search-for-leaves-diagonally");
dropOriginalLog = tree.getBoolean("drop-original-log");
dropOriginalLeaf = tree.getBoolean("drop-original-leaf");
ConfigurationSection logLootSection = tree.getConfigurationSection("log-loot");
if (logLootSection != null) {
for (String lootKey : logLootSection.getKeys(false)) {
logLoot.add(this.getTreeLootEntry(TreeBlockType.LOG, logLootSection.getConfigurationSection(lootKey)));
}
}
ConfigurationSection leafLootSection = tree.getConfigurationSection("leaf-loot");
if (leafLootSection != null) {
for (String lootKey : leafLootSection.getKeys(false)) {
leafLoot.add(this.getTreeLootEntry(TreeBlockType.LEAF, leafLootSection.getConfigurationSection(lootKey)));
}
}
ConfigurationSection entireTreeLootSection = tree.getConfigurationSection("entire-tree-loot");
if (entireTreeLootSection != null) {
for (String lootKey : entireTreeLootSection.getKeys(false)) {
entireTreeLoot.add(this.getTreeLootEntry(TreeBlockType.LEAF, entireTreeLootSection.getConfigurationSection(lootKey)));
}
}
for (String itemStackString : tree.getStringList("required-tools")) {
Optional<XMaterial> material = CompatibleMaterial.getMaterial(itemStackString);
if (!material.isPresent()) {
continue top;
}
requiredTools.add(material.get().parseItem());
}
requiredAxe = tree.getBoolean("required-axe", false);
this.treeDefinitions.add(new TreeDefinition(key, logMaterials, leafMaterials, saplingMaterial, plantableSoilMaterial, maxLogDistanceFromTrunk,
maxLeafDistanceFromLog, detectLeavesDiagonally, dropOriginalLog, dropOriginalLeaf, logLoot, leafLoot, entireTreeLoot, requiredTools, requiredAxe));
}
// Load global plantable soil
for (String material : config.getStringList("global-plantable-soil")) {
this.globalPlantableSoil.add(CompatibleMaterial.getMaterial(material).get());
}
// Load global log drops
ConfigurationSection logSection = config.getConfigurationSection("global-log-loot");
if (logSection != null) {
for (String lootKey : logSection.getKeys(false)) {
this.globalLogLoot.add(this.getTreeLootEntry(TreeBlockType.LOG, logSection.getConfigurationSection(lootKey)));
}
}
// Load global leaf drops
ConfigurationSection leafSection = config.getConfigurationSection("global-leaf-loot");
if (leafSection != null) {
for (String lootKey : leafSection.getKeys(false)) {
this.globalLeafLoot.add(this.getTreeLootEntry(TreeBlockType.LEAF, leafSection.getConfigurationSection(lootKey)));
}
}
// Load global entire tree drops
ConfigurationSection entireTreeSection = config.getConfigurationSection("global-entire-tree-loot");
if (entireTreeSection != null) {
for (String lootKey : entireTreeSection.getKeys(false)) {
this.globalEntireTreeLoot.add(this.getTreeLootEntry(TreeBlockType.LOG, entireTreeSection.getConfigurationSection(lootKey)));
}
}
// Load global tools
for (String itemStackString : config.getStringList("global-required-tools")) {
Optional<XMaterial> tool = CompatibleMaterial.getMaterial(itemStackString);
if (!tool.isPresent()) {
continue;
}
this.globalRequiredTools.add(tool.get().parseItem());
}
this.globalAxeRequired = config.getBoolean("global-required-axe", false);
// Load required axe
if (config.contains("required-axe")) {
loadAxe(config);
}
}
private void loadAxe(YamlConfiguration config) {
// Reset the axe loaded, but load the NBT anyway in case someone wanted to use another plugin to give it.
this.requiredAxeKey = config.getString("required-axe.nbt");
this.requiredAxe = null;
String typeString = config.getString("required-axe.type");
if (Strings.isNullOrEmpty(typeString)) {
this.plugin.getLogger().warning("Required-axe has to have a material set.");
return;
}
Optional<XMaterial> material = CompatibleMaterial.getMaterial(typeString);
if (!material.isPresent()) {
this.plugin.getLogger().warning("Material " + typeString + " is invalid.");
return;
}
ItemStack item = material.get().parseItem();
// Add display name and lore
String displayName = TextUtils.formatText(config.getString("required-axe.name"));
List<String> lore = config.getStringList("required-axe.lore").stream()
.map(TextUtils::formatText)
.collect(Collectors.toList());
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(displayName);
meta.setLore(lore);
// Enchants
for (String enchantString : config.getStringList("required-axe.enchants")) {
String[] arr = enchantString.split(":");
int level = arr.length > 1 ? Math.max(1, parseInt(arr[1])) : 1;
// Enchantment#getKey is not present on versions below 1.13
Enchantment enchantment;
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
NamespacedKey key = NamespacedKey.minecraft(arr[0].trim().toLowerCase());
enchantment = Enchantment.getByKey(key);
// Try to fall back to #getByName() if someone uses the old names.
if (enchantment == null) {
enchantment = Enchantment.getByName(arr[0].trim());
}
} else {
enchantment = Enchantment.getByName(arr[0].trim());
}
if (enchantment == null) {
this.plugin.getLogger().warning("Enchantment " + arr[0].trim() + " is invalid.");
continue;
}
meta.addEnchant(enchantment, level, true);
}
item.setItemMeta(meta);
// Apply NBT
NBTItem nbtItem = new NBTItem(item);
nbtItem.setBoolean(this.requiredAxeKey, true);
item = nbtItem.getItem();
this.requiredAxe = item;
}
private int parseInt(String str) {
try {
return Integer.parseInt(str.trim());
} catch (NumberFormatException e) {
return -1;
}
}
public ItemStack getRequiredAxe() {
return this.requiredAxe;
}
public boolean isGlobalAxeRequired() {
return this.globalAxeRequired;
}
@Override
public void disable() {
this.treeDefinitions.clear();
}
/**
* Gets a Set of possible TreeDefinitions that match the given Block
*
* @param block The Block to check
* @return A Set of TreeDefinitions for the given Block
*/
public Set<TreeDefinition> getTreeDefinitionsForLog(Block block) {
return this.narrowTreeDefinition(this.treeDefinitions, block, TreeBlockType.LOG);
}
/**
* Narrows a Set of TreeDefinitions down to ones matching the given Block and TreeBlockType
*
* @param possibleTreeDefinitions The possible TreeDefinitions
* @param block The Block to narrow to
* @param treeBlockType The TreeBlockType of the given Block
* @return A Set of TreeDefinitions narrowed down
*/
public Set<TreeDefinition> narrowTreeDefinition(Set<TreeDefinition> possibleTreeDefinitions, Block block, TreeBlockType treeBlockType) {
Set<TreeDefinition> matchingTreeDefinitions = new HashSet<>();
switch (treeBlockType) {
case LOG:
for (TreeDefinition treeDefinition : possibleTreeDefinitions) {
for (XMaterial material : treeDefinition.getLogMaterial()) {
if (material == CompatibleMaterial.getMaterial(block.getType()).orElse(null)) {
matchingTreeDefinitions.add(treeDefinition);
break;
}
}
}
break;
case LEAF:
for (TreeDefinition treeDefinition : possibleTreeDefinitions) {
for (XMaterial material : treeDefinition.getLeafMaterial()) {
if (material == CompatibleMaterial.getMaterial(block.getType()).orElse(null)) {
matchingTreeDefinitions.add(treeDefinition);
break;
}
}
}
break;
}
return matchingTreeDefinitions;
}
/**
* Checks if a given tool is valid for any tree definitions, also takes into account global tools
*
* @param tool The tool to check
* @return True if the tool is allowed for toppling any trees
*/
public boolean isToolValidForAnyTreeDefinition(ItemStack tool) {
if (ConfigurationManager.Setting.IGNORE_REQUIRED_TOOLS.getBoolean()) {
return true;
}
for (TreeDefinition treeDefinition : this.treeDefinitions) {
if (treeDefinition.isRequiredAxe() || isGlobalAxeRequired()) {
if (tool != null && !tool.getType().isAir() && new NBTItem(tool).hasTag(this.requiredAxeKey)) {
return true;
}
}
}
for (TreeDefinition treeDefinition : this.treeDefinitions) {
for (ItemStack requiredTool : treeDefinition.getRequiredTools()) {
if (tool != null && requiredTool.getType() == tool.getType()) {
return true;
}
}
}
for (ItemStack requiredTool : this.globalRequiredTools) {
if (tool != null && requiredTool.getType() == tool.getType()) {
return true;
}
}
return false;
}
/**
* Checks if a given tool is valid for a given tree definition, also takes into account global tools
*
* @param treeDefinition The TreeDefinition to use
* @param tool The tool to check
* @return True if the tool is allowed for toppling the given TreeDefinition
*/
public boolean isToolValidForTreeDefinition(TreeDefinition treeDefinition, ItemStack tool) {
if (ConfigurationManager.Setting.IGNORE_REQUIRED_TOOLS.getBoolean()) {
return true;
}
// If the tree definition requires the custom axe, don't allow any other checks to pass.
if (treeDefinition.isRequiredAxe() || isGlobalAxeRequired()) {
return tool != null && !tool.getType().isAir() && new NBTItem(tool).hasTag(this.requiredAxeKey);
}
for (ItemStack requiredTool : treeDefinition.getRequiredTools()) {
if (requiredTool.getType() == tool.getType()) {
return true;
}
}
for (ItemStack requiredTool : this.globalRequiredTools) {
if (requiredTool.getType() == tool.getType()) {
return true;
}
}
return false;
}
/**
* Tries to spawn loot for a given TreeBlock with the given TreeDefinition for a given Player
*
* @param treeDefinition The TreeDefinition to use
* @param treeBlock The TreeBlock to drop for
* @param player The Player to drop for
* @param isForEntireTree If the loot is for the entire tree
*/
public void dropTreeLoot(TreeDefinition treeDefinition, ITreeBlock treeBlock, Player player, boolean hasSilkTouch, boolean isForEntireTree) {
boolean addToInventory = ConfigurationManager.Setting.ADD_ITEMS_TO_INVENTORY.getBoolean();
boolean hasBonusChance = player.hasPermission("ultimatetimber.bonusloot");
List<ItemStack> lootedItems = new ArrayList<>();
List<String> lootedCommands = new ArrayList<>();
// Get the loot that we should try to drop
List<TreeLoot> toTry = new ArrayList<>();
if (isForEntireTree) {
toTry.addAll(treeDefinition.getEntireTreeLoot());
toTry.addAll(this.globalEntireTreeLoot);
} else {
if (ConfigurationManager.Setting.APPLY_SILK_TOUCH.getBoolean() && hasSilkTouch) {
if (ConfigurationManager.Setting.HOOKS_APPLY_EXTRA_DROPS.getBoolean()
&& McMMOHook.hasWoodcuttingDoubleDrops(player)) {
lootedItems.addAll(BlockUtils.getBlockDrops(treeBlock));
}
lootedItems.addAll(BlockUtils.getBlockDrops(treeBlock));
} else {
switch (treeBlock.getTreeBlockType()) {
case LOG:
toTry.addAll(treeDefinition.getLogLoot());
toTry.addAll(this.globalLogLoot);
if (treeDefinition.shouldDropOriginalLog()) {
if (ConfigurationManager.Setting.HOOKS_APPLY_EXTRA_DROPS.getBoolean()
&& McMMOHook.hasWoodcuttingDoubleDrops(player)) {
lootedItems.addAll(BlockUtils.getBlockDrops(treeBlock));
}
lootedItems.addAll(BlockUtils.getBlockDrops(treeBlock));
}
break;
case LEAF:
toTry.addAll(treeDefinition.getLeafLoot());
toTry.addAll(this.globalLeafLoot);
if (treeDefinition.shouldDropOriginalLeaf()) {
if (ConfigurationManager.Setting.HOOKS_APPLY_EXTRA_DROPS.getBoolean()
&& McMMOHook.hasWoodcuttingDoubleDrops(player)) {
lootedItems.addAll(BlockUtils.getBlockDrops(treeBlock));
}
lootedItems.addAll(BlockUtils.getBlockDrops(treeBlock));
}
break;
}
}
}
// Roll the dice
double bonusLootMultiplier = ConfigurationManager.Setting.BONUS_LOOT_MULTIPLIER.getDouble();
for (TreeLoot treeLoot : toTry) {
if (treeLoot == null) {
continue;
}
double chance = hasBonusChance ? treeLoot.getChance() * bonusLootMultiplier : treeLoot.getChance();
if (this.random.nextDouble() > chance / 100) {
continue;
}
if (treeLoot.hasItem()) {
if (ConfigurationManager.Setting.HOOKS_APPLY_EXTRA_DROPS.getBoolean()
&& McMMOHook.hasWoodcuttingDoubleDrops(player)) {
lootedItems.add(treeLoot.getItem());
}
lootedItems.add(treeLoot.getItem());
}
if (treeLoot.hasCommand()) {
if (ConfigurationManager.Setting.HOOKS_APPLY_EXTRA_DROPS.getBoolean()
&& McMMOHook.hasWoodcuttingDoubleDrops(player)) {
lootedCommands.add(treeLoot.getCommand());
}
lootedCommands.add(treeLoot.getCommand());
}
}
// Add to inventory or drop on ground
if (addToInventory && player.getWorld().equals(treeBlock.getLocation().getWorld())) {
List<ItemStack> extraItems = new ArrayList<>();
for (ItemStack lootedItem : lootedItems) {
extraItems.addAll(player.getInventory().addItem(lootedItem).values());
}
Location location = player.getLocation().clone().subtract(0.5, 0, 0.5);
for (ItemStack extraItem : extraItems) {
location.getWorld().dropItemNaturally(location, extraItem);
}
} else {
Location location = treeBlock.getLocation().clone().add(0.5, 0.5, 0.5);
for (ItemStack lootedItem : lootedItems) {
location.getWorld().dropItemNaturally(location, lootedItem);
}
}
// Run looted commands
for (String lootedCommand : lootedCommands) {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(),
lootedCommand.replace("%player%", player.getName())
.replace("%type%", treeDefinition.getKey())
.replace("%xPos%", String.valueOf(treeBlock.getLocation().getBlockX()))
.replace("%yPos%", String.valueOf(treeBlock.getLocation().getBlockY()))
.replace("%zPos%", String.valueOf(treeBlock.getLocation().getBlockZ())));
}
}
/**
* Gets all possible plantable soil blocks for the given tree definition
*
* @param treeDefinition The TreeDefinition
* @return A Set of IBlockData of plantable soil
*/
public Set<XMaterial> getPlantableSoilMaterial(TreeDefinition treeDefinition) {
Set<XMaterial> plantableSoilBlockData = new HashSet<>();
plantableSoilBlockData.addAll(treeDefinition.getPlantableSoilMaterial());
plantableSoilBlockData.addAll(this.globalPlantableSoil);
return plantableSoilBlockData;
}
/**
* Gets a TreeLoot entry from a ConfigurationSection
*
* @param treeBlockType The TreeBlockType to use
* @param configurationSection The ConfigurationSection
* @return A TreeLoot entry from the section
*/
private TreeLoot getTreeLootEntry(TreeBlockType treeBlockType, ConfigurationSection configurationSection) {
String material = configurationSection.getString("material");
Optional<XMaterial> compatibleMaterial = material == null ? Optional.empty() : CompatibleMaterial.getMaterial(material);
ItemStack item = compatibleMaterial.map(XMaterial::parseItem).orElse(null);
String command = configurationSection.getString("command");
double chance = configurationSection.getDouble("chance");
return new TreeLoot(treeBlockType, item, command, chance);
}
}

View File

@ -0,0 +1,323 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockSet;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TreeDetectionManager extends Manager {
private final Set<Vector> VALID_TRUNK_OFFSETS, VALID_BRANCH_OFFSETS, VALID_LEAF_OFFSETS;
private TreeDefinitionManager treeDefinitionManager;
private PlacedBlockManager placedBlockManager;
private int numLeavesRequiredForTree;
private boolean onlyBreakLogsUpwards, entireTreeBase, destroyLeaves;
public TreeDetectionManager(UltimateTimber plugin) {
super(plugin);
this.VALID_BRANCH_OFFSETS = new HashSet<>();
this.VALID_TRUNK_OFFSETS = new HashSet<>();
this.VALID_LEAF_OFFSETS = new HashSet<>();
// 3x2x3 centered around log, excluding -y axis
for (int y = 0; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
this.VALID_BRANCH_OFFSETS.add(new Vector(x, y, z));
}
}
}
// 3x3x3 centered around log
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
this.VALID_TRUNK_OFFSETS.add(new Vector(x, y, z));
}
}
}
// Adjacent blocks to log
for (int i = -1; i <= 1; i += 2) {
this.VALID_LEAF_OFFSETS.add(new Vector(i, 0, 0));
this.VALID_LEAF_OFFSETS.add(new Vector(0, i, 0));
this.VALID_LEAF_OFFSETS.add(new Vector(0, 0, i));
}
}
@Override
public void reload() {
this.treeDefinitionManager = this.plugin.getTreeDefinitionManager();
this.placedBlockManager = this.plugin.getPlacedBlockManager();
this.numLeavesRequiredForTree = ConfigurationManager.Setting.LEAVES_REQUIRED_FOR_TREE.getInt();
this.onlyBreakLogsUpwards = ConfigurationManager.Setting.ONLY_DETECT_LOGS_UPWARDS.getBoolean();
this.entireTreeBase = ConfigurationManager.Setting.BREAK_ENTIRE_TREE_BASE.getBoolean();
this.destroyLeaves = ConfigurationManager.Setting.DESTROY_LEAVES.getBoolean();
}
@Override
public void disable() {
}
/**
* Detects a tree given an initial starting block
*
* @param initialBlock The starting Block of the detection
* @return A DetectedTree if one was found, otherwise null
*/
public DetectedTree detectTree(Block initialBlock) {
TreeDefinitionManager treeDefinitionManager = this.plugin.getTreeDefinitionManager();
TreeBlock initialTreeBlock = new TreeBlock(initialBlock, TreeBlockType.LOG);
TreeBlockSet<Block> detectedTreeBlocks = new TreeBlockSet<>(initialTreeBlock);
Set<TreeDefinition> possibleTreeDefinitions = this.treeDefinitionManager.getTreeDefinitionsForLog(initialBlock);
if (possibleTreeDefinitions.isEmpty()) {
return null;
}
// Detect tree trunk
List<Block> trunkBlocks = new ArrayList<>();
trunkBlocks.add(initialBlock);
Block targetBlock = initialBlock;
while (this.isValidLogType(possibleTreeDefinitions, null, (targetBlock = targetBlock.getRelative(BlockFace.UP)))) {
trunkBlocks.add(targetBlock);
possibleTreeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(possibleTreeDefinitions, targetBlock, TreeBlockType.LOG));
}
if (!this.onlyBreakLogsUpwards) {
targetBlock = initialBlock;
while (this.isValidLogType(possibleTreeDefinitions, null, (targetBlock = targetBlock.getRelative(BlockFace.DOWN)))) {
trunkBlocks.add(targetBlock);
possibleTreeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(possibleTreeDefinitions, targetBlock, TreeBlockType.LOG));
}
}
// Lowest blocks at the front of the list
Collections.reverse(trunkBlocks);
// Detect branches off the main trunk
for (Block trunkBlock : trunkBlocks) {
this.recursiveBranchSearch(possibleTreeDefinitions, trunkBlocks, detectedTreeBlocks, trunkBlock, initialBlock.getLocation().getBlockY());
}
// Detect leaves off the trunk/branches
Set<ITreeBlock<Block>> branchBlocks = new HashSet<>(detectedTreeBlocks.getLogBlocks());
for (ITreeBlock<Block> branchBlock : branchBlocks) {
this.recursiveLeafSearch(possibleTreeDefinitions, detectedTreeBlocks, branchBlock.getBlock(), new HashSet<>());
}
// Use the first tree definition in the set
TreeDefinition actualTreeDefinition = possibleTreeDefinitions.iterator().next();
// Trees need at least a certain number of leaves
if (detectedTreeBlocks.getLeafBlocks().size() < this.numLeavesRequiredForTree) {
return null;
}
// Remove leaves if we don't care about the leaves
if (!this.destroyLeaves) {
detectedTreeBlocks.removeAll(TreeBlockType.LEAF);
}
// Check that the tree isn't on the ground if enabled
if (this.entireTreeBase) {
Set<Block> groundBlocks = new HashSet<>();
for (ITreeBlock<Block> treeBlock : detectedTreeBlocks.getLogBlocks()) {
if (treeBlock != detectedTreeBlocks.getInitialLogBlock() && treeBlock.getLocation().getBlockY() == initialBlock.getLocation().getBlockY()) {
groundBlocks.add(treeBlock.getBlock());
}
}
for (Block block : groundBlocks) {
Block blockBelow = block.getRelative(BlockFace.DOWN);
boolean blockBelowIsLog = this.isValidLogType(possibleTreeDefinitions, null, blockBelow);
boolean blockBelowIsSoil = false;
for (XMaterial material : treeDefinitionManager.getPlantableSoilMaterial(actualTreeDefinition)) {
if (material == CompatibleMaterial.getMaterial(blockBelow.getType()).orElse(null)) {
blockBelowIsSoil = true;
break;
}
}
if (blockBelowIsLog || blockBelowIsSoil) {
return null;
}
}
}
return new DetectedTree(actualTreeDefinition, detectedTreeBlocks);
}
/**
* Recursively searches for branches off a given block
*
* @param treeDefinitions The possible tree definitions
* @param trunkBlocks The tree trunk blocks
* @param treeBlocks The detected tree blocks
* @param block The next block to check for a branch
* @param startingBlockY The Y coordinate of the initial block
*/
private void recursiveBranchSearch(Set<TreeDefinition> treeDefinitions, List<Block> trunkBlocks, TreeBlockSet<Block> treeBlocks, Block block, int startingBlockY) {
for (Vector offset : this.onlyBreakLogsUpwards ? this.VALID_BRANCH_OFFSETS : this.VALID_TRUNK_OFFSETS) {
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LOG);
if (this.isValidLogType(treeDefinitions, trunkBlocks, targetBlock) && !treeBlocks.contains(treeBlock)) {
treeBlocks.add(treeBlock);
treeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(treeDefinitions, targetBlock, TreeBlockType.LOG));
if (!this.onlyBreakLogsUpwards || targetBlock.getLocation().getBlockY() > startingBlockY) {
this.recursiveBranchSearch(treeDefinitions, trunkBlocks, treeBlocks, targetBlock, startingBlockY);
}
}
}
}
/**
* Recursively searches for leaves that are next to this tree
*
* @param treeDefinitions The possible tree definitions
* @param treeBlocks The detected tree blocks
* @param block The next block to check for a leaf
*/
private void recursiveLeafSearch(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block, Set<Block> visitedBlocks) {
boolean detectLeavesDiagonally = treeDefinitions.stream().anyMatch(TreeDefinition::shouldDetectLeavesDiagonally);
for (Vector offset : !detectLeavesDiagonally ? this.VALID_LEAF_OFFSETS : this.VALID_TRUNK_OFFSETS) {
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
if (visitedBlocks.contains(targetBlock)) {
continue;
}
visitedBlocks.add(targetBlock);
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LEAF);
if (this.isValidLeafType(treeDefinitions, treeBlocks, targetBlock) && !treeBlocks.contains(treeBlock) && !this.doesLeafBorderInvalidLog(treeDefinitions, treeBlocks, targetBlock)) {
treeBlocks.add(treeBlock);
treeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(treeDefinitions, targetBlock, TreeBlockType.LEAF));
this.recursiveLeafSearch(treeDefinitions, treeBlocks, targetBlock, visitedBlocks);
}
}
}
/**
* Checks if a leaf is bordering a log that isn't part of this tree
*
* @param treeDefinitions The possible tree definitions
* @param treeBlocks The detected tree blocks
* @param block The block to check
* @return True if the leaf borders an invalid log, otherwise false
*/
private boolean doesLeafBorderInvalidLog(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block) {
for (Vector offset : this.VALID_TRUNK_OFFSETS) {
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
if (this.isValidLogType(treeDefinitions, null, targetBlock) && !treeBlocks.contains(new TreeBlock(targetBlock, TreeBlockType.LOG))) {
return true;
}
}
return false;
}
/**
* Checks if a given block is valid for the given TreeDefinitions
*
* @param treeDefinitions The Set of TreeDefinitions to compare against
* @param trunkBlocks The trunk blocks of the tree for checking the distance
* @param block The Block to check
* @return True if the block is a valid log type, otherwise false
*/
private boolean isValidLogType(Set<TreeDefinition> treeDefinitions, List<Block> trunkBlocks, Block block) {
// Check if block is placed
if (this.placedBlockManager.isBlockPlaced(block)) {
return false;
}
// Check if it matches the tree definition
boolean isCorrectType = false;
for (TreeDefinition treeDefinition : treeDefinitions) {
for (XMaterial material : treeDefinition.getLogMaterial()) {
if (material == CompatibleMaterial.getMaterial(block.getType()).orElse(null)) {
isCorrectType = true;
break;
}
}
}
if (!isCorrectType) {
return false;
}
// Check that it is close enough to the trunk
if (trunkBlocks == null || trunkBlocks.isEmpty()) {
return true;
}
Location location = block.getLocation();
for (TreeDefinition treeDefinition : treeDefinitions) {
double maxDistance = treeDefinition.getMaxLogDistanceFromTrunk() * treeDefinition.getMaxLogDistanceFromTrunk();
if (!this.onlyBreakLogsUpwards) // Help detect logs more often if the tree isn't broken at the base
{
maxDistance *= 1.5;
}
for (Block trunkBlock : trunkBlocks) {
if (location.distanceSquared(trunkBlock.getLocation()) < maxDistance) {
return true;
}
}
}
return false;
}
/**
* Checks if a given block is valid for the given TreeDefinitions
*
* @param treeDefinitions The Set of TreeDefinitions to compare against
* @param treeBlocks The detected blocks of the tree for checking leaf distance
* @param block The Block to check
* @return True if the block is a valid log type, otherwise false
*/
private boolean isValidLeafType(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block) {
// Check if block is placed
if (this.placedBlockManager.isBlockPlaced(block)) {
return false;
}
// Check if it matches the tree definition
boolean isCorrectType = false;
for (TreeDefinition treeDefinition : treeDefinitions) {
for (XMaterial material : treeDefinition.getLeafMaterial()) {
if (material == CompatibleMaterial.getMaterial(block.getType()).orElse(null)) {
isCorrectType = true;
break;
}
}
}
if (!isCorrectType) {
return false;
}
// Check that it is close enough to a log
if (treeBlocks == null || treeBlocks.isEmpty()) {
return true;
}
int maxDistanceFromLog = treeDefinitions.stream().map(TreeDefinition::getMaxLeafDistanceFromLog).max(Integer::compareTo).orElse(0);
return treeBlocks.getLogBlocks().stream().anyMatch(x -> x.getLocation().distanceSquared(block.getLocation()) < maxDistanceFromLog * maxDistanceFromLog);
}
}

View File

@ -0,0 +1,226 @@
package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleHand;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.core.hooks.JobsHook;
import com.craftaro.core.hooks.LogManager;
import com.craftaro.core.hooks.McMMOHook;
import com.craftaro.core.utils.ItemUtils;
import com.craftaro.core.world.SItemStack;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.events.TreeFallEvent;
import com.craftaro.ultimatetimber.events.TreeFellEvent;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockSet;
import com.craftaro.ultimatetimber.misc.OnlyToppleWhile;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.stream.Collectors;
public class TreeFallManager extends Manager implements Listener {
private int maxLogBlocksAllowed;
public TreeFallManager(UltimateTimber plugin) {
super(plugin);
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void reload() {
this.maxLogBlocksAllowed = ConfigurationManager.Setting.MAX_LOGS_PER_CHOP.getInt();
}
@Override
public void disable() {
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
TreeDefinitionManager treeDefinitionManager = this.plugin.getTreeDefinitionManager();
TreeDetectionManager treeDetectionManager = this.plugin.getTreeDetectionManager();
TreeAnimationManager treeAnimationManager = this.plugin.getTreeAnimationManager();
ChoppingManager choppingManager = this.plugin.getChoppingManager();
SaplingManager saplingManager = this.plugin.getSaplingManager();
Player player = event.getPlayer();
Block block = event.getBlock();
ItemStack tool = CompatibleHand.getHand(event).getItem(player);
// Protect saplings
if (saplingManager.isSaplingProtected(block)) {
event.setCancelled(true);
return;
}
// Condition checks
boolean isValid = true;
if (ConfigurationManager.Setting.DISABLED_WORLDS.getStringList().contains(player.getWorld().getName())) {
isValid = false;
}
if (!ConfigurationManager.Setting.ALLOW_CREATIVE_MODE.getBoolean() && player.getGameMode().equals(GameMode.CREATIVE)) {
isValid = false;
}
if (!this.checkToppleWhile(player)) {
isValid = false;
}
if (ConfigurationManager.Setting.REQUIRE_CHOP_PERMISSION.getBoolean() && !player.hasPermission("ultimatetimber.chop")) {
isValid = false;
}
if (!choppingManager.isChopping(player)) {
isValid = false;
}
if (choppingManager.isInCooldown(player)) {
isValid = false;
}
if (treeAnimationManager.isBlockInAnimation(block)) {
isValid = false;
event.setCancelled(true);
}
if (!treeDefinitionManager.isToolValidForAnyTreeDefinition(tool)) {
isValid = false;
}
if (ConfigurationManager.Setting.HOOKS_REQUIRE_ABILITY_ACTIVE.getBoolean() && !McMMOHook.isUsingTreeFeller(player)) {
isValid = false;
}
boolean alwaysReplantSapling = ConfigurationManager.Setting.ALWAYS_REPLANT_SAPLING.getBoolean();
if (!isValid && !alwaysReplantSapling) {
return;
}
DetectedTree detectedTree = treeDetectionManager.detectTree(block);
if (detectedTree == null) {
return;
}
if (alwaysReplantSapling) {
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () ->
saplingManager.replantSapling(detectedTree.getTreeDefinition(), detectedTree.getDetectedTreeBlocks().getInitialLogBlock()));
if (!isValid) {
return;
}
}
if (!treeDefinitionManager.isToolValidForTreeDefinition(detectedTree.getTreeDefinition(), tool)) {
return;
}
short toolDamage = this.getToolDamage(detectedTree.getDetectedTreeBlocks(), tool.containsEnchantment(Enchantment.SILK_TOUCH));
if (ConfigurationManager.Setting.PROTECT_TOOL.getBoolean() && !ItemUtils.hasEnoughDurability(tool, toolDamage)) {
return;
}
// Trigger fall event
TreeFallEvent treeFallEvent = new TreeFallEvent(player, detectedTree);
Bukkit.getPluginManager().callEvent(treeFallEvent);
if (treeFallEvent.isCancelled()) {
return;
}
// Valid tree and meets all conditions past this point
event.setCancelled(true);
detectedTree.getDetectedTreeBlocks().sortAndLimit(this.maxLogBlocksAllowed);
choppingManager.cooldownPlayer(player);
// Destroy initiated block if enabled
if (ConfigurationManager.Setting.DESTROY_INITIATED_BLOCK.getBoolean()) {
detectedTree.getDetectedTreeBlocks().getInitialLogBlock().getBlock().setType(Material.AIR);
detectedTree.getDetectedTreeBlocks().remove(detectedTree.getDetectedTreeBlocks().getInitialLogBlock());
}
boolean isCreative = player.getGameMode() == GameMode.CREATIVE;
if (!isCreative) {
SItemStack sstack = new SItemStack(tool);
sstack.addDamage(player, toolDamage, true);
//Destroy item if durability is less than 0
ItemMeta meta = sstack.getItem().getItemMeta();
if (meta instanceof Damageable) {
Damageable damageable = (Damageable)meta;
int damage = damageable.getDamage();
if (damage >= sstack.getItem().getType().getMaxDurability()) {
//Break tool
player.getInventory().setItemInMainHand(null);
}
}
}
if (ConfigurationManager.Setting.HOOKS_APPLY_EXPERIENCE.getBoolean()) {
McMMOHook.addWoodcutting(player, detectedTree.getDetectedTreeBlocks().getAllTreeBlocks().stream()
.map(ITreeBlock::getBlock).collect(Collectors.toList()));
if (!isCreative && JobsHook.isEnabled()) {
for (ITreeBlock<Block> treeBlock : detectedTree.getDetectedTreeBlocks().getLogBlocks()) {
JobsHook.breakBlock(player, treeBlock.getBlock());
}
}
}
for (ITreeBlock<Block> treeBlock : detectedTree.getDetectedTreeBlocks().getAllTreeBlocks()) {
LogManager.logRemoval(player, treeBlock.getBlock());
}
treeAnimationManager.runAnimation(detectedTree, player);
treeDefinitionManager.dropTreeLoot(detectedTree.getTreeDefinition(), detectedTree.getDetectedTreeBlocks().getInitialLogBlock(), player, false, true);
// Trigger fell event
TreeFellEvent treeFellEvent = new TreeFellEvent(player, detectedTree);
Bukkit.getPluginManager().callEvent(treeFellEvent);
}
/**
* Checks if a player is doing a certain action required to topple a tree
*
* @param player The player to check
* @return True if the check passes, otherwise false
*/
private boolean checkToppleWhile(Player player) {
switch (OnlyToppleWhile.fromString(ConfigurationManager.Setting.ONLY_TOPPLE_WHILE.getString())) {
case SNEAKING:
return player.isSneaking();
case NOT_SNEAKING:
return !player.isSneaking();
default:
return true;
}
}
private short getToolDamage(TreeBlockSet<Block> treeBlocks, boolean hasSilkTouch) {
if (!ConfigurationManager.Setting.REALISTIC_TOOL_DAMAGE.getBoolean()) {
return 1;
}
if (ConfigurationManager.Setting.APPLY_SILK_TOUCH_TOOL_DAMAGE.getBoolean() && hasSilkTouch) {
return (short) treeBlocks.size();
} else {
return (short) treeBlocks.getLogBlocks().size();
}
}
}

View File

@ -0,0 +1,20 @@
package com.craftaro.ultimatetimber.misc;
public enum OnlyToppleWhile {
SNEAKING, NOT_SNEAKING, ALWAYS;
/**
* Gets an OnlyToppleWhile from a given string
*
* @return The TreeAnimationType, returns {@link #ALWAYS} if the string is an invalid type
*/
public static OnlyToppleWhile fromString(String string) {
for (OnlyToppleWhile value : values()) {
if (value.name().equalsIgnoreCase(string)) {
return value;
}
}
return OnlyToppleWhile.ALWAYS;
}
}

View File

@ -0,0 +1,31 @@
package com.craftaro.ultimatetimber.tree;
import org.bukkit.block.Block;
public class DetectedTree {
private final TreeDefinition treeDefinition;
private final TreeBlockSet<Block> detectedTreeBlocks;
public DetectedTree(TreeDefinition treeDefinition, TreeBlockSet<Block> detectedTreeBlocks) {
this.treeDefinition = treeDefinition;
this.detectedTreeBlocks = detectedTreeBlocks;
}
/**
* Gets the TreeDefinition of this detected tree
*
* @return The TreeDefinition of this detected tree
*/
public TreeDefinition getTreeDefinition() {
return this.treeDefinition;
}
/**
* Gets the blocks that were detected as part of this tree
*
* @return A TreeBlockSet of detected Blocks
*/
public TreeBlockSet<Block> getDetectedTreeBlocks() {
return this.detectedTreeBlocks;
}
}

View File

@ -0,0 +1,29 @@
package com.craftaro.ultimatetimber.tree;
import org.bukkit.Location;
import org.bukkit.entity.FallingBlock;
public class FallingTreeBlock implements ITreeBlock<FallingBlock> {
private final FallingBlock fallingBlock;
private final TreeBlockType treeBlockType;
public FallingTreeBlock(FallingBlock fallingBlock, TreeBlockType treeBlockType) {
this.fallingBlock = fallingBlock;
this.treeBlockType = treeBlockType;
}
@Override
public FallingBlock getBlock() {
return this.fallingBlock;
}
@Override
public Location getLocation() {
return this.fallingBlock.getLocation();
}
@Override
public TreeBlockType getTreeBlockType() {
return this.treeBlockType;
}
}

View File

@ -0,0 +1,26 @@
package com.craftaro.ultimatetimber.tree;
import org.bukkit.Location;
public interface ITreeBlock<BlockType> {
/**
* Gets the block this TreeBlock represents
*
* @return The Block for this TreeBlock
*/
BlockType getBlock();
/**
* Gets the location of this TreeBlock
*
* @return The Location of this TreeBlock
*/
Location getLocation();
/**
* Gets what type of TreeBlock this is
*
* @return The TreeBlockType
*/
TreeBlockType getTreeBlockType();
}

View File

@ -0,0 +1,49 @@
package com.craftaro.ultimatetimber.tree;
import org.bukkit.Location;
import org.bukkit.block.Block;
import java.util.Objects;
public class TreeBlock implements ITreeBlock<Block> {
private final Block block;
private final TreeBlockType treeBlockType;
public TreeBlock(Block block, TreeBlockType treeBlockType) {
this.block = block;
this.treeBlockType = treeBlockType;
}
@Override
public Block getBlock() {
return this.block;
}
@Override
public Location getLocation() {
return this.block.getLocation();
}
@Override
public TreeBlockType getTreeBlockType() {
return this.treeBlockType;
}
@Override
public int hashCode() {
return Objects.hash(this.block, this.treeBlockType);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TreeBlock)) {
return false;
}
if (obj == this) {
return true;
}
TreeBlock oTreeBlock = (TreeBlock) obj;
return oTreeBlock.block.equals(this.block) && oTreeBlock.treeBlockType == this.treeBlockType;
}
}

View File

@ -0,0 +1,233 @@
package com.craftaro.ultimatetimber.tree;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class TreeBlockSet<BlockType> implements Collection {
private final ITreeBlock<BlockType> initialLogBlock;
private List<ITreeBlock<BlockType>> logBlocks;
private final List<ITreeBlock<BlockType>> leafBlocks;
public TreeBlockSet() {
this.initialLogBlock = null;
this.logBlocks = new LinkedList<>();
this.leafBlocks = new LinkedList<>();
}
public TreeBlockSet(ITreeBlock<BlockType> initialLogBlock) {
this.initialLogBlock = initialLogBlock;
this.logBlocks = new LinkedList<>();
this.leafBlocks = new LinkedList<>();
if (initialLogBlock != null) {
this.logBlocks.add(initialLogBlock);
}
}
/**
* Gets the TreeBlock that initiated the tree topple
*
* @return The TreeBlock of the initial topple point
*/
public ITreeBlock<BlockType> getInitialLogBlock() {
return this.initialLogBlock;
}
/**
* Gets all logs in this TreeBlockSet
*
* @return A Set of TreeBlocks
*/
public List<ITreeBlock<BlockType>> getLogBlocks() {
return Collections.unmodifiableList(this.logBlocks);
}
/**
* Gets all leaves in this TreeBlockSet
*
* @return A Set of TreeBlocks
*/
public List<ITreeBlock<BlockType>> getLeafBlocks() {
return Collections.unmodifiableList(this.leafBlocks);
}
/**
* Gets all blocks in this TreeBlockSet
*
* @return A Set of all TreeBlocks
*/
public Set<ITreeBlock<BlockType>> getAllTreeBlocks() {
Set<ITreeBlock<BlockType>> treeBlocks = new HashSet<>();
treeBlocks.addAll(this.logBlocks);
treeBlocks.addAll(this.leafBlocks);
return treeBlocks;
}
@Override
public int size() {
return this.logBlocks.size() + this.leafBlocks.size();
}
@Override
public boolean isEmpty() {
return this.logBlocks.isEmpty() && this.leafBlocks.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.logBlocks.contains(o) || this.leafBlocks.contains(o);
}
@Override
public Iterator iterator() {
return this.getAllTreeBlocks().iterator();
}
@Override
public Object[] toArray() {
return this.getAllTreeBlocks().toArray();
}
@Override
@SuppressWarnings("unchecked")
public boolean add(Object o) {
if (!(o instanceof ITreeBlock)) {
return false;
}
ITreeBlock treeBlock = (ITreeBlock) o;
switch (treeBlock.getTreeBlockType()) {
case LOG:
return this.logBlocks.add(treeBlock);
case LEAF:
return this.leafBlocks.add(treeBlock);
}
return false;
}
@Override
public boolean remove(Object o) {
if (!(o instanceof ITreeBlock)) {
return false;
}
ITreeBlock treeBlock = (ITreeBlock) o;
switch (treeBlock.getTreeBlockType()) {
case LOG:
return this.logBlocks.remove(treeBlock);
case LEAF:
return this.leafBlocks.remove(treeBlock);
}
return false;
}
@Override
public boolean addAll(Collection c) {
boolean allAdded = true;
for (Object o : c) {
if (!this.add(o)) {
allAdded = false;
}
}
return allAdded;
}
@Override
public void clear() {
this.logBlocks.clear();
this.leafBlocks.clear();
}
@Override
public boolean retainAll(Collection c) {
boolean retainedAll = true;
for (Object o : c) {
if (!this.contains(o)) {
this.remove(o);
} else {
retainedAll = false;
}
}
return retainedAll;
}
@Override
public boolean removeAll(Collection c) {
boolean removedAll = true;
for (Object o : c) {
if (this.contains(o)) {
this.remove(o);
} else {
removedAll = false;
}
}
return removedAll;
}
public void sortAndLimit(int max) {
if (this.logBlocks.size() < max) {
return;
}
this.logBlocks = this.logBlocks.stream().sorted(Comparator.comparingInt(b -> b.getLocation().getBlockY()))
.limit(max).collect(Collectors.toList());
int highest = this.logBlocks.get(this.logBlocks.size() - 1).getLocation().getBlockY();
if (this.logBlocks.size() >= max) {
for (ITreeBlock<BlockType> leafBlock : new LinkedList<>(this.leafBlocks)) {
if (leafBlock.getLocation().getY() > highest) {
this.leafBlocks.remove(leafBlock);
}
}
}
}
/**
* Removes all tree blocks of a given type
*
* @param treeBlockType The type of tree block to remove
* @return If any blocks were removed
*/
public boolean removeAll(TreeBlockType treeBlockType) {
if (treeBlockType == TreeBlockType.LOG) {
boolean removedAny = !this.logBlocks.isEmpty();
this.logBlocks.clear();
return removedAny;
}
if (treeBlockType == TreeBlockType.LEAF) {
boolean removedAny = !this.leafBlocks.isEmpty();
this.leafBlocks.clear();
return removedAny;
}
return false;
}
@Override
public boolean containsAll(Collection c) {
for (Object o : c) {
if (!this.contains(o)) {
return false;
}
}
return true;
}
@Override
@SuppressWarnings("unchecked")
public Object[] toArray(Object[] a) {
Set<ITreeBlock<BlockType>> treeBlocks = new HashSet<>();
for (Object o : a) {
if (o instanceof ITreeBlock) {
treeBlocks.add((ITreeBlock<BlockType>) o);
}
}
return treeBlocks.toArray();
}
}

View File

@ -0,0 +1,5 @@
package com.craftaro.ultimatetimber.tree;
public enum TreeBlockType {
LOG, LEAF
}

View File

@ -0,0 +1,176 @@
package com.craftaro.ultimatetimber.tree;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.Set;
public class TreeDefinition {
private final String key;
private final Set<XMaterial> logMaterial, leafMaterial, plantableSoilMaterial;
private final XMaterial saplingMaterial;
private final double maxLogDistanceFromTrunk;
private final int maxLeafDistanceFromLog;
private final boolean detectLeavesDiagonally;
private final boolean dropOriginalLog, dropOriginalLeaf;
private final Set<TreeLoot> logLoot, leafLoot, entireTreeLoot;
private final Set<ItemStack> requiredTools;
private final boolean requiredAxe;
public TreeDefinition(String key, Set<XMaterial> logMaterial, Set<XMaterial> leafMaterial, XMaterial saplingMaterial,
Set<XMaterial> plantableSoilMaterial, double maxLogDistanceFromTrunk, int maxLeafDistanceFromLog,
boolean detectLeavesDiagonally, boolean dropOriginalLog, boolean dropOriginalLeaf, Set<TreeLoot> logLoot,
Set<TreeLoot> leafLoot, Set<TreeLoot> entireTreeLoot, Set<ItemStack> requiredTools, boolean requiredAxe) {
this.key = key;
this.logMaterial = logMaterial;
this.leafMaterial = leafMaterial;
this.saplingMaterial = saplingMaterial;
this.plantableSoilMaterial = plantableSoilMaterial;
this.maxLogDistanceFromTrunk = maxLogDistanceFromTrunk;
this.maxLeafDistanceFromLog = maxLeafDistanceFromLog;
this.detectLeavesDiagonally = detectLeavesDiagonally;
this.dropOriginalLog = dropOriginalLog;
this.dropOriginalLeaf = dropOriginalLeaf;
this.logLoot = logLoot;
this.leafLoot = leafLoot;
this.entireTreeLoot = entireTreeLoot;
this.requiredTools = requiredTools;
this.requiredAxe = requiredAxe;
}
/**
* Gets the key of this TreeDefinition in the config
*
* @return The key
*/
public String getKey() {
return this.key;
}
/**
* Gets a set of valid log block data for this TreeDefinition
*
* @return A Set of CompatibleMaterial
*/
public Set<XMaterial> getLogMaterial() {
return Collections.unmodifiableSet(this.logMaterial);
}
/**
* Gets a set of valid leaf block data for this TreeDefinition
*
* @return A Set of CompatibleMaterial
*/
public Set<XMaterial> getLeafMaterial() {
return Collections.unmodifiableSet(this.leafMaterial);
}
/**
* Gets the sapling block data of this TreeDefinition
*
* @return An CompatibleMaterial instance for the sapling
*/
public XMaterial getSaplingMaterial() {
return this.saplingMaterial;
}
/**
* Gets a set of plantable soil block data for this TreeDefinition
*
* @return A Set of CompatibleMaterial
*/
public Set<XMaterial> getPlantableSoilMaterial() {
return Collections.unmodifiableSet(this.plantableSoilMaterial);
}
/**
* Gets the max distance away a log can be from the tree trunk in order to be part of the tree
*
* @return The max distance a log can be from the tree trunk
*/
public double getMaxLogDistanceFromTrunk() {
return this.maxLogDistanceFromTrunk;
}
/**
* Gets the max distance away a leaf can be from a log in order to be part of the tree
*
* @return The max distance a leaf can be from a log
*/
public int getMaxLeafDistanceFromLog() {
return this.maxLeafDistanceFromLog;
}
/**
* Gets if tree detection should check for leaves diagonally
*
* @return True if leaves should be searched for diagonally, otherwise false
*/
public boolean shouldDetectLeavesDiagonally() {
return this.detectLeavesDiagonally;
}
/**
* Gets if the logs of this tree should drop their original block
*
* @return True if the original log block should be dropped, otherwise false
*/
public boolean shouldDropOriginalLog() {
return this.dropOriginalLog;
}
/**
* Gets if the leaves of this tree should drop their original block
*
* @return True if the original leaf block should be dropped, otherwise false
*/
public boolean shouldDropOriginalLeaf() {
return this.dropOriginalLeaf;
}
/**
* Gets the log loot for this TreeDefinition
*
* @return A Set of TreeLoot
*/
public Set<TreeLoot> getLogLoot() {
return Collections.unmodifiableSet(this.logLoot);
}
/**
* Gets the leaf loot for this TreeDefinition
*
* @return A Set of TreeLoot
*/
public Set<TreeLoot> getLeafLoot() {
return Collections.unmodifiableSet(this.leafLoot);
}
/**
* Gets the loot for this TreeDefinition
*
* @return A Set of TreeLoot
*/
public Set<TreeLoot> getEntireTreeLoot() {
return Collections.unmodifiableSet(this.entireTreeLoot);
}
/**
* Gets the tools that can be used to activate this tree topple
*
* @return A Set of ItemStacks
*/
public Set<ItemStack> getRequiredTools() {
return Collections.unmodifiableSet(this.requiredTools);
}
/**
* Returns whether this TreeDefinition requires a custom axe.
*
* @return True if the TreeDefinition requires a custom axe
*/
public boolean isRequiredAxe() {
return this.requiredAxe;
}
}

View File

@ -0,0 +1,81 @@
package com.craftaro.ultimatetimber.tree;
import org.bukkit.inventory.ItemStack;
public class TreeLoot {
private final TreeBlockType treeBlockType;
private final ItemStack item;
private final String command;
private final double chance;
public TreeLoot(TreeBlockType treeBlockType, ItemStack item, String command, double chance) {
this.treeBlockType = treeBlockType;
this.item = item;
this.command = command;
this.chance = chance;
}
/**
* Gets the tree block type this loot is for
*
* @return The tree block type this loot is for
*/
public TreeBlockType getTreeBlockType() {
return this.treeBlockType;
}
/**
* Checks if this TreeLoot has an item to drop
*
* @return True if an item exists, otherwise false
*/
public boolean hasItem() {
return this.item != null;
}
/**
* Gets the item that this tree loot can drop
*
* @return An ItemStack this tree loot can drop
*/
public ItemStack getItem() {
return this.item;
}
/**
* Checks if this TreeLoot has a command to run
*
* @return True if a command exists, otherwise false
*/
public boolean hasCommand() {
return this.command != null;
}
/**
* Gets the command that this tree loot can run
*
* @return The command that this tree loot can run
*/
public String getCommand() {
return this.command;
}
/**
* Gets the percent chance this tree loot will drop
*
* @return The percent chance this tree loot can drop
*/
public double getChance() {
return this.chance;
}
@Override
public String toString() {
return "TreeLoot{" +
"treeBlockType=" + this.treeBlockType +
", item=" + this.item +
", command='" + this.command + '\'' +
", chance=" + this.chance +
'}';
}
}

View File

@ -0,0 +1,52 @@
package com.craftaro.ultimatetimber.utils;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.FallingBlock;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
public class BlockUtils {
public static Collection<ItemStack> getBlockDrops(ITreeBlock treeBlock) {
Set<ItemStack> drops = new HashSet<>();
if (treeBlock.getBlock() instanceof Block) {
Block block = (Block) treeBlock.getBlock();
Optional<XMaterial> material = CompatibleMaterial.getMaterial(block.getType());
if (!material.isPresent() || CompatibleMaterial.isAir(material.get())) {
return drops;
}
drops.add(material.get().parseItem());
} else if (treeBlock.getBlock() instanceof FallingBlock) {
Optional<XMaterial> material = CompatibleMaterial.getMaterial(((FallingBlock) treeBlock.getBlock()).getBlockData().getMaterial());
if (!material.isPresent()) {
return drops;
}
drops.add(material.get().parseItem());
}
return drops;
}
public static void toggleGravityFallingBlock(FallingBlock fallingBlock, boolean applyGravity) {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)) {
fallingBlock.setGravity(applyGravity);
}
}
public static FallingBlock spawnFallingBlock(Location location, XMaterial material) {
return location.getWorld().spawnFallingBlock(location, material.parseMaterial(), material.getData());
}
public static void configureFallingBlock(FallingBlock fallingBlock) {
toggleGravityFallingBlock(fallingBlock, false);
fallingBlock.setDropItem(false);
fallingBlock.setHurtEntities(false);
}
}

View File

@ -0,0 +1,75 @@
package com.craftaro.ultimatetimber.utils;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.FallingBlock;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
public class ParticleUtils {
public static void playFallingParticles(ITreeBlock treeBlock) {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
BlockData blockData;
if (treeBlock.getBlock() instanceof Block) {
blockData = ((Block) treeBlock.getBlock()).getBlockData();
} else if (treeBlock.getBlock() instanceof FallingBlock) {
blockData = ((FallingBlock) treeBlock.getBlock()).getBlockData();
} else {
return;
}
Location location = treeBlock.getLocation().clone().add(0.5, 0.5, 0.5);
location.getWorld().spawnParticle(Particle.BLOCK_DUST, location, 10, blockData);
return;
}
Collection<ItemStack> blockDrops = BlockUtils.getBlockDrops(treeBlock);
if (!blockDrops.iterator().hasNext()) {
return;
}
Location location = treeBlock.getLocation().clone().add(0.5, 0.5, 0.5);
if (ServerVersion.isServerVersion(ServerVersion.V1_8)) {
location.getWorld().playEffect(location, Effect.SMOKE, 4);
} else {
location.getWorld().spawnParticle(Particle.BLOCK_DUST, location, 10, blockDrops.iterator().next().getData());
}
}
public static void playLandingParticles(ITreeBlock treeBlock) {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
BlockData blockData;
if (treeBlock.getBlock() instanceof Block) {
blockData = ((Block) treeBlock.getBlock()).getBlockData();
} else if (treeBlock.getBlock() instanceof FallingBlock) {
blockData = ((FallingBlock) treeBlock.getBlock()).getBlockData();
} else {
return;
}
Location location = treeBlock.getLocation().clone().add(0.5, 0.5, 0.5);
location.getWorld().spawnParticle(Particle.BLOCK_CRACK, location, 10, blockData);
return;
}
Collection<ItemStack> blockDrops = BlockUtils.getBlockDrops(treeBlock);
if (!blockDrops.iterator().hasNext()) {
return;
}
Location location = treeBlock.getLocation().clone().add(0.5, 0.5, 0.5);
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)) {
location.getWorld().spawnParticle(Particle.BLOCK_CRACK, location, 10, blockDrops.iterator().next().getData());
} else {
location.getWorld().playEffect(location, Effect.SMOKE, 4);
}
}
}

View File

@ -0,0 +1,29 @@
package com.craftaro.ultimatetimber.utils;
import com.craftaro.third_party.com.cryptomorin.xseries.XSound;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import org.bukkit.Location;
public class SoundUtils {
public static void playFallingSound(ITreeBlock block) {
Location location = block.getLocation();
if (location.getWorld() == null) {
return;
}
XSound.BLOCK_CHEST_OPEN.play(location, 2, .1f);
}
public static void playLandingSound(ITreeBlock block) {
Location location = block.getLocation();
if (location.getWorld() == null) {
return;
}
if (block.getTreeBlockType() == TreeBlockType.LOG) {
XSound.BLOCK_WOOD_FALL.play(location, 2, .1f);
} else {
XSound.BLOCK_GRASS_BREAK.play(location, .5f, .75f);
}
}
}

View File

@ -1,85 +0,0 @@
package com.songoda.ultimatetimber;
import com.songoda.ultimatetimber.commands.CommandHandler;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import com.songoda.ultimatetimber.treefall.CustomLoot;
import com.songoda.ultimatetimber.treefall.TreeFallAnimation;
import com.songoda.ultimatetimber.treefall.TreeFallEvent;
import com.songoda.ultimatetimber.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
Note: In this plugin, I have called the act of a tree falling over with pseudo-physics "toppling over". This is reflected
in the documentation, config files and variable names.
PS: MagmaGuy was here
*/
public class UltimateTimber extends JavaPlugin {
private static CommandSender console = Bukkit.getConsoleSender();
private final String prefix = "&8[&6UltimateTimber&8]";
private static UltimateTimber INSTANCE;
private List<World> validWorlds = new ArrayList<>();
@Override
public void onEnable() {
INSTANCE = this;
console.sendMessage(Methods.formatText("&a============================="));
console.sendMessage(Methods.formatText("&7" + this.getDescription().getName() + " " + this.getDescription().getVersion() + " by &5Brianna <3&7!"));
console.sendMessage(Methods.formatText("&7Action: &aEnabling&7..."));
/*
Register the main event that handles toppling down trees
*/
Bukkit.getServer().getPluginManager().registerEvents(new TreeFallEvent(), this);
/*
Prevent falling blocks from forming new blocks on the floor
*/
Bukkit.getServer().getPluginManager().registerEvents(new TreeFallAnimation(), this);
/*
Initialize config
*/
DefaultConfig.initialize();
/*
Initialize custom loot
*/
CustomLoot.initializeCustomItems();
/*
Cache valid worlds for later use
*/
for (World world : Bukkit.getWorlds())
if (getConfig().getBoolean(DefaultConfig.VALID_WORLDS + world.getName()))
validWorlds.add(world);
this.getCommand("ultimatetimber").setExecutor(new CommandHandler(this));
console.sendMessage(Methods.formatText("&a============================="));
}
@Override
public void onDisable() {
validWorlds.clear();
}
public static UltimateTimber getInstance() {
return INSTANCE;
}
public List<World> getValidWorlds() {
return Collections.unmodifiableList(validWorlds);
}
public String getPrefix() {
return prefix;
}
}

View File

@ -1,34 +0,0 @@
package com.songoda.ultimatetimber.commands;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.utils.Methods;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class CommandHandler implements CommandExecutor {
private final UltimateTimber plugin;
public CommandHandler(UltimateTimber plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) {
if (args.length > 0)
if (args[0].equalsIgnoreCase("reload")) {
ReloadCommand.reloadConfig(commandSender);
return true;
}
commandSender.sendMessage("");
commandSender.sendMessage(Methods.formatText(plugin.getPrefix() + " &7Version " + plugin.getDescription().getVersion() + " Created with <3 by &5&l&oBrianna"));
commandSender.sendMessage(Methods.formatText("&8 - &a/ut reload &7 - Reloads the config."));
commandSender.sendMessage("");
return true;
}
}

View File

@ -1,17 +0,0 @@
package com.songoda.ultimatetimber.commands;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.utils.Methods;
import org.bukkit.command.CommandSender;
public class ReloadCommand {
public static void reloadConfig(CommandSender commandSender) {
UltimateTimber plugin = UltimateTimber.getInstance();
plugin.reloadConfig();
commandSender.sendMessage(Methods.formatText(plugin.getPrefix() + " &7Configuration reloaded"));
}
}

View File

@ -1,65 +0,0 @@
package com.songoda.ultimatetimber.configurations;
import com.songoda.ultimatetimber.UltimateTimber;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.configuration.Configuration;
import java.util.Collections;
public class DefaultConfig {
/*
This value is just cached so it can easily and safely be accessed during runtime
*/
// public static Configuration configuration;
/*
Storing these values in final strings makes it so you can change the keys or refactor their names later on without
ever having to alter any code directly.
Also they are easier to refer to using an IDE.
*/
public static final String AXES_ONLY = "Only topple down trees cut down using axes";
public static final String ACCURATE_AXE_DURABILITY = "Lower durability proportionately to the amount of blocks toppled down";
public static final String CREATIVE_DISALLOWED = "Players in creative mode can't topple down trees";
public static final String PERMISSIONS_ONLY = "Only allow players with the permission node to topple down trees";
public static final String VALID_WORLDS = "Valid worlds.";
public static final String DAMAGE_PLAYERS = "Damage players when trees fall on them";
public static final String REPLANT_SAPLING = "Replant sapling when tree is cut down";
public static final String REPLANT_FROM_LEAVES = "Fallen leaves have a chance to plant saplings";
public static final String CUSTOM_AUDIO = "Use custom sounds for trees falling";
public static final String SHOW_ANIMATION = "Show tree fall animation";
public static final String CUSTOM_LOOT_LIST = "Custom loot";
private static final String CUSTOM_LOOT_ITEM = "Material:GOLDEN_APPLE,Chance:1";
public static void initialize() {
UltimateTimber plugin = UltimateTimber.getInstance();
Configuration configuration = plugin.getConfig();
configuration.addDefault(AXES_ONLY, true);
configuration.addDefault(ACCURATE_AXE_DURABILITY, true);
configuration.addDefault(CREATIVE_DISALLOWED, true);
configuration.addDefault(PERMISSIONS_ONLY, true);
configuration.addDefault(DAMAGE_PLAYERS, true);
configuration.addDefault(REPLANT_SAPLING, true);
configuration.addDefault(REPLANT_FROM_LEAVES, true);
configuration.addDefault(CUSTOM_AUDIO, true);
configuration.addDefault(SHOW_ANIMATION, true);
/*
Add all worlds that exist in the world at startup
*/
for (World world : Bukkit.getServer().getWorlds())
configuration.addDefault(VALID_WORLDS + world.getName(), true);
configuration.addDefault(CUSTOM_LOOT_LIST, Collections.singletonList(CUSTOM_LOOT_ITEM));
configuration.options().copyDefaults(true);
plugin.saveConfig();
plugin.saveDefaultConfig();
}
}

View File

@ -1,49 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.HashSet;
import java.util.LinkedHashSet;
public class AxeDurability {
/*
This class handles all durability damage dealt to the axe used to chop down the tree, only takes into account
wood blocks chopped down
*/
public static void adjustAxeDamage(HashSet<Block> blocks, Player player) {
if (!(player.getInventory().getItemInMainHand().getType().equals(Material.DIAMOND_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.GOLDEN_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.IRON_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.STONE_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.WOODEN_AXE))) return;
ItemStack itemStack = player.getInventory().getItemInMainHand();
ItemMeta itemMeta = itemStack.getItemMeta();
Damageable damageableMeta = (Damageable) itemMeta;
for (Block block : blocks)
if (block.getType().equals(Material.ACACIA_LOG) ||
block.getType().equals(Material.BIRCH_LOG) ||
block.getType().equals(Material.DARK_OAK_LOG) ||
block.getType().equals(Material.JUNGLE_LOG) ||
block.getType().equals(Material.OAK_LOG) ||
block.getType().equals(Material.SPRUCE_LOG) ||
block.getType().equals(Material.STRIPPED_ACACIA_LOG) ||
block.getType().equals(Material.STRIPPED_BIRCH_LOG) ||
block.getType().equals(Material.STRIPPED_DARK_OAK_LOG) ||
block.getType().equals(Material.STRIPPED_JUNGLE_LOG) ||
block.getType().equals(Material.STRIPPED_OAK_LOG) ||
block.getType().equals(Material.STRIPPED_SPRUCE_LOG))
damageableMeta.setDamage(damageableMeta.getDamage() + 1);
itemStack.setItemMeta((ItemMeta) damageableMeta);
}
}

View File

@ -1,67 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class CustomLoot {
/*
This is a very simple config parser for items
Each item is a new line in a list
Each line includes the keywords "Material:" and "Chance:" seperated by a ","
The chance is a percentage
It throws specific errors on startup when an invalid configuration is detected
*/
private static HashMap<ItemStack, Double> itemMap = new HashMap<>();
public static void doCustomItemDrop(Location location) {
for (ItemStack itemStack : itemMap.keySet())
if ((ThreadLocalRandom.current().nextDouble()) < itemMap.get(itemStack) / 100)
location.getWorld().dropItem(location, itemStack);
}
public static void initializeCustomItems() {
FileConfiguration fileConfiguration = UltimateTimber.getInstance().getConfig();
List<String> arrayList = (List<String>) fileConfiguration.getList(DefaultConfig.CUSTOM_LOOT_LIST);
for (String string : arrayList) {
Material material = null;
double chance = 0;
String materialString = string.split(",")[0].replace("Material:", "");
try {
material = Material.valueOf(materialString);
} catch (Exception ex) {
Bukkit.getLogger().warning("[UltimateTimber] Warning: " + materialString + " is not a valid material name.");
}
String chanceString = string.split(",")[1].replace("Chance:", "");
try {
chance = Double.parseDouble(chanceString);
} catch (Exception ex) {
Bukkit.getLogger().warning("[UltimateTimber] Warning: " + chanceString + " is not a valid chance.");
}
if (material == null || chance == 0) continue;
ItemStack itemStack = new ItemStack(material);
itemMap.put(itemStack, chance);
}
}
}

View File

@ -1,50 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.block.BlockBreakEvent;
public class EventFilter {
/*
Incorporate all checks that would disqualify this event from happening
Mostly config settings, also permissions
*/
public static boolean eventIsValid(BlockBreakEvent event) {
UltimateTimber plugin = UltimateTimber.getInstance();
/*
General catchers
*/
if (event.isCancelled()) return false;
if (!plugin.getValidWorlds().contains(event.getPlayer().getWorld())) return false;
if (!TreeChecker.validMaterials.contains(event.getBlock().getType())) return false;
FileConfiguration fileConfiguration = UltimateTimber.getInstance().getConfig();
/*
Config-based catchers
*/
if (fileConfiguration.getBoolean(DefaultConfig.CREATIVE_DISALLOWED) &&
event.getPlayer().getGameMode().equals(GameMode.CREATIVE))
return false;
if (fileConfiguration.getBoolean(DefaultConfig.AXES_ONLY) &&
!(event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.DIAMOND_AXE) ||
event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.GOLDEN_AXE) ||
event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.IRON_AXE) ||
event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.STONE_AXE) ||
event.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WOODEN_AXE)))
return false;
return !fileConfiguration.getBoolean(DefaultConfig.PERMISSIONS_ONLY) ||
event.getPlayer().hasPermission("ultimatetimber.chop");
}
}

View File

@ -1,67 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.utils.LeafToSaplingConverter;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.inventory.ItemStack;
import java.util.HashSet;
import java.util.concurrent.ThreadLocalRandom;
public class NoAnimationTreeDestroyer {
/*
Only ever triggers when people have tree falling animations off in the config
*/
public static void destroyTree(HashSet<Block> blocks, boolean hasBonusLoot, boolean hasSilkTouch) {
for (Block block : blocks) {
Material material = LeafToSaplingConverter.convertLeaves(block.getType());
if (material.equals(Material.AIR)) continue;
if (material.equals(Material.VINE)) continue;
if (hasSilkTouch) {
if (hasBonusLoot)
block.getWorld().dropItem(block.getLocation(), new ItemStack(block.getType(), 1));
block.getWorld().dropItem(block.getLocation(), new ItemStack(block.getType(), 1));
CustomLoot.doCustomItemDrop(block.getLocation());
block.setType(Material.AIR);
continue;
}
if (material.equals(Material.ACACIA_SAPLING) ||
material.equals(Material.BIRCH_SAPLING) ||
material.equals(Material.DARK_OAK_SAPLING) ||
material.equals(Material.JUNGLE_SAPLING) ||
material.equals(Material.OAK_SAPLING) ||
material.equals(Material.SPRUCE_SAPLING)) {
if (ThreadLocalRandom.current().nextDouble() < 0.05) {
if (hasBonusLoot) {
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
}
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
block.setType(Material.AIR);
CustomLoot.doCustomItemDrop(block.getLocation());
continue;
} else {
block.setType(Material.AIR);
CustomLoot.doCustomItemDrop(block.getLocation());
continue;
}
}
if (hasBonusLoot)
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
block.getWorld().dropItem(block.getLocation(), new ItemStack(material, 1));
block.setType(Material.AIR);
CustomLoot.doCustomItemDrop(block.getLocation());
}
}
}

View File

@ -1,279 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.utils.LogToLeafConverter;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.util.Vector;
import java.util.*;
public class TreeChecker {
/*
This stores all the blocks returned later on
*/
private HashSet<Block> allBlocks = new HashSet<>();
public HashSet<Block> validTreeHandler(Block block) {
HashSet<Block> blocks = parseTree(block);
if (blocks == null)
return null;
boolean containsLeaves = false;
for (Block localBlock : blocks)
if (TreeChecker.validTreeMaterials.contains(localBlock.getType())) {
containsLeaves = true;
break;
}
if (!containsLeaves)
return null;
return blocks;
}
/**
* This parses a tree; returns a hashset if it is a valid tree, or returns null if it isn't
*
* @param block block the player originally destroys
* @return returns null if the tree isn't valid or all blocks in the tree if it isn't
*/
public HashSet<Block> parseTree(Block block) {
/*
Check if material is parsed by this plugin
*/
if (!validMaterials.contains(block.getType())) return null;
/*
offset determines the search radius around the main trunk
maxheight sets the maximum height the plugin will crawl through to find a tree
*/
int offset = 5;
int maxHeight = 31;
/*
Keep track of the location of the original block to see how much we've deviated from it
*/
Location centralBlockLocation = block.getLocation().clone();
/*
Keep a list of all location that are considered to be a part of the trunk. This is necessary as scans are made
around each one as the search crawls up to detect leaves or building blocks.
*/
HashSet<Location> trunkList = new HashSet<>();
trunkList.add(centralBlockLocation);
Material originalMaterial = block.getType();
for (int i = 0; i < maxHeight; i++) {
/*
For some reason, using the iterator to gradually clear hashset elements isn't working as the hashset
claims not to contain said elements. This is a bit of a dirty workarounf dor that issue.
*/
HashSet<Location> cleanLogSet = new HashSet<>();
for (Location location : trunkList)
if (location.getBlock().getType().equals(originalMaterial) ||
location.getBlock().getType().equals(LogToLeafConverter.convert(originalMaterial)) ||
location.clone().add(new Vector(0, -1, 0)).getBlock().getType().equals(originalMaterial))
cleanLogSet.add(location);
if (cleanLogSet.isEmpty()) {
if (i > 2)
return allBlocks;
else
return null;
}
trunkList = cleanLogSet;
/*
Search for adjacent trunks
*/
Iterator<Location> iterator = trunkList.iterator();
HashSet<Location> expandedTrunkSet = new HashSet<>();
while (iterator.hasNext()) {
Location trunkLocation = iterator.next();
allBlocks.add(trunkLocation.getBlock());
int radMin, radMax;
if (i > 5) {
radMin = -2;
radMax = 3;
} else {
radMin = -1;
radMax = 2;
}
for (int x = radMin; x < radMax; x++)
for (int z = radMin; z < radMax; z++) {
Location currentLocation = trunkLocation.clone().add(new Vector(x, 0, z));
if (Math.abs(currentLocation.getX() - trunkLocation.getX()) > offset ||
Math.abs(currentLocation.getZ() - trunkLocation.getZ()) > offset)
continue;
if (currentLocation.getBlock().getType().equals(originalMaterial)) {
expandedTrunkSet.add(currentLocation);
allBlocks.add(currentLocation.getBlock());
}
}
}
trunkList.addAll(expandedTrunkSet);
/*
Check if the tree is valid and add leaves
*/
for (Location location : trunkList) {
int radMin, radMax;
if (i > 5) {
radMin = -3;
radMax = 4;
} else {
radMin = -2;
radMax = 3;
}
for (int x = radMin; x < radMax; x++)
for (int z = radMin; z < radMax; z++) {
Block currentBlock = location.clone().add(x, 0, z).getBlock();
/*
Check if this block is already in the block list
*/
if (allBlocks.contains(currentBlock))
continue;
/*
Add a bit of tolerance for trees that exist on dirt ledges
*/
if ((currentBlock.getType().equals(Material.DIRT) ||
currentBlock.getType().equals(Material.COARSE_DIRT) ||
currentBlock.getType().equals(Material.GRASS_BLOCK)) &&
i > 1) {
return null;
}
/*
Exclude anything that isn't a part of a tree or a forest to avoid destroying houses
*/
if (!validMaterials.contains(currentBlock.getType()) &&
!validTreeMaterials.contains(currentBlock.getType()) &&
!forestMaterials.contains(currentBlock.getType()))
return null;
/*
This adds blocks to later be felled
Only take blocks of the same tree type
*/
if ((LogToLeafConverter.convert(originalMaterial) != null &&
LogToLeafConverter.convert(originalMaterial).equals(currentBlock.getType())) ||
(originalMaterial.equals(Material.MUSHROOM_STEM) &&
(currentBlock.getType().equals(Material.RED_MUSHROOM_BLOCK) ||
currentBlock.getType().equals(Material.BROWN_MUSHROOM_BLOCK)))) {
allBlocks.add(currentBlock);
}
}
location.add(new Vector(0, 1, 0));
}
}
return allBlocks;
}
/*
Used to check if a tree is a tree
*/
public static List<Material> validMaterials = new ArrayList<>(Arrays.asList(
Material.ACACIA_LOG,
Material.STRIPPED_ACACIA_LOG,
Material.BIRCH_LOG,
Material.STRIPPED_BIRCH_LOG,
Material.DARK_OAK_LOG,
Material.STRIPPED_DARK_OAK_LOG,
Material.JUNGLE_LOG,
Material.STRIPPED_JUNGLE_LOG,
Material.OAK_LOG,
Material.STRIPPED_OAK_LOG,
Material.SPRUCE_LOG,
Material.STRIPPED_SPRUCE_LOG,
Material.MUSHROOM_STEM
));
/*
Used to limit the blocks that constitute a tree
*/
public static List<Material> validTreeMaterials = new ArrayList<>(Arrays.asList(
Material.ACACIA_LEAVES,
Material.BIRCH_LEAVES,
Material.DARK_OAK_LEAVES,
Material.JUNGLE_LEAVES,
Material.OAK_LEAVES,
Material.SPRUCE_LEAVES,
Material.COCOA_BEANS,
Material.BROWN_MUSHROOM_BLOCK,
Material.RED_MUSHROOM_BLOCK
));
/*
A list of materials found in a forest, allows the plugin to work in dense woods
*/
private static List<Material> forestMaterials = new ArrayList<>(Arrays.asList(
Material.AIR,
Material.CAVE_AIR,
Material.VOID_AIR,
Material.VINE,
Material.ROSE_BUSH,
Material.ORANGE_TULIP,
Material.PINK_TULIP,
Material.RED_TULIP,
Material.POPPY,
Material.WHITE_TULIP,
Material.OXEYE_DAISY,
Material.AZURE_BLUET,
Material.BLUE_ORCHID,
Material.ALLIUM,
Material.DANDELION,
Material.DANDELION_YELLOW,
Material.LILAC,
Material.PEONY,
Material.TALL_GRASS,
Material.FERN,
Material.LARGE_FERN,
Material.DEAD_BUSH,
Material.BROWN_MUSHROOM,
Material.RED_MUSHROOM,
Material.GRASS,
Material.SPRUCE_SAPLING,
Material.OAK_SAPLING,
Material.JUNGLE_SAPLING,
Material.ACACIA_SAPLING,
Material.BIRCH_SAPLING,
Material.DARK_OAK_SAPLING,
Material.DIRT,
Material.COARSE_DIRT,
Material.GRASS_BLOCK,
Material.SNOW,
Material.SNOW_BLOCK
));
}

View File

@ -1,21 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import org.bukkit.entity.Entity;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.LivingEntity;
public class TreeEntityDamage {
public static void runDamage(FallingBlock fallingBlock) {
for (Entity entity : fallingBlock.getNearbyEntities(0.5, 0.5, 0.5)) {
if (!(entity instanceof LivingEntity)) continue;
((LivingEntity) entity).damage(1);
}
}
}

View File

@ -1,272 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.block.Block;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
public class TreeFallAnimation implements Listener {
/*
This field gets updated based on player permissions, doubles loot from trees
*/
private boolean hasBonusLoot;
public boolean hasBonusLoot() {
return this.hasBonusLoot;
}
private void setHasBonusLoot(boolean bool) {
this.hasBonusLoot = bool;
}
/*
If a player's tool has the silk touch enchantment, it changes the loot table
*/
private boolean hasSilkTouch;
public boolean hasSilkTouch() {
return this.hasSilkTouch;
}
private void setHasSilkTouch(boolean bool) {
this.hasSilkTouch = bool;
}
/*
This field stores every falling block in this instance of the animation
This list is also used to identify if a falling block is a part of an animation
*/
private ArrayList<FallingBlock> fallingBlocks = new ArrayList<>();
public boolean isFallingTreeBlock(FallingBlock fallingBlock) {
return this.fallingBlocks.contains(fallingBlock);
}
private void registerFallingBlock(FallingBlock fallingBlock) {
this.fallingBlocks.add(fallingBlock);
}
private void unregisterFallingBlock(FallingBlock fallingBlock) {
this.fallingBlocks.remove(fallingBlock);
}
private ArrayList<FallingBlock> getAllFallingBlocks() {
return this.fallingBlocks;
}
/*
Register all instances of falling trees.
*/
public static ArrayList<TreeFallAnimation> treeFallAnimationInstances = new ArrayList<>();
public boolean isInTreeFallInstance(FallingBlock fallingBlock) {
for (TreeFallAnimation treeFallAnimation : treeFallAnimationInstances)
if (treeFallAnimation.isFallingTreeBlock(fallingBlock))
return true;
return false;
}
public TreeFallAnimation getTreeFallAnimation(FallingBlock fallingBlock) {
for (TreeFallAnimation treeFallAnimation : treeFallAnimationInstances)
if (treeFallAnimation.isFallingTreeBlock(fallingBlock))
return treeFallAnimation;
return null;
}
private void registerTreeFallInstance() {
treeFallAnimationInstances.add(this);
}
private void unregisterTreeFallAnimation() {
if (this.fallingBlocks.isEmpty())
treeFallAnimationInstances.remove(this);
}
/*
This animation has multiple phases.
Initially, the tree will start slowly toppling over.
After a short while, it goes over the tipping point and the fall accelerates.
*/
public void startAnimation(Block originalBlock, HashSet<Block> blocks, Player player) {
/*
This vector makes sure that the entire tree falls in the same direction from the same reference point
*/
Vector velocityVector = originalBlock.getLocation().clone().subtract(player.getLocation().clone()).toVector().normalize().setY(0);
registerTreeFallInstance();
setHasBonusLoot(player.hasPermission("ultimatetimber.bonusloot"));
/*
Register private properties
*/
if (player.getInventory().getItemInMainHand().getType().equals(Material.DIAMOND_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.GOLDEN_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.IRON_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.STONE_AXE) ||
player.getInventory().getItemInMainHand().getType().equals(Material.WOODEN_AXE))
if (player.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.SILK_TOUCH))
setHasSilkTouch(true);
else
setHasSilkTouch(false);
else
setHasSilkTouch(false);
for (Block block : blocks) {
FallingBlock fallingBlock = block.getWorld().spawnFallingBlock(block.getLocation(), block.getBlockData());
fallingBlock.setDropItem(false);
registerFallingBlock(fallingBlock);
/*
Dropping air causes some issues
*/
if (block.getType().equals(Material.AIR)) continue;
/*
Remove original block
*/
TreeReplant.replaceOriginalBlock(block);
/*
Set tipping over effect
The horizontal velocity going away from the player increases as the Y moves away from the player
*/
double multiplier = (block.getLocation().getY() - player.getLocation().getY()) * 0.1;
startPhaseOneAnimation(fallingBlock, velocityVector, multiplier);
}
}
/*
Phase one of the animation, the tree starts slowly tipping over
*/
private void startPhaseOneAnimation(FallingBlock fallingBlock, Vector velocityVector, double multiplier) {
/*
Vertical offset so top of the tree sways faster than the base
*/
fallingBlock.setVelocity(velocityVector.clone().multiply(multiplier));
/*
No gravity helps with the initial surrounding block detection (somehow) and with the initial trunk rigidity aspect
required for the effect to look convincing
*/
fallingBlock.setGravity(false);
fallingBlock.setVelocity(fallingBlock.getVelocity().multiply(0.2));
new BukkitRunnable() {
@Override
public void run() {
fallingBlock.setGravity(true);
/*
Phase 2 has to be launched from here as to not override effects
*/
runPhaseTwoAnimation(fallingBlock);
}
}.runTaskLater(UltimateTimber.getInstance(), 20);
}
/*
Phase two of the animation, the tree picks up speed until it is on the ground
For safety's sake, it disintegrates after a 4 seconds
*/
private void runPhaseTwoAnimation(FallingBlock fallingBlock) {
UltimateTimber plugin = UltimateTimber.getInstance();
new BukkitRunnable() {
int counter = 0;
@Override
public void run() {
if (!fallingBlock.isValid()) {
cancel();
return;
}
/*
Safeguard to prevent errors that come from glitchy Minecraft behavior
*/
if (counter > 20 * 3) {
runFallingBlockImpact(fallingBlock);
cancel();
}
if (counter < 10)
fallingBlock.setVelocity(fallingBlock.getVelocity().multiply(1.3));
counter++;
}
}.runTaskTimer(plugin, 0, 1);
}
/*
Catch tree blocks falling down
*/
@EventHandler
public void blockDrop(EntityChangeBlockEvent event) {
if (!(event.getEntity() instanceof FallingBlock)) return;
if (!isInTreeFallInstance((FallingBlock) event.getEntity())) return;
event.setCancelled(true);
FallingBlock fallingBlock = (FallingBlock) event.getEntity();
runFallingBlockImpact(fallingBlock);
}
private void runFallingBlockImpact(FallingBlock fallingBlock) {
TreeFallAnimation treeFallAnimation = getTreeFallAnimation(fallingBlock);
treeFallAnimation.unregisterFallingBlock(fallingBlock);
if (treeFallAnimation.getAllFallingBlocks().isEmpty())
unregisterTreeFallAnimation();
UltimateTimber plugin = UltimateTimber.getInstance();
FileConfiguration fileConfiguration = plugin.getConfig();
/*
Run block fall aftermath
*/
TreeLoot.convertFallingBlock(fallingBlock, treeFallAnimation.hasBonusLoot(), treeFallAnimation.hasSilkTouch());
if (UltimateTimber.getInstance().getConfig().getBoolean(DefaultConfig.REPLANT_FROM_LEAVES))
TreeReplant.leafFallReplant(fallingBlock);
if (fileConfiguration.getBoolean(DefaultConfig.DAMAGE_PLAYERS))
TreeEntityDamage.runDamage(fallingBlock);
if (fileConfiguration.getBoolean(DefaultConfig.CUSTOM_AUDIO))
TreeSounds.fallNoise(fallingBlock);
fallingBlock.getLocation().getWorld().spawnParticle(Particle.SMOKE_LARGE, fallingBlock.getLocation(), 3, 0.2, 0.2, 0.2, 0.05);
/*
Make sure the falling block gets culled
*/
fallingBlock.remove();
}
}

View File

@ -1,55 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.block.Block;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import java.util.HashSet;
public class TreeFallEvent implements Listener {
/*
This is the starting point for the whole effect
It's been broken up instead of chained in order to make step-by-step debugging easier
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onTreeBreak(BlockBreakEvent event) {
if (!EventFilter.eventIsValid(event)) return;
TreeChecker treeChecker = new TreeChecker();
HashSet<Block> blocks = treeChecker.validTreeHandler(event.getBlock());
/*
Previous list will be null if no valid tree is found
*/
if (blocks == null)
return;
/*
Everything beyond this point assumes that the tree was valid
*/
FileConfiguration fileConfiguration = UltimateTimber.getInstance().getConfig();
if (fileConfiguration.getBoolean(DefaultConfig.ACCURATE_AXE_DURABILITY))
AxeDurability.adjustAxeDamage(blocks, event.getPlayer());
if (fileConfiguration.getBoolean(DefaultConfig.CUSTOM_AUDIO))
TreeSounds.tipOverNoise(event.getBlock().getLocation());
if (fileConfiguration.getBoolean(DefaultConfig.SHOW_ANIMATION)) {
TreeFallAnimation treeFallAnimation = new TreeFallAnimation();
treeFallAnimation.startAnimation(event.getBlock(), blocks, event.getPlayer());
} else {
NoAnimationTreeDestroyer.destroyTree(blocks, event.getPlayer().hasPermission("ultimatetimber.bonusloot"),
event.getPlayer().getInventory().getItemInMainHand().containsEnchantment(Enchantment.SILK_TOUCH));
}
}
}

View File

@ -1,69 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.utils.LeafToSaplingConverter;
import org.bukkit.Material;
import org.bukkit.entity.FallingBlock;
import org.bukkit.inventory.ItemStack;
import java.util.concurrent.ThreadLocalRandom;
public class TreeLoot {
public static void convertFallingBlock(FallingBlock fallingBlock, boolean hasBonusLoot, boolean hasSilkTouch) {
Material material = LeafToSaplingConverter.convertLeaves(fallingBlock.getBlockData().getMaterial());
if (material.equals(Material.VINE))
return;
if (hasSilkTouch) {
if (hasBonusLoot)
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(fallingBlock.getBlockData().getMaterial(), 1));
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(fallingBlock.getBlockData().getMaterial(), 1));
CustomLoot.doCustomItemDrop(fallingBlock.getLocation());
return;
}
if (material.equals(Material.BROWN_MUSHROOM_BLOCK)) {
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(Material.BROWN_MUSHROOM, 1));
return;
}
if (material.equals(Material.RED_MUSHROOM_BLOCK)) {
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(Material.RED_MUSHROOM, 1));
return;
}
if (material.equals(Material.MUSHROOM_STEM)) {
return;
}
if (material.equals(Material.ACACIA_SAPLING) ||
material.equals(Material.BIRCH_SAPLING) ||
material.equals(Material.DARK_OAK_SAPLING) ||
material.equals(Material.JUNGLE_SAPLING) ||
material.equals(Material.OAK_SAPLING) ||
material.equals(Material.SPRUCE_SAPLING)) {
if (ThreadLocalRandom.current().nextDouble() < 0.05) {
if (hasBonusLoot) {
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
}
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
CustomLoot.doCustomItemDrop(fallingBlock.getLocation());
return;
} else {
CustomLoot.doCustomItemDrop(fallingBlock.getLocation());
return;
}
}
if (hasBonusLoot)
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
fallingBlock.getWorld().dropItem(fallingBlock.getLocation(), new ItemStack(material, 1));
}
}

View File

@ -1,110 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import com.songoda.ultimatetimber.UltimateTimber;
import com.songoda.ultimatetimber.configurations.DefaultConfig;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.FallingBlock;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.concurrent.ThreadLocalRandom;
public class TreeReplant {
public static void replaceOriginalBlock(Block block) {
if (!UltimateTimber.getInstance().getConfig().getBoolean(DefaultConfig.REPLANT_SAPLING)) {
block.setType(Material.AIR);
return;
}
if (!block.getLocation().clone().subtract(new Vector(0, 1, 0)).getBlock().getType().equals(Material.DIRT) &&
!block.getLocation().clone().subtract(new Vector(0, 1, 0)).getBlock().getType().equals(Material.COARSE_DIRT)) {
block.setType(Material.AIR);
return;
}
Material material = block.getType();
new BukkitRunnable() {
@Override
public void run() {
switch (material) {
case ACACIA_LOG:
case STRIPPED_ACACIA_LOG:
block.setType(Material.ACACIA_SAPLING);
return;
case BIRCH_LOG:
case STRIPPED_BIRCH_LOG:
block.setType(Material.BIRCH_SAPLING);
return;
case DARK_OAK_LOG:
case STRIPPED_DARK_OAK_LOG:
block.setType(Material.DARK_OAK_SAPLING);
return;
case JUNGLE_LOG:
case STRIPPED_JUNGLE_LOG:
block.setType(Material.JUNGLE_SAPLING);
return;
case OAK_LOG:
case STRIPPED_OAK_LOG:
block.setType(Material.OAK_SAPLING);
return;
case SPRUCE_LOG:
case STRIPPED_SPRUCE_LOG:
block.setType(Material.SPRUCE_SAPLING);
return;
default:
block.setType(Material.AIR);
}
}
}.runTaskLater(UltimateTimber.getInstance(), 1);
}
public static void leafFallReplant(FallingBlock fallingBlock) {
Material material;
switch (fallingBlock.getBlockData().getMaterial()) {
case ACACIA_LEAVES:
material = Material.ACACIA_SAPLING;
break;
case BIRCH_LEAVES:
material = Material.BIRCH_SAPLING;
break;
case DARK_OAK_LEAVES:
material = Material.DARK_OAK_SAPLING;
break;
case JUNGLE_LEAVES:
material = Material.JUNGLE_SAPLING;
break;
case OAK_LEAVES:
material = Material.OAK_SAPLING;
break;
case SPRUCE_LEAVES:
material = Material.SPRUCE_SAPLING;
break;
default:
material = null;
}
if (material == null) return;
if (ThreadLocalRandom.current().nextDouble() > 0.01) return;
Block block = fallingBlock.getLocation().clone().subtract(new Vector(0, 1, 0)).getBlock();
if (block.getType().equals(Material.DIRT) || block.getType().equals(Material.COARSE_DIRT) || block.getType().equals(Material.GRASS_BLOCK)) {
Block blockAbove = block.getLocation().clone().add(new Vector(0, 1, 0)).getBlock();
if (blockAbove.getType().equals(Material.AIR))
fallingBlock.getLocation().getBlock().setType(material);
}
}
}

View File

@ -1,23 +0,0 @@
package com.songoda.ultimatetimber.treefall;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.FallingBlock;
public class TreeSounds {
public static void tipOverNoise(Location location) {
location.getWorld().playSound(location, Sound.BLOCK_CHEST_OPEN, 3F, 0.1F);
}
public static void fallNoise(FallingBlock fallingBlock) {
if (fallingBlock.getTicksLived() < 20)
fallingBlock.getWorld().playSound(fallingBlock.getLocation(), Sound.BLOCK_ANVIL_FALL, 3F, 0.1F);
else
fallingBlock.getWorld().playSound(fallingBlock.getLocation(), Sound.BLOCK_WOOD_FALL, 3F, 0.1F);
}
}

View File

@ -1,41 +0,0 @@
package com.songoda.ultimatetimber.utils;
import org.bukkit.Material;
public class LeafToSaplingConverter {
/*
Defaults to returning the same material type that is fed into it
*/
public static Material convertLeaves(Material material) {
switch (material) {
case ACACIA_LEAVES:
material = org.bukkit.Material.ACACIA_SAPLING;
break;
case BIRCH_LEAVES:
material = org.bukkit.Material.BIRCH_SAPLING;
break;
case DARK_OAK_LEAVES:
material = org.bukkit.Material.DARK_OAK_SAPLING;
break;
case JUNGLE_LEAVES:
material = org.bukkit.Material.JUNGLE_SAPLING;
break;
case OAK_LEAVES:
material = org.bukkit.Material.OAK_SAPLING;
break;
case SPRUCE_LEAVES:
material = org.bukkit.Material.SPRUCE_SAPLING;
break;
default:
material = material;
}
return material;
}
}

View File

@ -1,36 +0,0 @@
package com.songoda.ultimatetimber.utils;
import org.bukkit.Material;
public class LogToLeafConverter {
public static Material convert(Material material) {
switch (material) {
case ACACIA_LOG:
case STRIPPED_ACACIA_LOG:
return Material.ACACIA_LEAVES;
case BIRCH_LOG:
case STRIPPED_BIRCH_LOG:
return Material.BIRCH_LEAVES;
case DARK_OAK_LOG:
case STRIPPED_DARK_OAK_LOG:
return Material.DARK_OAK_LEAVES;
case JUNGLE_LOG:
case STRIPPED_JUNGLE_LOG:
return Material.JUNGLE_LEAVES;
case OAK_LOG:
case STRIPPED_OAK_LOG:
return Material.OAK_LEAVES;
case SPRUCE_LOG:
case STRIPPED_SPRUCE_LOG:
return Material.SPRUCE_LEAVES;
default:
return null;
}
}
}

View File

@ -1,20 +0,0 @@
package com.songoda.ultimatetimber.utils;
import org.bukkit.ChatColor;
public class Methods {
public static String formatText(String text) {
if (text == null || text.equals(""))
return "";
return formatText(text, false);
}
public static String formatText(String text, boolean cap) {
if (text == null || text.equals(""))
return "";
if (cap)
text = text.substring(0, 1).toUpperCase() + text.substring(1);
return ChatColor.translateAlternateColorCodes('&', text);
}
}

View File

@ -0,0 +1,580 @@
# ____ ___ __ __ __ __ ___________ __ ___
# | | \ |_/ |_|__| _____ _____ _/ |_ ___\__ ___/|__| _____\_ |__ ___________
# | | / |\ __\ |/ \\__ \\ __\/ __ \| | | |/ \| __ \_/ __ \_ __ \
# | | /| |_| | | | Y Y \/ __ \| | \ ___/| | | | Y Y \ \_\ \ ___/| | \/
# |______/ |____/__| |__|__|_| (____ /__| \___ |____| |__|__|_| /___ /\___ >__|
# The type of server you are running in relation to this plugin
# Do not change this value
# Default: CURRENT
server-type: CURRENT
# The locale to use in the /locale folder
# Default: en_US
locale: en_US
# A list of worlds that the plugin is disabled in
# Default:
# - disabled_world_name
disabled-worlds:
- disabled_world_name
# The max number of logs that can be broken at one time
# Default: 150
max-logs-per-chop: 150
# The minimum number of leaves required for something to be considered a tree
# Default: 5
leaves-required-for-tree: 5
# If leaves should be destroyed
# Default: true
destroy-leaves: true
# Apply realistic damage to the tools based on the number of logs chopped
# If false, only one durability will be removed from the tool
# Default: true
realistic-tool-damage: true
# Protect the tool used to chop down the tree from breaking
# Prevents the tree from being toppled if the tool would break
# Default: false
protect-tool: false
# Use the silk touch enchantment if the tool has it
# Logs and leaves will drop their original block 100% of the time
# Default: true
apply-silk-touch: true
# Damage the tool extra for each leaf block broken, this is vanilla behavior but can be disabled here
# Does nothing if realistic-tool-damage is false
# Default: true
apply-silk-touch-tool-damage: true
# Require the entire base of the tree to be broken before it topples
# Default: false
break-entire-tree-base: false
# Don't drop a block for the block that initiates the tree fall
# Default: false
destroy-initiated-block: false
# Only detect logs above the initiated block
# Default: true
only-detect-logs-upwards: true
# Only topple trees while the player is doing something
# Valid values: SNEAKING, NOT_SNEAKING, ALWAYS
# Default: ALWAYS
only-topple-while: ALWAYS
# Allow toppling trees in creative mode
# Default: true
allow-creative-mode: true
# Require the player to have the permission 'ultimatetimber.chop' to topple trees
# Default: false
require-chop-permission: false
# If a player should only be allowed to chop one tree per cooldown length
# Default: false
player-tree-topple-cooldown: false
# The amount of seconds a player has to wait before they can chop a tree again
# Does nothing if player-tree-topple-cooldown is false
# The time is in seconds and must be a postive whole number
# Default: 5
player-tree-topple-cooldown-length: 5
# Allow players to topple trees regardless of what they are holding in their hand
# Default: false
ignore-required-tools: false
# Automatically replant saplings when a tree is toppled
# Default: true
replant-saplings: true
# Always replant saplings for base tree blocks, regardless of player permissions
# Default: false
always-replant-sapling: false
# How many seconds to prevent players from breaking replanted saplings
# Set to 0 to disable
# Does nothing if replant-saplings is false
# The time is in seconds and must be a postive whole number
# Default: 3
replant-saplings-cooldown: 3
# Give fallen leaf blocks a chance to replant saplings when they hit the ground
# Default: true
falling-blocks-replant-saplings: true
# The percent chance that fallen leaves have of planting a sapling
# Does nothing if falling-blocks-replant-saplings is false
# The chance is out of 100 and may contain decimals
# Default: 1
falling-blocks-replant-saplings-chance: 1
# Make falling tree blocks deal damage to players if they get hit
# Default: true
falling-blocks-deal-damage: true
# The amount of damage that falling tree blocks do
# This does nothing if falling-blocks-deal-damage is false
# Default: 1
falling-block-damage: 1
# Automatically add tree blocks to the player's inventory instead of dropping them
# Default: false
add-items-to-inventory: false
# Use custom sounds when toppling trees
# Default: true
use-custom-sounds: true
# Use custom particles when toppling trees
# Default: true
use-custom-particles: true
# The bonus loot multiplier when a player has the permission ultimatetimber.bonusloot
# Multiplies the chance of tree drops by this value
# Decimal values are allowed
# Default: 2
bonus-loot-multiplier: 2
# If placed blocks should be ignored for toppling trees
# Note: This only keeps track of blocks placed during the current server load
# If your server restarts, the placed tree blocks could be toppled again
# Default: true
ignore-placed-blocks: true
# The maximum number of blocks to keep track of in memory at once
# Use a lower number if this starts to take up too much memory or trees start taking too long to detect
# Default: 5000
ignore-placed-blocks-memory-size: 5000
# Applies experience when using Jobs/mcMMO
# Only does something if Jobs or mcMMO is installed
# Default: true
hooks-apply-experience: true
# Applies extra drops passive ability when using mcMMO
# Only does something if mcMMO is installed
# Default: true
hooks-apply-extra-drops: true
# Requires the tree feller ability in mcMMO to be active to use timber
# Only does something if mcMMO is installed
# Default: false
hooks-require-ability-active: false
# The type of animation to use for tree toppling
# Types: FANCY, DISINTEGRATE, CRUMBLE, NONE
tree-animation-type: FANCY
# If the tree-animation-type is FANCY or CRUMBLE, make the blocks stick to the ground
# Does nothing if tree-animation-type is not FANCY or CRUMBLE
# Default: false
scatter-tree-blocks-on-ground: false
# Tree configuration
# Allows for extreme fine-tuning of tree detection and what are considered trees
# Multiple log and leaf types are allowed, only one sapling type is allowed
# You can add your own custom tree types here, just add a new section
trees:
oak:
logs:
- OAK_LOG
- STRIPPED_OAK_LOG
- OAK_WOOD
- STRIPPED_OAK_WOOD
leaves:
- OAK_LEAVES
- VINE
sapling: OAK_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 6
max-leaf-distance-from-log: 6
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: OAK_SAPLING
chance: 5
1:
material: APPLE
chance: 0.5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
spruce:
logs:
- SPRUCE_LOG
- STRIPPED_SPRUCE_LOG
- SPRUCE_WOOD
- STRIPPED_SPRUCE_WOOD
leaves:
- SPRUCE_LEAVES
- VINE
sapling: SPRUCE_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 2
max-leaf-distance-from-log: 6
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: SPRUCE_SAPLING
chance: 5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
birch:
logs:
- BIRCH_LOG
- STRIPPED_BIRCH_LOG
- BIRCH_WOOD
- STRIPPED_BIRCH_WOOD
leaves:
- BIRCH_LEAVES
- VINE
sapling: BIRCH_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 1
max-leaf-distance-from-log: 4
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: BIRCH_SAPLING
chance: 5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
jungle:
logs:
- JUNGLE_LOG
- STRIPPED_JUNGLE_LOG
- JUNGLE_WOOD
- STRIPPED_JUNGLE_WOOD
leaves:
- JUNGLE_LEAVES
- VINE
sapling: JUNGLE_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 6
max-leaf-distance-from-log: 6
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: JUNGLE_SAPLING
chance: 2.5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
acacia:
logs:
- ACACIA_LOG
- STRIPPED_ACACIA_LOG
- ACACIA_WOOD
- STRIPPED_ACACIA_WOOD
leaves:
- ACACIA_LEAVES
sapling: ACACIA_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 4
max-leaf-distance-from-log: 5
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: ACACIA_SAPLING
chance: 5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
dark_oak:
logs:
- DARK_OAK_LOG
- STRIPPED_DARK_OAK_LOG
- DARK_OAK_WOOD
- STRIPPED_DARK_OAK_WOOD
leaves:
- DARK_OAK_LEAVES
- VINE
sapling: DARK_OAK_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 3
max-leaf-distance-from-log: 5
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: DARK_OAK_SAPLING
chance: 5
1:
material: APPLE
chance: 0.5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
azalea:
logs:
- OAK_LOG
- STRIPPED_OAK_LOG
- OAK_WOOD
- STRIPPED_OAK_WOOD
leaves:
- AZALEA_LEAVES
- FLOWERING_AZALEA_LEAVES
- VINE
sapling: AZALEA
plantable-soil: [ ]
max-log-distance-from-trunk: 3
max-leaf-distance-from-log: 4
search-for-leaves-diagonally: true
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: AZALEA
chance: 5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
cherry:
logs:
- CHERRY_LOG
- STRIPPED_CHERRY_LOG
- CHERRY_WOOD
- STRIPPED_CHERRY_WOOD
leaves:
- CHERRY_LEAVES
sapling: CHERRY_SAPLING
plantable-soil: [ ]
max-log-distance-from-trunk: 10
max-leaf-distance-from-log: 6
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: CHERRY_SAPLING
chance: 5
1:
material: STICK
chance: 2.5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
mangrove:
logs:
- MANGROVE_LOG
- STRIPPED_MANGROVE_LOG
- MANGROVE_WOOD
- STRIPPED_MANGROVE_WOOD
leaves:
- MANGROVE_LEAVES
- MANGROVE_ROOTS
- MOSS_CARPET
- MANGROVE_PROPAGULE
- VINE
sapling: MANGROVE_PROPAGULE
plantable-soil: [ ]
max-log-distance-from-trunk: 30
max-leaf-distance-from-log: 10
search-for-leaves-diagonally: true
drop-original-log: true
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: MANGROVE_PROPAGULE
chance: 5
1:
material: APPLE
chance: 0.5
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
brown_mushroom:
logs:
- MUSHROOM_STEM
leaves:
- BROWN_MUSHROOM_BLOCK
sapling: BROWN_MUSHROOM
plantable-soil:
- MYCELIUM
max-log-distance-from-trunk: 4
max-leaf-distance-from-log: 4
search-for-leaves-diagonally: false
drop-original-log: false
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: BROWN_MUSHROOM
chance: 25
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
red_mushroom:
logs:
- MUSHROOM_STEM
leaves:
- RED_MUSHROOM_BLOCK
sapling: RED_MUSHROOM
plantable-soil:
- MYCELIUM
max-log-distance-from-trunk: 4
max-leaf-distance-from-log: 4
search-for-leaves-diagonally: true
drop-original-log: false
drop-original-leaf: false
log-loot: [ ]
leaf-loot:
0:
material: RED_MUSHROOM
chance: 25
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
huge_crimson_fungus:
logs:
- CRIMSON_STEM
- STRIPPED_CRIMSON_STEM
- CRIMSON_HYPHAE
- STRIPPED_CRIMSON_HYPHAE
leaves:
- NETHER_WART_BLOCK
- SHROOMLIGHT
sapling: CRIMSON_FUNGUS
plantable-soil:
- CRIMSON_NYLIUM
max-log-distance-from-trunk: 27
max-leaf-distance-from-log: 5
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: true
log-loot: [ ]
leaf-loot: [ ]
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
huge_warpped_fungus:
logs:
- WARPED_STEM
- STRIPPED_WARPED_STEM
- WARPED_HYPHAE
- STRIPPED_WARPED_HYPHAE
leaves:
- WARPED_WART_BLOCK
- SHROOMLIGHT
sapling: WARPED_FUNGUS
plantable-soil:
- WARPED_NYLIUM
max-log-distance-from-trunk: 27
max-leaf-distance-from-log: 5
search-for-leaves-diagonally: false
drop-original-log: true
drop-original-leaf: true
log-loot: [ ]
leaf-loot: [ ]
entire-tree-loot: [ ]
required-tools: [ ]
required-axe: false
# All soil types that the tree type's saplings can be planted on
global-plantable-soil:
- GRASS_BLOCK
- DIRT
- COARSE_DIRT
- PODZOL
- ROOTED_DIRT
# Custom loot that is available for all tree types
# The loot applies to each log broken in the tree
# To add more, increment the number by 1
# The chance is out of 100 and can contain decimals
# The default examples here are to show what you can do with custom loot
# Valid command placeholders: %player%, %type%, %xPos%, %yPos%, %zPos%
global-log-loot:
0:
material: DIAMOND
chance: 0
1:
command: 'eco give %player% 5'
chance: 0
2:
material: GOLDEN_APPLE
command: 'broadcast %player% found a golden apple in a %type% tree at %xPos% %yPos% %zPos%!'
chance: 0
# Custom loot that is available for all tree types
# The loot applies to each leaf broken in the tree
# To add more, increment the number by 1
# The chance is out of 100 and can contain decimals
# Valid command placeholders: %player%, %type%, %xPos%, %yPos%, %zPos%
global-leaf-loot:
0:
material: GOLDEN_APPLE
chance: 0.1
# Custom entire tree loot that is available for all tree types
# The loot will be dropped only one time for the entire tree
# To add more, increment the number by 1
# The chance is out of 100 and can contain decimals
# Valid command placeholders: %player%, %type%, %xPos%, %yPos%, %zPos%
global-entire-tree-loot:
0:
material: DIAMOND
chance: 0
# Tools that must be used to topple over a tree
# Applies to all tree types
global-required-tools:
- WOODEN_AXE
- STONE_AXE
- IRON_AXE
- GOLDEN_AXE
- DIAMOND_AXE
- NETHERITE_AXE
# Require the custom axe
# Applies to all tree types
global-required-axe: false
# Axe item
required-axe:
type: DIAMOND_AXE
name: '&aAn Epic Axe'
lore:
- "&7This axe... it's awesome."
- "&7It can chop down trees real fast."
enchants:
- 'DURABILITY:3'
- 'DIG_SPEED:5'
# NBT to identify the axe by.
nbt: 'ultimatetimber_axe'
# If a tree lands on these blocks they will be destroyed.
fragile-blocks:
- GLASS
- ICE
- PACKED_ICE
- BLUE_ICE

View File

@ -0,0 +1,27 @@
# General Messages
general:
nametag:
prefix: '&8[&6UltimateTimber&8] '
nopermission: '&cYou don''t have permission for that!'
# Command Messages
command:
reload:
description: 'Reloads the config.'
reloaded: '&7Configuration and locale files have been reloaded.'
toggle:
description: 'Toggles your chopping mode'
enabled: '&7Chopping Mode: &aEnabled'
disabled: '&7Chopping Mode: &cDisabled'
give:
not-a-player: '&cNot a player.'
given: '&fGiven to player &a%player%'
no-axe: '&cAxe could not be loaded.'
# Event Messages
event:
'on':
cooldown: '&eYou are on cooldown and cannot topple trees right now.'

View File

@ -1,13 +1,24 @@
name: UltimateTimber
version: 0.0.10
author: Songoda
main: com.songoda.ultimatetimber.UltimateTimber
name: ${project.name}
description: ${project.description}
version: ${project.version}
api-version: 1.13
main: com.craftaro.ultimatetimber.UltimateTimber
softdepend:
- mcMMO
- Jobs
- CoreProtect
author: Craftaro
website: ${project.url}
# TODO: cleanup commands and permissions sections
commands:
ultimatetimber:
ut:
description: Reloads the configuration file
usage: /ultimatetimber reload
aliases: [ut]
aliases: [ ultimatetimber ]
permissions:
ultimatetimber.*:
description: Inherits all plugin permissions
@ -15,12 +26,20 @@ permissions:
ultimatetimber.chop: true
ultimatetimber.bonusloot: true
ultimatetimber.reload: true
ultimatetimber.bypasscooldown: true
ultimatetimber.chop:
description: Allows players to trigger the trees toppling down effect
default: op
ultimatetimber.bonusloot:
description: Doubles the loot obtained from trees
default: op
ultimatetimber.reload:
description: Reloads the configuration file
default: op
default: op
ultimatetimber.bypasscooldown:
description: Allows a player to bypass the tree topple cooldown
default: op

View File

@ -1,26 +0,0 @@
name: UltimateTimber
version: 0.0.8
author: Songoda
main: com.songoda.ultimatetimber.UltimateTimber
api-version: 1.13
commands:
ultimatetimber:
description: Reloads the configuration file
usage: /ultimatetimber reload
aliases: [ut]
permissions:
ultimatetimber.*:
description: Inherits all plugin permissions
children:
ultimatetimber.chop: true
ultimatetimber.bonusloot: true
ultimatetimber.reload: true
ultimatetimber.chop:
description: Allows players to trigger the trees toppling down effect
default: op
ultimatetimber.bonusloot:
description: Doubles the loot obtained from trees
default: op
ultimatetimber.reload:
description: Reloads the configuration file
default: op