300 Commits

Author SHA1 Message Date
1482b2c4fe Merge branch 'master' into inventory-logging 2021-04-25 01:59:46 +02:00
cb1231eab5 Snapshots 2021-04-15 03:23:55 +02:00
1df380741b Release 1.16.5.1 2021-04-15 03:19:14 +02:00
2049a7a7a4 Use a different way to check for existing tables 2021-04-14 18:52:06 +02:00
8148386e3e Back to snapshots 2021-04-13 21:01:37 +02:00
87074da8a1 Release Logblock 1.16.5 2021-04-13 20:53:56 +02:00
0fd3266c7b Load the new mysql driver class if available 2021-04-13 18:32:16 +02:00
c20b677507 Improve table creation logic 2021-04-13 18:24:54 +02:00
fe7e244898 Do not scroll in a result set when we don't have to 2021-03-18 21:25:10 +01:00
4f7c02b285 Flush other viewers of the same inventory; add some documentation 2021-02-22 07:42:50 +01:00
8a8471c3e6 Initial work for improved inventory logging 2021-02-22 00:18:02 +01:00
fe98370acd Avoid syncronous query in /lb me
and improve changes count display in sumarized changes

Fixes #819
2021-02-20 06:32:53 +01:00
76df1a4913 Use GSON in UUIDFetcher 2021-02-17 07:31:50 +01:00
841ce89f21 UUIDs are stored differently since 1.16 2021-02-17 07:10:51 +01:00
033a53e338 Improve database upgrade logic 2021-01-30 17:34:24 +01:00
f42649adc3 Do not forget to close connection when upgrading a table fails 2021-01-29 20:10:43 +01:00
81e0135046 Remove tool from offhand if it is not found in the main inventory 2021-01-16 07:37:56 +01:00
e181c85647 Fix table update and to not break on update issues 2021-01-16 07:16:01 +01:00
dde8dc8289 Do not log bees leaving beehives 2021-01-16 06:21:53 +01:00
ac462261dc Add missing entityid index for entities tables 2021-01-16 06:20:36 +01:00
42715de265 getExistingEntityTypeId should return Integer instead of int
Fixes #804
2020-08-31 00:28:12 +02:00
d548206c3a Workaround for SPIGOT-6025 (catch the exception and log it) 2020-08-28 05:34:48 +02:00
24b5455f08 Use Spigots new HoverEvent api 2020-08-23 07:10:11 +02:00
35f921a9a0 Fix loading old walls 2020-08-23 05:40:15 +02:00
0d7a8016a1 Do not add unnecessary materials or entity types to the database 2020-08-09 23:25:30 +02:00
6a398a67ab Protect against sql injections when using the api 2020-07-20 07:13:32 +02:00
6dcca54637 Do not use the numeric material/entity ids for the api 2020-07-20 06:47:43 +02:00
650f7e20f1 Make MaterialConverter and EntityTypeConverter thread safe 2020-07-20 06:17:06 +02:00
5c22beb2e5 Make MaterialConverter type safe 2020-07-20 06:02:23 +02:00
a63c97bd70 Back to snapshots 2020-07-11 05:45:35 +02:00
1562bbacea Release 1.16.1.1 2020-07-11 05:43:54 +02:00
aba6e4d9c8 Add some missing special block handlings for 1.16 2020-07-02 04:33:18 +02:00
1ef7c78c0d Snapshots for 1.16.1.1 2020-06-27 22:45:20 +02:00
788d8fd4d5 Release 1.16.1.0 2020-06-27 22:43:33 +02:00
3bfb19cdfa Correctly log 1.16 blocks 2020-06-27 22:40:08 +02:00
04b5d9e7ed Snapshots for 1.16.1 2020-06-27 22:39:29 +02:00
a96f82efae Back to snapshots 2020-02-13 05:09:27 +01:00
39f58a6bd4 Release 1.15.2 2020-02-13 05:05:21 +01:00
e1064dd0b1 Load mysql driver on startup
Fixes #784
2020-02-13 04:17:01 +01:00
fc1cd5ef2c Fix oldConfig check 2020-02-08 17:33:39 +01:00
1dba9f20f1 create tables and register events if logging commands 2020-01-26 06:27:09 +01:00
8eb93411ec Configurable command logging 2020-01-26 03:21:12 +01:00
068ac89819 Add optional logging of shulker box contents
Fixes #781
2020-01-24 05:51:18 +01:00
6b71a3c30d smart log breaking ice 2020-01-24 05:17:33 +01:00
6dec1b6c37 Improve rollback ability by accepting similar blocks to the expected one
For example grass_block instead of dirt
2020-01-24 05:06:48 +01:00
b9513df20e Log bamboo growth (new world logging option)
Fixes #769
2020-01-24 04:52:13 +01:00
8b34e39797 Log getting stung by bees
Fixes #780
2020-01-24 04:23:59 +01:00
1cda6506c7 Merge pull request #782 from TheMolkaPL/tool-event
Call ToolUseEvent when a tool is about to be used
2020-01-24 03:49:44 +01:00
5b0e2d9adb Call ToolUseEvent when a tool is about to be used 2020-01-21 17:44:56 +01:00
cdf6c1df04 Fix conversion to roman numbers
Fixes #779
2019-12-27 18:04:41 +01:00
cdee5b3609 Back to snapshots 2019-12-20 05:37:55 +01:00
8e948e857f Release 1.15.1 2019-12-20 05:27:55 +01:00
793df218e5 Bump version to 1.15.1, update dependencies 2019-12-20 05:27:24 +01:00
31428d60e4 Add some hover texts and clickable coords 2019-12-20 05:15:45 +01:00
169328e159 Use ChatComponent API for log messages 2019-12-18 01:33:40 +01:00
60a771224b improve logging of entitiy damaged by projectiles or tnt 2019-12-01 05:14:10 +01:00
3135fe8696 Merge branch 'master' into pretty-chat 2019-11-24 06:19:05 +01:00
59d0794c3d Improve scaffolding logging with fallable blocks
- Log fallable blocks above scaffoldings
- Log scaffolding breaking because blocks below are falling down
2019-11-24 06:16:07 +01:00
8214e7d177 Log falling scaffoldings 2019-11-24 05:46:51 +01:00
e77e95cae0 Merge branch 'master' into pretty-chat 2019-11-17 04:00:59 +01:00
241a7adc48 Wildcard block names in querys
For example "*_slab" to match any slab
2019-11-17 04:00:34 +01:00
27cc59f922 Merge branch 'master' into pretty-chat 2019-11-17 03:19:33 +01:00
8192aa4fb8 Allow block tags in querys
"block #signs" and similar: https://minecraft.gamepedia.com/Tag#List_of_tags
2019-11-17 03:17:32 +01:00
078fe7f423 missing diff for log formatting 2019-10-16 02:37:07 +02:00
76a81c124a Merge branch 'master' into pretty-chat 2019-10-16 02:25:43 +02:00
d98d46d0c9 Fix logging when waterlogging waterlogged blocks 2019-10-16 00:52:29 +02:00
1e1dce99c0 Merge remote-tracking branch 'remotes/origin/master' into pretty-chat 2019-09-30 06:15:29 +02:00
921df872d1 Expose useSSL flag to config 2019-09-30 06:11:26 +02:00
cd38ac9866 Remove unused methods 2019-08-27 22:30:37 +02:00
a7967e9b1e Do not require WorldEdit for CuboidRegion 2019-08-27 20:10:12 +02:00
af895aa21d Merge branch 'master' into pretty-chat 2019-08-11 06:09:24 +02:00
2f92fd3426 Fix beacon logging, they are not a container 2019-08-11 04:56:13 +02:00
f298a5f70f Merge branch 'master' into pretty-chat 2019-08-10 04:34:22 +02:00
091bdca142 Merge pull request #763 from paul-maxime/public-chestaccess
Add public to the ChestAccess variables
2019-08-10 04:25:48 +02:00
07bf9421dd Switch to openjdk8 in travis builds 2019-08-10 04:24:03 +02:00
06f24bf632 Add public to the ChestAccess variables 2019-08-10 00:24:27 +02:00
3f7ace7f70 Merge branch 'master' into pretty-chat 2019-07-09 03:46:09 +02:00
6f4ce7e6d0 Fix lectern logging
Fixes #761
2019-07-09 03:42:33 +02:00
d03bbe68ba Add missing newline 2019-06-24 04:14:52 +02:00
e9d78bffb1 Change action type for some entries 2019-06-23 21:59:07 +02:00
d829005c7e Apply new message formatting to entity changes 2019-06-23 21:57:21 +02:00
a6e4d79e0c Merge remote-tracking branch 'remotes/DarkArk/feature/pretty-chat' into pretty-chat 2019-06-23 21:47:14 +02:00
e8aaadf37b Code style 2019-06-23 17:14:17 +02:00
1525d7682f Reformat 2019-06-23 16:45:14 +02:00
76f7f8701d Improve logging of 1.14 blocks 2019-06-23 05:23:31 +02:00
9b5e0c9025 WorldEdit logging should only log modified blocks
Fixes #757
2019-06-23 05:22:18 +02:00
e6b0108bc5 Chunk.load is just as World.loadChunk 2019-06-20 06:08:59 +02:00
3efd92d9df loadChunk will keep chunks loaded forever, so do not use that 2019-06-20 05:37:55 +02:00
05d7652bcc Improve log messages when the block/entity type is unknown 2019-05-31 18:00:55 +02:00
72fc78b3c0 Add missing break 2019-05-28 07:30:56 +02:00
9eef03aa90 Fix lectern logging 2019-05-28 06:07:57 +02:00
da692ed01a Log lectern book change 2019-05-28 05:25:25 +02:00
424ef3b02b Only log sign base color changes when we log SIGNTEXT for this world 2019-05-27 23:53:50 +02:00
4fda020dfc Log sign base color change 2019-05-27 22:46:32 +02:00
8f429afbeb Made LogBlock's messages far more visual appealing 2019-05-20 23:29:29 -04:00
f6522b73f4 create chat table without fulltext index if the database does not support it. 2019-05-16 01:35:46 +02:00
ac233a3920 Spigot 1.14.1 2019-05-14 17:59:30 +02:00
7a946fc23d Merge branch 'master' into 1.14pre 2019-05-14 17:56:39 +02:00
1602fdb03a Configuration option to require SSL on the database connection 2019-05-14 17:54:33 +02:00
33c18a9e62 Improve compatibility when running 1.13 and 1.14 servers on the same logblock database
Old materials are no longer renamed, instead new materials are added and the old entries are modified
2019-05-05 05:55:47 +02:00
e66b5a8f9d Merge branch 'master' into 1.14pre 2019-05-04 17:50:48 +02:00
bd23c93071 Allow logblock to load when it encounters unknown material/entity types 2019-05-04 17:49:29 +02:00
503541ad4e Fix version parsing 2019-05-02 21:51:16 +02:00
82d61d5ee7 Spigot 1.14 instead of 1.14-pre5 2019-04-27 04:53:34 +02:00
254c856b2f Update to 1.14pre5 2019-04-24 17:49:52 +02:00
d4b127244e Disable tool on drop: Only cancel dropping if there is space for that item 2019-03-21 01:44:33 +01:00
87c09766d3 Allow tool use when the interact event is cancelled 2019-03-06 04:23:19 +01:00
8b1ee254b4 Fix NPE when entities die without a damager
Fixes #745
2019-02-23 18:17:14 +01:00
34eeb52c8d Use EnumSet instead of boolean array 2019-02-22 04:13:36 +01:00
399cbc901f Null check 2019-02-18 21:50:01 +01:00
7dce1776e7 Log pumpkin carving
Fixes #743
2019-02-18 21:15:42 +01:00
92de737e4e Allow null weapons for kills
Fixes #742
2019-02-14 21:24:09 +01:00
025950a8c4 Fix armor stand logging on kill in survival mode 2019-01-14 03:29:07 +01:00
dc62524d0d Fix logging fluids flowing down 2019-01-09 03:58:12 +01:00
947163477b Do not go over the maximum level for fluids 2019-01-08 16:38:26 +01:00
2e81e2be9d Catch failed ItemStack serialization
This will still not serialize the problematic ItemStacks, at that is not in the scope of logblock, but it will avoid problems with logging is this problem occurs.
Fixes #735
2019-01-05 17:16:18 +01:00
58223b7612 Fix startup exception when running without WorldEdit
Fixes #736
2019-01-04 18:10:11 +01:00
51f84251dc Adapt to latest WorldEdit changes 2019-01-04 18:09:12 +01:00
31ef2d942b Return idle connection from Consumer to avoid timeout
Fixes #732
2018-12-11 05:11:13 +01:00
9f8fd3e1ca Use constants for magic values 2018-12-08 18:04:36 +01:00
71527530f2 Reduce converter max batch size
Some popular hosters have quite low memory limits.. Fixes #730
2018-11-27 06:15:36 +01:00
d5ee15ebba Improve stability for material table updates 2018-11-27 06:05:00 +01:00
ab464e1dd5 Use long instead of int to avoid overflow in converter 2018-11-26 06:20:02 +01:00
d847908d3c Skip duplicates for chest/sign conversion too 2018-11-24 03:39:05 +01:00
7e47a0c375 Skip duplicate rows instead of canceling the conversion 2018-11-24 03:34:09 +01:00
ab7c8ffbb2 Fix key limit issues with some mysql versions
Fixes #728
2018-11-13 23:51:35 +01:00
0e8bb2599c WorldEdit changed the bstats artifact group again 2018-11-12 04:30:50 +01:00
7f3bb300ef Version 1.13.2 2018-11-10 04:26:27 +01:00
83157ad64f clearlog for entities 2018-11-10 04:22:52 +01:00
cd798e86de Summarization for entities 2018-11-10 04:00:10 +01:00
c3b0fda017 Improve worldeditor row ordering 2018-11-09 21:05:08 +01:00
27c72b43c1 Error message if entity logging is enabled, but no compatible WorldEdit is found 2018-11-09 17:31:39 +01:00
8a1897e102 Add entities to params command 2018-11-09 06:29:26 +01:00
9ea7a47b4e Animals should not contain ambient entities 2018-11-09 06:27:21 +01:00
76aeb5f602 Filter for entity types 2018-11-09 06:24:36 +01:00
1eace44cce Better messages 2018-11-09 05:04:37 +01:00
a5ffa3b709 Log & rollback armor stand/item frame modifications 2018-11-09 05:00:32 +01:00
4e1a79ca0f Configurable entity logging 2018-11-09 04:04:31 +01:00
ea16656fcb Log armor stands, item frames and paintings 2018-11-09 02:37:48 +01:00
9d1baee2e2 Improve entity logging/rollbacks
- Allow UUID changes on respawn
- Log Spawn
- Implement rollback and redo for entities
2018-11-09 00:41:05 +01:00
3a2c1d8d6f Rollback of killed entities works! 2018-11-08 19:13:13 +01:00
868c56ef6a Allow other edits than block edits 2018-11-08 15:54:59 +01:00
f9d246dd63 Implement basic entity change lookup 2018-11-08 05:44:21 +01:00
707e0a1eed Incomplete entity logging 2018-11-08 05:19:59 +01:00
5ad0f06d16 Don't use MyISAM tables 2018-11-08 02:44:04 +01:00
4ce7ad0f7d Tool lookups no longer require WorldEdit for double chests 2018-11-07 20:56:58 +01:00
4af5b3f1ea There can only one of selection/location be set 2018-11-07 20:55:44 +01:00
8c6ee4cf0c Improve CuboidRegion not to use WorldEdit vectors when not needed 2018-11-07 20:45:57 +01:00
737afcd1fa Simplify we adapter 2018-11-07 02:55:29 +01:00
f2dc3daad0 Adapt to latest WorldEdit changes + Compile against Spigot 1.13.2 2018-11-06 01:56:25 +01:00
35e62e03e9 order results by id only (date is not needed there) 2018-11-02 23:57:12 +01:00
40531988b0 optimize log lookup 2018-11-02 03:33:38 +01:00
fde6927aeb Improve recovering from broken connections 2018-10-21 04:21:59 +02:00
3e836c2f50 Remove LogBlockQuestioner soft dependency, as it is no longer a separate plugin 2018-10-19 16:40:17 +02:00
396b79ab68 Remove unused import 2018-10-19 15:50:04 +02:00
be5ee9f792 Fix NPE in FluidFlowLogging
Fixes #724
2018-10-17 14:59:35 +02:00
497e844486 Split long chat lines (for example from command blocks) 2018-10-15 03:05:58 +02:00
90dbc8d8df Improve water flow logging 2018-10-15 02:49:46 +02:00
91a08e7ea0 Fix worldedit dependency (one more exclusion..) 2018-10-12 22:35:23 +02:00
eec8c91a42 Fix worldedit dependency (again..) 2018-10-12 22:31:24 +02:00
a4368ea77f Fix sign logging 2018-10-12 22:30:34 +02:00
7a8551d94f Get the session by entity, not by name 2018-09-22 14:21:25 +02:00
96c9b694b8 Fix: nocache parameter showed entries from the previous lookup 2018-09-22 14:20:13 +02:00
b1e0f91bd7 exclude bStats from WorldEdit dependency as it breaks the build 2018-09-21 22:36:34 +02:00
91315b10c8 New param "nocache" to prevent replacing the lookup cache (useful for tools) 2018-09-21 22:22:52 +02:00
210d6cec37 Log turtle egg trampling 2018-09-21 00:42:28 +02:00
eb99b6d278 Add players: Try a less odd query if the first one failed 2018-09-15 04:05:17 +02:00
a55cbabbdd Add some debug output when adding players fails 2018-09-15 03:36:52 +02:00
421f16784f Don't try to process row if actors could not be inserted 2018-09-13 02:29:21 +02:00
faaab7c4f6 Limit UUIDs to 36 chars + prevent null UUIDs 2018-09-13 02:25:34 +02:00
0bdeb7dc86 Escape UUID in database querys 2018-09-13 02:17:19 +02:00
2bb9e09959 Cleanup Actor.equals() and .hashCode() + some formatting 2018-09-13 02:04:33 +02:00
d214b646db cleanup: getBlockSet/Replaced in class BlockChange 2018-09-12 21:18:43 +02:00
3ef6e48ca4 remove mcstats metrics
its dead for a long time now
2018-09-05 16:21:34 +02:00
1b316dc11f add alternative mvn repo for worldedit 2018-09-05 13:28:33 +02:00
3c64376dd1 improve log importer
- try importing line by line if the batch import did not work
- print more informative error messages
- fix writing failed imports to failed.txt
2018-09-05 12:59:06 +02:00
c390504b70 Sort files before importing them 2018-09-05 03:05:41 +02:00
df8c8bcdda Fix clearing containers on rollback 2018-09-02 04:58:18 +02:00
544385f2ad Log replaced fluids at the correct position 2018-08-29 22:54:35 +02:00
9c2a93dafb Param for force replace not matching blocks 2018-08-29 22:26:37 +02:00
404c9b91c0 Fix bucket empty with waterlogged blocks 2018-08-29 22:05:39 +02:00
1cbc192b31 Log BlockFormEvent by fluids 2018-08-29 19:38:19 +02:00
76fce15305 Log watering waterlogged 2018-08-29 18:33:31 +02:00
8045ab1ecd Ignore deprecation in updater + remove unused method 2018-08-29 03:56:34 +02:00
2faa9cbd6d There is no older WorldEdit for Minecraft 1.13
Resolves #714
2018-08-29 02:43:07 +02:00
f65509408e Log fish buckets as water place, not lava place
Fixes #715
2018-08-29 02:41:10 +02:00
2afa3c88cd Update to Spigot 1.13.1 2018-08-27 17:13:44 +02:00
282090459f Optionally log fluid flow as the player who created the fluid 2018-08-21 06:50:09 +02:00
681c4a2033 Reduce amount of string allocations 2018-08-21 06:29:44 +02:00
56404533db Improved fire logging and rollback 2018-08-21 06:17:24 +02:00
710de5b35a Fix time restriction checking 2018-08-20 18:25:40 +02:00
eb0b969477 add jenkins ci to the readme 2018-08-16 04:18:13 +02:00
c997fb9db9 improve clearlog output when no rows are found 2018-08-13 16:12:03 +02:00
572df51d28 Change the api from using byte[] to YamlConfiguration
byte[] is only used for database rows
2018-08-09 05:00:11 +02:00
ce1a1c3bd2 Fix fire spread logging 2018-08-05 05:45:05 +02:00
51b72bafa6 Update slf4j-jdk14 dependency 2018-08-04 15:42:55 +02:00
dc4d7e0319 We might need the player in clearlog 2018-08-04 15:19:30 +02:00
466775631b Improve clearlog
- Allow clearing of chat & kills table, Resolves #662
- Improve performance by using muti table delete, Fixes #179
- Do not use INTO OUTFILE for dumping logs
2018-08-04 15:09:57 +02:00
83506e6cd9 Cleanup query generation 2018-08-04 05:36:06 +02:00
89d9da02e3 Do not enforce row limit for auto clear logs 2018-08-03 23:04:35 +02:00
c623525847 More secure writelogfile command 2018-08-03 18:29:22 +02:00
cfea33e1fd Fix clearlog table 2018-08-03 18:28:33 +02:00
8b71a8a62b Tool config option dropToDisable
If set to true, the tool will be disabled (and if removeOnDisable is true removed)
when you try to drop it.

Adds #548
2018-08-03 16:59:44 +02:00
e6310e6174 Config option if a tool should be removed on disable 2018-08-03 16:04:07 +02:00
8d11ea3f53 Only remove tools on disable if the player could get them 2018-08-03 15:59:33 +02:00
d04501baf4 Load the config after worlds are loaded
Fixes #267
2018-08-03 15:36:39 +02:00
47f7ddec01 Limit query results
lookup.limesLimit is now correctly used as default limit for lookups
The new lookup.hardLimesLimit is the max limit that is enforced in all
lookups + rollbacks (default 100000)

Fixes #636, Fixes #128
2018-08-03 14:57:08 +02:00
cbf0011cd3 Print a warning and skip invalid data in serialized blockstates 2018-08-03 14:52:39 +02:00
66070e2b38 Do not spam exceptions when the database connection pool is closed
When some database access is still running asynchronously, it will
create an SQLException when the pool is closed (on shutdown). Just
silently ignore them in this case.
2018-08-03 14:20:55 +02:00
6168ef1713 Have an extra map for uncommited playerIds
They will only added to the main list after a successful
commit and cleared otherwise
2018-08-03 14:09:09 +02:00
d0f64070ae Update the javadocs for the Consumer 2018-08-03 03:14:16 +02:00
2c06c90c6d Set a name for the consumer thread 2018-08-03 03:08:23 +02:00
60a049ed49 Rewrite the Consumer to be more stable and much faster
- Use prepared statements and batching for all inserts
- There is now one continuously running Consumer thread
- Use proper synchronization
Fixes #712, Fixes #710

Its a large rewrite so there could still be bugs..
2018-08-03 01:53:28 +02:00
a37dd9cff1 Material mappings don't use the consumer any more 2018-08-03 01:42:15 +02:00
1878b94781 Use batch import for dumped files, its much faster 2018-08-03 01:35:33 +02:00
a853230c8d I don't like tags 2018-08-03 01:04:38 +02:00
3d1f57cc79 ServerPrepStmts ftw 2018-08-03 01:04:02 +02:00
f85dbdbbdf Logging both halfes of a door on interact was a bad idea 2018-08-03 01:03:36 +02:00
e98910615f Not all bisected blocks are double blocks 2018-08-03 01:03:07 +02:00
39a0fbeafa Set default time for tools in the config 2018-08-03 01:02:18 +02:00
ecae2abaf0 Tags are still evil 2018-08-03 01:01:54 +02:00
cc939ab413 Spaces instead of tabs in the pom.xml 2018-08-02 05:28:09 +02:00
680db124a9 Build on java 8 2018-08-02 05:14:26 +02:00
d8e53173e0 Set default time limit also for lookups
Fixes #309
2018-08-02 03:43:18 +02:00
33d97ed971 Improved + rollbackable interact logging
Rollback door openings, comparator, repeater and noteblock modifications and similar things
2018-08-02 02:47:20 +02:00
3dbb7a6c43 Improve chest rollbacks
Fixes #533
2018-08-02 01:18:43 +02:00
0eb2b0dbc8 Force CAVE_AIR and VOID_AIR to AIR + Cleanup rollbacks 2018-08-02 00:50:03 +02:00
37ce2303dc Improve sign logging
Fixes #407
2018-08-01 20:28:21 +02:00
95b7be57fc Dragon Egg teleport logging 2018-08-01 20:02:15 +02:00
92b1a2f394 Improve logging of fallable blocks
Also in this commit: Bukkit's Tags are just broken
2018-08-01 20:01:51 +02:00
5a7ba77095 Make the materials mappings safe to be used by multiple servers at the same time 2018-08-01 16:53:34 +02:00
7a5e46b65f Increase Updater batch size 2018-08-01 03:38:19 +02:00
8084b3e4c0 Respect LAVAFLOW config setting in all cases
Fixes #648
2018-08-01 02:53:49 +02:00
dd58019be1 Clone QueryParams before using them async
Fixes #696, Fixes #684
2018-08-01 02:47:27 +02:00
c30aba4f90 Validate config tool params on execution
Fixes #642 #686
2018-08-01 02:36:06 +02:00
6d13c6436c BEDEXPLOSION logging
Fixes #693
2018-08-01 01:46:50 +02:00
4baa989e60 Player name auto-completion does more harm than good, so don't do that
Fixes #388
2018-08-01 00:38:49 +02:00
1b0a575945 Remove worldedit-core from the pom
It is implicitly added by worldedit-bukkit
2018-08-01 00:32:33 +02:00
cd2dbc0813 Integrate Questioner into Logblock
You do not need the extra plugin any more. If you want no questions asked, just disable it in the config.
2018-08-01 00:28:13 +02:00
fd450aee80 Check if world is logged in LogBlock.getCount 2018-07-31 23:17:26 +02:00
e5097a1577 Compare all aspects if ItemStacks, not only Material 2018-07-31 22:00:17 +02:00
c541847225 Lets keywords have a min amount of arguments
So querys like */lb world world* (Find logs in the world named "world") or */lb player destroyed* (Find logs for a player named "destroyed") are possible
2018-07-31 21:00:50 +02:00
2fe886205b Calculate online time locally
Fixes #645
2018-07-31 18:58:48 +02:00
28aabd09c7 Rename RegionContainer to CuboidRegion 2018-07-31 18:00:28 +02:00
b80777a9a0 Improve WorldEdit encapsulation 2018-07-31 17:58:31 +02:00
c73b29e43b Fix cloning of QueryParams and using them async 2018-07-31 17:40:14 +02:00
354c9078b1 Case insensitive check for ignored chat commands
Fixes #651
2018-07-31 16:16:02 +02:00
759cd230a1 sanity checks for some command params
rollback/redo of chat or kills is not possible
2018-07-31 15:54:21 +02:00
19762691d0 Fix chest access logging (incorrect field name) 2018-07-31 15:35:11 +02:00
1e8779cb9a Add null check for inventory logging
Maybe fix #681
2018-07-31 15:18:06 +02:00
a1d622ebbd Use the plugin logger instead of the global logger 2018-07-31 14:16:34 +02:00
76500f2e51 Always check collation when upgrading tables 2018-07-31 14:08:50 +02:00
d403af42d9 Cancel early when trying to lookup chat while chat is not logged
Fixes #683
2018-07-31 13:33:33 +02:00
f83fc7cee2 Typo 2018-07-31 13:23:45 +02:00
6680e9e3eb Allow querying for item types in chestaccess 2018-07-31 03:01:26 +02:00
58e5cdf890 Serialize BlockStates
Banner, spawner and player heads can now be rolled back
2018-07-31 02:19:40 +02:00
fca9c90032 Improve version parsing for update check 2018-07-30 16:48:09 +02:00
f6e440c1df cleanup (de)serializing of ItemStacks 2018-07-30 16:41:55 +02:00
afb81f850d The uncolored skulker box is also a block 2018-07-30 16:41:12 +02:00
166548da18 Upgrade config, chestaccess and kills too 2018-07-29 15:37:28 +02:00
dc5c8a82c3 Log stack traces for some more exceptions 2018-07-29 15:37:28 +02:00
9f1fc3fe7b Do not check for invalid materials in the config before upgrading them 2018-07-29 15:37:28 +02:00
0bdbce59b8 Legacy materials are not iterated, so don't have to check for that 2018-07-29 15:37:28 +02:00
88faff8230 Formating 2018-07-29 15:37:28 +02:00
71a950a48b Add the new airs to the config 2018-07-29 15:37:28 +02:00
f41a2f0b45 Update repositorys/Remove dead repos 2018-07-29 15:37:28 +02:00
0a7ea8e747 Treat CAVE_AIR and VOID_AIR the same as AIR 2018-07-29 15:37:28 +02:00
905c25a943 Set the maximum size for the connection pool 2018-07-29 15:37:28 +02:00
6e2325d346 The weapon might be null 2018-07-29 15:37:28 +02:00
5cb9dfda50 Update dependencies & maven plugins, add relocations 2018-07-29 15:37:28 +02:00
bdf18fac1e Fix MaterialConverter query 2018-07-29 15:37:28 +02:00
8f18af2252 Convert old blockchange logs 2018-07-29 15:37:28 +02:00
d1d16a6246 Update from 1.13-pre7 to 1.13 2018-07-29 15:37:28 +02:00
84270c59c6 Update default config 2018-07-29 15:37:28 +02:00
ea87835595 Update to WorldEdit for 1.13 2018-07-29 15:37:28 +02:00
6d35da12e3 Access MaterialConverter only from main thread, avoid sync 2018-07-29 15:37:28 +02:00
0792fbd32a Not every material has a BlockData 2018-07-29 15:37:28 +02:00
51505cf34a Better rollback of doors 2018-07-29 15:37:28 +02:00
ce30d33824 Update to 1.13 / Logging/Rollbacks work but largely untested
Still missing: Import data from 1.12 and older
2018-07-29 15:37:28 +02:00
1ce92e1d3f Updated HikariCP to latest version (#707) 2017-12-12 10:01:32 +11:00
0ed8728d01 #703: Chest rollback issues on 1.12.1 2017-08-18 17:57:09 +10:00
40c1cc1c22 #702: MySQL error in upgrader 2017-08-16 20:52:28 +10:00
a51651c306 repo.md-5.net -> https 2017-08-13 19:41:34 +10:00
72851ca52f #700: Change chat columns to 256 2017-08-11 18:31:55 +10:00
defcfeeb07 #698: Misc explosion logging 2017-07-08 18:04:45 +10:00
ba8c1281e3 Change materials.yml location to relative (#697) 2017-06-30 09:01:30 +10:00
80de6eb383 Update for 1.12 (#694) 2017-06-10 08:27:28 +10:00
207c66809f Add separate logging entry for ender crystal explosions and default all posible explosions to true (#669) 2017-02-10 08:19:26 +11:00
ba0e83e437 Update for 1.11 and added shulker boxes (#687) 2016-12-01 09:37:50 +11:00
4195995ca9 Begin 1.10.1 development cycle 2016-05-06 17:12:39 +10:00
5a1c7ec422 1.10.0 release 2016-05-06 17:11:51 +10:00
0e3a04766a We no longer build on java 6 2016-03-01 19:52:45 +00:00
9f319dfb7f Merge pull request #658 from parryjacob/master
Update to use Spigot 1.9
2016-03-02 06:44:50 +11:00
1f1483efab Update to use Spigot 1.9 2016-03-01 03:33:27 -05:00
6faa37928a Update HikariCP, shift over to Java7 2015-09-22 02:33:46 -04:00
080450a278 Include lb-players table in UTF8(mb4) checks
Mojang enforce a very restricted set of characters for names, however offline servers can be sent a much wider set of characters.
Also, fix not using UTF8mb4 on table creation if possible.

Fixes #630
2015-08-03 20:28:44 +01:00
a2c162489a Move to proper three-part versioning (ie 1.9.4 instead of 1.94) and set the dev version to 1.10.0
If it detects a version number of the form 1.xy in the config, it will change that to 1.x.y, so we should make sure we always use e.g. 1.10.0 for version 1.10

Fixes #629
2015-07-30 22:27:06 +01:00
232ddc6323 Added changelog file
If this is reasonably up to date we should be able to copy and paste it directly into DBO and github release pages
2015-07-28 15:59:37 +01:00
f4f4369861 Regularised MySQL String escaping and made name inserts use it 2015-07-27 20:01:15 +01:00
eb31555aa6 Update version for next dev series 2015-07-25 20:25:44 +01:00
93 changed files with 13480 additions and 5351 deletions

View File

@ -1,6 +1,5 @@
language: java
jdk:
- oraclejdk7
- openjdk6
- openjdk8
notifications:
email: false
email: false

103
CHANGELOG.md Normal file
View File

@ -0,0 +1,103 @@
# 1.94
This supports all versions of Bukkit from 1.7.2-R0.3 onwards, including 1.8
## Main changes
* Supports UUIDs - see [this wiki page](https://github.com/LogBlock/LogBlock/wiki/Converting-a-pre-existing-database-to-use-UUIDs) for a short description of the limitations of the migration system
* Supports WorldEdit version 6 and up - unfortunately this means dropping support for previous versions of WorldEdit
* Database insertion optimisation which in tests logged actions six times faster using a local database, and over **250** times faster when the database was on a different server than LogBlock
## Fixes and improvements
* Logs itemdata with values over 256 (logs potion types and item durability)
* Better support for inventory types such as trapped chests
* Fix error when trying to query a double chest without WorldEdit installed
* Correctly log fireballs
* Log players extinguishing fire
* Support utf8mb4 text logging (fixes error when using some obscure characters)
* Overhauled parameter parsing
* Added "/lb hide on" and "/lb hide off" to complement the existing "/lb hide" toggle
* Don't log chat from hidden players
* Fixed error when trying to query illegal triple chests
* Fixed error when a projectile that kills an entity had no source
# 1.80
* Wooden button logging
* Better fireball logging
* Better location querying
* Smarter smart logging
* Add TNT Carts
* Ability to disable sanity ID
* Allow more log rows
* Use UTF-8 for logging
* Comparator, tripwire, pressure plate, crop trample logging added
* Door log fix
* Better messages
* Block spreading
* Fix timezone support
* Death logging fix
* Chest decay
* Various SQL improvements (see source)
# v1.70
* More prepared statements
* Increase to accuracy (Thanks DarkArc)
* WorldEdit logging
* Case insensitive player ignoring
* Consumer warnings + more aggressive defaults (Thanks DarkArc)
# v1.61
* Use more prepared statements
* Fix timestamps on some entries
* Report progress for large rollbacks
# v1.60
* Falling blocks (sand/gravel) are now logged properly in the location they land
* The format of the date displayed in lookups is now customizable under "lookup.dateFormat" in config.yml, please refer to http://docs.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html for the display options
* Parameters can now be escaped with double quotes, allowing for spaces in parameters e.g (world "world with spaces")
* Blocks placed now have their data logged properly. Blocks with extra data such as stairs will now be resetted properly during redos
* Fixed bug whereby lava and water logging options in world config weren't considered
* Fixed duplicate "[LogBlock]" tags in message logging
* Fixed y axis not being considered in the radius specified by area
* Fixed possible crash on /lb tp
# v1.59
* Fix * permission
# v1.58
* Require WorldEdit once again.
* 1.57 table updates are optional
# v1.57
* PermissionDefault to OP
* Increase default table sizes
# v1.56
* Invert chest logging
* Remove worldedit auto download
# v1.55
* Transition to maven
* Use async chat event
* Add metrics
* Small performance increases
# v1.54
* Fix derpy fake inventory plugins throwing npes.
# v1.53
* Make use of the new chest logging system
* Remove the use of old permissions systems
# v1.52
* Properly log block replacement.
* Added option to disallow tool dropping to prevent item duplication.
* Removed double logblock.tp permission.
* Fixed config updater error.
* Check if EntityChangeBlockEvent is triggered by Enderman.
* Fixed onlinetime logging for players.
* Fixed npe in /lb me.

Binary file not shown.

View File

@ -4,4 +4,4 @@ LogBlock
This plugin logs block changes such as breaking, placing, modifying, or burning to a MySQL Database. It can be used as an anti-griefing tool to find out who made a particular edit, or even roll back changes by certain players.
Originally written by bootswithdefer, for hMod, ported to Bukkit by me, because of the inability to identfy griefers. BigBrother also did't work, so I was forced to do it myself. The honor belongs to bootswithdefer for the sourcecode, I only spent about 8 hours to transcribe. All functions except sign text logging shold work as in hMod. The use of permissions plugin is possible, but not necessary.
Questioner: http://git.io/u2MxKQ
You can download development builds [from our Jenkins server](https://www.iani.de/jenkins/job/LogBlock/).

67
pom.xml
View File

@ -1,10 +1,10 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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>de.diddiz</groupId>
<artifactId>logblock</artifactId>
<version>1.94</version>
<version>1.16.5.2-SNAPSHOT</version>
<packaging>jar</packaging>
<name>LogBlock</name>
@ -30,32 +30,25 @@
<distributionManagement>
<repository>
<id>md_5-releases</id>
<url>http://repo.md-5.net/content/repositories/releases/</url>
<url>https://repo.md-5.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>md_5-snapshots</id>
<url>http://repo.md-5.net/content/repositories/snapshots/</url>
<url>https://repo.md-5.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.7.2-R0.3</version>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.16.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>questioner</artifactId>
<version>${project.version}</version>
<scope>system</scope>
<systemPath>${project.basedir}/LogBlockQuestioner.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sk89q</groupId>
<artifactId>worldedit</artifactId>
<version>6.0.0-SNAPSHOT</version>
<groupId>com.sk89q.worldedit</groupId>
<artifactId>worldedit-bukkit</artifactId>
<version>7.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -66,29 +59,29 @@
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java6</artifactId>
<version>2.3.8</version>
<artifactId>HikariCP</artifactId>
<version>3.4.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.10</version>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>repobo-snap</id>
<url>http://repo.bukkit.org/content/groups/public</url>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>sk89q-repo</id>
<url>http://maven.sk89q.com/repo/</url>
<url>https://maven.sk89q.com/repo/</url>
</repository>
<repository>
<id>kitteh-repo</id>
<url>http://repo.kitteh.org/content/groups/public</url>
<id>brokkonaut-repo</id>
<url>https://www.iani.de/nexus/content/groups/public/</url>
</repository>
</repositories>
<profiles>
@ -123,23 +116,23 @@
<resources>
<resource>
<filtering>true</filtering>
<directory>${project.basedir}/src/main/resources</directory>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<version>3.7.0</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<version>3.0.0</version>
<executions>
<execution>
<id>regex-property</id>
@ -159,7 +152,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<version>3.1.1</version>
<configuration>
</configuration>
<executions>
@ -168,6 +161,18 @@
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>de.diddiz.lib.com.zaxxer.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>de.diddiz.lib.org.slf4j</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>

View File

@ -3,55 +3,71 @@ package de.diddiz.LogBlock;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.projectiles.BlockProjectileSource;
import org.bukkit.projectiles.ProjectileSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import static de.diddiz.util.BukkitUtils.entityName;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Block;
public class Actor {
@Override
public int hashCode() {
int hash = 5;
hash = 79 * hash + (this.UUID != null ? this.UUID.hashCode() : 0);
return hash;
return this.UUID != null ? this.UUID.hashCode() : 0;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final Actor other = (Actor) obj;
return ((this.UUID == null && other.UUID == null) || this.UUID.equals(other.UUID));
return (this.UUID == null) ? (other.UUID == null) : this.UUID.equals(other.UUID);
}
final String name;
final String UUID;
final Location blockLocation;
public Actor(String name, String UUID) {
this.name = name;
this.UUID = UUID;
this.UUID = UUID == null ? "unknown" : (UUID.length() > 36 ? UUID.substring(0, 36) : UUID);
this.blockLocation = null;
}
public Actor(String name, String UUID, Block block) {
this.name = name;
this.UUID = UUID == null ? "unknown" : (UUID.length() > 36 ? UUID.substring(0, 36) : UUID);
this.blockLocation = block == null ? null : block.getLocation();
}
public Actor(String name, java.util.UUID UUID) {
this.name = name;
this.UUID = UUID.toString();
this.blockLocation = null;
}
public Actor(String name, java.util.UUID UUID, Block block) {
this.name = name;
this.UUID = UUID.toString();
this.blockLocation = block == null ? null : block.getLocation();
}
public Actor(String name) {
this(name, generateUUID(name));
}
public Actor(String name, Block block) {
this(name, generateUUID(name), block);
}
public Actor(ResultSet rs) throws SQLException {
this(rs.getString("playername"), rs.getString("UUID"));
}
@ -64,16 +80,25 @@ public class Actor {
return UUID;
}
public Location getBlockLocation() {
return blockLocation;
}
public static Actor actorFromEntity(Entity entity) {
if (entity instanceof Player) {
return new Actor(entityName(entity), entity.getUniqueId());
} else {
return new Actor(entityName(entity));
}
if (entity instanceof Projectile) {
ProjectileSource shooter = ((Projectile) entity).getShooter();
if (shooter != null) {
return actorFromProjectileSource(shooter);
}
}
return new Actor(entityName(entity));
}
public static Actor actorFromEntity(EntityType entity) {
return new Actor(entity.getName());
return new Actor(entity.name());
}
public static Actor actorFromProjectileSource(ProjectileSource psource) {
@ -87,29 +112,33 @@ public class Actor {
}
}
/**
* Generate an Actor object from a String name, trying to guess if it's an online player
* and if so, setting the UUID accordingly. This only checks against currently online
* players and is a "best effort" attempt for use with the pre-UUID API
* <p>
* If you know something is an entity (player or otherwise) use the {@link #actorFromEntity(org.bukkit.entity.Entity) }
* or {@link #actorFromEntity(org.bukkit.entity.EntityType) } methods
* <p>
* If you know something is a server effect (like gravity) use {@link #Actor(java.lang.String)}
* @deprecated Only use this if you have a String of unknown origin
*
* @param actorName String of unknown origin
* @return
*/
/**
* Generate an Actor object from a String name, trying to guess if it's an online player
* and if so, setting the UUID accordingly. This only checks against currently online
* players and is a "best effort" attempt for use with the pre-UUID API
* <p>
* If you know something is an entity (player or otherwise) use the {@link #actorFromEntity(org.bukkit.entity.Entity) }
* or {@link #actorFromEntity(org.bukkit.entity.EntityType) } methods
* <p>
* If you know something is a server effect (like gravity) use {@link #Actor(java.lang.String)}
*
* @deprecated Only use this if you have a String of unknown origin
*
* @param actorName
* String of unknown origin
* @return
*/
@Deprecated
public static Actor actorFromString(String actorName) {
Player[] players = Bukkit.getServer().getOnlinePlayers();
Collection<? extends Player> players = Bukkit.getServer().getOnlinePlayers();
for (Player p : players) {
if (p.getName().equalsIgnoreCase(actorName)) {
return actorFromEntity(p);
}
}
// No player found online with that name, assuming non-player entity/effect
return new Actor(actorName);
// No player found online with that name, assuming non-player entity/effect
return new Actor(actorName);
}
public static boolean isValidUUID(String uuid) {

View File

@ -19,6 +19,7 @@ public class AutoClearLog implements Runnable {
for (final String paramStr : autoClearLog) {
try {
final QueryParams params = new QueryParams(logblock, getConsoleSender(), Arrays.asList(paramStr.split(" ")));
params.noForcedLimit = true;
handler.new CommandClearLog(getServer().getConsoleSender(), params, false);
} catch (final Exception ex) {
getLogger().log(Level.SEVERE, "Failed to schedule auto ClearLog: ", ex);

View File

@ -1,35 +1,60 @@
package de.diddiz.LogBlock;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Location;
import org.bukkit.Material;
import static de.diddiz.util.ActionColor.CREATE;
import static de.diddiz.util.ActionColor.DESTROY;
import static de.diddiz.util.ActionColor.INTERACT;
import static de.diddiz.util.TypeColor.DEFAULT;
import static de.diddiz.util.MessagingUtil.createTextComponentWithColor;
import static de.diddiz.util.MessagingUtil.prettyDate;
import static de.diddiz.util.MessagingUtil.prettyLocation;
import static de.diddiz.util.MessagingUtil.prettyMaterial;
import static de.diddiz.util.MessagingUtil.prettyState;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.Utils;
import java.sql.ResultSet;
import java.sql.SQLException;
import static de.diddiz.util.LoggingUtil.checkText;
import static de.diddiz.util.MaterialName.materialName;
import java.util.logging.Level;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Note;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Openable;
import org.bukkit.block.data.Powerable;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.block.data.type.Comparator;
import org.bukkit.block.data.type.DaylightDetector;
import org.bukkit.block.data.type.Lectern;
import org.bukkit.block.data.type.NoteBlock;
import org.bukkit.block.data.type.Repeater;
import org.bukkit.block.data.type.Sign;
import org.bukkit.block.data.type.Switch;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.inventory.ItemStack;
public class BlockChange implements LookupCacheElement {
public final long id, date;
public final Location loc;
public final Actor actor;
public final String playerName;
public final int replaced, type;
public final byte data;
public final String signtext;
public final int replacedMaterial, replacedData, typeMaterial, typeData;
public final byte[] replacedState, typeState;
public final ChestAccess ca;
public BlockChange(long date, Location loc, Actor actor, int replaced, int type, byte data, String signtext, ChestAccess ca) {
public BlockChange(long date, Location loc, Actor actor, int replaced, int replacedData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) {
id = 0;
this.date = date;
this.loc = loc;
this.actor = actor;
this.replaced = replaced;
this.type = type;
this.data = data;
this.signtext = checkText(signtext);
this.replacedMaterial = replaced;
this.replacedData = replacedData;
this.replacedState = replacedState;
this.typeMaterial = type;
this.typeData = typeData;
this.typeState = typeState;
this.ca = ca;
this.playerName = actor == null ? null : actor.getName();
}
@ -40,89 +65,189 @@ public class BlockChange implements LookupCacheElement {
loc = p.needCoords ? new Location(p.world, rs.getInt("x"), rs.getInt("y"), rs.getInt("z")) : null;
actor = p.needPlayer ? new Actor(rs) : null;
playerName = p.needPlayer ? rs.getString("playername") : null;
replaced = p.needType ? rs.getInt("replaced") : 0;
type = p.needType ? rs.getInt("type") : 0;
data = p.needData ? rs.getByte("data") : (byte) 0;
signtext = p.needSignText ? rs.getString("signtext") : null;
ca = p.needChestAccess && rs.getShort("itemtype") != 0 && rs.getShort("itemamount") != 0 ? new ChestAccess(rs.getShort("itemtype"), rs.getShort("itemamount"), rs.getShort("itemdata")) : null;
replacedMaterial = p.needType ? rs.getInt("replaced") : 0;
replacedData = p.needType ? rs.getInt("replacedData") : -1;
typeMaterial = p.needType ? rs.getInt("type") : 0;
typeData = p.needType ? rs.getInt("typeData") : -1;
replacedState = p.needType ? rs.getBytes("replacedState") : null;
typeState = p.needType ? rs.getBytes("typeState") : null;
ChestAccess catemp = null;
if (p.needChestAccess) {
ItemStack stack = Utils.loadItemStack(rs.getBytes("item"));
if (stack != null) {
catemp = new ChestAccess(stack, rs.getBoolean("itemremove"), rs.getInt("itemtype"));
}
}
ca = catemp;
}
private String getTypeDetails(BlockData type, byte[] typeState) {
String typeDetails = null;
if (BlockStateCodecs.hasCodec(type.getMaterial())) {
try {
typeDetails = BlockStateCodecs.toString(type.getMaterial(), Utils.deserializeYamlConfiguration(typeState));
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not parse BlockState for " + type.getMaterial(), e);
}
}
if (typeDetails == null) {
return "";
} else {
return " " + typeDetails;
}
}
@Override
public String toString() {
final StringBuilder msg = new StringBuilder();
return BaseComponent.toPlainText(getLogMessage(-1));
}
@Override
public BaseComponent[] getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.append(Config.formatter.format(date)).append(" ");
msg.addExtra(prettyDate(date));
msg.addExtra(" ");
}
if (actor != null) {
msg.append(actor.getName()).append(" ");
msg.addExtra(actor.getName());
msg.addExtra(" ");
}
if (signtext != null) {
final String action = type == 0 ? "destroyed " : "created ";
if (!signtext.contains("\0")) {
msg.append(action).append(signtext);
} else {
msg.append(action).append(materialName(type != 0 ? type : replaced)).append(" [").append(signtext.replace("\0", "] [")).append("]");
}
} else if (type == replaced) {
if (type == 0) {
msg.append("did an unspecified action");
BlockData type = getBlockSet();
BlockData replaced = getBlockReplaced();
if (type == null || replaced == null) {
msg.addExtra("did an unknown block modification");
return new BaseComponent[] { msg };
}
// Process type details once for later use.
String typeDetails = getTypeDetails(type, typeState);
String replacedDetails = getTypeDetails(replaced, replacedState);
if (type.getMaterial().equals(replaced.getMaterial())) {
if (BukkitUtils.isEmpty(type.getMaterial())) {
msg.addExtra(createTextComponentWithColor("did an unspecified action", INTERACT.getColor()));
} else if (ca != null) {
if (ca.itemType == 0 || ca.itemAmount == 0) {
msg.append("looked inside ").append(materialName(type));
} else if (ca.itemAmount < 0) {
msg.append("took ").append(-ca.itemAmount).append("x ").append(materialName(ca.itemType, ca.itemData)).append(" from ").append(materialName(type));
if (ca.itemStack == null) {
msg.addExtra(createTextComponentWithColor("looked inside ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (ca.remove) {
msg.addExtra(createTextComponentWithColor("took ", DESTROY.getColor()));
msg.addExtra(BukkitUtils.toString(ca.itemStack));
msg.addExtra(createTextComponentWithColor(" from ", DESTROY.getColor()));
msg.addExtra(prettyMaterial(type));
} else {
msg.append("put ").append(ca.itemAmount).append("x ").append(materialName(ca.itemType, ca.itemData)).append(" into ").append(materialName(type));
msg.addExtra(createTextComponentWithColor("put ", CREATE.getColor()));
msg.addExtra(BukkitUtils.toString(ca.itemStack));
msg.addExtra(createTextComponentWithColor(" into ", CREATE.getColor()));
msg.addExtra(prettyMaterial(type));
}
} else if (BukkitUtils.getContainerBlocks().contains(Material.getMaterial(type))) {
msg.append("opened ").append(materialName(type));
} else if (type == 64 || type == 71)
// This is a problem that will have to be addressed in LB 2,
// there is no way to tell from the top half of the block if
// the door is opened or closed.
{
msg.append("moved ").append(materialName(type));
} else if (type instanceof Waterlogged && ((Waterlogged) type).isWaterlogged() != ((Waterlogged) replaced).isWaterlogged()) {
if (((Waterlogged) type).isWaterlogged()) {
msg.addExtra(createTextComponentWithColor("waterlogged ", CREATE.getColor()));
msg.addExtra(prettyMaterial(type));
} else {
msg.addExtra(createTextComponentWithColor("dried ", DESTROY.getColor()));
msg.addExtra(prettyMaterial(type));
}
} else if (BukkitUtils.getContainerBlocks().contains(type.getMaterial())) {
msg.addExtra(createTextComponentWithColor("opened ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (type instanceof Openable && ((Openable) type).isOpen() != ((Openable) replaced).isOpen()) {
// Door, Trapdoor, Fence gate
msg.addExtra(createTextComponentWithColor(((Openable) type).isOpen() ? "opened " : "closed ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (type.getMaterial() == Material.LEVER && ((Switch) type).isPowered() != ((Switch) replaced).isPowered()) {
msg.addExtra(createTextComponentWithColor("switched ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(prettyState(((Switch) type).isPowered() ? " on" : " off"));
} else if (type instanceof Switch && ((Switch) type).isPowered() != ((Switch) replaced).isPowered()) {
msg.addExtra(createTextComponentWithColor("pressed ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (type.getMaterial() == Material.CAKE) {
msg.addExtra(createTextComponentWithColor("ate a piece of ", DESTROY.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (type.getMaterial() == Material.NOTE_BLOCK) {
Note note = ((NoteBlock) type).getNote();
msg.addExtra(createTextComponentWithColor("set ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(" to ");
msg.addExtra(prettyState(note.getTone().name() + (note.isSharped() ? "#" : "")));
} else if (type.getMaterial() == Material.REPEATER) {
msg.addExtra(createTextComponentWithColor("set ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(" to ");
msg.addExtra(prettyState(((Repeater) type).getDelay()));
msg.addExtra(createTextComponentWithColor(" ticks delay", DEFAULT.getColor()));
} else if (type.getMaterial() == Material.COMPARATOR) {
msg.addExtra(createTextComponentWithColor("set ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(" to ");
msg.addExtra(prettyState(((Comparator) type).getMode()));
} else if (type.getMaterial() == Material.DAYLIGHT_DETECTOR) {
msg.addExtra(createTextComponentWithColor("set ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(" to ");
msg.addExtra(prettyState(((DaylightDetector) type).isInverted() ? "inverted" : "normal"));
} else if (type instanceof Lectern) {
msg.addExtra(createTextComponentWithColor("changed the book on a ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(" to");
msg.addExtra(prettyState(typeDetails.length() == 0 ? " empty" : typeDetails));
} else if (type instanceof Powerable) {
msg.addExtra(createTextComponentWithColor("stepped on ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (type.getMaterial() == Material.TRIPWIRE) {
msg.addExtra(createTextComponentWithColor("ran into ", INTERACT.getColor()));
msg.addExtra(prettyMaterial(type));
} else if (type instanceof Sign || type instanceof WallSign) {
msg.addExtra(createTextComponentWithColor("edited a ", CREATE.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(createTextComponentWithColor(" to ", CREATE.getColor()));
msg.addExtra(prettyState(typeDetails));
} else {
msg.addExtra(createTextComponentWithColor("replaced ", CREATE.getColor()));
msg.addExtra(prettyMaterial(replaced));
msg.addExtra(prettyState(replacedDetails));
msg.addExtra(createTextComponentWithColor(" with ", CREATE.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(prettyState(typeDetails));
}
// Trapdoor
else if (type == 96) {
msg.append((data < 8 || data > 11) ? "opened" : "closed").append(" ").append(materialName(type));
}
// Fence gate
else if (type == 107) {
msg.append(data > 3 ? "opened" : "closed").append(" ").append(materialName(type));
} else if (type == 69) {
msg.append("switched ").append(materialName(type));
} else if (type == 77 || type == 143) {
msg.append("pressed ").append(materialName(type));
} else if (type == 92) {
msg.append("ate a piece of ").append(materialName(type));
} else if (type == 25 || type == 93 || type == 94 || type == 149 || type == 150) {
msg.append("changed ").append(materialName(type));
} else if (type == 70 || type == 72 || type == 147 || type == 148) {
msg.append("stepped on ").append(materialName(type));
} else if (type == 132) {
msg.append("ran into ").append(materialName(type));
}
} else if (type == 0) {
msg.append("destroyed ").append(materialName(replaced, data));
} else if (replaced == 0) {
msg.append("created ").append(materialName(type, data));
} else if (BukkitUtils.isEmpty(type.getMaterial())) {
msg.addExtra(createTextComponentWithColor("destroyed ", DESTROY.getColor()));
msg.addExtra(prettyMaterial(replaced));
msg.addExtra(prettyState(replacedDetails));
} else if (BukkitUtils.isEmpty(replaced.getMaterial())) {
msg.addExtra(createTextComponentWithColor("created ", CREATE.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(prettyState(typeDetails));
} else {
msg.append("replaced ").append(materialName(replaced, (byte) 0)).append(" with ").append(materialName(type, data));
msg.addExtra(createTextComponentWithColor("replaced ", CREATE.getColor()));
msg.addExtra(prettyMaterial(replaced));
msg.addExtra(prettyState(replacedDetails));
msg.addExtra(createTextComponentWithColor(" with ", CREATE.getColor()));
msg.addExtra(prettyMaterial(type));
msg.addExtra(prettyState(typeDetails));
}
if (loc != null) {
msg.append(" at ").append(loc.getBlockX()).append(":").append(loc.getBlockY()).append(":").append(loc.getBlockZ());
msg.addExtra(" at ");
msg.addExtra(prettyLocation(loc, entry));
}
return msg.toString();
return new BaseComponent[] { msg };
}
public BlockData getBlockReplaced() {
return MaterialConverter.getBlockData(replacedMaterial, replacedData);
}
public BlockData getBlockSet() {
return MaterialConverter.getBlockData(typeMaterial, typeData);
}
@Override
public Location getLocation() {
return loc;
}
@Override
public String getMessage() {
return toString();
}
}

View File

@ -1,11 +1,16 @@
package de.diddiz.LogBlock;
import org.bukkit.Location;
import static de.diddiz.util.LoggingUtil.checkText;
import static de.diddiz.util.MessagingUtil.brackets;
import static de.diddiz.util.MessagingUtil.prettyDate;
import de.diddiz.util.MessagingUtil;
import de.diddiz.util.MessagingUtil.BracketType;
import java.sql.ResultSet;
import java.sql.SQLException;
import static de.diddiz.util.LoggingUtil.checkText;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Location;
public class ChatMessage implements LookupCacheElement {
final long id, date;
@ -34,7 +39,21 @@ public class ChatMessage implements LookupCacheElement {
}
@Override
public String getMessage() {
return (player != null ? "<" + player.getName() + "> " : "") + (message != null ? message : "");
public BaseComponent[] getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
msg.addExtra(" ");
}
if (playerName != null) {
msg.addExtra(brackets(BracketType.ANGLE, MessagingUtil.createTextComponentWithColor(playerName, net.md_5.bungee.api.ChatColor.WHITE)));
msg.addExtra(" ");
}
if (message != null) {
for (BaseComponent messageComponent : TextComponent.fromLegacyText(message)) {
msg.addExtra(messageComponent);
}
}
return new BaseComponent[] { msg };
}
}

View File

@ -1,11 +1,15 @@
package de.diddiz.LogBlock;
public class ChestAccess {
final short itemType, itemAmount, itemData;
import org.bukkit.inventory.ItemStack;
public ChestAccess(short itemType, short itemAmount, short itemData) {
public class ChestAccess {
public final ItemStack itemStack;
public final boolean remove;
public final int itemType;
public ChestAccess(ItemStack itemStack, boolean remove, int itemType) {
this.itemStack = itemStack;
this.remove = remove;
this.itemType = itemType;
this.itemAmount = itemAmount;
this.itemData = itemData >= 0 ? itemData : 0;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,12 @@ import java.io.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.regex.Pattern;
import static de.diddiz.util.Utils.newline;
import static org.bukkit.Bukkit.getLogger;
public class DumpedLogImporter implements Runnable {
private final LogBlock logblock;
@ -20,9 +22,10 @@ public class DumpedLogImporter implements Runnable {
@Override
public void run() {
final File[] imports = new File("plugins/LogBlock/import/").listFiles(new ExtensionFilenameFilter("sql"));
final File[] imports = new File(logblock.getDataFolder(), "import").listFiles(new ExtensionFilenameFilter("sql"));
if (imports != null && imports.length > 0) {
getLogger().info("Found " + imports.length + " imports.");
logblock.getLogger().info("Found " + imports.length + " imports.");
Arrays.sort(imports, new ImportsComparator());
Connection conn = null;
try {
conn = logblock.getConnection();
@ -33,30 +36,65 @@ public class DumpedLogImporter implements Runnable {
final Statement st = conn.createStatement();
final BufferedWriter writer = new BufferedWriter(new FileWriter(new File(logblock.getDataFolder(), "import/failed.txt")));
int successes = 0, errors = 0;
for (final File sqlFile : imports) {
getLogger().info("Trying to import " + sqlFile.getName() + " ...");
final BufferedReader reader = new BufferedReader(new FileReader(sqlFile));
String line;
while ((line = reader.readLine()) != null) {
try {
for (final File sqlFile : imports) {
String line = null;
try {
st.execute(line);
successes++;
} catch (final Exception ex) {
getLogger().warning("Error while importing: '" + line + "': " + ex.getMessage());
writer.write(line + newline);
errors++;
logblock.getLogger().info("Trying to import " + sqlFile.getName() + " ...");
// first try batch import the whole file
final BufferedReader reader = new BufferedReader(new FileReader(sqlFile));
int statements = 0;
while ((line = reader.readLine()) != null) {
if (line.endsWith(";")) {
line = line.substring(0, line.length() - 1);
}
if (!line.isEmpty()) {
statements++;
st.addBatch(line);
}
}
st.executeBatch();
conn.commit();
reader.close();
sqlFile.delete();
successes += statements;
logblock.getLogger().info("Successfully imported " + sqlFile.getName() + ".");
} catch (final Exception ignored) {
// if the batch import did not work, retry line by line
try {
final BufferedReader reader = new BufferedReader(new FileReader(sqlFile));
while ((line = reader.readLine()) != null) {
if (line.endsWith(";")) {
line = line.substring(0, line.length() - 1);
}
if (!line.isEmpty()) {
try {
st.execute(line);
successes++;
} catch (final SQLException ex) {
logblock.getLogger().severe("Error while importing: '" + line + "': " + ex.getMessage());
writer.write(line + newline);
errors++;
}
}
}
conn.commit();
reader.close();
sqlFile.delete();
logblock.getLogger().info("Successfully imported " + sqlFile.getName() + ".");
} catch (final Exception ex) {
logblock.getLogger().severe("Error while importing " + sqlFile.getName() + ": " + ex.getMessage());
errors++;
}
}
}
conn.commit();
reader.close();
sqlFile.delete();
getLogger().info("Successfully imported " + sqlFile.getName() + ".");
} finally {
writer.close();
}
writer.close();
st.close();
getLogger().info("Successfully imported stored queue. (" + successes + " rows imported, " + errors + " errors)");
logblock.getLogger().info("Successfully imported stored queue. (" + successes + " rows imported, " + errors + " errors)");
} catch (final Exception ex) {
getLogger().log(Level.WARNING, "Error while importing: ", ex);
logblock.getLogger().log(Level.WARNING, "Error while importing: ", ex);
} finally {
if (conn != null) {
try {
@ -67,4 +105,44 @@ public class DumpedLogImporter implements Runnable {
}
}
}
private static class ImportsComparator implements Comparator<File> {
private final Pattern splitPattern = Pattern.compile("[\\-\\.]");
@Override
public int compare(File o1, File o2) {
String[] name1 = splitPattern.split(o1.getName());
String[] name2 = splitPattern.split(o2.getName());
if (name1.length > name2.length) {
return 1;
} else if (name1.length < name2.length) {
return -1;
}
for (int i = 0; i < name1.length; i++) {
String part1 = name1[i];
String part2 = name2[i];
if (part1.length() > 0 && part2.length() > 0) {
char first1 = part1.charAt(0);
char first2 = part2.charAt(0);
if (first1 >= '0' && first1 <= '9' && first2 >= '0' && first2 <= '9') {
try {
long long1 = Long.parseLong(part1);
long long2 = Long.parseLong(part2);
if (long1 == long2) {
continue;
}
return long1 > long2 ? 1 : -1;
} catch (NumberFormatException e) {
// fallthrough to string compare
}
}
}
int compareString = part1.compareTo(part2);
if (compareString != 0) {
return compareString;
}
}
return 0;
}
}
}

View File

@ -0,0 +1,138 @@
package de.diddiz.LogBlock;
import static de.diddiz.util.ActionColor.CREATE;
import static de.diddiz.util.ActionColor.DESTROY;
import static de.diddiz.util.ActionColor.INTERACT;
import static de.diddiz.util.MessagingUtil.createTextComponentWithColor;
import static de.diddiz.util.MessagingUtil.prettyDate;
import static de.diddiz.util.MessagingUtil.prettyEntityType;
import static de.diddiz.util.MessagingUtil.prettyLocation;
import static de.diddiz.util.MessagingUtil.prettyMaterial;
import de.diddiz.util.Utils;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Location;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.inventory.ItemStack;
public class EntityChange implements LookupCacheElement {
public static enum EntityChangeType {
CREATE,
KILL,
MODIFY,
ADDEQUIP,
REMOVEEQUIP,
GET_STUNG;
private static EntityChangeType[] values = values();
public static EntityChangeType valueOf(int ordinal) {
return values[ordinal];
}
}
public final long id, date;
public final Location loc;
public final Actor actor;
public final EntityType type;
public final int entityId;
public final UUID entityUUID;
public final EntityChangeType changeType;
public final byte[] data;
public EntityChange(long date, Location loc, Actor actor, EntityType type, UUID entityid, EntityChangeType changeType, byte[] data) {
id = 0;
this.date = date;
this.loc = loc;
this.actor = actor;
this.type = type;
this.entityId = -1;
this.entityUUID = entityid;
this.changeType = changeType;
this.data = data;
}
public EntityChange(ResultSet rs, QueryParams p) throws SQLException {
id = p.needId ? rs.getInt("id") : 0;
date = p.needDate ? rs.getTimestamp("date").getTime() : 0;
loc = p.needCoords ? new Location(p.world, rs.getInt("x"), rs.getInt("y"), rs.getInt("z")) : null;
actor = p.needPlayer ? new Actor(rs) : null;
type = p.needType ? EntityTypeConverter.getEntityType(rs.getInt("entitytypeid")) : null;
entityId = p.needData ? rs.getInt("entityid") : 0;
entityUUID = p.needData ? UUID.fromString(rs.getString("entityuuid")) : null;
changeType = p.needType ? EntityChangeType.valueOf(rs.getInt("action")) : null;
data = p.needData ? rs.getBytes("data") : null;
}
@Override
public String toString() {
return BaseComponent.toPlainText(getLogMessage());
}
@Override
public BaseComponent[] getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
msg.addExtra(" ");
}
if (actor != null) {
msg.addExtra(actor.getName());
msg.addExtra(" ");
}
if (changeType == EntityChangeType.CREATE) {
msg.addExtra(createTextComponentWithColor("created ", CREATE.getColor()));
} else if (changeType == EntityChangeType.KILL) {
boolean living = type != null && LivingEntity.class.isAssignableFrom(type.getEntityClass()) && !ArmorStand.class.isAssignableFrom(type.getDeclaringClass());
msg.addExtra(createTextComponentWithColor(living ? "killed " : "destroyed ", DESTROY.getColor()));
} else if (changeType == EntityChangeType.ADDEQUIP) {
YamlConfiguration conf = Utils.deserializeYamlConfiguration(data);
ItemStack stack = conf == null ? null : conf.getItemStack("item");
if (stack == null) {
msg.addExtra(createTextComponentWithColor("added an item to ", CREATE.getColor()));
} else {
msg.addExtra(createTextComponentWithColor("added ", CREATE.getColor()));
msg.addExtra(prettyMaterial(stack.getType()));
msg.addExtra(" to ");
}
} else if (changeType == EntityChangeType.REMOVEEQUIP) {
YamlConfiguration conf = Utils.deserializeYamlConfiguration(data);
ItemStack stack = conf == null ? null : conf.getItemStack("item");
if (stack == null) {
msg.addExtra(createTextComponentWithColor("removed an item from ", DESTROY.getColor()));
} else {
msg.addExtra(createTextComponentWithColor("removed ", DESTROY.getColor()));
msg.addExtra(prettyMaterial(stack.getType()));
msg.addExtra(" from ");
}
} else if (changeType == EntityChangeType.MODIFY) {
msg.addExtra(createTextComponentWithColor("modified ", INTERACT.getColor()));
} else if (changeType == EntityChangeType.GET_STUNG) {
msg.addExtra(createTextComponentWithColor("got stung by ", DESTROY.getColor()));
} else {
msg.addExtra(createTextComponentWithColor("did an unknown action to ", INTERACT.getColor()));
}
if (type != null) {
msg.addExtra(prettyEntityType(type));
} else {
msg.addExtra(prettyMaterial("an unknown entity"));
}
if (loc != null) {
msg.addExtra(" at ");
msg.addExtra(prettyLocation(loc, entry));
}
return new BaseComponent[] { msg };
}
@Override
public Location getLocation() {
return loc;
}
}

View File

@ -0,0 +1,114 @@
package de.diddiz.LogBlock;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.logging.Level;
import org.bukkit.entity.EntityType;
public class EntityTypeConverter {
private static EntityType[] idToEntityType = new EntityType[10];
private static HashMap<EntityType, Integer> entityTypeToId = new HashMap<>();
private static int nextEntityTypeId;
public synchronized static Integer getExistingEntityTypeId(EntityType entityType) {
return entityType == null ? null : entityTypeToId.get(entityType);
}
public synchronized static int getOrAddEntityTypeId(EntityType entityType) {
Integer key = entityTypeToId.get(entityType);
int tries = 0;
while (key == null && tries < 10) {
tries++;
key = nextEntityTypeId;
Connection conn = LogBlock.getInstance().getConnection();
try {
conn.setAutoCommit(false);
PreparedStatement smt = conn.prepareStatement("INSERT IGNORE INTO `lb-entitytypes` (id, name) VALUES (?, ?)");
smt.setInt(1, key);
smt.setString(2, entityType.name());
boolean couldAdd = smt.executeUpdate() > 0;
conn.commit();
smt.close();
if (couldAdd) {
internalAddEntityType(key, entityType);
} else {
initializeEntityTypes(conn);
}
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not update lb-entitytypes", e);
reinitializeEntityTypesCatchException();
if (tries == 10) {
throw new RuntimeException(e);
}
} finally {
try {
conn.close();
} catch (SQLException e) {
// ignored
}
}
key = entityTypeToId.get(entityType);
}
return key.intValue();
}
public synchronized static EntityType getEntityType(int entityTypeId) {
return entityTypeId >= 0 && entityTypeId < idToEntityType.length ? idToEntityType[entityTypeId] : null;
}
private static void reinitializeEntityTypesCatchException() {
Connection conn = LogBlock.getInstance().getConnection();
try {
initializeEntityTypes(conn);
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not reinitialize lb-entitytypes", e);
} finally {
try {
conn.close();
} catch (Exception e) {
// ignored
}
}
}
protected synchronized static void initializeEntityTypes(Connection connection) throws SQLException {
Statement smt = connection.createStatement();
ResultSet rs = smt.executeQuery("SELECT id, name FROM `lb-entitytypes`");
while (rs.next()) {
int key = rs.getInt(1);
try {
EntityType entityType = EntityType.valueOf(rs.getString(2));
internalAddEntityType(key, entityType);
} catch (IllegalArgumentException ignored) {
// the key is used, but not available in this version
if (nextEntityTypeId <= key) {
nextEntityTypeId = key + 1;
}
}
}
rs.close();
smt.close();
connection.close();
}
private static void internalAddEntityType(int key, EntityType entityType) {
entityTypeToId.put(entityType, key);
int length = idToEntityType.length;
while (length <= key) {
length = (length * 3 / 2) + 5;
}
if (length > idToEntityType.length) {
idToEntityType = Arrays.copyOf(idToEntityType, length);
}
idToEntityType[key] = entityType;
if (nextEntityTypeId <= key) {
nextEntityTypeId = key + 1;
}
}
}

View File

@ -1,11 +1,17 @@
package de.diddiz.LogBlock;
import de.diddiz.LogBlock.config.Config;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
import static de.diddiz.util.ActionColor.DESTROY;
import static de.diddiz.util.MessagingUtil.prettyDate;
import static de.diddiz.util.MessagingUtil.prettyLocation;
import static de.diddiz.util.MessagingUtil.prettyMaterial;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.MessagingUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Location;
import org.bukkit.Material;
public class Kill implements LookupCacheElement {
final long id, date;
@ -33,17 +39,7 @@ public class Kill implements LookupCacheElement {
@Override
public String toString() {
final StringBuilder msg = new StringBuilder();
if (date > 0) {
msg.append(Config.formatter.format(date)).append(" ");
}
msg.append(killerName).append(" killed ").append(victimName);
if (loc != null) {
msg.append(" at ").append(loc.getBlockX()).append(":").append(loc.getBlockY()).append(":").append(loc.getBlockZ());
}
String weaponName = prettyItemName(new ItemStack(weapon));
msg.append(" with " + weaponName); // + ("aeiou".contains(weaponName.substring(0, 1)) ? "an " : "a " )
return msg.toString();
return BaseComponent.toPlainText(getLogMessage());
}
@Override
@ -52,15 +48,29 @@ public class Kill implements LookupCacheElement {
}
@Override
public String getMessage() {
return toString();
public BaseComponent[] getLogMessage(int entry) {
TextComponent msg = new TextComponent();
if (date > 0) {
msg.addExtra(prettyDate(date));
msg.addExtra(" ");
}
msg.addExtra(MessagingUtil.createTextComponentWithColor(killerName + " killed ", DESTROY.getColor()));
msg.addExtra(new TextComponent(victimName));
if (loc != null) {
msg.addExtra(" at ");
msg.addExtra(prettyLocation(loc, entry));
}
if (weapon != 0) {
msg.addExtra(" with ");
msg.addExtra(prettyItemName(MaterialConverter.getMaterial(weapon)));
}
return new BaseComponent[] { msg };
}
public String prettyItemName(ItemStack i) {
String item = i.getType().toString().replace('_', ' ').toLowerCase();
if (item.equals("air")) {
item = "fist";
public TextComponent prettyItemName(Material t) {
if (t == null || BukkitUtils.isEmpty(t)) {
return prettyMaterial("fist");
}
return item;
return prettyMaterial(t.toString().replace('_', ' '));
}
}

View File

@ -2,9 +2,13 @@ package de.diddiz.LogBlock;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.listeners.*;
import de.diddiz.LogBlock.questioner.Questioner;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.MySQLConnectionPool;
import de.diddiz.worldedit.WorldEditHelper;
import de.diddiz.worldedit.WorldEditLoggingHook;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@ -12,19 +16,17 @@ import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.logging.Level;
import static de.diddiz.LogBlock.config.Config.*;
import static de.diddiz.util.MaterialName.materialName;
import static org.bukkit.Bukkit.getPluginManager;
public class LogBlock extends JavaPlugin {
@ -32,14 +34,20 @@ public class LogBlock extends JavaPlugin {
private MySQLConnectionPool pool;
private Consumer consumer = null;
private CommandsHandler commandsHandler;
private Updater updater = null;
private Timer timer = null;
private boolean errorAtLoading = false, noDb = false, connected = true;
private boolean noDb = false, connected = true;
private PlayerInfoLogging playerInfoLogging;
private ScaffoldingLogging scaffoldingLogging;
private Questioner questioner;
private volatile boolean isCompletelyEnabled;
public static LogBlock getInstance() {
return logblock;
}
public boolean isCompletelyEnabled() {
return isCompletelyEnabled;
}
public Consumer getConsumer() {
return consumer;
}
@ -48,19 +56,30 @@ public class LogBlock extends JavaPlugin {
return commandsHandler;
}
Updater getUpdater() {
return updater;
}
@Override
public void onLoad() {
public void onEnable() {
logblock = this;
BukkitUtils.isDoublePlant(Material.AIR); // Force static code to run
final PluginManager pm = getPluginManager();
consumer = new Consumer(this);
try {
updater = new Updater(this);
Config.load(this);
} catch (final Exception ex) {
getLogger().log(Level.SEVERE, "Could not load LogBlock config! " + ex.getMessage());
pm.disablePlugin(this);
return;
}
try {
getLogger().info("Connecting to " + user + "@" + url + "...");
pool = new MySQLConnectionPool(url, user, password);
final Connection conn = getConnection();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException ignored) {
Class.forName("com.mysql.jdbc.Driver");
}
pool = new MySQLConnectionPool(url, user, password, mysqlUseSSL, mysqlRequireSSL);
final Connection conn = getConnection(true);
if (conn == null) {
noDb = true;
return;
@ -73,84 +92,55 @@ public class LogBlock extends JavaPlugin {
st.executeQuery("SET NAMES utf8mb4;");
}
conn.close();
Updater updater = new Updater(this);
updater.checkTables();
MaterialConverter.initializeMaterials(getConnection());
MaterialConverter.getOrAddMaterialId(Material.AIR); // AIR must be the first entry
EntityTypeConverter.initializeEntityTypes(getConnection());
if (updater.update()) {
load(this);
}
updater.checkTables();
} catch (final NullPointerException ex) {
getLogger().log(Level.SEVERE, "Error while loading: ", ex);
} catch (final Exception ex) {
getLogger().severe("Error while loading: " + ex.getMessage());
errorAtLoading = true;
return;
}
consumer = new Consumer(this);
}
@Override
public void onEnable() {
materialName(0); // Force static code to run
final PluginManager pm = getPluginManager();
if (errorAtLoading) {
getLogger().log(Level.SEVERE, "Error while loading: " + ex.getMessage(), ex);
pm.disablePlugin(this);
return;
}
if (noDb) {
return;
}
if (pm.getPlugin("WorldEdit") != null) {
if (Integer.parseInt(pm.getPlugin("WorldEdit").getDescription().getVersion().substring(0, 1)) > 5) {
new WorldEditLoggingHook(this).hook();
} else {
getLogger().warning("Failed to hook into WorldEdit. Your WorldEdit version seems to be outdated, please make sure WorldEdit is at least version 6.");
}
if (WorldEditHelper.hasWorldEdit()) {
new WorldEditLoggingHook(this).hook();
}
commandsHandler = new CommandsHandler(this);
getCommand("lb").setExecutor(commandsHandler);
if (enableAutoClearLog && autoClearLogDelay > 0) {
getServer().getScheduler().runTaskTimerAsynchronously(this, new AutoClearLog(this), 6000, autoClearLogDelay * 60 * 20);
}
getServer().getScheduler().runTaskAsynchronously(this, new DumpedLogImporter(this));
new DumpedLogImporter(this).run();
registerEvents();
if (useBukkitScheduler) {
if (getServer().getScheduler().runTaskTimerAsynchronously(this, consumer, delayBetweenRuns < 20 ? 20 : delayBetweenRuns, delayBetweenRuns).getTaskId() > 0) {
getLogger().info("Scheduled consumer with bukkit scheduler.");
} else {
getLogger().warning("Failed to schedule consumer with bukkit scheduler. Now trying schedule with timer.");
timer = new Timer();
timer.schedule(consumer, delayBetweenRuns < 20 ? 1000 : delayBetweenRuns * 50, delayBetweenRuns * 50);
}
} else {
timer = new Timer();
timer.schedule(consumer, delayBetweenRuns < 20 ? 1000 : delayBetweenRuns * 50, delayBetweenRuns * 50);
getLogger().info("Scheduled consumer with timer.");
}
getServer().getScheduler().runTaskAsynchronously(this, new Updater.PlayerCountChecker(this));
consumer.start();
for (final Tool tool : toolsByType.values()) {
if (pm.getPermission("logblock.tools." + tool.name) == null) {
final Permission perm = new Permission("logblock.tools." + tool.name, tool.permissionDefault);
pm.addPermission(perm);
}
}
try {
Metrics metrics = new Metrics(this);
metrics.start();
} catch (IOException ex) {
getLogger().info("Could not start metrics: " + ex.getMessage());
}
questioner = new Questioner(this);
isCompletelyEnabled = true;
getServer().getScheduler().runTaskAsynchronously(this, new Updater.PlayerCountChecker(this));
}
private void registerEvents() {
final PluginManager pm = getPluginManager();
pm.registerEvents(new ToolListener(this), this);
pm.registerEvents(new PlayerInfoLogging(this), this);
pm.registerEvents(playerInfoLogging = new PlayerInfoLogging(this), this);
if (askRollbackAfterBan) {
pm.registerEvents(new BanListener(this), this);
}
if (isLogging(Logging.BLOCKPLACE)) {
pm.registerEvents(new BlockPlaceLogging(this), this);
}
if (isLogging(Logging.BLOCKPLACE) || isLogging(Logging.LAVAFLOW) || isLogging(Logging.WATERFLOW)) {
if (isLogging(Logging.LAVAFLOW) || isLogging(Logging.WATERFLOW)) {
pm.registerEvents(new FluidFlowLogging(this), this);
}
if (isLogging(Logging.BLOCKBREAK)) {
@ -168,6 +158,9 @@ public class LogBlock extends JavaPlugin {
if (isLogging(Logging.SNOWFADE)) {
pm.registerEvents(new SnowFadeLogging(this), this);
}
if (isLogging(Logging.SCAFFOLDING)) {
pm.registerEvents(scaffoldingLogging = new ScaffoldingLogging(this), this);
}
if (isLogging(Logging.CREEPEREXPLOSION) || isLogging(Logging.TNTEXPLOSION) || isLogging(Logging.GHASTFIREBALLEXPLOSION) || isLogging(Logging.ENDERDRAGON) || isLogging(Logging.MISCEXPLOSION)) {
pm.registerEvents(new ExplosionLogging(this), this);
}
@ -177,7 +170,8 @@ public class LogBlock extends JavaPlugin {
if (isLogging(Logging.CHESTACCESS)) {
pm.registerEvents(new ChestAccessLogging(this), this);
}
if (isLogging(Logging.SWITCHINTERACT) || isLogging(Logging.DOORINTERACT) || isLogging(Logging.CAKEEAT) || isLogging(Logging.DIODEINTERACT) || isLogging(Logging.COMPARATORINTERACT) || isLogging(Logging.NOTEBLOCKINTERACT) || isLogging(Logging.PRESUREPLATEINTERACT) || isLogging(Logging.TRIPWIREINTERACT) || isLogging(Logging.CROPTRAMPLE)) {
if (isLogging(Logging.BLOCKBREAK) || isLogging(Logging.BLOCKPLACE) || isLogging(Logging.SWITCHINTERACT) || isLogging(Logging.DOORINTERACT) || isLogging(Logging.CAKEEAT) || isLogging(Logging.DIODEINTERACT) || isLogging(Logging.COMPARATORINTERACT) || isLogging(Logging.NOTEBLOCKINTERACT)
|| isLogging(Logging.PRESUREPLATEINTERACT) || isLogging(Logging.TRIPWIREINTERACT) || isLogging(Logging.CROPTRAMPLE)) {
pm.registerEvents(new InteractLogging(this), this);
}
if (isLogging(Logging.CREATURECROPTRAMPLE)) {
@ -186,7 +180,7 @@ public class LogBlock extends JavaPlugin {
if (isLogging(Logging.KILL)) {
pm.registerEvents(new KillLogging(this), this);
}
if (isLogging(Logging.CHAT)) {
if (isLogging(Logging.CHAT) || isLogging(Logging.PLAYER_COMMANDS) || isLogging(Logging.CONSOLE_COMMANDS) || isLogging(Logging.COMMANDBLOCK_COMMANDS)) {
pm.registerEvents(new ChatLogging(this), this);
}
if (isLogging(Logging.ENDERMEN)) {
@ -201,43 +195,41 @@ public class LogBlock extends JavaPlugin {
if (isLogging(Logging.GRASSGROWTH) || isLogging(Logging.MYCELIUMSPREAD) || isLogging(Logging.VINEGROWTH) || isLogging(Logging.MUSHROOMSPREAD)) {
pm.registerEvents(new BlockSpreadLogging(this), this);
}
if (isLogging(Logging.LOCKEDCHESTDECAY)) {
pm.registerEvents(new LockedChestDecayLogging(this), this);
if (isLogging(Logging.DRAGONEGGTELEPORT)) {
pm.registerEvents(new DragonEggLogging(this), this);
}
if (isLogging(Logging.LECTERNBOOKCHANGE)) {
pm.registerEvents(new LecternLogging(this), this);
}
if (Config.isLoggingAnyEntities()) {
if (!WorldEditHelper.hasFullWorldEdit()) {
getLogger().severe("No compatible WorldEdit found, entity logging will not work!");
} else {
pm.registerEvents(new AdvancedEntityLogging(this), this);
getLogger().info("Entity logging enabled!");
}
}
}
@Override
public void onDisable() {
if (timer != null) {
timer.cancel();
}
isCompletelyEnabled = false;
getServer().getScheduler().cancelTasks(this);
if (consumer != null) {
if (logPlayerInfo && getServer().getOnlinePlayers() != null) {
if (logPlayerInfo && playerInfoLogging != null) {
for (final Player player : getServer().getOnlinePlayers()) {
consumer.queueLeave(player);
playerInfoLogging.onPlayerQuit(player);
}
}
getLogger().info("Waiting for consumer ...");
consumer.run();
consumer.shutdown();
if (consumer.getQueueSize() > 0) {
int tries = 9;
while (consumer.getQueueSize() > 0) {
getLogger().info("Remaining queue size: " + consumer.getQueueSize());
if (tries > 0) {
getLogger().info("Remaining tries: " + tries);
} else {
getLogger().info("Unable to save queue to database. Trying to write to a local file.");
try {
consumer.writeToFile();
getLogger().info("Successfully dumped queue.");
} catch (final FileNotFoundException ex) {
getLogger().info("Failed to write. Given up.");
break;
}
}
consumer.run();
tries--;
getLogger().info("Remaining queue size: " + consumer.getQueueSize() + ". Trying to write to a local file.");
try {
consumer.writeToFile();
getLogger().info("Successfully dumped queue.");
} catch (final FileNotFoundException ex) {
getLogger().info("Failed to write. Given up.");
}
}
}
@ -259,6 +251,10 @@ public class LogBlock extends JavaPlugin {
}
public Connection getConnection() {
return getConnection(false);
}
public Connection getConnection(boolean testConnection) {
try {
final Connection conn = pool.getConnection();
if (!connected) {
@ -267,11 +263,13 @@ public class LogBlock extends JavaPlugin {
}
return conn;
} catch (final Exception ex) {
if (connected) {
if (testConnection) {
getLogger().log(Level.SEVERE, "Could not connect to the Database! Please check your config! " + ex.getMessage());
} else if (connected) {
getLogger().log(Level.SEVERE, "Error while fetching connection: ", ex);
connected = false;
} else {
getLogger().severe("MySQL connection lost");
getLogger().log(Level.SEVERE, "MySQL connection lost", ex);
}
return null;
}
@ -298,7 +296,7 @@ public class LogBlock extends JavaPlugin {
try {
state = conn.createStatement();
final ResultSet rs = state.executeQuery(params.getQuery());
final List<BlockChange> blockchanges = new ArrayList<BlockChange>();
final List<BlockChange> blockchanges = new ArrayList<>();
while (rs.next()) {
blockchanges.add(new BlockChange(rs, params));
}
@ -312,6 +310,9 @@ public class LogBlock extends JavaPlugin {
}
public int getCount(QueryParams params) throws SQLException {
if (params == null || params.world == null || !Config.isLogged(params.world)) {
throw new IllegalArgumentException("World is not logged: " + ((params == null || params.world == null) ? "null" : params.world.getName()));
}
final Connection conn = getConnection();
Statement state = null;
if (conn == null) {
@ -333,4 +334,17 @@ public class LogBlock extends JavaPlugin {
conn.close();
}
}
@Override
public File getFile() {
return super.getFile();
}
public Questioner getQuestioner() {
return questioner;
}
public ScaffoldingLogging getScaffoldingLogging() {
return scaffoldingLogging;
}
}

View File

@ -1,27 +1,68 @@
package de.diddiz.LogBlock;
public enum Logging {
BLOCKPLACE(true), BLOCKBREAK(true), SIGNTEXT, TNTEXPLOSION(true), CREEPEREXPLOSION(true),
GHASTFIREBALLEXPLOSION(true), ENDERDRAGON(true), MISCEXPLOSION, FIRE(true), LEAVESDECAY,
LAVAFLOW, WATERFLOW, CHESTACCESS, KILL, CHAT, SNOWFORM, SNOWFADE, DOORINTERACT,
SWITCHINTERACT, CAKEEAT, ENDERMEN, NOTEBLOCKINTERACT, DIODEINTERACT, COMPARATORINTERACT,
PRESUREPLATEINTERACT, TRIPWIREINTERACT, CREATURECROPTRAMPLE, CROPTRAMPLE,
NATURALSTRUCTUREGROW, GRASSGROWTH, MYCELIUMSPREAD, VINEGROWTH, MUSHROOMSPREAD,
WITHER(true), WITHER_SKULL(true), BONEMEALSTRUCTUREGROW,
WORLDEDIT, TNTMINECARTEXPLOSION(true), LOCKEDCHESTDECAY;
public static final int length = Logging.values().length;
private final boolean defaultEnabled;
private Logging() {
this(false);
}
private Logging(boolean defaultEnabled) {
this.defaultEnabled = defaultEnabled;
}
public boolean isDefaultEnabled() {
return defaultEnabled;
}
}
package de.diddiz.LogBlock;
public enum Logging {
BLOCKPLACE(true),
BLOCKBREAK(true),
SIGNTEXT(true),
TNTEXPLOSION(true),
CREEPEREXPLOSION(true),
GHASTFIREBALLEXPLOSION(true),
ENDERDRAGON(true),
MISCEXPLOSION(true),
FIRE(true),
LEAVESDECAY,
LAVAFLOW,
WATERFLOW,
CHESTACCESS,
KILL,
CHAT,
SNOWFORM,
SNOWFADE,
DOORINTERACT,
SWITCHINTERACT,
CAKEEAT,
ENDERMEN,
NOTEBLOCKINTERACT,
DIODEINTERACT,
COMPARATORINTERACT,
PRESUREPLATEINTERACT,
TRIPWIREINTERACT,
CREATURECROPTRAMPLE,
CROPTRAMPLE,
NATURALSTRUCTUREGROW,
GRASSGROWTH,
MYCELIUMSPREAD,
VINEGROWTH,
MUSHROOMSPREAD,
BAMBOOGROWTH,
WITHER(true),
WITHER_SKULL(true),
BONEMEALSTRUCTUREGROW,
WORLDEDIT,
TNTMINECARTEXPLOSION(true),
ENDERCRYSTALEXPLOSION(true),
BEDEXPLOSION(true),
DRAGONEGGTELEPORT(true),
DAYLIGHTDETECTORINTERACT,
LECTERNBOOKCHANGE(true),
SCAFFOLDING(true),
SHULKER_BOX_CONTENT,
PLAYER_COMMANDS,
COMMANDBLOCK_COMMANDS,
CONSOLE_COMMANDS;
public static final int length = Logging.values().length;
private final boolean defaultEnabled;
private Logging() {
this(false);
}
private Logging(boolean defaultEnabled) {
this.defaultEnabled = defaultEnabled;
}
public boolean isDefaultEnabled() {
return defaultEnabled;
}
}

View File

@ -1,9 +1,18 @@
package de.diddiz.LogBlock;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.Location;
public interface LookupCacheElement {
public Location getLocation();
public String getMessage();
public default BaseComponent[] getLogMessage() {
return getLogMessage(-1);
}
public BaseComponent[] getLogMessage(int entry);
public default int getNumChanges() {
return 1;
}
}

View File

@ -26,6 +26,12 @@ public class LookupCacheElementFactory {
return new SummedKills(rs, params, spaceFactor);
}
}
if (params.bct == BlockChangeType.ENTITIES || params.bct == BlockChangeType.ENTITIES_CREATED || params.bct == BlockChangeType.ENTITIES_KILLED) {
if (params.sum == SummarizationMode.NONE) {
return new EntityChange(rs, params);
}
return new SummedEntityChanges(rs, params, spaceFactor);
}
if (params.sum == SummarizationMode.NONE) {
return new BlockChange(rs, params);
}

View File

@ -0,0 +1,256 @@
package de.diddiz.LogBlock;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
public class MaterialConverter {
private static String[] idToMaterial = new String[10];
private static HashMap<String, Integer> materialToID = new HashMap<>();
private static int nextMaterialId;
private static String[] idToBlockState = new String[10];
private static HashMap<String, Integer> blockStateToID = new HashMap<>();
private static int nextBlockStateId;
private static HashMap<String, Material> materialKeyToMaterial = new HashMap<>();
static {
for (Material m : Material.values()) {
materialKeyToMaterial.put(m.getKey().toString(), m);
}
}
public synchronized static Integer getExistingMaterialId(BlockData blockData) {
return blockData == null ? null : getExistingMaterialId(blockData.getMaterial());
}
public synchronized static Integer getExistingMaterialId(Material material) {
if (material == null) {
return null;
}
String materialString = material.getKey().toString();
return materialToID.get(materialString);
}
public synchronized static int getOrAddMaterialId(BlockData blockData) {
return getOrAddMaterialId(blockData == null ? Material.AIR : blockData.getMaterial());
}
public synchronized static int getOrAddMaterialId(Material material) {
if (material == null) {
material = Material.AIR;
}
String materialString = material.getKey().toString();
Integer key = materialToID.get(materialString);
int tries = 0;
while (key == null && tries < 10) {
tries++;
key = nextMaterialId;
Connection conn = LogBlock.getInstance().getConnection();
try {
conn.setAutoCommit(false);
PreparedStatement smt = conn.prepareStatement("INSERT IGNORE INTO `lb-materials` (id, name) VALUES (?, ?)");
smt.setInt(1, key);
smt.setString(2, materialString);
boolean couldAdd = smt.executeUpdate() > 0;
conn.commit();
smt.close();
if (couldAdd) {
internalAddMaterial(key, materialString);
} else {
initializeMaterials(conn);
}
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not update lb-materials", e);
reinitializeMaterialsCatchException();
if (tries == 10) {
throw new RuntimeException(e);
}
} finally {
try {
conn.close();
} catch (SQLException e) {
// ignored
}
}
key = materialToID.get(materialString);
}
return key.intValue();
}
public synchronized static Integer getExistingBlockStateId(BlockData blockData) {
if (blockData == null) {
return -1;
}
String blockDataString = blockData.getAsString();
int dataPart = blockDataString.indexOf("[");
if (dataPart < 0) {
return -1;
}
String materialString = blockDataString.substring(dataPart);
return blockStateToID.get(materialString);
}
public synchronized static int getOrAddBlockStateId(BlockData blockData) {
if (blockData == null) {
return -1;
}
String blockDataString = blockData.getAsString();
int dataPart = blockDataString.indexOf("[");
if (dataPart < 0) {
return -1;
}
String materialString = blockDataString.substring(dataPart);
Integer key = blockStateToID.get(materialString);
int tries = 0;
while (key == null && tries < 10) {
tries++;
key = nextBlockStateId;
Connection conn = LogBlock.getInstance().getConnection();
try {
conn.setAutoCommit(false);
PreparedStatement smt = conn.prepareStatement("INSERT IGNORE INTO `lb-blockstates` (id, name) VALUES (?, ?)");
smt.setInt(1, key);
smt.setString(2, materialString);
boolean couldAdd = smt.executeUpdate() > 0;
conn.commit();
smt.close();
if (couldAdd) {
internalAddBlockState(key, materialString);
} else {
initializeMaterials(conn);
}
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not update lb-blockstates", e);
reinitializeMaterialsCatchException();
if (tries == 10) {
throw new RuntimeException(e);
}
} finally {
try {
conn.close();
} catch (SQLException e) {
// ignored
}
}
key = blockStateToID.get(materialString);
}
return key.intValue();
}
public synchronized static BlockData getBlockData(int materialId, int blockStateId) {
String material = materialId >= 0 && materialId < idToMaterial.length ? idToMaterial[materialId] : null;
if (material == null) {
return null;
}
if (blockStateId >= 0 && blockStateId < idToBlockState.length && idToBlockState[blockStateId] != null) {
material = material + updateBlockState(material, idToBlockState[blockStateId]);
}
try {
return Bukkit.createBlockData(material);
} catch (IllegalArgumentException ignored) {
// fall back to create the default block data for the material
try {
return Bukkit.createBlockData(idToMaterial[materialId]);
} catch (IllegalArgumentException ignored2) {
return null;
}
}
}
public synchronized static Material getMaterial(int materialId) {
return materialId >= 0 && materialId < idToMaterial.length ? materialKeyToMaterial.get(idToMaterial[materialId]) : null;
}
private static void reinitializeMaterialsCatchException() {
Connection conn = LogBlock.getInstance().getConnection();
try {
initializeMaterials(conn);
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not reinitialize lb-materials", e);
} finally {
try {
conn.close();
} catch (Exception e) {
// ignored
}
}
}
protected synchronized static void initializeMaterials(Connection connection) throws SQLException {
Statement smt = connection.createStatement();
ResultSet rs = smt.executeQuery("SELECT id, name FROM `lb-materials`");
while (rs.next()) {
int key = rs.getInt(1);
String materialString = rs.getString(2);
internalAddMaterial(key, materialString);
}
rs.close();
rs = smt.executeQuery("SELECT id, name FROM `lb-blockstates`");
while (rs.next()) {
int key = rs.getInt(1);
String materialString = rs.getString(2);
internalAddBlockState(key, materialString);
}
rs.close();
smt.close();
connection.close();
}
private static void internalAddMaterial(int key, String materialString) {
materialToID.put(materialString, key);
int length = idToMaterial.length;
while (length <= key) {
length = (length * 3 / 2) + 5;
}
if (length > idToMaterial.length) {
idToMaterial = Arrays.copyOf(idToMaterial, length);
}
idToMaterial[key] = materialString;
if (nextMaterialId <= key) {
nextMaterialId = key + 1;
}
}
private static void internalAddBlockState(int key, String materialString) {
blockStateToID.put(materialString, key);
int length = idToBlockState.length;
while (length <= key) {
length = (length * 3 / 2) + 5;
}
if (length > idToBlockState.length) {
idToBlockState = Arrays.copyOf(idToBlockState, length);
}
idToBlockState[key] = materialString;
if (nextBlockStateId <= key) {
nextBlockStateId = key + 1;
}
}
private static String updateBlockState(String material, String blockState) {
// since 1.16
if (material.endsWith("_wall")) {
if (blockState.contains("east=false") || blockState.contains("east=true")) {
blockState = blockState.replace("east=false", "east=none");
blockState = blockState.replace("west=false", "west=none");
blockState = blockState.replace("north=false", "north=none");
blockState = blockState.replace("south=false", "south=none");
blockState = blockState.replace("east=true", "east=low");
blockState = blockState.replace("west=true", "west=low");
blockState = blockState.replace("north=true", "north=low");
blockState = blockState.replace("south=true", "south=low");
}
}
return blockState;
}
}

View File

@ -1,750 +0,0 @@
/*
* Copyright 2011-2013 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package de.diddiz.LogBlock;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.scheduler.BukkitTask;
import java.io.*;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.*;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
public class Metrics {
/**
* The current revision number
*/
private final static int REVISION = 7;
/**
* The base url of the metrics domain
*/
private static final String BASE_URL = "http://report.mcstats.org";
/**
* The url used to report a server's status
*/
private static final String REPORT_URL = "/plugin/%s";
/**
* Interval of time to ping (in minutes)
*/
private static final int PING_INTERVAL = 15;
/**
* The plugin this metrics submits for
*/
private final Plugin plugin;
/**
* All of the custom graphs to submit to metrics
*/
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* The plugin configuration file
*/
private final YamlConfiguration configuration;
/**
* The plugin configuration file
*/
private final File configurationFile;
/**
* Unique server id
*/
private final String guid;
/**
* Debug mode
*/
private final boolean debug;
/**
* Lock for synchronization
*/
private final Object optOutLock = new Object();
/**
* The scheduled task
*/
private volatile BukkitTask task = null;
public Metrics(final Plugin plugin) throws IOException {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null");
}
this.plugin = plugin;
// load the config
configurationFile = getConfigFile();
configuration = YamlConfiguration.loadConfiguration(configurationFile);
// add some defaults
configuration.addDefault("opt-out", false);
configuration.addDefault("guid", UUID.randomUUID().toString());
configuration.addDefault("debug", false);
// Do we need to create the file?
if (configuration.get("guid", null) == null) {
configuration.options().header("http://mcstats.org").copyDefaults(true);
configuration.save(configurationFile);
}
// Load the guid then
guid = configuration.getString("guid");
debug = configuration.getBoolean("debug", false);
}
/**
* Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
* website. Plotters can be added to the graph object returned.
*
* @param name The name of the graph
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
*/
public Graph createGraph(final String name) {
if (name == null) {
throw new IllegalArgumentException("Graph name cannot be null");
}
// Construct the graph object
final Graph graph = new Graph(name);
// Now we can add our graph
graphs.add(graph);
// and return back
return graph;
}
/**
* Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
*
* @param graph The name of the graph
*/
public void addGraph(final Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
graphs.add(graph);
}
/**
* Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
* initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
* ticks.
*
* @return True if statistics measuring is running, otherwise false.
*/
public boolean start() {
synchronized (optOutLock) {
// Did we opt out?
if (isOptOut()) {
return false;
}
// Is metrics already running?
if (task != null) {
return true;
}
// Begin hitting the server with glorious data
task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new Runnable() {
private boolean firstPost = true;
public void run() {
try {
// This has to be synchronized or it can collide with the disable method.
synchronized (optOutLock) {
// Disable Task, if it is running and the server owner decided to opt-out
if (isOptOut() && task != null) {
task.cancel();
task = null;
// Tell all plotters to stop gathering information.
for (Graph graph : graphs) {
graph.onOptOut();
}
}
}
// We use the inverse of firstPost because if it is the first time we are posting,
// it is not a interval ping, so it evaluates to FALSE
// Each time thereafter it will evaluate to TRUE, i.e PING!
postPlugin(!firstPost);
// After the first post we set firstPost to false
// Each post thereafter will be a ping
firstPost = false;
} catch (IOException e) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
}
}
}
}, 0, PING_INTERVAL * 1200);
return true;
}
}
/**
* Has the server owner denied plugin metrics?
*
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (optOutLock) {
try {
// Reload the metrics file
configuration.load(getConfigFile());
} catch (IOException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (InvalidConfigurationException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
}
return configuration.getBoolean("opt-out", false);
}
}
/**
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
*
* @throws java.io.IOException
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (isOptOut()) {
configuration.set("opt-out", false);
configuration.save(configurationFile);
}
// Enable Task, if it is not running
if (task == null) {
start();
}
}
}
/**
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
*
* @throws java.io.IOException
*/
public void disable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (!isOptOut()) {
configuration.set("opt-out", true);
configuration.save(configurationFile);
}
// Disable Task, if it is running
if (task != null) {
task.cancel();
task = null;
}
}
}
/**
* Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
*
* @return the File object for the config file
*/
public File getConfigFile() {
// I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
// is to abuse the plugin object we already have
// plugin.getDataFolder() => base/plugins/PluginA/
// pluginsFolder => base/plugins/
// The base is not necessarily relative to the startup directory.
File pluginsFolder = plugin.getDataFolder().getParentFile();
// return => base/plugins/PluginMetrics/config.yml
return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
}
/**
* Generic method that posts a plugin to the metrics website
*/
private void postPlugin(final boolean isPing) throws IOException {
// Server software specific section
PluginDescriptionFile description = plugin.getDescription();
String pluginName = description.getName();
boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
String pluginVersion = description.getVersion();
String serverVersion = Bukkit.getVersion();
int playersOnline = Bukkit.getServer().getOnlinePlayers().length;
// END server software specific section -- all code below does not use any code outside of this class / Java
// Construct the post data
StringBuilder json = new StringBuilder(1024);
json.append('{');
// The plugin's description file containg all of the plugin data such as name, version, author, etc
appendJSONPair(json, "guid", guid);
appendJSONPair(json, "plugin_version", pluginVersion);
appendJSONPair(json, "server_version", serverVersion);
appendJSONPair(json, "players_online", Integer.toString(playersOnline));
// New data as of R6
String osname = System.getProperty("os.name");
String osarch = System.getProperty("os.arch");
String osversion = System.getProperty("os.version");
String java_version = System.getProperty("java.version");
int coreCount = Runtime.getRuntime().availableProcessors();
// normalize os arch .. amd64 -> x86_64
if (osarch.equals("amd64")) {
osarch = "x86_64";
}
appendJSONPair(json, "osname", osname);
appendJSONPair(json, "osarch", osarch);
appendJSONPair(json, "osversion", osversion);
appendJSONPair(json, "cores", Integer.toString(coreCount));
appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0");
appendJSONPair(json, "java_version", java_version);
// If we're pinging, append it
if (isPing) {
appendJSONPair(json, "ping", "1");
}
if (graphs.size() > 0) {
synchronized (graphs) {
json.append(',');
json.append('"');
json.append("graphs");
json.append('"');
json.append(':');
json.append('{');
boolean firstGraph = true;
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
Graph graph = iter.next();
StringBuilder graphJson = new StringBuilder();
graphJson.append('{');
for (Plotter plotter : graph.getPlotters()) {
appendJSONPair(graphJson, plotter.getColumnName(), Integer.toString(plotter.getValue()));
}
graphJson.append('}');
if (!firstGraph) {
json.append(',');
}
json.append(escapeJSON(graph.getName()));
json.append(':');
json.append(graphJson);
firstGraph = false;
}
json.append('}');
}
}
// close json
json.append('}');
// Create the url
URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName)));
// Connect to the website
URLConnection connection;
// Mineshafter creates a socks proxy, so we can safely bypass it
// It does not reroute POST requests so we need to go around it
if (isMineshafterPresent()) {
connection = url.openConnection(Proxy.NO_PROXY);
} else {
connection = url.openConnection();
}
byte[] uncompressed = json.toString().getBytes();
byte[] compressed = gzip(json.toString());
// Headers
connection.addRequestProperty("User-Agent", "MCStats/" + REVISION);
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", Integer.toString(compressed.length));
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.setDoOutput(true);
if (debug) {
System.out.println("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length);
}
// Write the data
OutputStream os = connection.getOutputStream();
os.write(compressed);
os.flush();
// Now read the response
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = reader.readLine();
// close resources
os.close();
reader.close();
if (response == null || response.startsWith("ERR") || response.startsWith("7")) {
if (response == null) {
response = "null";
} else if (response.startsWith("7")) {
response = response.substring(response.startsWith("7,") ? 2 : 1);
}
throw new IOException(response);
} else {
// Is this the first update this hour?
if (response.equals("1") || response.contains("This is your first update this hour")) {
synchronized (graphs) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
for (Plotter plotter : graph.getPlotters()) {
plotter.reset();
}
}
}
}
}
}
/**
* GZip compress a string of bytes
*
* @param input
* @return
*/
public static byte[] gzip(String input) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = null;
try {
gzos = new GZIPOutputStream(baos);
gzos.write(input.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (gzos != null) {
try {
gzos.close();
} catch (IOException ignore) {
}
}
}
return baos.toByteArray();
}
/**
* Check if mineshafter is present. If it is, we need to bypass it to send POST requests
*
* @return true if mineshafter is installed on the server
*/
private boolean isMineshafterPresent() {
try {
Class.forName("mineshafter.MineServer");
return true;
} catch (Exception e) {
return false;
}
}
/**
* Appends a json encoded key/value pair to the given string builder.
*
* @param json
* @param key
* @param value
* @throws UnsupportedEncodingException
*/
private static void appendJSONPair(StringBuilder json, String key, String value) throws UnsupportedEncodingException {
boolean isValueNumeric = false;
try {
if (value.equals("0") || !value.endsWith("0")) {
Double.parseDouble(value);
isValueNumeric = true;
}
} catch (NumberFormatException e) {
isValueNumeric = false;
}
if (json.charAt(json.length() - 1) != '{') {
json.append(',');
}
json.append(escapeJSON(key));
json.append(':');
if (isValueNumeric) {
json.append(value);
} else {
json.append(escapeJSON(value));
}
}
/**
* Escape a string to create a valid JSON string
*
* @param text
* @return
*/
private static String escapeJSON(String text) {
StringBuilder builder = new StringBuilder();
builder.append('"');
for (int index = 0; index < text.length(); index++) {
char chr = text.charAt(index);
switch (chr) {
case '"':
case '\\':
builder.append('\\');
builder.append(chr);
break;
case '\b':
builder.append("\\b");
break;
case '\t':
builder.append("\\t");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
default:
if (chr < ' ') {
String t = "000" + Integer.toHexString(chr);
builder.append("\\u" + t.substring(t.length() - 4));
} else {
builder.append(chr);
}
break;
}
}
builder.append('"');
return builder.toString();
}
/**
* Encode text as UTF-8
*
* @param text the text to encode
* @return the encoded text, as UTF-8
*/
private static String urlEncode(final String text) throws UnsupportedEncodingException {
return URLEncoder.encode(text, "UTF-8");
}
/**
* Represents a custom graph on the website
*/
public static class Graph {
/**
* The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
* rejected
*/
private final String name;
/**
* The set of plotters that are contained within this graph
*/
private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
private Graph(final String name) {
this.name = name;
}
/**
* Gets the graph's name
*
* @return the Graph's name
*/
public String getName() {
return name;
}
/**
* Add a plotter to the graph, which will be used to plot entries
*
* @param plotter the plotter to add to the graph
*/
public void addPlotter(final Plotter plotter) {
plotters.add(plotter);
}
/**
* Remove a plotter from the graph
*
* @param plotter the plotter to remove from the graph
*/
public void removePlotter(final Plotter plotter) {
plotters.remove(plotter);
}
/**
* Gets an <b>unmodifiable</b> set of the plotter objects in the graph
*
* @return an unmodifiable {@link java.util.Set} of the plotter objects
*/
public Set<Plotter> getPlotters() {
return Collections.unmodifiableSet(plotters);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Graph)) {
return false;
}
final Graph graph = (Graph) object;
return graph.name.equals(name);
}
/**
* Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
*/
protected void onOptOut() {
}
}
/**
* Interface used to collect custom data for a plugin
*/
public static abstract class Plotter {
/**
* The plot's name
*/
private final String name;
/**
* Construct a plotter with the default plot name
*/
public Plotter() {
this("Default");
}
/**
* Construct a plotter with a specific plot name
*
* @param name the name of the plotter to use, which will show up on the website
*/
public Plotter(final String name) {
this.name = name;
}
/**
* Get the current value for the plotted point. Since this function defers to an external function it may or may
* not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
* from any thread so care should be taken when accessing resources that need to be synchronized.
*
* @return the current value for the point to be plotted.
*/
public abstract int getValue();
/**
* Get the column name for the plotted point
*
* @return the plotted point's column name
*/
public String getColumnName() {
return name;
}
/**
* Called after the website graphs have been updated
*/
public void reset() {
}
@Override
public int hashCode() {
return getColumnName().hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Plotter)) {
return false;
}
final Plotter plotter = (Plotter) object;
return plotter.name.equals(name) && plotter.getValue() == getValue();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,14 +10,14 @@ import static de.diddiz.LogBlock.config.Config.toolsByType;
import static org.bukkit.Bukkit.getServer;
public class Session {
private static final Map<String, Session> sessions = new HashMap<String, Session>();
private static final Map<String, Session> sessions = new HashMap<>();
public QueryParams lastQuery = null;
public LookupCacheElement[] lookupCache = null;
public int page = 1;
public Map<Tool, ToolData> toolData;
private Session(Player player) {
toolData = new HashMap<Tool, ToolData>();
toolData = new HashMap<>();
final LogBlock logblock = LogBlock.getInstance();
if (player != null) {
for (final Tool tool : toolsByType.values()) {

View File

@ -1,16 +1,17 @@
package de.diddiz.LogBlock;
import static de.diddiz.util.MessagingUtil.prettyMaterial;
import de.diddiz.LogBlock.QueryParams.SummarizationMode;
import org.bukkit.Location;
import de.diddiz.util.MessagingUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import static de.diddiz.util.MaterialName.materialName;
import static de.diddiz.util.Utils.spaces;
import java.util.Objects;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Location;
public class SummedBlockChanges implements LookupCacheElement {
private final String group;
private final int type;
private final int created, destroyed;
private final float spaceFactor;
private final Actor actor;
@ -18,7 +19,7 @@ public class SummedBlockChanges implements LookupCacheElement {
public SummedBlockChanges(ResultSet rs, QueryParams p, float spaceFactor) throws SQLException {
// Actor currently useless here as we don't yet output UUID in results anywhere
actor = p.sum == SummarizationMode.PLAYERS ? new Actor(rs) : null;
group = actor == null ? materialName(rs.getInt("type")) : actor.getName();
type = p.sum == SummarizationMode.TYPES ? rs.getInt("type") : 0;
created = rs.getInt("created");
destroyed = rs.getInt("destroyed");
this.spaceFactor = spaceFactor;
@ -30,7 +31,12 @@ public class SummedBlockChanges implements LookupCacheElement {
}
@Override
public String getMessage() {
return created + spaces((int) ((10 - String.valueOf(created).length()) / spaceFactor)) + destroyed + spaces((int) ((10 - String.valueOf(destroyed).length()) / spaceFactor)) + group;
public BaseComponent[] getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(created, destroyed, actor != null ? new TextComponent(actor.getName()) : prettyMaterial(Objects.toString(MaterialConverter.getMaterial(type))), 10, 10, spaceFactor);
}
@Override
public int getNumChanges() {
return created + destroyed;
}
}

View File

@ -0,0 +1,44 @@
package de.diddiz.LogBlock;
import de.diddiz.LogBlock.QueryParams.SummarizationMode;
import de.diddiz.util.MessagingUtil;
import org.bukkit.Location;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import static de.diddiz.util.MessagingUtil.prettyMaterial;
public class SummedEntityChanges implements LookupCacheElement {
private final int type;
private final int created, destroyed;
private final float spaceFactor;
private final Actor actor;
public SummedEntityChanges(ResultSet rs, QueryParams p, float spaceFactor) throws SQLException {
// Actor currently useless here as we don't yet output UUID in results anywhere
actor = p.sum == SummarizationMode.PLAYERS ? new Actor(rs) : null;
type = p.sum == SummarizationMode.TYPES ? rs.getInt("entitytypeid") : 0;
created = rs.getInt("created");
destroyed = rs.getInt("destroyed");
this.spaceFactor = spaceFactor;
}
@Override
public Location getLocation() {
return null;
}
@Override
public BaseComponent[] getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(created, destroyed, actor != null ? new TextComponent(actor.getName()) : prettyMaterial(Objects.toString(EntityTypeConverter.getEntityType(type))), 10, 10, spaceFactor);
}
@Override
public int getNumChanges() {
return created + destroyed;
}
}

View File

@ -1,11 +1,11 @@
package de.diddiz.LogBlock;
import org.bukkit.Location;
import de.diddiz.util.MessagingUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import static de.diddiz.util.Utils.spaces;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Location;
public class SummedKills implements LookupCacheElement {
private final Actor player;
@ -25,7 +25,12 @@ public class SummedKills implements LookupCacheElement {
}
@Override
public String getMessage() {
return kills + spaces((int) ((6 - String.valueOf(kills).length()) / spaceFactor)) + killed + spaces((int) ((7 - String.valueOf(killed).length()) / spaceFactor)) + player.getName();
public BaseComponent[] getLogMessage(int entry) {
return MessagingUtil.formatSummarizedChanges(kills, killed, new TextComponent(player.getName()), 6, 7, spaceFactor);
}
@Override
public int getNumChanges() {
return kills + killed;
}
}

View File

@ -1,5 +1,6 @@
package de.diddiz.LogBlock;
import org.bukkit.Material;
import org.bukkit.permissions.PermissionDefault;
import java.util.List;
@ -9,13 +10,15 @@ public class Tool {
public final List<String> aliases;
public final ToolBehavior leftClickBehavior, rightClickBehavior;
public final boolean defaultEnabled;
public final int item;
public final Material item;
public final boolean canDrop;
public final QueryParams params;
public final ToolMode mode;
public final PermissionDefault permissionDefault;
public final boolean removeOnDisable;
public final boolean dropToDisable;
public Tool(String name, List<String> aliases, ToolBehavior leftClickBehavior, ToolBehavior rightClickBehavior, boolean defaultEnabled, int item, boolean canDrop, QueryParams params, ToolMode mode, PermissionDefault permissionDefault) {
public Tool(String name, List<String> aliases, ToolBehavior leftClickBehavior, ToolBehavior rightClickBehavior, boolean defaultEnabled, Material item, boolean canDrop, QueryParams params, ToolMode mode, PermissionDefault permissionDefault, boolean removeOnDisable, boolean dropToDisable) {
this.name = name;
this.aliases = aliases;
this.leftClickBehavior = leftClickBehavior;
@ -26,5 +29,7 @@ public class Tool {
this.params = params;
this.mode = mode;
this.permissionDefault = permissionDefault;
this.removeOnDisable = removeOnDisable;
this.dropToDisable = dropToDisable;
}
}

View File

@ -1,5 +1,7 @@
package de.diddiz.LogBlock;
public enum ToolBehavior {
TOOL, BLOCK, NONE
TOOL,
BLOCK,
NONE
}

View File

@ -1,7 +1,11 @@
package de.diddiz.LogBlock;
public enum ToolMode {
CLEARLOG("logblock.clearlog"), LOOKUP("logblock.lookup"), REDO("logblock.rollback"), ROLLBACK("logblock.rollback"), WRITELOGFILE("logblock.rollback");
CLEARLOG("logblock.clearlog"),
LOOKUP("logblock.lookup"),
REDO("logblock.rollback"),
ROLLBACK("logblock.rollback"),
WRITELOGFILE("logblock.rollback");
private final String permission;
private ToolMode(String permission) {

File diff suppressed because it is too large Load Diff

View File

@ -1,269 +1,490 @@
package de.diddiz.LogBlock;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.command.CommandSender;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Bed;
import org.bukkit.material.PistonBaseMaterial;
import org.bukkit.material.PistonExtensionMaterial;
import java.io.File;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import static de.diddiz.LogBlock.config.Config.dontRollback;
import static de.diddiz.LogBlock.config.Config.replaceAnyway;
import static de.diddiz.util.BukkitUtils.*;
import static de.diddiz.util.MaterialName.materialName;
import static org.bukkit.Bukkit.getLogger;
public class WorldEditor implements Runnable {
private final LogBlock logblock;
private final Queue<Edit> edits = new LinkedBlockingQueue<Edit>();
private final World world;
/**
* The player responsible for editing the world, used to report progress
*/
private CommandSender sender;
private int taskID;
private int successes = 0, blacklistCollisions = 0;
private long elapsedTime = 0;
public LookupCacheElement[] errors;
public WorldEditor(LogBlock logblock, World world) {
this.logblock = logblock;
this.world = world;
}
public int getSize() {
return edits.size();
}
public int getSuccesses() {
return successes;
}
public int getErrors() {
return errors.length;
}
public int getBlacklistCollisions() {
return blacklistCollisions;
}
public void setSender(CommandSender sender) {
this.sender = sender;
}
public void queueEdit(int x, int y, int z, int replaced, int type, byte data, String signtext, short itemType, short itemAmount, short itemData) {
edits.add(new Edit(0, new Location(world, x, y, z), null, replaced, type, data, signtext, new ChestAccess(itemType, itemAmount, itemData)));
}
public long getElapsedTime() {
return elapsedTime;
}
synchronized public void start() throws Exception {
final long start = System.currentTimeMillis();
taskID = logblock.getServer().getScheduler().scheduleSyncRepeatingTask(logblock, this, 0, 1);
if (taskID == -1) {
throw new Exception("Failed to schedule task");
}
try {
this.wait();
} catch (final InterruptedException ex) {
throw new Exception("Interrupted");
}
elapsedTime = System.currentTimeMillis() - start;
}
@Override
public synchronized void run() {
final List<WorldEditorException> errorList = new ArrayList<WorldEditorException>();
int counter = 0;
float size = edits.size();
while (!edits.isEmpty() && counter < 100) {
try {
switch (edits.poll().perform()) {
case SUCCESS:
successes++;
break;
case BLACKLISTED:
blacklistCollisions++;
break;
}
} catch (final WorldEditorException ex) {
errorList.add(ex);
} catch (final Exception ex) {
getLogger().log(Level.WARNING, "[WorldEditor] Exeption: ", ex);
}
counter++;
if (sender != null) {
float percentage = ((size - edits.size()) / size) * 100.0F;
if (percentage % 20 == 0) {
sender.sendMessage(ChatColor.GOLD + "[LogBlock]" + ChatColor.YELLOW + " Rollback progress: " + percentage + "%" +
" Blocks edited: " + counter);
}
}
}
if (edits.isEmpty()) {
logblock.getServer().getScheduler().cancelTask(taskID);
if (errorList.size() > 0) {
try {
final File file = new File("plugins/LogBlock/error/WorldEditor-" + new SimpleDateFormat("yy-MM-dd-HH-mm-ss").format(System.currentTimeMillis()) + ".log");
file.getParentFile().mkdirs();
final PrintWriter writer = new PrintWriter(file);
for (final LookupCacheElement err : errorList) {
writer.println(err.getMessage());
}
writer.close();
} catch (final Exception ex) {
}
}
errors = errorList.toArray(new WorldEditorException[errorList.size()]);
notify();
}
}
private static enum PerformResult {
SUCCESS, BLACKLISTED, NO_ACTION
}
private class Edit extends BlockChange {
public Edit(long time, Location loc, Actor actor, int replaced, int type, byte data, String signtext, ChestAccess ca) {
super(time, loc, actor, replaced, type, data, signtext, ca);
}
PerformResult perform() throws WorldEditorException {
if (dontRollback.contains(replaced)) {
return PerformResult.BLACKLISTED;
}
final Block block = loc.getBlock();
if (replaced == 0 && block.getTypeId() == 0) {
return PerformResult.NO_ACTION;
}
final BlockState state = block.getState();
if (!world.isChunkLoaded(block.getChunk())) {
world.loadChunk(block.getChunk());
}
if (type == replaced) {
if (type == 0) {
if (!block.setTypeId(0)) {
throw new WorldEditorException(block.getTypeId(), 0, block.getLocation());
}
} else if (ca != null) {
if (getContainerBlocks().contains(Material.getMaterial(type))) {
int leftover;
try {
leftover = modifyContainer(state, new ItemStack(ca.itemType, -ca.itemAmount, ca.itemData));
// Special-case blocks which might be double chests
if (leftover > 0 && (type == 54 || type == 146)) {
for (final BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) {
if (block.getRelative(face).getTypeId() == type) {
leftover = modifyContainer(block.getRelative(face).getState(), new ItemStack(ca.itemType, ca.itemAmount < 0 ? leftover : -leftover, ca.itemData));
}
}
}
} catch (final Exception ex) {
throw new WorldEditorException(ex.getMessage(), block.getLocation());
}
if (!state.update()) {
throw new WorldEditorException("Failed to update inventory of " + materialName(block.getTypeId()), block.getLocation());
}
if (leftover > 0 && ca.itemAmount < 0) {
throw new WorldEditorException("Not enough space left in " + materialName(block.getTypeId()), block.getLocation());
}
}
} else {
return PerformResult.NO_ACTION;
}
return PerformResult.SUCCESS;
}
if (!(equalTypes(block.getTypeId(), type) || replaceAnyway.contains(block.getTypeId()))) {
return PerformResult.NO_ACTION;
}
if (state instanceof InventoryHolder) {
((InventoryHolder) state).getInventory().clear();
state.update();
}
if (block.getTypeId() == replaced) {
if (block.getData() != (type == 0 ? data : (byte) 0)) {
block.setData(type == 0 ? data : (byte) 0, true);
} else {
return PerformResult.NO_ACTION;
}
} else if (!block.setTypeIdAndData(replaced, type == 0 ? data : (byte) 0, true)) {
throw new WorldEditorException(block.getTypeId(), replaced, block.getLocation());
}
final int curtype = block.getTypeId();
if (signtext != null && (curtype == 63 || curtype == 68)) {
final Sign sign = (Sign) block.getState();
final String[] lines = signtext.split("\0", 4);
if (lines.length < 4) {
return PerformResult.NO_ACTION;
}
for (int i = 0; i < 4; i++) {
sign.setLine(i, lines[i]);
}
if (!sign.update()) {
throw new WorldEditorException("Failed to update signtext of " + materialName(block.getTypeId()), block.getLocation());
}
} else if (curtype == 26) {
final Bed bed = (Bed) block.getState().getData();
final Block secBlock = bed.isHeadOfBed() ? block.getRelative(bed.getFacing().getOppositeFace()) : block.getRelative(bed.getFacing());
if (secBlock.getTypeId() == 0 && !secBlock.setTypeIdAndData(26, (byte) (bed.getData() | 8), true)) {
throw new WorldEditorException(secBlock.getTypeId(), 26, secBlock.getLocation());
}
} else if ((curtype == 29 || curtype == 33) && (block.getData() & 8) > 0) {
final PistonBaseMaterial piston = (PistonBaseMaterial) block.getState().getData();
final Block secBlock = block.getRelative(piston.getFacing());
if (secBlock.getTypeId() == 0 && !secBlock.setTypeIdAndData(34, curtype == 29 ? (byte) (block.getData() | 8) : (byte) (block.getData() & ~8), true)) {
throw new WorldEditorException(secBlock.getTypeId(), 34, secBlock.getLocation());
}
} else if (curtype == 34) {
final PistonExtensionMaterial piston = (PistonExtensionMaterial) block.getState().getData();
final Block secBlock = block.getRelative(piston.getFacing().getOppositeFace());
if (secBlock.getTypeId() == 0 && !secBlock.setTypeIdAndData(piston.isSticky() ? 29 : 33, (byte) (block.getData() | 8), true)) {
throw new WorldEditorException(secBlock.getTypeId(), piston.isSticky() ? 29 : 33, secBlock.getLocation());
}
} else if (curtype == 18 && (block.getData() & 8) > 0) {
block.setData((byte) (block.getData() & 0xF7));
}
return PerformResult.SUCCESS;
}
}
@SuppressWarnings("serial")
public static class WorldEditorException extends Exception implements LookupCacheElement {
private final Location loc;
public WorldEditorException(int typeBefore, int typeAfter, Location loc) {
this("Failed to replace " + materialName(typeBefore) + " with " + materialName(typeAfter), loc);
}
public WorldEditorException(String msg, Location loc) {
super(msg + " at " + loc.getWorld().getName() + ":" + loc.getBlockX() + ":" + loc.getBlockY() + ":" + loc.getBlockZ());
this.loc = loc;
}
@Override
public Location getLocation() {
return loc;
}
}
}
package de.diddiz.LogBlock;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Container;
import org.bukkit.block.data.Bisected.Half;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Bed;
import org.bukkit.block.data.type.Chest;
import org.bukkit.block.data.type.Bed.Part;
import org.bukkit.block.data.type.Piston;
import org.bukkit.block.data.type.PistonHead;
import org.bukkit.block.data.type.TechnicalPiston.Type;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Bee;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemFrame;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.QueryParams.Order;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.Utils;
import de.diddiz.worldedit.WorldEditHelper;
import java.io.File;
import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import static de.diddiz.LogBlock.config.Config.dontRollback;
import static de.diddiz.LogBlock.config.Config.replaceAnyway;
import static de.diddiz.util.BukkitUtils.*;
public class WorldEditor implements Runnable {
private final LogBlock logblock;
private final ArrayList<Edit> edits = new ArrayList<>();
private final World world;
/**
* The player responsible for editing the world, used to report progress
*/
private CommandSender sender;
private int taskID;
private int successes = 0, blacklistCollisions = 0;
private long elapsedTime = 0;
public LookupCacheElement[] errors;
private boolean forceReplace;
private HashMap<Integer, UUID> uuidReplacements = new HashMap<>();
private boolean started = false;
public WorldEditor(LogBlock logblock, World world) {
this(logblock, world, false);
}
public WorldEditor(LogBlock logblock, World world, boolean forceReplace) {
this.logblock = logblock;
this.world = world;
this.forceReplace = forceReplace;
}
public int getSize() {
return edits.size();
}
public int getSuccesses() {
return successes;
}
public int getErrors() {
return errors.length;
}
public int getBlacklistCollisions() {
return blacklistCollisions;
}
public void setSender(CommandSender sender) {
this.sender = sender;
}
public void queueBlockEdit(long time, int x, int y, int z, int replaced, int replaceData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess item) {
if (started) {
throw new IllegalStateException("Already started");
}
edits.add(new BlockEdit(time, new Location(world, x, y, z), null, replaced, replaceData, replacedState, type, typeData, typeState, item));
}
public void queueEntityEdit(ResultSet rs, QueryParams p, boolean rollback) throws SQLException {
if (started) {
throw new IllegalStateException("Already started");
}
edits.add(new EntityEdit(rs, p, rollback));
}
public void reverseRowOrder() {
if (started) {
throw new IllegalStateException("Already started");
}
Collections.reverse(edits);
}
public void sortRows(QueryParams.Order order) {
if (started) {
throw new IllegalStateException("Already started");
}
edits.sort(new EditComparator(order));
}
public long getElapsedTime() {
return elapsedTime;
}
synchronized public void start() throws Exception {
if (started) {
throw new IllegalStateException("Already started");
}
started = true;
final long start = System.currentTimeMillis();
taskID = logblock.getServer().getScheduler().scheduleSyncRepeatingTask(logblock, this, 0, 1);
if (taskID == -1) {
throw new Exception("Failed to schedule task");
}
try {
this.wait();
} catch (final InterruptedException ex) {
throw new Exception("Interrupted");
}
elapsedTime = System.currentTimeMillis() - start;
}
@Override
public synchronized void run() {
final List<WorldEditorException> errorList = new ArrayList<>();
int counter = 0;
float size = edits.size();
while (!edits.isEmpty() && counter < 100) {
try {
switch (edits.remove(edits.size() - 1).perform()) {
case SUCCESS:
successes++;
break;
case BLACKLISTED:
blacklistCollisions++;
break;
case NO_ACTION:
break;
}
} catch (final WorldEditorException ex) {
errorList.add(ex);
} catch (final Exception ex) {
logblock.getLogger().log(Level.WARNING, "[WorldEditor] Exeption: ", ex);
}
counter++;
if (sender != null) {
float percentage = ((size - edits.size()) / size) * 100.0F;
if (percentage % 20 == 0) {
sender.sendMessage(ChatColor.GOLD + "[LogBlock]" + ChatColor.YELLOW + " Rollback progress: " + percentage + "%" +
" Blocks edited: " + counter);
}
}
}
if (edits.isEmpty()) {
logblock.getServer().getScheduler().cancelTask(taskID);
if (errorList.size() > 0) {
try {
final File file = new File("plugins/LogBlock/error/WorldEditor-" + new SimpleDateFormat("yy-MM-dd-HH-mm-ss").format(System.currentTimeMillis()) + ".log");
file.getParentFile().mkdirs();
final PrintWriter writer = new PrintWriter(file);
for (final LookupCacheElement err : errorList) {
writer.println(BaseComponent.toPlainText(err.getLogMessage()));
}
writer.close();
} catch (final Exception ex) {
}
}
errors = errorList.toArray(new WorldEditorException[errorList.size()]);
notify();
}
}
protected UUID getReplacedUUID(int entityid, UUID unreplaced) {
UUID replaced = uuidReplacements.get(entityid);
return replaced != null ? replaced : unreplaced;
}
public static enum PerformResult {
SUCCESS,
BLACKLISTED,
NO_ACTION
}
public interface Edit {
PerformResult perform() throws WorldEditorException;
public long getTime();
}
public class EntityEdit extends EntityChange implements Edit {
private boolean rollback;
public EntityEdit(ResultSet rs, QueryParams p, boolean rollback) throws SQLException {
super(rs, p);
this.rollback = rollback;
}
@Override
public long getTime() {
return date;
}
@Override
public PerformResult perform() throws WorldEditorException {
if (type == null) {
throw new WorldEditorException("Unkown entity type for entity " + entityUUID, loc);
}
if (changeType == (rollback ? EntityChangeType.KILL : EntityChangeType.CREATE)) {
// spawn entity
UUID uuid = getReplacedUUID(entityId, entityUUID);
Entity result = null;
YamlConfiguration deserialized = Utils.deserializeYamlConfiguration(data);
double x = deserialized.getDouble("x");
double y = deserialized.getDouble("y");
double z = deserialized.getDouble("z");
float yaw = (float) deserialized.getDouble("yaw");
float pitch = (float) deserialized.getDouble("pitch");
Location location = new Location(world, x, y, z, yaw, pitch);
Entity existing = BukkitUtils.loadEntityAround(location.getChunk(), uuid);
if (existing != null) {
return PerformResult.NO_ACTION;
}
byte[] serializedWorldEditEntity = (byte[]) deserialized.get("worldedit");
if (serializedWorldEditEntity != null) {
result = WorldEditHelper.restoreEntity(location, type, serializedWorldEditEntity);
}
if (result == null) {
throw new WorldEditorException("Could not restore " + type, location);
} else {
if (!result.getUniqueId().equals(uuid)) {
logblock.getConsumer().queueEntityUUIDChange(world, entityId, result.getUniqueId());
uuidReplacements.put(entityId, result.getUniqueId());
}
}
return PerformResult.SUCCESS;
} else if (changeType == (rollback ? EntityChangeType.CREATE : EntityChangeType.KILL)) {
// kill entity
UUID uuid = getReplacedUUID(entityId, entityUUID);
YamlConfiguration deserialized = Utils.deserializeYamlConfiguration(data);
double x = deserialized.getDouble("x");
double y = deserialized.getDouble("y");
double z = deserialized.getDouble("z");
float yaw = (float) deserialized.getDouble("yaw");
float pitch = (float) deserialized.getDouble("pitch");
Location location = new Location(world, x, y, z, yaw, pitch);
Entity existing = BukkitUtils.loadEntityAround(location.getChunk(), uuid);
if (existing != null) {
existing.remove();
return PerformResult.SUCCESS;
}
return PerformResult.NO_ACTION; // the entity is not there, so we cannot do anything
} else if (changeType == (rollback ? EntityChangeType.REMOVEEQUIP : EntityChangeType.ADDEQUIP)) {
// set equip
UUID uuid = getReplacedUUID(entityId, entityUUID);
Entity existing = BukkitUtils.loadEntityAround(loc.getChunk(), uuid);
if (existing != null) {
YamlConfiguration deserialized = Utils.deserializeYamlConfiguration(data);
ItemStack item = deserialized.getItemStack("item");
if (item != null && existing instanceof ItemFrame) {
ItemStack old = ((ItemFrame) existing).getItem();
if (old == null || old.getType() == Material.AIR) {
((ItemFrame) existing).setItem(item);
return PerformResult.SUCCESS;
}
} else if (item != null && existing instanceof ArmorStand) {
EquipmentSlot slot = EquipmentSlot.valueOf(deserialized.getString("slot"));
ArmorStand stand = (ArmorStand) existing;
ItemStack old = BukkitUtils.getItemInSlot(stand, slot);
if (old == null || old.getType() == Material.AIR) {
BukkitUtils.setItemInSlot(stand, slot, item);
return PerformResult.SUCCESS;
}
}
}
return PerformResult.NO_ACTION; // the entity is not there, or equip does not match
} else if (changeType == (rollback ? EntityChangeType.ADDEQUIP : EntityChangeType.REMOVEEQUIP)) {
// remove equip
UUID uuid = getReplacedUUID(entityId, entityUUID);
Entity existing = BukkitUtils.loadEntityAround(loc.getChunk(), uuid);
if (existing != null) {
YamlConfiguration deserialized = Utils.deserializeYamlConfiguration(data);
ItemStack item = deserialized.getItemStack("item");
if (item != null && existing instanceof ItemFrame) {
ItemStack old = ((ItemFrame) existing).getItem();
if (old != null && old.isSimilar(item)) {
((ItemFrame) existing).setItem(null);
return PerformResult.SUCCESS;
}
} else if (item != null && existing instanceof ArmorStand) {
EquipmentSlot slot = EquipmentSlot.valueOf(deserialized.getString("slot"));
ArmorStand stand = (ArmorStand) existing;
ItemStack old = BukkitUtils.getItemInSlot(stand, slot);
if (old != null && old.isSimilar(item)) {
BukkitUtils.setItemInSlot(stand, slot, null);
return PerformResult.SUCCESS;
}
}
}
return PerformResult.NO_ACTION; // the entity is not there, or equip does not match
} else if (changeType == EntityChangeType.GET_STUNG) {
UUID uuid = getReplacedUUID(entityId, entityUUID);
Entity existing = BukkitUtils.loadEntityAround(loc.getChunk(), uuid);
if (existing != null && existing instanceof Bee) {
((Bee) existing).setHasStung(!rollback);
}
}
return PerformResult.NO_ACTION;
}
}
public class BlockEdit extends BlockChange implements Edit {
public BlockEdit(long time, Location loc, Actor actor, int replaced, int replaceData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) {
super(time, loc, actor, replaced, replaceData, replacedState, type, typeData, typeState, ca);
}
@Override
public long getTime() {
return date;
}
@Override
public PerformResult perform() throws WorldEditorException {
BlockData replacedBlock = getBlockReplaced();
BlockData setBlock = getBlockSet();
if (replacedBlock == null || setBlock == null) {
throw new WorldEditorException("Could not parse the material", loc.clone());
}
// action: set to replaced
if (dontRollback.contains(replacedBlock.getMaterial())) {
return PerformResult.BLACKLISTED;
}
final Block block = loc.getBlock();
if (BukkitUtils.isEmpty(replacedBlock.getMaterial()) && BukkitUtils.isEmpty(block.getType())) {
return PerformResult.NO_ACTION;
}
BlockState state = block.getState();
if (setBlock.equals(replacedBlock)) {
if (ca != null) {
if (state instanceof Container && state.getType() == replacedBlock.getMaterial()) {
int leftover;
try {
leftover = modifyContainer(state, new ItemStack(ca.itemStack), !ca.remove);
} catch (final Exception ex) {
throw new WorldEditorException(ex.getMessage(), block.getLocation());
}
if (leftover > 0 && ca.remove) {
throw new WorldEditorException("Not enough space left in " + block.getType(), block.getLocation());
}
return PerformResult.SUCCESS;
}
return PerformResult.NO_ACTION;
}
}
if (!forceReplace && !BukkitUtils.isSimilarForRollback(setBlock.getMaterial(), block.getType()) && !block.isEmpty() && !replaceAnyway.contains(block.getType())) {
return PerformResult.NO_ACTION;
}
if (state instanceof Container && replacedBlock.getMaterial() != block.getType()) {
((Container) state).getSnapshotInventory().clear();
state.update();
}
block.setBlockData(replacedBlock);
BlockData newData = block.getBlockData();
if (BlockStateCodecs.hasCodec(replacedBlock.getMaterial())) {
state = block.getState();
try {
BlockStateCodecs.deserialize(state, Utils.deserializeYamlConfiguration(replacedState));
state.update();
} catch (Exception e) {
throw new WorldEditorException("Failed to restore blockstate of " + block.getType() + ": " + e, block.getLocation());
}
}
final Material curtype = block.getType();
if (newData instanceof Bed) {
final Bed bed = (Bed) newData;
final Block secBlock = bed.getPart() == Part.HEAD ? block.getRelative(bed.getFacing().getOppositeFace()) : block.getRelative(bed.getFacing());
if (secBlock.isEmpty()) {
Bed bed2 = (Bed) bed.clone();
bed2.setPart(bed.getPart() == Part.HEAD ? Part.FOOT : Part.HEAD);
secBlock.setBlockData(bed2);
}
} else if (curtype == Material.IRON_DOOR || BukkitUtils.isWoodenDoor(curtype) || BukkitUtils.isDoublePlant(curtype)) {
final Bisected firstPart = (Bisected) newData;
final Block secBlock = block.getRelative(firstPart.getHalf() == Half.TOP ? BlockFace.DOWN : BlockFace.UP);
if (secBlock.isEmpty()) {
Bisected secondPart = (Bisected) firstPart.clone();
secondPart.setHalf(firstPart.getHalf() == Half.TOP ? Half.BOTTOM : Half.TOP);
secBlock.setBlockData(secondPart);
}
} else if ((curtype == Material.PISTON || curtype == Material.STICKY_PISTON)) {
Piston piston = (Piston) newData;
if (piston.isExtended()) {
final Block secBlock = block.getRelative(piston.getFacing());
if (secBlock.isEmpty()) {
PistonHead head = (PistonHead) Material.PISTON_HEAD.createBlockData();
head.setFacing(piston.getFacing());
head.setType(curtype == Material.PISTON ? Type.NORMAL : Type.STICKY);
secBlock.setBlockData(head);
}
}
} else if (curtype == Material.PISTON_HEAD) {
PistonHead head = (PistonHead) newData;
final Block secBlock = block.getRelative(head.getFacing().getOppositeFace());
if (secBlock.isEmpty()) {
Piston piston = (Piston) (head.getType() == Type.NORMAL ? Material.PISTON : Material.STICKY_PISTON).createBlockData();
piston.setFacing(head.getFacing());
piston.setExtended(true);
secBlock.setBlockData(piston);
}
} else if (newData instanceof Chest) {
Chest chest = (Chest) newData;
if (chest.getType() != org.bukkit.block.data.type.Chest.Type.SINGLE) {
if (getConnectedChest(block) == null) {
chest.setType(org.bukkit.block.data.type.Chest.Type.SINGLE);
block.setBlockData(chest);
}
}
}
return PerformResult.SUCCESS;
}
}
public static class EditComparator implements Comparator<Edit> {
private final int mult;
public EditComparator(QueryParams.Order order) {
mult = order == Order.DESC ? 1 : -1;
}
@Override
public int compare(Edit edit1, Edit edit2) {
long time1 = edit1.getTime();
long time2 = edit2.getTime();
return time1 > time2 ? mult : time1 < time2 ? -mult : 0;
}
}
@SuppressWarnings("serial")
public static class WorldEditorException extends Exception implements LookupCacheElement {
private final Location loc;
public WorldEditorException(Material typeBefore, Material typeAfter, Location loc) {
this("Failed to replace " + typeBefore.name() + " with " + typeAfter.name(), loc);
}
public WorldEditorException(String msg, Location loc) {
super(msg + " at " + loc.getWorld().getName() + ":" + loc.getBlockX() + ":" + loc.getBlockY() + ":" + loc.getBlockZ());
this.loc = loc;
}
@Override
public Location getLocation() {
return loc;
}
@Override
public BaseComponent[] getLogMessage(int entry) {
return TextComponent.fromLegacyText(getMessage());
}
}
}

View File

@ -0,0 +1,38 @@
package de.diddiz.LogBlock;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.QueryParams.BlockChangeType;
import de.diddiz.util.Utils;
public class WorldEditorEditFactory {
private final WorldEditor editor;
private final boolean rollback;
private final QueryParams params;
public WorldEditorEditFactory(WorldEditor editor, QueryParams params, boolean rollback) {
this.editor = editor;
this.params = params;
this.rollback = rollback;
}
public void processRow(ResultSet rs) throws SQLException {
if (params.bct == BlockChangeType.ENTITIES) {
editor.queueEntityEdit(rs, params, rollback);
return;
}
ChestAccess chestaccess = null;
ItemStack stack = Utils.loadItemStack(rs.getBytes("item"));
if (stack != null) {
chestaccess = new ChestAccess(stack, rs.getBoolean("itemremove") == rollback, rs.getInt("itemtype"));
}
if (rollback) {
editor.queueBlockEdit(rs.getTimestamp("date").getTime(), rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getBytes("replacedState"), rs.getInt("type"), rs.getInt("typeData"), rs.getBytes("typeState"), chestaccess);
} else {
editor.queueBlockEdit(rs.getTimestamp("date").getTime(), rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("type"), rs.getInt("typeData"), rs.getBytes("typeState"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getBytes("replacedState"), chestaccess);
}
}
}

View File

@ -0,0 +1,15 @@
package de.diddiz.LogBlock.blockstate;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.configuration.file.YamlConfiguration;
public interface BlockStateCodec {
Material[] getApplicableMaterials();
YamlConfiguration serialize(BlockState state);
void deserialize(BlockState state, YamlConfiguration conf);
String toString(YamlConfiguration conf);
}

View File

@ -0,0 +1,70 @@
package de.diddiz.LogBlock.blockstate;
import java.util.List;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.block.Banner;
import org.bukkit.block.BlockState;
import org.bukkit.block.banner.Pattern;
import org.bukkit.block.banner.PatternType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecBanner implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.WHITE_BANNER, Material.ORANGE_BANNER, Material.MAGENTA_BANNER, Material.LIGHT_BLUE_BANNER, Material.YELLOW_BANNER, Material.LIME_BANNER, Material.PINK_BANNER, Material.GRAY_BANNER, Material.LIGHT_GRAY_BANNER, Material.CYAN_BANNER, Material.PURPLE_BANNER,
Material.BLUE_BANNER, Material.BROWN_BANNER, Material.GREEN_BANNER, Material.RED_BANNER, Material.BLACK_BANNER, Material.WHITE_WALL_BANNER, Material.ORANGE_WALL_BANNER, Material.MAGENTA_WALL_BANNER, Material.LIGHT_BLUE_WALL_BANNER, Material.YELLOW_WALL_BANNER,
Material.LIME_WALL_BANNER, Material.PINK_WALL_BANNER, Material.GRAY_WALL_BANNER, Material.LIGHT_GRAY_WALL_BANNER, Material.CYAN_WALL_BANNER, Material.PURPLE_WALL_BANNER, Material.BLUE_WALL_BANNER, Material.BROWN_WALL_BANNER, Material.GREEN_WALL_BANNER, Material.RED_WALL_BANNER,
Material.BLACK_WALL_BANNER };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Banner) {
Banner banner = (Banner) state;
int nr = 0;
List<Pattern> patterns = banner.getPatterns();
if (!patterns.isEmpty()) {
YamlConfiguration conf = new YamlConfiguration();
ConfigurationSection patternsSection = conf.createSection("patterns");
for (Pattern pattern : patterns) {
ConfigurationSection section = patternsSection.createSection(Integer.toString(nr));
section.set("color", pattern.getColor().name());
section.set("pattern", pattern.getPattern().name());
nr++;
}
return conf;
}
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Banner) {
Banner banner = (Banner) state;
int oldPatterns = banner.getPatterns().size();
for (int i = 0; i < oldPatterns; i++) {
banner.removePattern(0);
}
ConfigurationSection patternsSection = conf == null ? null : conf.getConfigurationSection("patterns");
if (patternsSection != null) {
for (String key : patternsSection.getKeys(false)) {
ConfigurationSection section = patternsSection.getConfigurationSection(key);
if (section != null) {
DyeColor color = DyeColor.valueOf(section.getString("color"));
PatternType type = PatternType.valueOf(section.getString("pattern"));
banner.addPattern(new Pattern(color, type));
}
}
}
}
}
@Override
public String toString(YamlConfiguration conf) {
return null;
}
}

View File

@ -0,0 +1,54 @@
package de.diddiz.LogBlock.blockstate;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.Lectern;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
public class BlockStateCodecLectern implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.LECTERN };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Lectern) {
Lectern lectern = (Lectern) state;
ItemStack book = lectern.getSnapshotInventory().getItem(0);
if (book != null && book.getType() != Material.AIR) {
YamlConfiguration conf = new YamlConfiguration();
conf.set("book", book);
return conf;
}
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Lectern) {
Lectern lectern = (Lectern) state;
ItemStack book = null;
if (conf != null) {
book = conf.getItemStack("book");
}
try {
lectern.getSnapshotInventory().setItem(0, book);
} catch (NullPointerException e) {
//ignored
}
}
}
@Override
public String toString(YamlConfiguration conf) {
if (conf != null) {
StringBuilder sb = new StringBuilder();
sb.append("[").append("book").append("]");
return sb.toString();
}
return null;
}
}

View File

@ -0,0 +1,85 @@
package de.diddiz.LogBlock.blockstate;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.ShulkerBox;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
public class BlockStateCodecShulkerBox implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return BukkitUtils.getShulkerBoxBlocks().toArray(new Material[BukkitUtils.getShulkerBoxBlocks().size()]);
}
@Override
public YamlConfiguration serialize(BlockState state) {
WorldConfig wcfg = getWorldConfig(state.getWorld());
if (wcfg == null || !wcfg.isLogging(Logging.SHULKER_BOX_CONTENT)) {
return null;
}
if (state instanceof ShulkerBox) {
ShulkerBox shulkerBox = (ShulkerBox) state;
ItemStack[] content = shulkerBox.getSnapshotInventory().getStorageContents();
YamlConfiguration conf = new YamlConfiguration();
boolean anySlot = false;
for (int i = 0; i < content.length; i++) {
ItemStack stack = content[i];
if (stack != null && stack.getType() != Material.AIR) {
conf.set("slot" + i, stack);
anySlot = true;
}
}
if (anySlot) {
return conf;
}
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof ShulkerBox) {
ShulkerBox shulkerBox = (ShulkerBox) state;
if (conf != null) {
ItemStack[] content = shulkerBox.getSnapshotInventory().getStorageContents();
for (int i = 0; i < content.length; i++) {
ItemStack stack = conf.getItemStack("slot" + i);
if (stack != null && stack.getType() != Material.AIR) {
content[i] = stack;
}
}
shulkerBox.getSnapshotInventory().setContents(content);
}
}
}
@Override
public String toString(YamlConfiguration conf) {
if (conf != null) {
StringBuilder sb = new StringBuilder();
sb.append("[");
boolean anySlot = false;
for (String key : conf.getKeys(false)) {
if (key.startsWith("slot")) {
ItemStack stack = conf.getItemStack(key);
if (stack != null && stack.getType() != Material.AIR) {
if (anySlot) {
sb.append(",");
}
anySlot = true;
sb.append(stack.getAmount()).append("x").append(stack.getType());
}
}
}
sb.append("]");
return anySlot ? sb.toString() : null;
}
return null;
}
}

View File

@ -0,0 +1,98 @@
package de.diddiz.LogBlock.blockstate;
import de.diddiz.util.BukkitUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecSign implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return BukkitUtils.getAllSignsArray();
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Sign) {
Sign sign = (Sign) state;
String[] lines = sign.getLines();
boolean hasText = false;
for (int i = 0; i < lines.length; i++) {
if (lines[i] != null && lines[i].length() > 0) {
hasText = true;
break;
}
}
DyeColor signColor = sign.getColor();
if (signColor == null) {
signColor = DyeColor.BLACK;
}
if (hasText || signColor != DyeColor.BLACK) {
YamlConfiguration conf = new YamlConfiguration();
if (hasText) {
conf.set("lines", Arrays.asList(lines));
}
if (signColor != DyeColor.BLACK) {
conf.set("color", signColor.name());
}
return conf;
}
}
return null;
}
/**
* This is required for the SignChangeEvent, because we have no BlockState there.
*/
public static YamlConfiguration serialize(String[] lines) {
YamlConfiguration conf = new YamlConfiguration();
conf.set("lines", Arrays.asList(lines));
return conf;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Sign) {
Sign sign = (Sign) state;
DyeColor signColor = DyeColor.BLACK;
List<String> lines = Collections.emptyList();
if (conf != null) {
if (conf.contains("lines")) {
lines = conf.getStringList("lines");
}
if (conf.contains("color")) {
try {
signColor = DyeColor.valueOf(conf.getString("color"));
} catch (IllegalArgumentException | NullPointerException e) {
// ignored
}
}
}
for (int i = 0; i < 4; i++) {
String line = lines.size() > i && lines.get(i) != null ? lines.get(i) : "";
sign.setLine(i, line);
}
sign.setColor(signColor);
}
}
@Override
public String toString(YamlConfiguration conf) {
if (conf != null) {
StringBuilder sb = new StringBuilder();
for (String line : conf.getStringList("lines")) {
if (sb.length() > 0) {
sb.append(" ");
}
sb.append("[").append(line).append("]");
}
return sb.toString();
}
return null;
}
}

View File

@ -0,0 +1,54 @@
package de.diddiz.LogBlock.blockstate;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.block.BlockState;
import org.bukkit.block.Skull;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecSkull implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.PLAYER_WALL_HEAD, Material.PLAYER_HEAD };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Skull) {
Skull skull = (Skull) state;
OfflinePlayer owner = skull.hasOwner() ? skull.getOwningPlayer() : null;
if (owner != null) {
YamlConfiguration conf = new YamlConfiguration();
conf.set("owner", owner.getUniqueId().toString());
return conf;
}
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Skull) {
Skull skull = (Skull) state;
UUID ownerId = conf == null ? null : UUID.fromString(conf.getString("owner"));
if (ownerId == null) {
skull.setOwningPlayer(null);
} else {
skull.setOwningPlayer(Bukkit.getOfflinePlayer(ownerId));
}
}
}
@Override
public String toString(YamlConfiguration conf) {
UUID ownerId = conf == null ? null : UUID.fromString(conf.getString("owner"));
if (ownerId != null) {
OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerId);
return "[" + (owner.getName() != null ? owner.getName() : owner.getUniqueId().toString()) + "]";
}
return null;
}
}

View File

@ -0,0 +1,60 @@
package de.diddiz.LogBlock.blockstate;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
public class BlockStateCodecSpawner implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.SPAWNER };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner) state;
YamlConfiguration conf = new YamlConfiguration();
conf.set("delay", spawner.getDelay());
conf.set("maxNearbyEntities", spawner.getMaxNearbyEntities());
conf.set("maxSpawnDelay", spawner.getMaxSpawnDelay());
conf.set("minSpawnDelay", spawner.getMinSpawnDelay());
conf.set("requiredPlayerRange", spawner.getRequiredPlayerRange());
conf.set("spawnCount", spawner.getSpawnCount());
conf.set("spawnedType", spawner.getSpawnedType().name());
conf.set("spawnRange", spawner.getSpawnRange());
return conf;
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner) state;
if (conf != null) {
spawner.setDelay(conf.getInt("delay"));
spawner.setMaxNearbyEntities(conf.getInt("maxNearbyEntities"));
spawner.setMaxSpawnDelay(conf.getInt("maxSpawnDelay"));
spawner.setMinSpawnDelay(conf.getInt("minSpawnDelay"));
spawner.setRequiredPlayerRange(conf.getInt("requiredPlayerRange"));
spawner.setSpawnCount(conf.getInt("spawnCount"));
spawner.setSpawnedType(EntityType.valueOf(conf.getString("spawnedType")));
spawner.setSpawnRange(conf.getInt("spawnRange"));
}
}
}
@Override
public String toString(YamlConfiguration conf) {
if (conf != null) {
EntityType entity = EntityType.valueOf(conf.getString("spawnedType"));
if (entity != null) {
return "[" + entity + "]";
}
}
return null;
}
}

View File

@ -0,0 +1,61 @@
package de.diddiz.LogBlock.blockstate;
import java.util.EnumMap;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecs {
private static Map<Material, BlockStateCodec> codecs = new EnumMap<>(Material.class);
public static void registerCodec(BlockStateCodec codec) {
Material[] materials = codec.getApplicableMaterials();
for (Material material : materials) {
if (codecs.containsKey(material)) {
throw new IllegalArgumentException("BlockStateCodec for " + material + " already registered!");
}
codecs.put(material, codec);
}
}
static {
registerCodec(new BlockStateCodecSign());
registerCodec(new BlockStateCodecSkull());
registerCodec(new BlockStateCodecBanner());
registerCodec(new BlockStateCodecSpawner());
registerCodec(new BlockStateCodecLectern());
registerCodec(new BlockStateCodecShulkerBox());
}
public static boolean hasCodec(Material material) {
return codecs.containsKey(material);
}
public static YamlConfiguration serialize(BlockState state) {
BlockStateCodec codec = codecs.get(state.getType());
if (codec != null) {
YamlConfiguration serialized = codec.serialize(state);
if (serialized != null && !serialized.getKeys(false).isEmpty()) {
return serialized;
}
}
return null;
}
public static void deserialize(BlockState block, YamlConfiguration state) {
BlockStateCodec codec = codecs.get(block.getType());
if (codec != null) {
codec.deserialize(block, state);
}
}
public static String toString(Material material, YamlConfiguration state) {
BlockStateCodec codec = codecs.get(material);
if (codec != null) {
return codec.toString(state);
}
return null;
}
}

View File

@ -1,293 +1,376 @@
package de.diddiz.LogBlock.config;
import de.diddiz.LogBlock.*;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.permissions.PermissionDefault;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.zip.DataFormatException;
import static de.diddiz.util.BukkitUtils.friendlyWorldname;
import static de.diddiz.util.Utils.parseTimeSpec;
import static org.bukkit.Bukkit.*;
public class Config {
private static LoggingEnabledMapping superWorldConfig;
private static Map<String, WorldConfig> worldConfigs;
public static String url, user, password;
public static int delayBetweenRuns, forceToProcessAtLeast, timePerRun;
public static boolean fireCustomEvents;
public static boolean useBukkitScheduler;
public static int queueWarningSize;
public static boolean enableAutoClearLog;
public static List<String> autoClearLog;
public static int autoClearLogDelay;
public static boolean dumpDeletedLog;
public static boolean logCreeperExplosionsAsPlayerWhoTriggeredThese, logPlayerInfo;
public static LogKillsLevel logKillsLevel;
public static Set<Integer> dontRollback, replaceAnyway;
public static int rollbackMaxTime, rollbackMaxArea;
public static Map<String, Tool> toolsByName;
public static Map<Integer, Tool> toolsByType;
public static int defaultDist, defaultTime;
public static int linesPerPage, linesLimit;
public static boolean askRollbacks, askRedos, askClearLogs, askClearLogAfterRollback, askRollbackAfterBan;
public static String banPermission;
public static Set<Integer> hiddenBlocks;
public static Set<String> hiddenPlayers;
public static Set<String> ignoredChat;
public static SimpleDateFormat formatter;
public static boolean safetyIdCheck;
public static boolean debug;
public static boolean logEnvironmentalKills;
// Not loaded from config - checked at runtime
public static boolean mb4 = false;
public static enum LogKillsLevel {
PLAYERS, MONSTERS, ANIMALS;
}
public static void load(LogBlock logblock) throws DataFormatException, IOException {
final ConfigurationSection config = logblock.getConfig();
final Map<String, Object> def = new HashMap<String, Object>();
def.put("version", logblock.getDescription().getVersion());
final List<String> worldNames = new ArrayList<String>();
for (final World world : getWorlds()) {
worldNames.add(world.getName());
}
if (worldNames.isEmpty()) {
worldNames.add("world");
worldNames.add("world_nether");
worldNames.add("world_the_end");
}
def.put("loggedWorlds", worldNames);
def.put("mysql.host", "localhost");
def.put("mysql.port", 3306);
def.put("mysql.database", "minecraft");
def.put("mysql.user", "username");
def.put("mysql.password", "pass");
def.put("consumer.delayBetweenRuns", 2);
def.put("consumer.forceToProcessAtLeast", 200);
def.put("consumer.timePerRun", 1000);
def.put("consumer.fireCustomEvents", false);
def.put("consumer.useBukkitScheduler", true);
def.put("consumer.queueWarningSize", 1000);
def.put("clearlog.dumpDeletedLog", false);
def.put("clearlog.enableAutoClearLog", false);
def.put("clearlog.auto", Arrays.asList("world \"world\" before 365 days all", "world \"world\" player lavaflow waterflow leavesdecay before 7 days all", "world world_nether before 365 days all", "world world_nether player lavaflow before 7 days all"));
def.put("clearlog.autoClearLogDelay", "6h");
def.put("logging.logCreeperExplosionsAsPlayerWhoTriggeredThese", false);
def.put("logging.logKillsLevel", "PLAYERS");
def.put("logging.logEnvironmentalKills", false);
def.put("logging.logPlayerInfo", false);
def.put("logging.hiddenPlayers", new ArrayList<String>());
def.put("logging.hiddenBlocks", Arrays.asList(0));
def.put("logging.ignoredChat", Arrays.asList("/register", "/login"));
def.put("rollback.dontRollback", Arrays.asList(10, 11, 46, 51));
def.put("rollback.replaceAnyway", Arrays.asList(8, 9, 10, 11, 51));
def.put("rollback.maxTime", "2 days");
def.put("rollback.maxArea", 50);
def.put("lookup.defaultDist", 20);
def.put("lookup.defaultTime", "30 minutes");
def.put("lookup.linesPerPage", 15);
def.put("lookup.linesLimit", 1500);
try {
formatter = new SimpleDateFormat(config.getString("lookup.dateFormat", "MM-dd HH:mm:ss"));
} catch (IllegalArgumentException e) {
throw new DataFormatException("Invalid specification for date format, please see http://docs.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html : " + e.getMessage());
}
def.put("lookup.dateFormat", "MM-dd HH:mm:ss");
def.put("questioner.askRollbacks", true);
def.put("questioner.askRedos", true);
def.put("questioner.askClearLogs", true);
def.put("questioner.askClearLogAfterRollback", true);
def.put("questioner.askRollbackAfterBan", false);
def.put("questioner.banPermission", "mcbans.ban.local");
def.put("tools.tool.aliases", Arrays.asList("t"));
def.put("tools.tool.leftClickBehavior", "NONE");
def.put("tools.tool.rightClickBehavior", "TOOL");
def.put("tools.tool.defaultEnabled", true);
def.put("tools.tool.item", 270);
def.put("tools.tool.canDrop", true);
def.put("tools.tool.params", "area 0 all sum none limit 15 desc silent");
def.put("tools.tool.mode", "LOOKUP");
def.put("tools.tool.permissionDefault", "OP");
def.put("tools.toolblock.aliases", Arrays.asList("tb"));
def.put("tools.toolblock.leftClickBehavior", "TOOL");
def.put("tools.toolblock.rightClickBehavior", "BLOCK");
def.put("tools.toolblock.defaultEnabled", true);
def.put("tools.toolblock.item", 7);
def.put("tools.toolblock.canDrop", false);
def.put("tools.toolblock.params", "area 0 all sum none limit 15 desc silent");
def.put("tools.toolblock.mode", "LOOKUP");
def.put("tools.toolblock.permissionDefault", "OP");
def.put("safety.id.check", true);
def.put("debug", false);
for (final Entry<String, Object> e : def.entrySet()) {
if (!config.contains(e.getKey())) {
config.set(e.getKey(), e.getValue());
}
}
logblock.saveConfig();
url = "jdbc:mysql://" + config.getString("mysql.host") + ":" + config.getInt("mysql.port") + "/" + getStringIncludingInts(config, "mysql.database");
user = getStringIncludingInts(config, "mysql.user");
password = getStringIncludingInts(config, "mysql.password");
delayBetweenRuns = config.getInt("consumer.delayBetweenRuns", 2);
forceToProcessAtLeast = config.getInt("consumer.forceToProcessAtLeast", 0);
timePerRun = config.getInt("consumer.timePerRun", 1000);
fireCustomEvents = config.getBoolean("consumer.fireCustomEvents", false);
useBukkitScheduler = config.getBoolean("consumer.useBukkitScheduler", true);
queueWarningSize = config.getInt("consumer.queueWarningSize", 1000);
enableAutoClearLog = config.getBoolean("clearlog.enableAutoClearLog");
autoClearLog = config.getStringList("clearlog.auto");
dumpDeletedLog = config.getBoolean("clearlog.dumpDeletedLog", false);
autoClearLogDelay = parseTimeSpec(config.getString("clearlog.autoClearLogDelay").split(" "));
logCreeperExplosionsAsPlayerWhoTriggeredThese = config.getBoolean("logging.logCreeperExplosionsAsPlayerWhoTriggeredThese", false);
logPlayerInfo = config.getBoolean("logging.logPlayerInfo", true);
try {
logKillsLevel = LogKillsLevel.valueOf(config.getString("logging.logKillsLevel").toUpperCase());
} catch (final IllegalArgumentException ex) {
throw new DataFormatException("logging.logKillsLevel doesn't appear to be a valid log level. Allowed are 'PLAYERS', 'MONSTERS' and 'ANIMALS'");
}
logEnvironmentalKills = config.getBoolean("logging.logEnvironmentalKills", false);
hiddenPlayers = new HashSet<String>();
for (final String playerName : config.getStringList("logging.hiddenPlayers")) {
hiddenPlayers.add(playerName.toLowerCase().trim());
}
hiddenBlocks = new HashSet<Integer>();
for (final Object blocktype : config.getList("logging.hiddenBlocks")) {
final Material mat = Material.matchMaterial(String.valueOf(blocktype));
if (mat != null) {
hiddenBlocks.add(mat.getId());
} else {
throw new DataFormatException("Not a valid material: '" + blocktype + "'");
}
}
ignoredChat = new HashSet<String>();
for (String chatCommand : config.getStringList("logging.ignoredChat")) {
ignoredChat.add(chatCommand);
}
dontRollback = new HashSet<Integer>(config.getIntegerList("rollback.dontRollback"));
replaceAnyway = new HashSet<Integer>(config.getIntegerList("rollback.replaceAnyway"));
rollbackMaxTime = parseTimeSpec(config.getString("rollback.maxTime").split(" "));
rollbackMaxArea = config.getInt("rollback.maxArea", 50);
defaultDist = config.getInt("lookup.defaultDist", 20);
defaultTime = parseTimeSpec(config.getString("lookup.defaultTime").split(" "));
linesPerPage = config.getInt("lookup.linesPerPage", 15);
linesLimit = config.getInt("lookup.linesLimit", 1500);
askRollbacks = config.getBoolean("questioner.askRollbacks", true);
askRedos = config.getBoolean("questioner.askRedos", true);
askClearLogs = config.getBoolean("questioner.askClearLogs", true);
askClearLogAfterRollback = config.getBoolean("questioner.askClearLogAfterRollback", true);
askRollbackAfterBan = config.getBoolean("questioner.askRollbackAfterBan", false);
safetyIdCheck = config.getBoolean("safety.id.check", true);
debug = config.getBoolean("debug", false);
banPermission = config.getString("questioner.banPermission");
final List<Tool> tools = new ArrayList<Tool>();
final ConfigurationSection toolsSec = config.getConfigurationSection("tools");
for (final String toolName : toolsSec.getKeys(false)) {
try {
final ConfigurationSection tSec = toolsSec.getConfigurationSection(toolName);
final List<String> aliases = tSec.getStringList("aliases");
final ToolBehavior leftClickBehavior = ToolBehavior.valueOf(tSec.getString("leftClickBehavior").toUpperCase());
final ToolBehavior rightClickBehavior = ToolBehavior.valueOf(tSec.getString("rightClickBehavior").toUpperCase());
final boolean defaultEnabled = tSec.getBoolean("defaultEnabled", false);
final int item = tSec.getInt("item", 0);
final boolean canDrop = tSec.getBoolean("canDrop", false);
final QueryParams params = new QueryParams(logblock);
params.prepareToolQuery = true;
params.parseArgs(getConsoleSender(), Arrays.asList(tSec.getString("params").split(" ")));
final ToolMode mode = ToolMode.valueOf(tSec.getString("mode").toUpperCase());
final PermissionDefault pdef = PermissionDefault.valueOf(tSec.getString("permissionDefault").toUpperCase());
tools.add(new Tool(toolName, aliases, leftClickBehavior, rightClickBehavior, defaultEnabled, item, canDrop, params, mode, pdef));
} catch (final Exception ex) {
getLogger().log(Level.WARNING, "Error at parsing tool '" + toolName + "': ", ex);
}
}
toolsByName = new HashMap<String, Tool>();
toolsByType = new HashMap<Integer, Tool>();
for (final Tool tool : tools) {
toolsByType.put(tool.item, tool);
toolsByName.put(tool.name.toLowerCase(), tool);
for (final String alias : tool.aliases) {
toolsByName.put(alias, tool);
}
}
final List<String> loggedWorlds = config.getStringList("loggedWorlds");
worldConfigs = new HashMap<String, WorldConfig>();
if (loggedWorlds.isEmpty()) {
throw new DataFormatException("No worlds configured");
}
for (final String world : loggedWorlds) {
worldConfigs.put(world, new WorldConfig(new File(logblock.getDataFolder(), friendlyWorldname(world) + ".yml")));
}
superWorldConfig = new LoggingEnabledMapping();
for (final WorldConfig wcfg : worldConfigs.values()) {
for (final Logging l : Logging.values()) {
if (wcfg.isLogging(l)) {
superWorldConfig.setLogging(l, true);
}
}
}
}
private static String getStringIncludingInts(ConfigurationSection cfg, String key) {
String str = cfg.getString(key);
if (str == null) {
str = String.valueOf(cfg.getInt(key));
}
if (str == null) {
str = "No value set for '" + key + "'";
}
return str;
}
public static boolean isLogging(World world, Logging l) {
final WorldConfig wcfg = worldConfigs.get(world.getName());
return wcfg != null && wcfg.isLogging(l);
}
public static boolean isLogging(String worldName, Logging l) {
final WorldConfig wcfg = worldConfigs.get(worldName);
return wcfg != null && wcfg.isLogging(l);
}
public static boolean isLogged(World world) {
return worldConfigs.containsKey(world.getName());
}
public static WorldConfig getWorldConfig(World world) {
return worldConfigs.get(world.getName());
}
public static WorldConfig getWorldConfig(String world) {
return worldConfigs.get(world);
}
public static boolean isLogging(Logging l) {
return superWorldConfig.isLogging(l);
}
public static Collection<WorldConfig> getLoggedWorlds() {
return worldConfigs.values();
}
}
class LoggingEnabledMapping {
private final boolean[] logging = new boolean[Logging.length];
public void setLogging(Logging l, boolean enabled) {
logging[l.ordinal()] = enabled;
}
public boolean isLogging(Logging l) {
return logging[l.ordinal()];
}
}
package de.diddiz.LogBlock.config;
import de.diddiz.LogBlock.*;
import de.diddiz.util.ComparableVersion;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.permissions.PermissionDefault;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.zip.DataFormatException;
import static de.diddiz.util.BukkitUtils.friendlyWorldname;
import static de.diddiz.util.Utils.parseTimeSpec;
import static org.bukkit.Bukkit.*;
public class Config {
private static LoggingEnabledMapping superWorldConfig;
private static Map<String, WorldConfig> worldConfigs;
public static String url, user, password;
public static String mysqlDatabase;
public static boolean mysqlUseSSL;
public static boolean mysqlRequireSSL;
public static int delayBetweenRuns, forceToProcessAtLeast, timePerRun;
public static boolean fireCustomEvents;
public static boolean useBukkitScheduler;
public static int queueWarningSize;
public static boolean enableAutoClearLog;
public static List<String> autoClearLog;
public static int autoClearLogDelay;
public static boolean dumpDeletedLog;
public static boolean logBedExplosionsAsPlayerWhoTriggeredThese;
public static boolean logCreeperExplosionsAsPlayerWhoTriggeredThese, logPlayerInfo;
public static boolean logFireSpreadAsPlayerWhoCreatedIt;
public static boolean logFluidFlowAsPlayerWhoTriggeredIt;
public static LogKillsLevel logKillsLevel;
public static Set<Material> dontRollback, replaceAnyway;
public static int rollbackMaxTime, rollbackMaxArea;
public static Map<String, Tool> toolsByName;
public static Map<Material, Tool> toolsByType;
public static int defaultDist, defaultTime;
public static int linesPerPage, linesLimit, hardLinesLimit;
public static boolean askRollbacks, askRedos, askClearLogs, askClearLogAfterRollback, askRollbackAfterBan;
public static String banPermission;
public static Set<Material> hiddenBlocks;
public static Set<String> hiddenPlayers;
public static List<String> ignoredChat;
public static SimpleDateFormat formatter;
public static SimpleDateFormat formatterShort;
public static boolean safetyIdCheck;
public static boolean debug;
public static boolean logEnvironmentalKills;
// Not loaded from config - checked at runtime
public static boolean mb4 = false;
public static final String CURRENT_CONFIG_VERSION = "1.16.0";
public static enum LogKillsLevel {
PLAYERS,
MONSTERS,
ANIMALS;
}
public static void load(LogBlock logblock) throws DataFormatException, IOException {
final ConfigurationSection config = logblock.getConfig();
final Map<String, Object> def = new HashMap<>();
def.put("version", CURRENT_CONFIG_VERSION);
final List<String> worldNames = new ArrayList<>();
for (final World world : getWorlds()) {
worldNames.add(world.getName());
}
if (worldNames.isEmpty()) {
worldNames.add("world");
worldNames.add("world_nether");
worldNames.add("world_the_end");
}
def.put("loggedWorlds", worldNames);
def.put("mysql.host", "localhost");
def.put("mysql.port", 3306);
def.put("mysql.database", "minecraft");
def.put("mysql.user", "username");
def.put("mysql.password", "pass");
def.put("mysql.useSSL", true);
def.put("mysql.requireSSL", false);
def.put("consumer.delayBetweenRuns", 2);
def.put("consumer.forceToProcessAtLeast", 200);
def.put("consumer.timePerRun", 1000);
def.put("consumer.fireCustomEvents", false);
def.put("consumer.useBukkitScheduler", true);
def.put("consumer.queueWarningSize", 1000);
def.put("clearlog.dumpDeletedLog", false);
def.put("clearlog.enableAutoClearLog", false);
final List<String> autoClearlog = new ArrayList<>();
for (final String world : worldNames) {
autoClearlog.add("world \"" + world + "\" before 365 days all");
autoClearlog.add("world \"" + world + "\" player lavaflow waterflow leavesdecay before 7 days all");
autoClearlog.add("world \"" + world + "\" entities before 365 days");
}
def.put("clearlog.auto", autoClearlog);
def.put("clearlog.autoClearLogDelay", "6h");
def.put("logging.logBedExplosionsAsPlayerWhoTriggeredThese", true);
def.put("logging.logCreeperExplosionsAsPlayerWhoTriggeredThese", false);
def.put("logging.logFireSpreadAsPlayerWhoCreatedIt", true);
def.put("logging.logFluidFlowAsPlayerWhoTriggeredIt", false);
def.put("logging.logKillsLevel", "PLAYERS");
def.put("logging.logEnvironmentalKills", false);
def.put("logging.logPlayerInfo", false);
def.put("logging.hiddenPlayers", new ArrayList<String>());
def.put("logging.hiddenBlocks", Arrays.asList(Material.AIR.name(), Material.CAVE_AIR.name(), Material.VOID_AIR.name()));
def.put("logging.ignoredChat", Arrays.asList("/register", "/login"));
def.put("rollback.dontRollback", Arrays.asList(Material.LAVA.name(), Material.TNT.name(), Material.FIRE.name()));
def.put("rollback.replaceAnyway", Arrays.asList(Material.LAVA.name(), Material.WATER.name(), Material.FIRE.name(), Material.GRASS_BLOCK.name()));
def.put("rollback.maxTime", "2 days");
def.put("rollback.maxArea", 50);
def.put("lookup.defaultDist", 20);
def.put("lookup.defaultTime", "30 minutes");
def.put("lookup.linesPerPage", 15);
def.put("lookup.linesLimit", 1500);
def.put("lookup.hardLinesLimit", 100000);
try {
formatter = new SimpleDateFormat(config.getString("lookup.dateFormat", "yyyy-MM-dd HH:mm:ss"));
} catch (IllegalArgumentException e) {
throw new DataFormatException("Invalid specification for date format, please see http://docs.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html : " + e.getMessage());
}
def.put("lookup.dateFormat", "yyyy-MM-dd HH:mm:ss");
try {
formatterShort = new SimpleDateFormat(config.getString("lookup.dateFormatShort", "MM-dd HH:mm"));
} catch (IllegalArgumentException e) {
throw new DataFormatException("Invalid specification for date format, please see http://docs.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html : " + e.getMessage());
}
def.put("lookup.dateFormatShort", "MM-dd HH:mm");
def.put("questioner.askRollbacks", true);
def.put("questioner.askRedos", true);
def.put("questioner.askClearLogs", true);
def.put("questioner.askClearLogAfterRollback", true);
def.put("questioner.askRollbackAfterBan", false);
def.put("questioner.banPermission", "mcbans.ban.local");
def.put("tools.tool.aliases", Arrays.asList("t"));
def.put("tools.tool.leftClickBehavior", "NONE");
def.put("tools.tool.rightClickBehavior", "TOOL");
def.put("tools.tool.defaultEnabled", true);
def.put("tools.tool.item", Material.WOODEN_PICKAXE.name());
def.put("tools.tool.canDrop", true);
def.put("tools.tool.removeOnDisable", true);
def.put("tools.tool.dropToDisable", false);
def.put("tools.tool.params", "area 0 all sum none limit 15 desc since 60d silent");
def.put("tools.tool.mode", "LOOKUP");
def.put("tools.tool.permissionDefault", "OP");
def.put("tools.toolblock.aliases", Arrays.asList("tb"));
def.put("tools.toolblock.leftClickBehavior", "TOOL");
def.put("tools.toolblock.rightClickBehavior", "BLOCK");
def.put("tools.toolblock.defaultEnabled", true);
def.put("tools.toolblock.item", Material.BEDROCK.name());
def.put("tools.toolblock.canDrop", false);
def.put("tools.toolblock.removeOnDisable", true);
def.put("tools.toolblock.dropToDisable", false);
def.put("tools.toolblock.params", "area 0 all sum none limit 15 desc since 60d silent");
def.put("tools.toolblock.mode", "LOOKUP");
def.put("tools.toolblock.permissionDefault", "OP");
def.put("safety.id.check", true);
def.put("debug", false);
for (final Entry<String, Object> e : def.entrySet()) {
if (!config.contains(e.getKey())) {
config.set(e.getKey(), e.getValue());
}
}
logblock.saveConfig();
ComparableVersion configVersion = new ComparableVersion(config.getString("version"));
boolean oldConfig = configVersion.compareTo(new ComparableVersion(CURRENT_CONFIG_VERSION)) < 0;
mysqlDatabase = getStringIncludingInts(config, "mysql.database");
url = "jdbc:mysql://" + config.getString("mysql.host") + ":" + config.getInt("mysql.port") + "/" + mysqlDatabase;
user = getStringIncludingInts(config, "mysql.user");
password = getStringIncludingInts(config, "mysql.password");
mysqlUseSSL = config.getBoolean("mysql.useSSL", true);
mysqlRequireSSL = config.getBoolean("mysql.requireSSL", false);
delayBetweenRuns = config.getInt("consumer.delayBetweenRuns", 2);
forceToProcessAtLeast = config.getInt("consumer.forceToProcessAtLeast", 0);
timePerRun = config.getInt("consumer.timePerRun", 1000);
fireCustomEvents = config.getBoolean("consumer.fireCustomEvents", false);
useBukkitScheduler = config.getBoolean("consumer.useBukkitScheduler", true);
queueWarningSize = config.getInt("consumer.queueWarningSize", 1000);
enableAutoClearLog = config.getBoolean("clearlog.enableAutoClearLog");
autoClearLog = config.getStringList("clearlog.auto");
dumpDeletedLog = config.getBoolean("clearlog.dumpDeletedLog", false);
autoClearLogDelay = parseTimeSpec(config.getString("clearlog.autoClearLogDelay").split(" "));
logBedExplosionsAsPlayerWhoTriggeredThese = config.getBoolean("logging.logBedExplosionsAsPlayerWhoTriggeredThese", true);
logCreeperExplosionsAsPlayerWhoTriggeredThese = config.getBoolean("logging.logCreeperExplosionsAsPlayerWhoTriggeredThese", false);
logFireSpreadAsPlayerWhoCreatedIt = config.getBoolean("logging.logFireSpreadAsPlayerWhoCreatedIt", true);
logFluidFlowAsPlayerWhoTriggeredIt = config.getBoolean("logging.logFluidFlowAsPlayerWhoTriggeredIt", false);
logPlayerInfo = config.getBoolean("logging.logPlayerInfo", true);
try {
logKillsLevel = LogKillsLevel.valueOf(config.getString("logging.logKillsLevel").toUpperCase());
} catch (final IllegalArgumentException ex) {
throw new DataFormatException("logging.logKillsLevel doesn't appear to be a valid log level. Allowed are 'PLAYERS', 'MONSTERS' and 'ANIMALS'");
}
logEnvironmentalKills = config.getBoolean("logging.logEnvironmentalKills", false);
hiddenPlayers = new HashSet<>();
for (final String playerName : config.getStringList("logging.hiddenPlayers")) {
hiddenPlayers.add(playerName.toLowerCase().trim());
}
hiddenBlocks = new HashSet<>();
for (final String blocktype : config.getStringList("logging.hiddenBlocks")) {
final Material mat = Material.matchMaterial(blocktype);
if (mat != null) {
hiddenBlocks.add(mat);
} else if (!oldConfig) {
throw new DataFormatException("Not a valid material in hiddenBlocks: '" + blocktype + "'");
}
}
ignoredChat = new ArrayList<>();
for (String chatCommand : config.getStringList("logging.ignoredChat")) {
ignoredChat.add(chatCommand.toLowerCase());
}
dontRollback = new HashSet<>();
for (String e : config.getStringList("rollback.dontRollback")) {
Material mat = Material.matchMaterial(e);
if (mat != null) {
dontRollback.add(mat);
} else if (!oldConfig) {
throw new DataFormatException("Not a valid material in dontRollback: '" + e + "'");
}
}
replaceAnyway = new HashSet<>();
for (String e : config.getStringList("rollback.replaceAnyway")) {
Material mat = Material.matchMaterial(e);
if (mat != null) {
replaceAnyway.add(mat);
} else if (!oldConfig) {
throw new DataFormatException("Not a valid material in replaceAnyway: '" + e + "'");
}
}
rollbackMaxTime = parseTimeSpec(config.getString("rollback.maxTime").split(" "));
rollbackMaxArea = config.getInt("rollback.maxArea", 50);
defaultDist = config.getInt("lookup.defaultDist", 20);
defaultTime = parseTimeSpec(config.getString("lookup.defaultTime").split(" "));
linesPerPage = config.getInt("lookup.linesPerPage", 15);
linesLimit = config.getInt("lookup.linesLimit", 1500);
hardLinesLimit = config.getInt("lookup.hardLinesLimit", 100000);
askRollbacks = config.getBoolean("questioner.askRollbacks", true);
askRedos = config.getBoolean("questioner.askRedos", true);
askClearLogs = config.getBoolean("questioner.askClearLogs", true);
askClearLogAfterRollback = config.getBoolean("questioner.askClearLogAfterRollback", true);
askRollbackAfterBan = config.getBoolean("questioner.askRollbackAfterBan", false);
safetyIdCheck = config.getBoolean("safety.id.check", true);
debug = config.getBoolean("debug", false);
banPermission = config.getString("questioner.banPermission");
final List<Tool> tools = new ArrayList<>();
final ConfigurationSection toolsSec = config.getConfigurationSection("tools");
for (final String toolName : toolsSec.getKeys(false)) {
try {
final ConfigurationSection tSec = toolsSec.getConfigurationSection(toolName);
final List<String> aliases = tSec.getStringList("aliases");
final ToolBehavior leftClickBehavior = ToolBehavior.valueOf(tSec.getString("leftClickBehavior").toUpperCase());
final ToolBehavior rightClickBehavior = ToolBehavior.valueOf(tSec.getString("rightClickBehavior").toUpperCase());
final boolean defaultEnabled = tSec.getBoolean("defaultEnabled", false);
final Material item = Material.matchMaterial(tSec.getString("item", "OAK_LOG"));
final boolean canDrop = tSec.getBoolean("canDrop", false);
final boolean removeOnDisable = tSec.getBoolean("removeOnDisable", true);
final boolean dropToDisable = tSec.getBoolean("dropToDisable", false);
final QueryParams params = new QueryParams(logblock);
params.prepareToolQuery = true;
params.parseArgs(getConsoleSender(), Arrays.asList(tSec.getString("params").split(" ")), false);
final ToolMode mode = ToolMode.valueOf(tSec.getString("mode").toUpperCase());
final PermissionDefault pdef = PermissionDefault.valueOf(tSec.getString("permissionDefault").toUpperCase());
tools.add(new Tool(toolName, aliases, leftClickBehavior, rightClickBehavior, defaultEnabled, item, canDrop, params, mode, pdef, removeOnDisable, dropToDisable));
} catch (final Exception ex) {
getLogger().log(Level.WARNING, "Error at parsing tool '" + toolName + "': ", ex);
}
}
toolsByName = new HashMap<>();
toolsByType = new HashMap<>();
for (final Tool tool : tools) {
toolsByType.put(tool.item, tool);
toolsByName.put(tool.name.toLowerCase(), tool);
for (final String alias : tool.aliases) {
toolsByName.put(alias, tool);
}
}
final List<String> loggedWorlds = config.getStringList("loggedWorlds");
worldConfigs = new HashMap<>();
if (loggedWorlds.isEmpty()) {
throw new DataFormatException("No worlds configured");
}
for (final String world : loggedWorlds) {
worldConfigs.put(world, new WorldConfig(world, new File(logblock.getDataFolder(), friendlyWorldname(world) + ".yml")));
}
superWorldConfig = new LoggingEnabledMapping();
for (final WorldConfig wcfg : worldConfigs.values()) {
for (final Logging l : Logging.values()) {
if (wcfg.isLogging(l)) {
superWorldConfig.setLogging(l, true);
}
}
}
}
private static String getStringIncludingInts(ConfigurationSection cfg, String key) {
String str = cfg.getString(key);
if (str == null) {
str = String.valueOf(cfg.getInt(key));
}
if (str == null) {
str = "No value set for '" + key + "'";
}
return str;
}
public static boolean isLogging(World world, Logging l) {
final WorldConfig wcfg = worldConfigs.get(world.getName());
return wcfg != null && wcfg.isLogging(l);
}
public static boolean isLogging(String worldName, Logging l) {
final WorldConfig wcfg = worldConfigs.get(worldName);
return wcfg != null && wcfg.isLogging(l);
}
public static boolean isLogged(World world) {
return worldConfigs.containsKey(world.getName());
}
public static WorldConfig getWorldConfig(World world) {
return worldConfigs.get(world.getName());
}
public static WorldConfig getWorldConfig(String world) {
return worldConfigs.get(world);
}
public static boolean isLogging(Logging l) {
return superWorldConfig.isLogging(l);
}
public static Collection<WorldConfig> getLoggedWorlds() {
return worldConfigs.values();
}
public static boolean isLogging(World world, EntityLogging logging, Entity entity) {
final WorldConfig wcfg = worldConfigs.get(world.getName());
return wcfg != null && wcfg.isLogging(logging, entity);
}
public static boolean isLoggingAnyEntities() {
for (WorldConfig worldConfig : worldConfigs.values()) {
if (worldConfig.isLoggingAnyEntities()) {
return true;
}
}
return false;
}
}
class LoggingEnabledMapping {
private final EnumSet<Logging> logging = EnumSet.noneOf(Logging.class);
public void setLogging(Logging l, boolean enabled) {
if (enabled) {
logging.add(l);
} else {
logging.remove(l);
}
}
public boolean isLogging(Logging l) {
return logging.contains(l);
}
}

View File

@ -0,0 +1,28 @@
package de.diddiz.LogBlock.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bukkit.entity.EntityType;
public enum EntityLogging {
SPAWN(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.IRON_GOLEM.name(), EntityType.SNOWMAN.name() }),
DESTROY(new String[] { EntityType.ARMOR_STAND.name(), EntityType.ITEM_FRAME.name(), EntityType.VILLAGER.name(), EntityType.IRON_GOLEM.name(), EntityType.SNOWMAN.name(), "ANIMAL" }),
MODIFY(new String[] { "ALL" });
public static final int length = EntityLogging.values().length;
private final List<String> defaultEnabled;
private EntityLogging() {
this(null);
}
private EntityLogging(String[] defaultEnabled) {
this.defaultEnabled = defaultEnabled == null ? Collections.emptyList() : Collections.unmodifiableList(Arrays.asList(defaultEnabled));
}
public List<String> getDefaultEnabled() {
return defaultEnabled;
}
}

View File

@ -1,19 +1,44 @@
package de.diddiz.LogBlock.config;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.util.BukkitUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Animals;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Monster;
import org.bukkit.entity.Player;
import org.bukkit.entity.WaterMob;
import java.io.File;
import java.io.IOException;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
public class WorldConfig extends LoggingEnabledMapping {
public final String world;
public final String table;
public final String insertBlockStatementString;
public final String selectBlockActorIdStatementString;
public final String insertBlockStateStatementString;
public final String insertBlockChestDataStatementString;
public final String insertEntityStatementString;
public final String updateEntityUUIDString;
public WorldConfig(File file) throws IOException {
final Map<String, Object> def = new HashMap<String, Object>();
private final EnumMap<EntityLogging, EntityLoggingList> entityLogging = new EnumMap<>(EntityLogging.class);
public WorldConfig(String world, File file) throws IOException {
this.world = world;
final Map<String, Object> def = new HashMap<>();
// "Before MySQL 5.1.6, database and table names cannot contain "/", "\", ".", or characters that are not permitted in file names" - MySQL manual
// They _can_ contain spaces, but replace them as well
def.put("table", "lb-" + file.getName().substring(0, file.getName().length() - 4).replaceAll("[ ./\\\\]", "_"));
@ -26,10 +51,97 @@ public class WorldConfig extends LoggingEnabledMapping {
config.set(e.getKey(), e.getValue());
}
}
for (EntityLogging el : EntityLogging.values()) {
if (!(config.get("entity." + el.name().toLowerCase()) instanceof List)) {
config.set("entity." + el.name().toLowerCase(), el.getDefaultEnabled());
}
entityLogging.put(el, new EntityLoggingList(config.getStringList("entity." + el.name().toLowerCase())));
}
config.save(file);
table = config.getString("table");
for (final Logging l : Logging.values()) {
setLogging(l, config.getBoolean("logging." + l.toString()));
}
insertBlockStatementString = "INSERT INTO `" + table + "-blocks` (date, playerid, replaced, replaceddata, type, typedata, x, y, z) VALUES (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?)";
selectBlockActorIdStatementString = "SELECT playerid FROM `" + table + "-blocks` WHERE x = ? AND y = ? AND z = ? ORDER BY date DESC LIMIT 1";
insertBlockStateStatementString = "INSERT INTO `" + table + "-state` (replacedState, typeState, id) VALUES(?, ?, ?)";
insertBlockChestDataStatementString = "INSERT INTO `" + table + "-chestdata` (item, itemremove, id, itemtype) values (?, ?, ?, ?)";
insertEntityStatementString = "INSERT INTO `" + table + "-entities` (date, playerid, entityid, entitytypeid, x, y, z, action, data) VALUES (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?)";
updateEntityUUIDString = "UPDATE `" + table + "-entityids` SET entityuuid = ? WHERE entityid = ?";
}
public boolean isLogging(EntityLogging logging, Entity entity) {
return entityLogging.get(logging).isLogging(entity);
}
public boolean isLoggingAnyEntities() {
for (EntityLoggingList list : entityLogging.values()) {
if (list.isLoggingAnyEntities()) {
return true;
}
}
return false;
}
private class EntityLoggingList {
private final EnumSet<EntityType> logged = EnumSet.noneOf(EntityType.class);
private final boolean logAll;
private final boolean logAnimals;
private final boolean logMonsters;
private final boolean logLiving;
public EntityLoggingList(List<String> types) {
boolean all = false;
boolean animals = false;
boolean monsters = false;
boolean living = false;
for (String type : types) {
EntityType et = BukkitUtils.matchEntityType(type);
if (et != null) {
logged.add(et);
} else {
if (type.equalsIgnoreCase("all")) {
all = true;
} else if (type.equalsIgnoreCase("animal") || type.equalsIgnoreCase("animals")) {
animals = true;
} else if (type.equalsIgnoreCase("monster") || type.equalsIgnoreCase("monsters")) {
monsters = true;
} else if (type.equalsIgnoreCase("living")) {
living = true;
} else {
LogBlock.getInstance().getLogger().log(Level.WARNING, "Unkown entity type in config for " + world + ": " + type);
}
}
}
logAll = all;
logAnimals = animals;
logMonsters = monsters;
logLiving = living;
}
public boolean isLogging(Entity entity) {
if (entity == null || (entity instanceof Player)) {
return false;
}
EntityType type = entity.getType();
if (logAll || logged.contains(type)) {
return true;
}
if (logLiving && LivingEntity.class.isAssignableFrom(entity.getClass()) && !(entity instanceof ArmorStand)) {
return true;
}
if (logAnimals && (Animals.class.isAssignableFrom(entity.getClass()) || WaterMob.class.isAssignableFrom(entity.getClass()))) {
return true;
}
if (logMonsters && (Monster.class.isAssignableFrom(entity.getClass()) || entity.getType() == EntityType.SLIME || entity.getType() == EntityType.WITHER || entity.getType() == EntityType.ENDER_DRAGON || entity.getType() == EntityType.SHULKER || entity.getType() == EntityType.GHAST)) {
return true;
}
return false;
}
public boolean isLoggingAnyEntities() {
return logAll || logAnimals || logLiving || logMonsters || !logged.isEmpty();
}
}
}

View File

@ -2,123 +2,93 @@ package de.diddiz.LogBlock.events;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.ChestAccess;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.HandlerList;
public class BlockChangePreLogEvent extends PreLogEvent {
private static final HandlerList handlers = new HandlerList();
private Location location;
private int typeBefore, typeAfter;
private byte data;
private String signText;
private BlockData typeBefore, typeAfter;
private ChestAccess chestAccess;
private YamlConfiguration stateBefore;
private YamlConfiguration stateAfter;
public BlockChangePreLogEvent(Actor owner, Location location, int typeBefore, int typeAfter, byte data,
String signText, ChestAccess chestAccess) {
public BlockChangePreLogEvent(Actor owner, Location location, BlockData typeBefore, BlockData typeAfter, YamlConfiguration stateBefore, YamlConfiguration stateAfter, ChestAccess chestAccess) {
super(owner);
this.location = location;
this.typeBefore = typeBefore;
this.typeAfter = typeAfter;
this.data = data;
this.signText = signText;
this.stateBefore = stateBefore;
this.stateAfter = stateAfter;
this.chestAccess = chestAccess;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public int getTypeBefore() {
public BlockData getTypeBefore() {
return typeBefore;
}
public void setTypeBefore(int typeBefore) {
public void setTypeBefore(BlockData typeBefore) {
if (typeBefore == null) {
typeBefore = Bukkit.createBlockData(Material.AIR);
}
this.typeBefore = typeBefore;
}
public int getTypeAfter() {
public BlockData getTypeAfter() {
return typeAfter;
}
public void setTypeAfter(int typeAfter) {
public void setTypeAfter(BlockData typeAfter) {
if (typeAfter == null) {
typeAfter = Bukkit.createBlockData(Material.AIR);
}
this.typeAfter = typeAfter;
}
public byte getData() {
return data;
public YamlConfiguration getStateBefore() {
return stateBefore;
}
public void setData(byte data) {
this.data = data;
public YamlConfiguration getStateAfter() {
return stateAfter;
}
public String getSignText() {
return signText;
public void setStateBefore(YamlConfiguration stateBefore) {
this.stateBefore = stateBefore;
}
public void setSignText(String[] signText) {
if (signText != null) {
// Check for block
Validate.isTrue(isValidSign(), "Must be valid sign block");
// Check for problems
Validate.noNullElements(signText, "No null lines");
Validate.isTrue(signText.length == 4, "Sign text must be 4 strings");
this.signText = signText[0] + "\0" + signText[1] + "\0" + signText[2] + "\0" + signText[3];
} else {
this.signText = null;
}
}
private boolean isValidSign() {
if ((typeAfter == 63 || typeAfter == 68) && typeBefore == 0) {
return true;
}
if ((typeBefore == 63 || typeBefore == 68) && typeAfter == 0) {
return true;
}
if ((typeAfter == 63 || typeAfter == 68) && typeBefore == typeAfter) {
return true;
}
return false;
public void setStateAfter(YamlConfiguration stateAfter) {
this.stateAfter = stateAfter;
}
public ChestAccess getChestAccess() {
return chestAccess;
}
public void setChestAccess(ChestAccess chestAccess) {
this.chestAccess = chestAccess;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -10,7 +10,6 @@ public abstract class PreLogEvent extends Event implements Cancellable {
protected Actor owner;
public PreLogEvent(Actor owner) {
this.owner = owner;
}
@ -21,8 +20,8 @@ public abstract class PreLogEvent extends Event implements Cancellable {
* @deprecated {@link #getOwnerActor() } returns an object encapsulating
* name and uuid. Names are not guaranteed to be unique.
*/
@Deprecated
public String getOwner() {
return owner.getName();
}
@ -41,17 +40,16 @@ public abstract class PreLogEvent extends Event implements Cancellable {
* @param owner The player/monster/cause who is involved in this event
*/
public void setOwner(Actor owner) {
this.owner = owner;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@ -0,0 +1,59 @@
package de.diddiz.LogBlock.events;
import de.diddiz.LogBlock.QueryParams;
import de.diddiz.LogBlock.Tool;
import de.diddiz.LogBlock.ToolBehavior;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
/**
* Fired whether a tool is about to be used by a player.
*/
public class ToolUseEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancel;
private final Tool tool;
private final ToolBehavior behavior;
private final QueryParams params;
public ToolUseEvent(Player who, Tool tool, ToolBehavior behavior, QueryParams params) {
super(who);
this.tool = tool;
this.behavior = behavior;
this.params = params;
}
@Override
public boolean isCancelled() {
return cancel;
}
@Override
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
public Tool getTool() {
return tool;
}
public ToolBehavior getBehavior() {
return behavior;
}
public QueryParams getParams() {
return params;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,283 @@
package de.diddiz.LogBlock.listeners;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Bee;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Hanging;
import org.bukkit.entity.IronGolem;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Snowman;
import org.bukkit.entity.Wither;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
import org.bukkit.event.hanging.HangingBreakEvent;
import org.bukkit.event.hanging.HangingPlaceEvent;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.EntityChange;
import de.diddiz.LogBlock.EntityChange.EntityChangeType;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.config.EntityLogging;
import de.diddiz.util.LoggingUtil;
import de.diddiz.worldedit.WorldEditHelper;
import java.util.UUID;
public class AdvancedEntityLogging extends LoggingListener {
private Player lastSpawner;
private Class<? extends Entity> lastSpawning;
private boolean lastSpawnerEgg;
// serialize them before the death event
private UUID lastEntityDamagedForDeathUUID;
private byte[] lastEntityDamagedForDeathSerialized;
public AdvancedEntityLogging(LogBlock lb) {
super(lb);
new BukkitRunnable() {
@Override
public void run() {
resetOnTick();
}
}.runTaskTimer(lb, 1, 1);
}
private void resetOnTick() {
lastSpawner = null;
lastSpawning = null;
lastSpawnerEgg = false;
lastEntityDamagedForDeathUUID = null;
lastEntityDamagedForDeathSerialized = null;
}
private void setLastSpawner(Player player, Class<? extends Entity> spawning, boolean spawnEgg) {
lastSpawner = player;
lastSpawning = spawning;
lastSpawnerEgg = spawnEgg;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerBlockPlace(BlockPlaceEvent event) {
Material placed = event.getBlock().getType();
if (placed == Material.WITHER_SKELETON_SKULL) {
setLastSpawner(event.getPlayer(), Wither.class, false);
} else if (placed == Material.CARVED_PUMPKIN) {
Material below = event.getBlock().getRelative(BlockFace.DOWN).getType();
if (below == Material.SNOW_BLOCK) {
setLastSpawner(event.getPlayer(), Snowman.class, false);
} else if (below == Material.IRON_BLOCK) {
setLastSpawner(event.getPlayer(), IronGolem.class, false);
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
ItemStack inHand = event.getItem();
if (inHand != null) {
Material mat = inHand.getType();
if (mat == Material.ARMOR_STAND) {
setLastSpawner(event.getPlayer(), ArmorStand.class, false);
} else if (mat.name().endsWith("_SPAWN_EGG")) {
setLastSpawner(event.getPlayer(), null, true);
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
ItemStack inHand = event.getHand() == EquipmentSlot.HAND ? event.getPlayer().getInventory().getItemInMainHand() : event.getPlayer().getInventory().getItemInOffHand();
if (inHand != null && inHand.getType() != Material.AIR) {
Material mat = inHand.getType();
if (mat.name().endsWith("_SPAWN_EGG")) {
setLastSpawner(event.getPlayer(), null, true);
}
Entity entity = event.getRightClicked();
if (entity instanceof ItemFrame) {
ItemStack oldItem = ((ItemFrame) entity).getItem();
if (oldItem == null || oldItem.getType() == Material.AIR) {
if (Config.isLogging(entity.getWorld(), EntityLogging.MODIFY, entity)) {
Actor actor = Actor.actorFromEntity(event.getPlayer());
YamlConfiguration data = new YamlConfiguration();
inHand = inHand.clone();
inHand.setAmount(1);
data.set("item", inHand);
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), entity.getLocation(), EntityChange.EntityChangeType.ADDEQUIP, data);
}
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onEntitySpawn(CreatureSpawnEvent event) {
if (!event.isCancelled()) {
if (event.getSpawnReason() == SpawnReason.CUSTOM || event.getSpawnReason() == SpawnReason.BEEHIVE) {
return;
}
LivingEntity entity = event.getEntity();
if (Config.isLogging(entity.getWorld(), EntityLogging.SPAWN, entity)) {
Actor actor = null;
if (lastSpawner != null && lastSpawner.getWorld() == entity.getWorld() && lastSpawner.getLocation().distance(entity.getLocation()) < 10) {
if (lastSpawnerEgg && event.getSpawnReason() == SpawnReason.SPAWNER_EGG) {
actor = Actor.actorFromEntity(lastSpawner);
} else if (lastSpawning != null && lastSpawning.isAssignableFrom(entity.getClass())) {
actor = Actor.actorFromEntity(lastSpawner);
}
}
if (actor == null) {
actor = new Actor(event.getSpawnReason().toString());
}
queueEntitySpawnOrKill(entity, actor, EntityChange.EntityChangeType.CREATE);
}
}
resetOnTick();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDeath(EntityDeathEvent event) {
LivingEntity entity = event.getEntity();
if (Config.isLogging(entity.getWorld(), EntityLogging.DESTROY, entity)) {
Actor actor = null;
EntityDamageEvent lastDamage = entity.getLastDamageCause();
if (lastDamage instanceof EntityDamageByEntityEvent) {
Entity damager = LoggingUtil.getRealDamager(((EntityDamageByEntityEvent) lastDamage).getDamager());
if (damager != null) {
actor = Actor.actorFromEntity(damager);
}
}
if (actor == null) {
actor = new Actor(lastDamage == null ? "UNKNOWN" : lastDamage.getCause().toString());
}
queueEntitySpawnOrKill(entity, actor, EntityChange.EntityChangeType.KILL);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onHangingPlace(HangingPlaceEvent event) {
Hanging entity = event.getEntity();
if (Config.isLogging(entity.getWorld(), EntityLogging.SPAWN, entity)) {
Actor actor = Actor.actorFromEntity(event.getPlayer());
queueEntitySpawnOrKill(entity, actor, EntityChange.EntityChangeType.CREATE);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onHangingBreak(HangingBreakEvent event) {
Entity entity = event.getEntity();
if (Config.isLogging(entity.getWorld(), EntityLogging.DESTROY, entity)) {
Actor actor;
if (event instanceof HangingBreakByEntityEvent) {
Entity damager = LoggingUtil.getRealDamager(((HangingBreakByEntityEvent) event).getRemover());
actor = Actor.actorFromEntity(damager);
} else {
actor = new Actor(event.getCause().toString());
}
queueEntitySpawnOrKill(entity, actor, EntityChange.EntityChangeType.KILL);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDamage(EntityDamageEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemFrame) {
ItemStack oldItem = ((ItemFrame) entity).getItem();
if (oldItem != null && oldItem.getType() != Material.AIR) {
if (Config.isLogging(entity.getWorld(), EntityLogging.MODIFY, entity)) {
Actor actor;
if (event instanceof EntityDamageByEntityEvent) {
Entity damager = LoggingUtil.getRealDamager(((EntityDamageByEntityEvent) event).getDamager());
actor = Actor.actorFromEntity(damager);
} else {
actor = new Actor(event.getCause().toString());
}
YamlConfiguration data = new YamlConfiguration();
data.set("item", oldItem);
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), entity.getLocation(), EntityChange.EntityChangeType.REMOVEEQUIP, data);
}
}
}
if (Config.isLogging(entity.getWorld(), EntityLogging.DESTROY, entity)) {
lastEntityDamagedForDeathUUID = entity.getUniqueId();
lastEntityDamagedForDeathSerialized = WorldEditHelper.serializeEntity(entity);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
Entity damager = event.getDamager();
if (damager instanceof Bee && !((Bee) damager).hasStung()) {
if (Config.isLogging(damager.getWorld(), EntityLogging.MODIFY, damager)) {
Actor actor = Actor.actorFromEntity(event.getEntity());
consumer.queueEntityModification(actor, damager.getUniqueId(), damager.getType(), damager.getLocation(), EntityChange.EntityChangeType.GET_STUNG, null);
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerArmorStandManipulate(PlayerArmorStandManipulateEvent event) {
ArmorStand entity = event.getRightClicked();
ItemStack oldItem = event.getArmorStandItem();
ItemStack newItem = event.getPlayerItem();
boolean oldEmpty = oldItem == null || oldItem.getType() == Material.AIR;
boolean newEmpty = newItem == null || newItem.getType() == Material.AIR;
if ((!oldEmpty || !newEmpty) && Config.isLogging(entity.getWorld(), EntityLogging.MODIFY, entity)) {
Actor actor = Actor.actorFromEntity(event.getPlayer());
if (!oldEmpty && !newEmpty && newItem.getAmount() > 1) {
return;
}
if (!oldEmpty) {
YamlConfiguration data = new YamlConfiguration();
data.set("item", oldItem);
data.set("slot", event.getSlot().name());
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), entity.getLocation(), EntityChange.EntityChangeType.REMOVEEQUIP, data);
}
if (!newEmpty) {
YamlConfiguration data = new YamlConfiguration();
data.set("item", newItem);
data.set("slot", event.getSlot().name());
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), entity.getLocation(), EntityChange.EntityChangeType.ADDEQUIP, data);
}
}
}
protected void queueEntitySpawnOrKill(Entity entity, Actor actor, EntityChange.EntityChangeType changeType) {
Location location = entity.getLocation();
YamlConfiguration data = new YamlConfiguration();
data.set("x", location.getX());
data.set("y", location.getY());
data.set("z", location.getZ());
data.set("yaw", location.getYaw());
data.set("pitch", location.getPitch());
if (changeType == EntityChangeType.KILL && entity.getUniqueId().equals(lastEntityDamagedForDeathUUID)) {
data.set("worldedit", lastEntityDamagedForDeathSerialized);
} else {
data.set("worldedit", WorldEditHelper.serializeEntity(entity));
}
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), location, changeType, data);
}
}

View File

@ -29,7 +29,7 @@ public class BanListener implements Listener {
p.setPlayer(split[1].equalsIgnoreCase("g") ? split[2] : split[1]);
p.since = 0;
p.silent = false;
getScheduler().scheduleAsyncDelayedTask(logblock, new Runnable() {
getScheduler().runTaskAsynchronously(logblock, new Runnable() {
@Override
public void run() {
for (final World world : logblock.getServer().getWorlds()) {

View File

@ -5,10 +5,13 @@ import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockBreakEvent;
@ -17,6 +20,7 @@ import org.bukkit.event.player.PlayerBucketFillEvent;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
import static de.diddiz.LogBlock.config.Config.isLogging;
import static de.diddiz.util.LoggingUtil.smartLogBlockBreak;
import static de.diddiz.util.LoggingUtil.smartLogBlockReplace;
import static de.diddiz.util.LoggingUtil.smartLogFallables;
public class BlockBreakLogging extends LoggingListener {
@ -34,19 +38,16 @@ public class BlockBreakLogging extends LoggingListener {
final Actor actor = Actor.actorFromEntity(event.getPlayer());
final Block origin = event.getBlock();
final int typeId = origin.getTypeId();
final Material type = origin.getType();
if (wcfg.isLogging(Logging.SIGNTEXT) && (typeId == 63 || typeId == 68)) {
consumer.queueSignBreak(actor, (Sign) origin.getState());
} else if (wcfg.isLogging(Logging.CHESTACCESS) && BukkitUtils.getContainerBlocks().contains(type)) {
if (wcfg.isLogging(Logging.CHESTACCESS) && BukkitUtils.getContainerBlocks().contains(type) && !BukkitUtils.getShulkerBoxBlocks().contains(type)) {
consumer.queueContainerBreak(actor, origin.getState());
} else if (type == Material.ICE) {
// When in creative mode ice doesn't form water
if (event.getPlayer().getGameMode().equals(GameMode.CREATIVE)) {
consumer.queueBlockBreak(actor, origin.getState());
smartLogBlockBreak(consumer, actor, origin);
} else {
consumer.queueBlockReplace(actor, origin.getState(), 9, (byte) 0);
smartLogBlockReplace(consumer, actor, origin, Bukkit.createBlockData(Material.WATER));
}
} else {
smartLogBlockBreak(consumer, actor, origin);
@ -58,7 +59,17 @@ public class BlockBreakLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerBucketFill(PlayerBucketFillEvent event) {
if (isLogging(event.getBlockClicked().getWorld(), Logging.BLOCKBREAK)) {
consumer.queueBlockBreak(Actor.actorFromEntity(event.getPlayer()), event.getBlockClicked().getState());
BlockData clickedBlockData = event.getBlockClicked().getBlockData();
if (clickedBlockData instanceof Waterlogged) {
Waterlogged clickedWaterlogged = (Waterlogged) clickedBlockData;
if (clickedWaterlogged.isWaterlogged()) {
Waterlogged clickedWaterloggedWithoutWater = (Waterlogged) clickedWaterlogged.clone();
clickedWaterloggedWithoutWater.setWaterlogged(false);
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), event.getBlockClicked().getLocation(), clickedWaterlogged, clickedWaterloggedWithoutWater);
}
} else {
consumer.queueBlockBreak(Actor.actorFromEntity(event.getPlayer()), event.getBlockClicked().getState());
}
}
}
}

View File

@ -3,6 +3,8 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.Config;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
@ -10,10 +12,13 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
import org.bukkit.event.player.PlayerInteractEvent;
import static de.diddiz.LogBlock.config.Config.isLogging;
import static de.diddiz.util.LoggingUtil.smartLogBlockBreak;
import static de.diddiz.util.LoggingUtil.smartLogBlockReplace;
import static de.diddiz.util.LoggingUtil.smartLogFallables;
public class BlockBurnLogging extends LoggingListener {
@ -24,8 +29,31 @@ public class BlockBurnLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockBurn(BlockBurnEvent event) {
if (isLogging(event.getBlock().getWorld(), Logging.FIRE)) {
smartLogBlockBreak(consumer, new Actor("Fire"), event.getBlock());
smartLogFallables(consumer, new Actor("Fire"), event.getBlock());
smartLogBlockReplace(consumer, new Actor("Fire", Config.logFireSpreadAsPlayerWhoCreatedIt ? event.getIgnitingBlock() : null), event.getBlock(), Material.FIRE.createBlockData());
smartLogFallables(consumer, new Actor("Fire", Config.logFireSpreadAsPlayerWhoCreatedIt ? event.getIgnitingBlock() : null), event.getBlock());
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockIgnite(BlockIgniteEvent event) {
Actor actor = new Actor("Fire", Config.logFireSpreadAsPlayerWhoCreatedIt ? event.getIgnitingBlock() : null);
if (event.getCause() == IgniteCause.FLINT_AND_STEEL) {
if (event.getIgnitingEntity() != null) {
return; // handled in block place
} else {
actor = new Actor("Dispenser");
}
} else if (event.getCause() == IgniteCause.LIGHTNING) {
actor = new Actor("Lightning");
} else if (event.getCause() == IgniteCause.EXPLOSION) {
actor = new Actor("Explosion");
} else if (event.getCause() == IgniteCause.LAVA) {
actor = new Actor("Lava");
} else if (event.getCause() == IgniteCause.ENDER_CRYSTAL) {
actor = new Actor("EnderCrystal");
}
if (isLogging(event.getBlock().getWorld(), Logging.FIRE)) {
consumer.queueBlockPlace(actor, event.getBlock().getLocation(), Material.FIRE.createBlockData());
}
}

View File

@ -3,18 +3,19 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Location;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.util.LoggingUtil;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
import static de.diddiz.LogBlock.config.Config.isLogging;
public class BlockPlaceLogging extends LoggingListener {
@ -24,69 +25,44 @@ public class BlockPlaceLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getBlock().getWorld());
if (wcfg != null && wcfg.isLogging(Logging.BLOCKPLACE)) {
final Material type = event.getBlock().getType();
if (Config.isLogging(event.getBlock().getWorld(), Logging.BLOCKPLACE)) {
final BlockState before = event.getBlockReplacedState();
final BlockState after = event.getBlockPlaced().getState();
final Actor actor = Actor.actorFromEntity(event.getPlayer());
//Handle falling blocks
if (BukkitUtils.getRelativeTopFallables().contains(type)) {
// Catch placed blocks overwriting something
if (before.getType() != Material.AIR) {
consumer.queueBlockBreak(actor, before);
}
Location loc = event.getBlock().getLocation();
int x = loc.getBlockX();
int y = loc.getBlockY();
int z = loc.getBlockZ();
// Blocks only fall if they have a chance to start a velocity
if (event.getBlock().getRelative(BlockFace.DOWN).getType() == Material.AIR) {
while (y > 0 && BukkitUtils.canFall(loc.getWorld(), x, (y - 1), z)) {
y--;
}
}
// If y is 0 then the sand block fell out of the world :(
if (y != 0) {
Location finalLoc = new Location(loc.getWorld(), x, y, z);
// Run this check to avoid false positives
if (!BukkitUtils.getFallingEntityKillers().contains(finalLoc.getBlock().getType())) {
if (finalLoc.getBlock().getType() == Material.AIR || finalLoc.equals(event.getBlock().getLocation())) {
consumer.queueBlockPlace(actor, finalLoc, type.getId(), event.getBlock().getData());
} else {
consumer.queueBlockReplace(actor, finalLoc, finalLoc.getBlock().getTypeId(), finalLoc.getBlock().getData(), type.getId(), event.getBlock().getData());
}
}
}
if (before.getType() == Material.LECTERN && after.getType() == Material.LECTERN) {
return;
}
//Sign logging is handled elsewhere
if (wcfg.isLogging(Logging.SIGNTEXT) && (type.getId() == 63 || type.getId() == 68)) {
return;
}
//Delay queuing by one tick to allow data to be updated
LogBlock.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(LogBlock.getInstance(), new Runnable() {
@Override
public void run() {
if (before.getTypeId() == 0) {
consumer.queueBlockPlace(actor, after);
} else {
consumer.queueBlockReplace(actor, before, after);
}
}
}, 1L);
LoggingUtil.smartLogBlockPlace(consumer, actor, before, after);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) {
if (isLogging(event.getPlayer().getWorld(), Logging.BLOCKPLACE)) {
consumer.queueBlockPlace(Actor.actorFromEntity(event.getPlayer()), event.getBlockClicked().getRelative(event.getBlockFace()).getLocation(), event.getBucket() == Material.WATER_BUCKET ? 9 : 11, (byte) 0);
Material placedMaterial = event.getBucket() == Material.LAVA_BUCKET ? Material.LAVA : Material.WATER;
BlockData clickedBlockData = event.getBlockClicked().getBlockData();
if (placedMaterial == Material.WATER && clickedBlockData instanceof Waterlogged) {
Waterlogged clickedWaterlogged = (Waterlogged) clickedBlockData;
if (!clickedWaterlogged.isWaterlogged()) {
Waterlogged clickedWaterloggedWithWater = (Waterlogged) clickedWaterlogged.clone();
clickedWaterloggedWithWater.setWaterlogged(true);
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), event.getBlockClicked().getLocation(), clickedWaterlogged, clickedWaterloggedWithWater);
return;
}
}
Block placedAt = event.getBlockClicked().getRelative(event.getBlockFace());
if (placedAt.isEmpty()) {
consumer.queueBlockPlace(Actor.actorFromEntity(event.getPlayer()), placedAt.getLocation(), placedMaterial.createBlockData());
} else {
BlockData placedAtBlock = placedAt.getBlockData();
if (placedAtBlock instanceof Waterlogged && !(((Waterlogged) placedAtBlock).isWaterlogged())) {
Waterlogged clickedWaterloggedWithWater = (Waterlogged) placedAtBlock.clone();
clickedWaterloggedWithWater.setWaterlogged(true);
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), placedAt.getLocation(), placedAtBlock, clickedWaterloggedWithWater);
} else {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), placedAt.getLocation(), placedAtBlock, placedMaterial.createBlockData());
}
}
}
}
}

View File

@ -32,7 +32,7 @@ public class BlockSpreadLogging extends LoggingListener {
}
name = "GrassGrowth";
break;
case MYCEL:
case MYCELIUM:
if (!isLogging(world, Logging.MYCELIUMSPREAD)) {
return;
}
@ -51,6 +51,18 @@ public class BlockSpreadLogging extends LoggingListener {
}
name = "MushroomSpread";
break;
case BAMBOO:
case BAMBOO_SAPLING: {
if (!isLogging(world, Logging.BAMBOOGROWTH)) {
return;
}
name = "BambooGrowth";
if (type == Material.BAMBOO_SAPLING) {
// bamboo sapling gets replaced by bamboo
consumer.queueBlockReplace(new Actor(name), event.getSource().getState(), Material.BAMBOO.createBlockData());
}
break;
}
default:
return;
}

View File

@ -3,6 +3,9 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.minecart.CommandMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.AsyncPlayerChatEvent;
@ -18,7 +21,7 @@ public class ChatLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
if (isLogging(event.getPlayer().getWorld(), Logging.CHAT)) {
if (isLogging(event.getPlayer().getWorld(), Logging.PLAYER_COMMANDS)) {
consumer.queueChat(Actor.actorFromEntity(event.getPlayer()), event.getMessage());
}
}
@ -32,6 +35,24 @@ public class ChatLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR)
public void onServerCommand(ServerCommandEvent event) {
consumer.queueChat(new Actor("Console"), "/" + event.getCommand());
CommandSender sender = event.getSender();
Actor actor;
if (sender instanceof BlockCommandSender) {
if (!isLogging(((BlockCommandSender) sender).getBlock().getWorld(), Logging.COMMANDBLOCK_COMMANDS)) {
return;
}
actor = new Actor("CommandBlock");
} else if (sender instanceof CommandMinecart) {
if (!isLogging(((CommandMinecart) sender).getWorld(), Logging.COMMANDBLOCK_COMMANDS)) {
return;
}
actor = new Actor("CommandMinecart");
} else {
if (!isLogging(Logging.CONSOLE_COMMANDS)) {
return;
}
actor = new Actor("Console");
}
consumer.queueChat(actor, "/" + event.getCommand());
}
}

View File

@ -4,24 +4,90 @@ import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.DoubleChest;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import static de.diddiz.LogBlock.config.Config.isLogging;
import static de.diddiz.util.BukkitUtils.*;
public class ChestAccessLogging extends LoggingListener {
private final Map<HumanEntity, ItemStack[]> containers = new HashMap<HumanEntity, ItemStack[]>();
private class PlayerActiveInventoryModifications {
private final HumanEntity actor;
private final Location location;
private final HashMap<ItemStack, Integer> modifications;
public PlayerActiveInventoryModifications(HumanEntity actor, Location location) {
this.actor = actor;
this.location = location;
this.modifications = new HashMap<>();
}
public void addModification(ItemStack stack, int amount) {
if (amount == 0) {
return;
}
// if we have other viewers, we have to flush their changes
ArrayList<PlayerActiveInventoryModifications> allViewers = containersByLocation.get(location);
if (allViewers.size() > 1) {
for (PlayerActiveInventoryModifications other : allViewers) {
if (other != this) {
other.flush();
}
}
}
consumer.getLogblock().getLogger().info("Modify container: " + stack + " change: " + amount);
stack = new ItemStack(stack);
stack.setAmount(1);
Integer existing = modifications.get(stack);
int newTotal = amount + (existing == null ? 0 : existing);
if (newTotal == 0) {
modifications.remove(stack);
} else {
modifications.put(stack, newTotal);
}
}
public void flush() {
if (!modifications.isEmpty()) {
for (Entry<ItemStack, Integer> e : modifications.entrySet()) {
ItemStack stack = e.getKey();
int amount = e.getValue();
stack.setAmount(Math.abs(amount));
consumer.getLogblock().getLogger().info("Store container: " + stack + " take: " + (amount < 0));
consumer.queueChestAccess(Actor.actorFromEntity(actor), location, location.getWorld().getBlockAt(location).getBlockData(), stack, amount < 0);
}
modifications.clear();
}
}
public HumanEntity getActor() {
return actor;
}
public Location getLocation() {
return location;
}
}
private final Map<HumanEntity, PlayerActiveInventoryModifications> containersByOwner = new HashMap<>();
private final Map<Location, ArrayList<PlayerActiveInventoryModifications>> containersByLocation = new HashMap<>();
public ChestAccessLogging(LogBlock lb) {
super(lb);
@ -29,37 +95,207 @@ public class ChestAccessLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryClose(InventoryCloseEvent event) {
if (!isLogging(event.getPlayer().getWorld(), Logging.CHESTACCESS)) {
final HumanEntity player = event.getPlayer();
if (!isLogging(player.getWorld(), Logging.CHESTACCESS)) {
return;
}
InventoryHolder holder = event.getInventory().getHolder();
if (holder instanceof BlockState || holder instanceof DoubleChest) {
final HumanEntity player = event.getPlayer();
final ItemStack[] before = containers.get(player);
if (before != null) {
final ItemStack[] after = compressInventory(event.getInventory().getContents());
final ItemStack[] diff = compareInventories(before, after);
final Location loc = getInventoryHolderLocation(holder);
for (final ItemStack item : diff) {
consumer.queueChestAccess(Actor.actorFromEntity(player), loc, loc.getWorld().getBlockTypeIdAt(loc), (short) item.getTypeId(), (short) item.getAmount(), rawData(item));
final PlayerActiveInventoryModifications modifications = containersByOwner.remove(player);
if (modifications != null) {
final Location loc = modifications.getLocation();
ArrayList<PlayerActiveInventoryModifications> atLocation = containersByLocation.get(loc);
atLocation.remove(modifications);
if (atLocation.isEmpty()) {
containersByLocation.remove(loc);
}
containers.remove(player);
modifications.flush();
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryOpen(InventoryOpenEvent event) {
if (!isLogging(event.getPlayer().getWorld(), Logging.CHESTACCESS)) {
final HumanEntity player = event.getPlayer();
if (!isLogging(player.getWorld(), Logging.CHESTACCESS)) {
return;
}
if (event.getInventory() != null) {
InventoryHolder holder = event.getInventory().getHolder();
if (holder instanceof BlockState || holder instanceof DoubleChest) {
if (getInventoryHolderType(holder) != 58) {
containers.put(event.getPlayer(), compressInventory(event.getInventory().getContents()));
if (getInventoryHolderType(holder) != Material.CRAFTING_TABLE) {
PlayerActiveInventoryModifications modifications = new PlayerActiveInventoryModifications(event.getPlayer(), getInventoryHolderLocation(holder));
containersByOwner.put(modifications.getActor(), modifications);
containersByLocation.compute(modifications.getLocation(), (k, v) -> {
if (v == null) {
v = new ArrayList<>();
}
v.add(modifications);
return v;
});
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
final HumanEntity player = event.getWhoClicked();
if (!isLogging(player.getWorld(), Logging.CHESTACCESS)) {
return;
}
InventoryHolder holder = event.getInventory().getHolder();
if (holder instanceof BlockState || holder instanceof DoubleChest) {
final PlayerActiveInventoryModifications modifications = containersByOwner.get(player);
if (modifications != null) {
switch (event.getAction()) {
case PICKUP_ONE:
case DROP_ONE_SLOT:
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
modifications.addModification(event.getCurrentItem(), -1);
}
break;
case PICKUP_HALF:
// server behaviour: round up
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
modifications.addModification(event.getCurrentItem(), -(event.getCurrentItem().getAmount() + 1) / 2);
}
break;
case PICKUP_SOME: // oversized stack - can not take all when clicking
// server behaviour: leave a full stack in the slot, take everything else
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
int taken = event.getCurrentItem().getAmount() - event.getCurrentItem().getMaxStackSize();
modifications.addModification(event.getCursor(), -taken);
}
break;
case PICKUP_ALL:
case DROP_ALL_SLOT:
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
modifications.addModification(event.getCurrentItem(), -event.getCurrentItem().getAmount());
}
break;
case PLACE_ONE:
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
modifications.addModification(event.getCursor(), 1);
}
break;
case PLACE_SOME: // not enough free place in target slot
// server behaviour: place as much as possible
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
int placeable = event.getCurrentItem().getMaxStackSize() - event.getCurrentItem().getAmount();
modifications.addModification(event.getCursor(), placeable);
}
break;
case PLACE_ALL:
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
modifications.addModification(event.getCursor(), event.getCursor().getAmount());
}
break;
case SWAP_WITH_CURSOR:
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
modifications.addModification(event.getCursor(), event.getCursor().getAmount());
modifications.addModification(event.getCurrentItem(), -event.getCurrentItem().getAmount());
}
break;
case MOVE_TO_OTHER_INVENTORY: // shift + click
boolean removed = event.getRawSlot() < event.getView().getTopInventory().getSize();
modifications.addModification(event.getCurrentItem(), event.getCurrentItem().getAmount() * (removed ? -1 : 1));
break;
case COLLECT_TO_CURSOR: // double click
// server behaviour: first collect all with an amount != maxstacksize, then others, starting from slot 0 (container)
ItemStack cursor = event.getCursor();
if (cursor == null) {
return;
}
int toPickUp = cursor.getMaxStackSize() - cursor.getAmount();
int takenFromContainer = 0;
boolean takeFromFullStacks = false;
Inventory top = event.getView().getTopInventory();
Inventory bottom = event.getView().getBottomInventory();
while (toPickUp > 0) {
for (ItemStack stack : top.getStorageContents()) {
if (cursor.isSimilar(stack)) {
if (takeFromFullStacks == (stack.getAmount() == stack.getMaxStackSize())) {
int take = Math.min(toPickUp, stack.getAmount());
toPickUp -= take;
takenFromContainer += take;
if (toPickUp <= 0) {
break;
}
}
}
}
if (toPickUp <= 0) {
break;
}
for (ItemStack stack : bottom.getStorageContents()) {
if (cursor.isSimilar(stack)) {
if (takeFromFullStacks == (stack.getAmount() == stack.getMaxStackSize())) {
int take = Math.min(toPickUp, stack.getAmount());
toPickUp -= take;
if (toPickUp <= 0) {
break;
}
}
}
}
if (takeFromFullStacks) {
break;
} else {
takeFromFullStacks = true;
}
}
if (takenFromContainer > 0) {
modifications.addModification(event.getCursor(), -takenFromContainer);
}
break;
case HOTBAR_SWAP: // number key or offhand key
case HOTBAR_MOVE_AND_READD: // something was in the other slot
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
ItemStack otherSlot = (event.getClick() == ClickType.SWAP_OFFHAND) ? event.getWhoClicked().getInventory().getItemInOffHand() : event.getWhoClicked().getInventory().getItem(event.getHotbarButton());
if (event.getCurrentItem() != null && event.getCurrentItem().getType() != Material.AIR) {
modifications.addModification(event.getCurrentItem(), -event.getCurrentItem().getAmount());
}
if (otherSlot != null && otherSlot.getType() != Material.AIR) {
modifications.addModification(otherSlot, otherSlot.getAmount());
}
}
break;
case DROP_ALL_CURSOR:
case DROP_ONE_CURSOR:
case CLONE_STACK:
case NOTHING:
// only the cursor or nothing (but not the inventory) was modified
break;
case UNKNOWN:
default:
// unable to log something we don't know
consumer.getLogblock().getLogger().warning("Unknown inventory action by " + event.getWhoClicked().getName() + ": " + event.getAction() + " Slot: " + event.getSlot() + " Slot type: " + event.getSlotType());
break;
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
final HumanEntity player = event.getWhoClicked();
if (!isLogging(player.getWorld(), Logging.CHESTACCESS)) {
return;
}
InventoryHolder holder = event.getInventory().getHolder();
if (holder instanceof BlockState || holder instanceof DoubleChest) {
final PlayerActiveInventoryModifications modifications = containersByOwner.get(player);
if (modifications != null) {
Inventory container = event.getView().getTopInventory();
int containerSize = container.getSize();
for (Entry<Integer, ItemStack> e : event.getNewItems().entrySet()) {
int slot = e.getKey();
if (slot < containerSize) {
ItemStack old = container.getItem(slot);
int oldAmount = (old == null || old.getType() == Material.AIR) ? 0 : old.getAmount();
modifications.addModification(e.getValue(), e.getValue().getAmount() - oldAmount);
}
}
}
}

View File

@ -9,6 +9,7 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.TurtleEgg;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -36,24 +37,31 @@ public class CreatureInteractLogging extends LoggingListener {
if (wcfg != null) {
final Block clicked = event.getBlock();
final Material type = clicked.getType();
final int typeId = type.getId();
final byte blockData = clicked.getData();
final Location loc = clicked.getLocation();
switch (type) {
case SOIL:
if (wcfg.isLogging(Logging.CREATURECROPTRAMPLE)) {
// 3 = Dirt ID
consumer.queueBlock(Actor.actorFromEntity(entityType), loc, typeId, 3, blockData);
// Log the crop on top as being broken
Block trampledCrop = clicked.getRelative(BlockFace.UP);
if (BukkitUtils.getCropBlocks().contains(trampledCrop.getType())) {
consumer.queueBlockBreak(new Actor("CreatureTrample"), trampledCrop.getState());
}
if (type == Material.FARMLAND) {
if (wcfg.isLogging(Logging.CREATURECROPTRAMPLE)) {
// 3 = Dirt ID
consumer.queueBlock(Actor.actorFromEntity(entityType), loc, type.createBlockData(), Material.DIRT.createBlockData());
// Log the crop on top as being broken
Block trampledCrop = clicked.getRelative(BlockFace.UP);
if (BukkitUtils.getCropBlocks().contains(trampledCrop.getType())) {
consumer.queueBlockBreak(new Actor("CreatureTrample"), trampledCrop.getState());
}
break;
}
} else if (type == Material.TURTLE_EGG) {
if (wcfg.isLogging(Logging.CREATURECROPTRAMPLE)) {
TurtleEgg turtleEggData = (TurtleEgg) clicked.getBlockData();
int eggs = turtleEggData.getEggs();
if (eggs > 1) {
TurtleEgg turtleEggData2 = (TurtleEgg) turtleEggData.clone();
turtleEggData2.setEggs(eggs - 1);
consumer.queueBlock(new Actor("CreatureTrample"), loc, turtleEggData, turtleEggData2);
} else {
consumer.queueBlock(new Actor("CreatureTrample"), loc, turtleEggData, Material.AIR.createBlockData());
}
}
}
}
}
}

View File

@ -0,0 +1,73 @@
package de.diddiz.LogBlock.listeners;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.scheduler.BukkitRunnable;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.util.LoggingUtil;
public class DragonEggLogging extends LoggingListener {
private UUID lastDragonEggInteractionPlayer;
private Location lastDragonEggInteractionLocation;
public DragonEggLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.hasBlock() && event.getClickedBlock().getType() == Material.DRAGON_EGG) {
Block block = event.getClickedBlock();
if (!Config.isLogging(block.getWorld(), Logging.DRAGONEGGTELEPORT)) {
return;
}
lastDragonEggInteractionPlayer = event.getPlayer().getUniqueId();
lastDragonEggInteractionLocation = block.getLocation();
new BukkitRunnable() {
@Override
public void run() {
lastDragonEggInteractionPlayer = null;
lastDragonEggInteractionLocation = null;
}
}.runTask(LogBlock.getInstance());
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onDragonEggTeleport(BlockFromToEvent event) {
Block block = event.getBlock();
Player teleportCause = null;
if (lastDragonEggInteractionPlayer != null && lastDragonEggInteractionLocation != null && lastDragonEggInteractionLocation.equals(block.getLocation())) {
teleportCause = Bukkit.getPlayer(lastDragonEggInteractionPlayer);
}
if (block.getType() == Material.DRAGON_EGG && Config.isLogging(block.getWorld(), Logging.DRAGONEGGTELEPORT)) {
Actor actor = new Actor("DragonEgg");
if (teleportCause != null) {
actor = Actor.actorFromEntity(teleportCause);
}
BlockData data = block.getBlockData();
consumer.queueBlockBreak(actor, block.getLocation(), data);
BlockState finalState = event.getToBlock().getState();
finalState.setBlockData(data);
LoggingUtil.smartLogBlockPlace(consumer, actor, event.getToBlock().getState(), finalState);
}
}
}

View File

@ -18,7 +18,7 @@ public class EndermenLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
if (event.getEntity() instanceof Enderman && isLogging(event.getBlock().getWorld(), Logging.ENDERMEN)) {
consumer.queueBlockReplace(new Actor("Enderman"), event.getBlock().getState(), event.getTo().getId(), (byte) 0); // Figure out how to get the data of the placed block;
consumer.queueBlockReplace(new Actor("Enderman"), event.getBlock().getState(), event.getBlockData()); // Figure out how to get the data of the placed block;
}
}
}

View File

@ -3,22 +3,36 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.entity.*;
import org.bukkit.entity.minecart.ExplosiveMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.projectiles.ProjectileSource;
import org.bukkit.scheduler.BukkitRunnable;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
import static de.diddiz.LogBlock.config.Config.logCreeperExplosionsAsPlayerWhoTriggeredThese;
import static de.diddiz.util.BukkitUtils.getContainerBlocks;
import java.util.UUID;
public class ExplosionLogging extends LoggingListener {
private UUID lastBedInteractionPlayer;
private Location lastBedInteractionLocation;
public ExplosionLogging(LogBlock lb) {
super(lb);
}
@ -85,16 +99,76 @@ public class ExplosionLogging extends LoggingListener {
return;
}
actor = Actor.actorFromEntity(source);
} else if (source instanceof EnderCrystal) {
if (!wcfg.isLogging(Logging.ENDERCRYSTALEXPLOSION)) {
return;
}
actor = Actor.actorFromEntity(source);
} else {
if (!wcfg.isLogging(Logging.MISCEXPLOSION)) {
return;
}
}
for (final Block block : event.blockList()) {
final int type = block.getTypeId();
if (wcfg.isLogging(Logging.SIGNTEXT) & (type == 63 || type == 68)) {
consumer.queueSignBreak(actor, (Sign) block.getState());
} else if (wcfg.isLogging(Logging.CHESTACCESS) && (getContainerBlocks().contains(Material.getMaterial(type)))) {
final Material type = block.getType();
if (wcfg.isLogging(Logging.CHESTACCESS) && getContainerBlocks().contains(type) && !BukkitUtils.getShulkerBoxBlocks().contains(type)) {
consumer.queueContainerBreak(actor, block.getState());
} else {
consumer.queueBlockBreak(actor, block.getState());
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.hasBlock() && BukkitUtils.isBed(event.getClickedBlock().getType())) {
Block block = event.getClickedBlock();
if (!Config.isLogging(block.getWorld(), Logging.BEDEXPLOSION)) {
return;
}
lastBedInteractionPlayer = event.getPlayer().getUniqueId();
lastBedInteractionLocation = block.getLocation();
new BukkitRunnable() {
@Override
public void run() {
lastBedInteractionPlayer = null;
lastBedInteractionLocation = null;
}
}.runTask(LogBlock.getInstance());
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockExplode(BlockExplodeEvent event) {
Player bedCause = null;
if (lastBedInteractionPlayer != null && lastBedInteractionLocation != null) {
Location block = event.getBlock().getLocation();
if (lastBedInteractionLocation.getWorld() == block.getWorld() && block.distanceSquared(lastBedInteractionLocation) <= 1) {
bedCause = Bukkit.getPlayer(lastBedInteractionPlayer);
}
}
for (final Block block : event.blockList()) {
final WorldConfig wcfg = getWorldConfig(block.getLocation().getWorld());
if (wcfg != null) {
Actor actor = new Actor("Explosion");
if (bedCause != null) {
if (!wcfg.isLogging(Logging.BEDEXPLOSION)) {
return;
}
if (Config.logBedExplosionsAsPlayerWhoTriggeredThese) {
actor = Actor.actorFromEntity(bedCause);
}
} else if (!wcfg.isLogging(Logging.MISCEXPLOSION)) {
return;
}
final Material type = block.getType();
if (wcfg.isLogging(Logging.CHESTACCESS) && getContainerBlocks().contains(type) && !BukkitUtils.getShulkerBoxBlocks().contains(type)) {
consumer.queueContainerBreak(actor, block.getState());
} else {
consumer.queueBlockBreak(actor, block.getState());

View File

@ -3,21 +3,24 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Levelled;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
public class FluidFlowLogging extends LoggingListener {
private static final Set<Integer> nonFluidProofBlocks = new HashSet<Integer>(Arrays.asList(27, 28, 31, 32, 37, 38, 39, 40, 50, 51, 55, 59, 66, 69, 70, 75, 76, 78, 93, 94, 104, 105, 106));
public FluidFlowLogging(LogBlock lb) {
super(lb);
@ -26,44 +29,71 @@ public class FluidFlowLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockFromTo(BlockFromToEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getBlock().getWorld());
if (wcfg != null) {
if (wcfg != null && (wcfg.isLogging(Logging.WATERFLOW) || wcfg.isLogging(Logging.LAVAFLOW))) {
final BlockData blockDataFrom = event.getBlock().getBlockData();
Material typeFrom = blockDataFrom.getMaterial();
boolean fromWaterlogged = false;
if (blockDataFrom instanceof Waterlogged) {
typeFrom = Material.WATER;
fromWaterlogged = true;
}
if (typeFrom == Material.SEAGRASS || typeFrom == Material.KELP_PLANT || typeFrom == Material.KELP) {
typeFrom = Material.WATER;
fromWaterlogged = true;
}
Block source = Config.logFluidFlowAsPlayerWhoTriggeredIt ? event.getBlock() : null;
final Block to = event.getToBlock();
final int typeFrom = event.getBlock().getTypeId();
final int typeTo = to.getTypeId();
final boolean canFlow = typeTo == 0 || nonFluidProofBlocks.contains(typeTo);
if (typeFrom == 10 || typeFrom == 11) {
if (canFlow && wcfg.isLogging(Logging.LAVAFLOW)) {
if (isSurroundedByWater(to) && event.getBlock().getData() <= 2) {
consumer.queueBlockReplace(new Actor("LavaFlow"), to.getState(), 4, (byte) 0);
} else if (typeTo == 0) {
consumer.queueBlockPlace(new Actor("LavaFlow"), to.getLocation(), 10, (byte) (event.getBlock().getData() + 1));
final Material typeTo = to.getType();
boolean down = event.getFace() == BlockFace.DOWN;
final boolean canFlow = BukkitUtils.isEmpty(typeTo) || BukkitUtils.getNonFluidProofBlocks().contains(typeTo);
if (typeFrom == Material.LAVA && wcfg.isLogging(Logging.LAVAFLOW)) {
Levelled levelledFrom = (Levelled) blockDataFrom;
if (canFlow) {
if (isSurroundedByWater(to) && levelledFrom.getLevel() <= 2) {
consumer.queueBlockReplace(new Actor("LavaFlow", source), to.getState(), Material.COBBLESTONE.createBlockData());
} else {
consumer.queueBlockReplace(new Actor("LavaFlow"), to.getState(), 10, (byte) (event.getBlock().getData() + 1));
Levelled newBlock = (Levelled) blockDataFrom.clone();
newBlock.setLevel(down ? 1 : levelledFrom.getLevel() + 1);
if (BukkitUtils.isEmpty(typeTo)) {
consumer.queueBlockPlace(new Actor("LavaFlow", source), to.getLocation(), newBlock);
} else {
consumer.queueBlockReplace(new Actor("LavaFlow", source), to.getState(), newBlock);
}
}
} else if (typeTo == 8 || typeTo == 9) {
if (event.getFace() == BlockFace.DOWN) {
consumer.queueBlockReplace(new Actor("LavaFlow"), to.getState(), 1, (byte) 0);
} else if (typeTo == Material.WATER) {
if (down) {
consumer.queueBlockReplace(new Actor("LavaFlow", source), to.getState(), Material.STONE.createBlockData());
} else {
consumer.queueBlockReplace(new Actor("LavaFlow"), to.getState(), 4, (byte) 0);
consumer.queueBlockReplace(new Actor("LavaFlow", source), to.getState(), Material.COBBLESTONE.createBlockData());
}
}
} else if ((typeFrom == 8 || typeFrom == 9) && wcfg.isLogging(Logging.WATERFLOW)) {
if (typeTo == 0) {
consumer.queueBlockPlace(new Actor("WaterFlow"), to.getLocation(), 8, (byte) (event.getBlock().getData() + 1));
} else if (nonFluidProofBlocks.contains(typeTo)) {
consumer.queueBlockReplace(new Actor("WaterFlow"), to.getState(), 8, (byte) (event.getBlock().getData() + 1));
} else if (typeTo == 10 || typeTo == 11) {
if (to.getData() == 0) {
consumer.queueBlockReplace(new Actor("WaterFlow"), to.getState(), 49, (byte) 0);
} else if ((typeFrom == Material.WATER) && wcfg.isLogging(Logging.WATERFLOW)) {
Levelled levelledFrom = fromWaterlogged ? null : (Levelled) blockDataFrom;
Levelled newBlock = (Levelled) Material.WATER.createBlockData();
newBlock.setLevel(fromWaterlogged || down ? 1 : levelledFrom.getLevel() + 1);
if (BukkitUtils.isEmpty(typeTo)) {
consumer.queueBlockPlace(new Actor("WaterFlow", source), to.getLocation(), newBlock);
} else if (BukkitUtils.getNonFluidProofBlocks().contains(typeTo)) {
consumer.queueBlockReplace(new Actor("WaterFlow", source), to.getState(), newBlock);
} else if (typeTo == Material.LAVA) {
int toLevel = ((Levelled) to.getBlockData()).getLevel();
if (toLevel == 0) {
consumer.queueBlockReplace(new Actor("WaterFlow", source), to.getState(), Material.OBSIDIAN.createBlockData());
} else if (event.getFace() == BlockFace.DOWN) {
consumer.queueBlockReplace(new Actor("LavaFlow"), to.getState(), 1, (byte) 0);
consumer.queueBlockReplace(new Actor("WaterFlow", source), to.getState(), Material.STONE.createBlockData());
}
}
if (typeTo == 0 || nonFluidProofBlocks.contains(typeTo)) {
for (final BlockFace face : new BlockFace[]{BlockFace.DOWN, BlockFace.NORTH, BlockFace.WEST, BlockFace.EAST, BlockFace.SOUTH}) {
if (BukkitUtils.isEmpty(typeTo) || BukkitUtils.getNonFluidProofBlocks().contains(typeTo)) {
for (final BlockFace face : new BlockFace[] { BlockFace.DOWN, BlockFace.NORTH, BlockFace.WEST, BlockFace.EAST, BlockFace.SOUTH }) {
final Block lower = to.getRelative(face);
if (lower.getTypeId() == 10 || lower.getTypeId() == 11) {
consumer.queueBlockReplace(new Actor("WaterFlow"), lower.getState(), lower.getData() == 0 ? 49 : 4, (byte) 0);
if (lower.getType() == Material.LAVA) {
int toLevel = ((Levelled) lower.getBlockData()).getLevel();
if (toLevel == 0) {
consumer.queueBlockReplace(new Actor("WaterFlow", source), lower.getState(), Material.OBSIDIAN.createBlockData());
} else if (event.getFace() == BlockFace.DOWN) {
consumer.queueBlockReplace(new Actor("WaterFlow", source), lower.getState(), Material.STONE.createBlockData());
}
}
}
}
@ -71,10 +101,25 @@ public class FluidFlowLogging extends LoggingListener {
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockForm(BlockFormEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getBlock().getWorld());
if (wcfg != null && (wcfg.isLogging(Logging.WATERFLOW) || wcfg.isLogging(Logging.LAVAFLOW))) {
if (wcfg.isLogging(Logging.LAVAFLOW) && event.getBlock().getType() == Material.WATER && event.getNewState().getType() == Material.COBBLESTONE) {
consumer.queueBlockReplace(new Actor("LavaFlow"), event.getBlock().getBlockData(), event.getNewState());
}
if (wcfg.isLogging(Logging.WATERFLOW) && event.getBlock().getType() == Material.LAVA) {
consumer.queueBlockReplace(new Actor("WaterFlow"), event.getBlock().getBlockData(), event.getNewState());
}
if (wcfg.isLogging(Logging.WATERFLOW) && BukkitUtils.isConcreteBlock(event.getNewState().getType())) {
consumer.queueBlockReplace(new Actor("WaterFlow"), event.getBlock().getBlockData(), event.getNewState());
}
}
}
private static boolean isSurroundedByWater(Block block) {
for (final BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.WEST, BlockFace.EAST, BlockFace.SOUTH}) {
final int type = block.getRelative(face).getTypeId();
if (type == 8 || type == 9) {
for (final BlockFace face : new BlockFace[] { BlockFace.NORTH, BlockFace.WEST, BlockFace.EAST, BlockFace.SOUTH }) {
if (block.getRelative(face).getType() == Material.WATER) {
return true;
}
}

View File

@ -5,15 +5,33 @@ import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.BukkitUtils;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Note;
import org.bukkit.Note.Tone;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.Openable;
import org.bukkit.block.data.type.Cake;
import org.bukkit.block.data.type.Comparator;
import org.bukkit.block.data.type.Comparator.Mode;
import org.bukkit.block.data.type.DaylightDetector;
import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.type.NoteBlock;
import org.bukkit.block.data.type.Repeater;
import org.bukkit.block.data.type.Switch;
import org.bukkit.block.data.type.TurtleEgg;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
@ -30,66 +48,101 @@ public class InteractLogging extends LoggingListener {
if (clicked == null) {
return;
}
final Material type = clicked.getType();
final int typeId = type.getId();
final byte blockData = clicked.getData();
final BlockData blockData = clicked.getBlockData();
final Material type = blockData.getMaterial();
final Player player = event.getPlayer();
final Location loc = clicked.getLocation();
switch (type) {
case LEVER:
case WOOD_BUTTON:
case STONE_BUTTON:
if (wcfg.isLogging(Logging.SWITCHINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
}
break;
case FENCE_GATE:
case WOODEN_DOOR:
case TRAP_DOOR:
case OAK_FENCE_GATE:
case SPRUCE_FENCE_GATE:
case BIRCH_FENCE_GATE:
case JUNGLE_FENCE_GATE:
case ACACIA_FENCE_GATE:
case DARK_OAK_FENCE_GATE:
case WARPED_FENCE_GATE:
case CRIMSON_FENCE_GATE:
case OAK_TRAPDOOR:
case SPRUCE_TRAPDOOR:
case BIRCH_TRAPDOOR:
case JUNGLE_TRAPDOOR:
case ACACIA_TRAPDOOR:
case DARK_OAK_TRAPDOOR:
case WARPED_TRAPDOOR:
case CRIMSON_TRAPDOOR:
if (wcfg.isLogging(Logging.DOORINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
Openable newBlockData = (Openable) blockData.clone();
newBlockData.setOpen(!newBlockData.isOpen());
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case CAKE_BLOCK:
case CAKE:
if (wcfg.isLogging(Logging.CAKEEAT) && event.getAction() == Action.RIGHT_CLICK_BLOCK && player.getFoodLevel() < 20) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
Cake newBlockData = (Cake) blockData.clone();
if (newBlockData.getBites() < 6) {
newBlockData.setBites(newBlockData.getBites() + 1);
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
} else {
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, Material.AIR.createBlockData());
}
}
break;
case NOTE_BLOCK:
if (wcfg.isLogging(Logging.NOTEBLOCKINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
NoteBlock newBlockData = (NoteBlock) blockData.clone();
if (newBlockData.getNote().getOctave() == 2) {
newBlockData.setNote(new Note(0, Tone.F, true));
} else {
newBlockData.setNote(newBlockData.getNote().sharped());
}
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case DIODE_BLOCK_OFF:
case DIODE_BLOCK_ON:
case REPEATER:
if (wcfg.isLogging(Logging.DIODEINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
Repeater newBlockData = (Repeater) blockData.clone();
newBlockData.setDelay((newBlockData.getDelay() % 4) + 1);
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case REDSTONE_COMPARATOR_OFF:
case REDSTONE_COMPARATOR_ON:
case COMPARATOR:
if (wcfg.isLogging(Logging.COMPARATORINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
Comparator newBlockData = (Comparator) blockData.clone();
newBlockData.setMode(newBlockData.getMode() == Mode.COMPARE ? Mode.SUBTRACT : Mode.COMPARE);
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case WOOD_PLATE:
case STONE_PLATE:
case IRON_PLATE:
case GOLD_PLATE:
case DAYLIGHT_DETECTOR:
if (wcfg.isLogging(Logging.DAYLIGHTDETECTORINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
DaylightDetector newBlockData = (DaylightDetector) blockData.clone();
newBlockData.setInverted(!newBlockData.isInverted());
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case OAK_PRESSURE_PLATE:
case SPRUCE_PRESSURE_PLATE:
case BIRCH_PRESSURE_PLATE:
case JUNGLE_PRESSURE_PLATE:
case ACACIA_PRESSURE_PLATE:
case DARK_OAK_PRESSURE_PLATE:
case WARPED_PRESSURE_PLATE:
case CRIMSON_PRESSURE_PLATE:
case STONE_PRESSURE_PLATE:
case HEAVY_WEIGHTED_PRESSURE_PLATE:
case LIGHT_WEIGHTED_PRESSURE_PLATE:
if (wcfg.isLogging(Logging.PRESUREPLATEINTERACT) && event.getAction() == Action.PHYSICAL) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, blockData);
}
break;
case TRIPWIRE:
if (wcfg.isLogging(Logging.TRIPWIREINTERACT) && event.getAction() == Action.PHYSICAL) {
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, typeId, blockData);
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, blockData);
}
break;
case SOIL:
case FARMLAND:
if (wcfg.isLogging(Logging.CROPTRAMPLE) && event.getAction() == Action.PHYSICAL) {
// 3 = Dirt ID
consumer.queueBlock(Actor.actorFromEntity(player), loc, typeId, 3, blockData);
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, Material.DIRT.createBlockData());
// Log the crop on top as being broken
Block trampledCrop = clicked.getRelative(BlockFace.UP);
if (BukkitUtils.getCropBlocks().contains(trampledCrop.getType())) {
@ -97,6 +150,108 @@ public class InteractLogging extends LoggingListener {
}
}
break;
case TURTLE_EGG:
if (wcfg.isLogging(Logging.BLOCKBREAK) && event.getAction() == Action.PHYSICAL) {
TurtleEgg turtleEggData = (TurtleEgg) blockData;
int eggs = turtleEggData.getEggs();
if (eggs > 1) {
TurtleEgg turtleEggData2 = (TurtleEgg) turtleEggData.clone();
turtleEggData2.setEggs(eggs - 1);
consumer.queueBlock(Actor.actorFromEntity(player), loc, turtleEggData, turtleEggData2);
} else {
consumer.queueBlock(Actor.actorFromEntity(player), loc, turtleEggData, Material.AIR.createBlockData());
}
}
break;
case PUMPKIN:
if ((wcfg.isLogging(Logging.BLOCKBREAK) || wcfg.isLogging(Logging.BLOCKPLACE)) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
ItemStack inHand = event.getItem();
if (inHand != null && inHand.getType() == Material.SHEARS) {
BlockFace clickedFace = event.getBlockFace();
Directional newBlockData = (Directional) Material.CARVED_PUMPKIN.createBlockData();
if (clickedFace == BlockFace.NORTH || clickedFace == BlockFace.SOUTH || clickedFace == BlockFace.EAST || clickedFace == BlockFace.WEST) {
newBlockData.setFacing(clickedFace);
} else {
// use player distance to calculate the facing
Location playerLoc = player.getLocation();
playerLoc.subtract(0.5, 0, 0.5);
double dx = playerLoc.getX() - loc.getX();
double dz = playerLoc.getZ() - loc.getZ();
if (Math.abs(dx) > Math.abs(dz)) {
newBlockData.setFacing(dx > 0 ? BlockFace.EAST : BlockFace.WEST);
} else {
newBlockData.setFacing(dz > 0 ? BlockFace.SOUTH : BlockFace.NORTH);
}
}
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
}
break;
case OAK_DOOR:
case SPRUCE_DOOR:
case BIRCH_DOOR:
case JUNGLE_DOOR:
case ACACIA_DOOR:
case DARK_OAK_DOOR:
case WARPED_DOOR:
case CRIMSON_DOOR:
if (wcfg.isLogging(Logging.DOORINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
Door newBlockData = (Door) blockData.clone();
newBlockData.setOpen(!newBlockData.isOpen());
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case STONE_BUTTON:
case OAK_BUTTON:
case SPRUCE_BUTTON:
case BIRCH_BUTTON:
case JUNGLE_BUTTON:
case ACACIA_BUTTON:
case DARK_OAK_BUTTON:
case WARPED_BUTTON:
case CRIMSON_BUTTON:
case LEVER:
if (wcfg.isLogging(Logging.SWITCHINTERACT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
Switch newBlockData = (Switch) blockData.clone();
if (!newBlockData.isPowered() || type == Material.LEVER) {
newBlockData.setPowered(!newBlockData.isPowered());
}
consumer.queueBlock(Actor.actorFromEntity(player), loc, blockData, newBlockData);
}
break;
case OAK_SIGN:
case SPRUCE_SIGN:
case BIRCH_SIGN:
case JUNGLE_SIGN:
case ACACIA_SIGN:
case DARK_OAK_SIGN:
case WARPED_SIGN:
case CRIMSON_SIGN:
case OAK_WALL_SIGN:
case SPRUCE_WALL_SIGN:
case BIRCH_WALL_SIGN:
case JUNGLE_WALL_SIGN:
case ACACIA_WALL_SIGN:
case DARK_OAK_WALL_SIGN:
case WARPED_WALL_SIGN:
case CRIMSON_WALL_SIGN:
if (wcfg.isLogging(Logging.SIGNTEXT) && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
ItemStack stack = event.getItem();
if (stack != null && BukkitUtils.isDye(stack.getType())) {
final BlockState before = event.getClickedBlock().getState();
if (before instanceof Sign) {
DyeColor newColor = BukkitUtils.dyeToDyeColor(stack.getType());
Sign signBefore = (Sign) before;
if (newColor != null && signBefore.getColor() != newColor) {
final Sign signAfter = (Sign) event.getClickedBlock().getState();
signAfter.setColor(newColor);
consumer.queueBlockReplace(Actor.actorFromEntity(player), signBefore, signAfter);
}
}
}
}
break;
default:
}
}
}

View File

@ -16,7 +16,6 @@ import org.bukkit.event.entity.EntityDeathEvent;
import static de.diddiz.LogBlock.config.Config.*;
public class KillLogging extends LoggingListener {
public KillLogging(LogBlock lb) {

View File

@ -0,0 +1,67 @@
package de.diddiz.LogBlock.listeners;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.config.WorldConfig;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.Lectern;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerTakeLecternBookEvent;
import org.bukkit.inventory.ItemStack;
public class LecternLogging extends LoggingListener {
public LecternLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getPlayer().getWorld());
if (wcfg != null && wcfg.isLogging(Logging.LECTERNBOOKCHANGE)) {
final BlockState before = event.getBlockReplacedState();
final BlockState after = event.getBlockPlaced().getState();
if (before.getType() == Material.LECTERN && after.getType() == Material.LECTERN) {
Lectern lecternBefore = (Lectern) before.getBlock().getState();
ItemStack book = lecternBefore.getSnapshotInventory().getItem(0);
try {
lecternBefore.getSnapshotInventory().setItem(0, null);
} catch (NullPointerException e) {
//ignored
}
lecternBefore.setBlockData(before.getBlockData());
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), lecternBefore, after);
try {
lecternBefore.getSnapshotInventory().setItem(0, book);
} catch (NullPointerException e) {
//ignored
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerTakeLecternBook(PlayerTakeLecternBookEvent event) {
final WorldConfig wcfg = getWorldConfig(event.getPlayer().getWorld());
if (wcfg != null && wcfg.isLogging(Logging.LECTERNBOOKCHANGE)) {
Lectern oldState = event.getLectern();
Lectern newState = (Lectern) oldState.getBlock().getState();
try {
newState.getSnapshotInventory().setItem(0, null);
} catch (NullPointerException e) {
//ignored
}
org.bukkit.block.data.type.Lectern oldBlockData = (org.bukkit.block.data.type.Lectern) oldState.getBlockData();
org.bukkit.block.data.type.Lectern blockData = (org.bukkit.block.data.type.Lectern) Material.LECTERN.createBlockData();
blockData.setFacing(oldBlockData.getFacing());
blockData.setPowered(oldBlockData.isPowered());
newState.setBlockData(blockData);
consumer.queueBlockReplace(Actor.actorFromEntity(event.getPlayer()), oldState, newState);
}
}
}

View File

@ -1,26 +0,0 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockFadeEvent;
import static de.diddiz.LogBlock.config.Config.isLogging;
public class LockedChestDecayLogging extends LoggingListener {
public LockedChestDecayLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockFade(BlockFadeEvent event) {
if (isLogging(event.getBlock().getWorld(), Logging.LOCKEDCHESTDECAY)) {
final int type = event.getBlock().getTypeId();
if (type == 95) {
consumer.queueBlockReplace(new Actor("LockedChestDecay"), event.getBlock().getState(), event.getNewState());
}
}
}
}

View File

@ -1,23 +1,43 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.config.Config;
import java.util.HashMap;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerInfoLogging extends LoggingListener {
private final HashMap<UUID, Long> playerLogins = new HashMap<>();
public PlayerInfoLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
playerLogins.put(event.getPlayer().getUniqueId(), System.currentTimeMillis());
consumer.queueJoin(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
consumer.queueLeave(event.getPlayer());
onPlayerQuit(event.getPlayer());
}
public void onPlayerQuit(Player player) {
Long joinTime = playerLogins.remove(player.getUniqueId());
if (Config.logPlayerInfo && joinTime != null) {
long onlineTime = (System.currentTimeMillis() - joinTime) / 1000;
if (onlineTime > 0) {
consumer.queueLeave(player, onlineTime);
}
}
}
}

View File

@ -0,0 +1,171 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import java.util.ArrayDeque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockFadeEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import static de.diddiz.LogBlock.config.Config.isLogging;
import static de.diddiz.util.LoggingUtil.smartLogFallables;
public class ScaffoldingLogging extends LoggingListener {
private final static long MAX_SCAFFOLDING_LOG_TIME_MS = 2000;
private final static EnumSet<BlockFace> NEIGHBOURS_SIDES_AND_UP = EnumSet.of(BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST);
private final static EnumSet<BlockFace> NEIGHBOURS_SIDES_AND_BELOW = EnumSet.of(BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST);
private final ArrayDeque<ScaffoldingBreaker> scaffoldingBreakersList = new ArrayDeque<>();
private final HashMap<Location, ScaffoldingBreaker> scaffoldingBreakersByLocation = new HashMap<>();
private final HashMap<Location, Actor> scaffoldingPlacersByLocation = new HashMap<>();
public ScaffoldingLogging(LogBlock lb) {
super(lb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockFade(BlockFadeEvent event) {
Block block = event.getBlock();
if (isLogging(block.getWorld(), Logging.SCAFFOLDING)) {
final Material type = block.getType();
if (type == Material.SCAFFOLDING) {
Actor actor = scaffoldingPlacersByLocation.get(block.getLocation()); // get placer before cleanupScaffoldingBreakers
cleanupScaffoldingBreakers();
if (actor == null) {
actor = getScaffoldingBreaker(block);
if (actor != null) {
for (BlockFace dir : NEIGHBOURS_SIDES_AND_UP) {
Block otherBlock = block.getRelative(dir);
if (otherBlock.getType() == Material.SCAFFOLDING) {
addScaffoldingBreaker(actor, otherBlock);
}
}
} else {
actor = new Actor("ScaffoldingFall");
}
}
consumer.queueBlockReplace(actor, block.getState(), event.getNewState());
smartLogFallables(consumer, actor, block);
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
Block block = event.getBlock();
if (isLogging(block.getWorld(), Logging.SCAFFOLDING)) {
cleanupScaffoldingBreakers();
Block otherBlock;
if (block.getType() == Material.SCAFFOLDING) {
for (BlockFace dir : NEIGHBOURS_SIDES_AND_UP) {
otherBlock = block.getRelative(dir);
if (otherBlock.getType() == Material.SCAFFOLDING) {
addScaffoldingBreaker(Actor.actorFromEntity(event.getPlayer()), otherBlock);
}
}
} else if ((otherBlock = block.getRelative(BlockFace.UP)).getType() == Material.SCAFFOLDING) {
addScaffoldingBreaker(Actor.actorFromEntity(event.getPlayer()), otherBlock);
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
Block block = event.getBlock();
if (isLogging(block.getWorld(), Logging.SCAFFOLDING)) {
cleanupScaffoldingBreakers();
if (block.getType() == Material.SCAFFOLDING) {
scaffoldingPlacersByLocation.put(block.getLocation(), Actor.actorFromEntity(event.getPlayer()));
}
}
}
public void addScaffoldingBreaker(Actor actor, Block block) {
ScaffoldingBreaker breaker = new ScaffoldingBreaker(actor, block.getLocation());
scaffoldingBreakersList.addLast(breaker);
scaffoldingBreakersByLocation.put(breaker.getLocation(), breaker);
}
private void cleanupScaffoldingBreakers() {
if (!scaffoldingPlacersByLocation.isEmpty()) {
scaffoldingPlacersByLocation.clear();
}
if (!scaffoldingBreakersList.isEmpty()) {
long time = System.currentTimeMillis() - MAX_SCAFFOLDING_LOG_TIME_MS;
while (!scaffoldingBreakersList.isEmpty() && scaffoldingBreakersList.getFirst().getTime() < time) {
ScaffoldingBreaker breaker = scaffoldingBreakersList.removeFirst();
scaffoldingBreakersByLocation.remove(breaker.getLocation(), breaker);
}
}
}
private Actor getScaffoldingBreaker(Block block) {
if (scaffoldingBreakersList.isEmpty()) {
return null;
}
ScaffoldingBreaker breaker = scaffoldingBreakersByLocation.get(block.getLocation());
if (breaker != null) {
return breaker.getActor();
}
// Search all connected scaffoldings
ArrayDeque<Block> front = new ArrayDeque<>();
HashSet<Block> frontAndDone = new HashSet<>();
front.addLast(block);
frontAndDone.add(block);
while (!front.isEmpty()) {
Block current = front.removeFirst();
Location loc = current.getLocation();
breaker = scaffoldingBreakersByLocation.get(loc);
if (breaker != null) {
return breaker.getActor();
}
for (BlockFace dir : NEIGHBOURS_SIDES_AND_BELOW) {
Block otherBlock = current.getRelative(dir);
if (!frontAndDone.contains(otherBlock) && otherBlock.getType() == Material.SCAFFOLDING) {
front.addLast(otherBlock);
frontAndDone.add(otherBlock);
}
}
}
return null;
}
class ScaffoldingBreaker {
protected final Actor actor;
protected final long time;
protected final Location location;
public ScaffoldingBreaker(Actor actor, Location location) {
this.actor = actor;
this.location = location;
this.time = System.currentTimeMillis();
}
public Actor getActor() {
return actor;
}
public Location getLocation() {
return location;
}
public long getTime() {
return time;
}
}
}

View File

@ -17,7 +17,7 @@ public class SignChangeLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSignChange(SignChangeEvent event) {
if (isLogging(event.getBlock().getWorld(), Logging.SIGNTEXT)) {
consumer.queueSignPlace(Actor.actorFromEntity(event.getPlayer()), event.getBlock().getLocation(), event.getBlock().getTypeId(), event.getBlock().getData(), event.getLines());
consumer.queueSignChange(Actor.actorFromEntity(event.getPlayer()), event.getBlock().getLocation(), event.getBlock().getBlockData(), event.getLines());
}
}
}

View File

@ -3,6 +3,8 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockFadeEvent;
@ -17,8 +19,8 @@ public class SnowFadeLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockFade(BlockFadeEvent event) {
if (isLogging(event.getBlock().getWorld(), Logging.SNOWFADE)) {
final int type = event.getBlock().getTypeId();
if (type == 78 || type == 79) {
final Material type = event.getBlock().getType();
if (type == Material.SNOW || type == Material.ICE) {
consumer.queueBlockReplace(new Actor("SnowFade"), event.getBlock().getState(), event.getNewState());
}
}

View File

@ -3,6 +3,8 @@ package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.Actor;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockFormEvent;
@ -14,17 +16,11 @@ public class SnowFormLogging extends LoggingListener {
super(lb);
}
// @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
// public void onLeavesDecay(LeavesDecayEvent event) {
// if (isLogging(event.getBlock().getWorld(), Logging.SNOWFORM))
// consumer.queueBlockBreak("LeavesDecay", event.getBlock().getState());
// }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockForm(BlockFormEvent event) {
if (isLogging(event.getBlock().getWorld(), Logging.SNOWFORM)) {
final int type = event.getNewState().getTypeId();
if (type == 78 || type == 79) {
final Material type = event.getNewState().getType();
if (type == Material.SNOW || type == Material.ICE) {
consumer.queueBlockReplace(new Actor("SnowForm"), event.getBlock().getState(), event.getNewState());
}
}

View File

@ -1,10 +1,12 @@
package de.diddiz.LogBlock.listeners;
import de.diddiz.LogBlock.*;
import de.diddiz.worldedit.RegionContainer;
import de.diddiz.LogBlock.events.ToolUseEvent;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.CuboidRegion;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@ -30,11 +32,11 @@ public class ToolListener implements Listener {
handler = logblock.getCommandsHandler();
}
@EventHandler(ignoreCancelled = true)
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getMaterial() != null) {
final Action action = event.getAction();
final int type = event.getMaterial().getId();
final Material type = event.getMaterial();
final Tool tool = toolsByType.get(type);
final Player player = event.getPlayer();
if (tool != null && (action == Action.RIGHT_CLICK_BLOCK || action == Action.LEFT_CLICK_BLOCK) && logblock.hasPermission(player, "logblock.tools." + tool.name)) {
@ -47,27 +49,26 @@ public class ToolListener implements Listener {
return;
}
final Block block = event.getClickedBlock();
final QueryParams params = toolData.params;
final QueryParams params = toolData.params.clone();
params.loc = null;
params.sel = null;
if (behavior == ToolBehavior.BLOCK) {
params.setLocation(block.getRelative(event.getBlockFace()).getLocation());
} else if ((block.getTypeId() != 54 && block.getTypeId() != 146) || tool.params.radius != 0) {
} else if (tool.params.radius != 0) {
params.setLocation(block.getLocation());
} else {
if (logblock.getServer().getPluginManager().isPluginEnabled("WorldEdit")) {
for (final BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) {
if (block.getRelative(face).getTypeId() == block.getTypeId()) {
params.setSelection(RegionContainer.fromCorners(event.getPlayer().getWorld(),
block.getLocation(), block.getRelative(face).getLocation()));
}
}
}
if (params.sel == null) {
Block otherHalfChest = BukkitUtils.getConnectedChest(block);
if (otherHalfChest == null) {
params.setLocation(block.getLocation());
} else {
params.setSelection(CuboidRegion.fromCorners(block.getLocation().getWorld(), block.getLocation(), otherHalfChest.getLocation()));
}
}
try {
params.validate();
if (this.callToolUseEvent(new ToolUseEvent(player, tool, behavior, params))) {
return;
}
if (toolData.mode == ToolMode.ROLLBACK) {
handler.new CommandRollback(player, params, true);
} else if (toolData.mode == ToolMode.REDO) {
@ -88,6 +89,11 @@ public class ToolListener implements Listener {
}
}
private boolean callToolUseEvent(ToolUseEvent event) {
this.logblock.getServer().getPluginManager().callEvent(event);
return event.isCancelled();
}
@EventHandler
public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
final Player player = event.getPlayer();
@ -98,7 +104,9 @@ public class ToolListener implements Listener {
final ToolData toolData = entry.getValue();
if (toolData.enabled && !logblock.hasPermission(player, "logblock.tools." + tool.name)) {
toolData.enabled = false;
player.getInventory().removeItem(new ItemStack(tool.item, 1));
if (tool.removeOnDisable && logblock.hasPermission(player, "logblock.spawnTools")) {
player.getInventory().removeItem(new ItemStack(tool.item, 1));
}
player.sendMessage(ChatColor.GREEN + "Tool disabled.");
}
}
@ -113,10 +121,29 @@ public class ToolListener implements Listener {
for (final Entry<Tool, ToolData> entry : session.toolData.entrySet()) {
final Tool tool = entry.getKey();
final ToolData toolData = entry.getValue();
final int item = event.getItemDrop().getItemStack().getTypeId();
if (item == tool.item && toolData.enabled && !tool.canDrop) {
player.sendMessage(ChatColor.RED + "You cannot drop this tool.");
event.setCancelled(true);
final Material item = event.getItemDrop().getItemStack().getType();
if (item == tool.item && toolData.enabled) {
if (tool.dropToDisable) {
toolData.enabled = false;
ItemStack stack = event.getItemDrop().getItemStack();
if (tool.removeOnDisable && logblock.hasPermission(player, "logblock.spawnTools")) {
if (stack.isSimilar(new ItemStack(item))) {
if (stack.getAmount() > 1) {
stack.setAmount(stack.getAmount() - 1);
event.getItemDrop().setItemStack(stack);
} else {
event.getItemDrop().remove();
}
}
}
if (BukkitUtils.hasInventoryStorageSpaceFor(player.getInventory(), stack)) {
event.setCancelled(true);
}
player.sendMessage(ChatColor.GREEN + "Tool disabled.");
} else if (!tool.canDrop) {
player.sendMessage(ChatColor.RED + "You cannot drop this tool.");
event.setCancelled(true);
}
}
}
}

View File

@ -18,7 +18,7 @@ public class WitherLogging extends LoggingListener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
if (event.getEntity() instanceof Wither && isLogging(event.getBlock().getWorld(), Logging.WITHER)) {
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getTo().getId(), event.getData()); // Wither walked through a block.
consumer.queueBlockReplace(Actor.actorFromEntity(event.getEntity()), event.getBlock().getState(), event.getBlockData()); // Wither walked through a block.
}
}
}

View File

@ -0,0 +1,73 @@
package de.diddiz.LogBlock.questioner;
import org.bukkit.entity.Player;
public class Question {
private String answer;
private final String[] answers;
private final String questionMessage;
private final Player respondent;
private final long start;
public Question(Player respondent, String questionMessage, String[] answers) {
this.start = System.currentTimeMillis();
this.respondent = respondent;
this.questionMessage = questionMessage;
this.answers = answers;
}
public synchronized String ask() {
StringBuilder options = new StringBuilder();
for (String ans : this.answers) {
options.append("/" + ans + ", ");
}
options.delete(options.length() - 2, options.length());
this.respondent.sendMessage(this.questionMessage);
this.respondent.sendMessage("- " + options + "?");
while (answer == null) {
try {
wait();
} catch (InterruptedException ex) {
if (answer == null) {
answer = "interrupted";
}
}
}
return this.answer;
}
public synchronized boolean isExpired(boolean forceExpire) {
if (forceExpire || System.currentTimeMillis() - this.start > 120000L || this.answer != null) {
if (answer == null) {
answer = "timed out";
}
notifyAll();
return true;
}
return false;
}
public boolean returnAnswer(String answer) {
return returnAnswer(answer, false);
}
public synchronized boolean returnAnswer(String answer, boolean forceReturn) {
if (forceReturn) {
if (this.answer == null) {
this.answer = answer;
}
notifyAll();
return true;
}
for (String s : answers) {
if (s.equalsIgnoreCase(answer)) {
if (this.answer == null) {
this.answer = s;
}
notifyAll();
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,76 @@
package de.diddiz.LogBlock.questioner;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map.Entry;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import de.diddiz.LogBlock.LogBlock;
public class Questioner {
private final LogBlock logBlock;
private final ConcurrentHashMap<UUID, Question> questions = new ConcurrentHashMap<>();
public Questioner(LogBlock logBlock) {
this.logBlock = logBlock;
logBlock.getServer().getPluginManager().registerEvents(new QuestionerListener(), logBlock);
logBlock.getServer().getScheduler().scheduleSyncRepeatingTask(logBlock, new QuestionsReaper(), 600, 600);
}
public String ask(Player respondent, String questionMessage, String... answers) {
if (Bukkit.isPrimaryThread()) {
throw new IllegalStateException("This method may not be called from the primary thread");
}
Question question = new Question(respondent, questionMessage, answers);
Question oldQuestion = this.questions.put(respondent.getUniqueId(), question);
if (oldQuestion != null) {
oldQuestion.returnAnswer("no", true);
// wait a little time to let the other thread continue
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return question.ask();
}
private class QuestionsReaper implements Runnable {
@Override
public void run() {
if (questions.isEmpty()) {
return;
}
Iterator<Entry<UUID, Question>> it = questions.entrySet().iterator();
while (it.hasNext()) {
Entry<UUID, Question> e = it.next();
Question question = e.getValue();
if (question.isExpired(logBlock.getServer().getPlayer(e.getKey()) == null)) {
it.remove();
}
}
}
}
private class QuestionerListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
UUID player = event.getPlayer().getUniqueId();
Question question;
question = questions.get(player);
if (question != null) {
String answer = event.getMessage().substring(1).toLowerCase();
if (question.returnAnswer(answer)) {
questions.remove(player, question);
event.setCancelled(true);
}
}
}
}
}

View File

@ -0,0 +1,24 @@
package de.diddiz.util;
import net.md_5.bungee.api.ChatColor;
public enum ActionColor {
DESTROY(ChatColor.RED),
CREATE(ChatColor.DARK_GREEN),
INTERACT(ChatColor.GRAY);
private final ChatColor color;
ActionColor(ChatColor color) {
this.color = color;
}
public ChatColor getColor() {
return color;
}
@Override
public String toString() {
return color.toString();
}
}

View File

@ -1,34 +0,0 @@
package de.diddiz.util;
import java.util.List;
public class Block {
private int block;
private int data;
/**
* @param block The id of the block
* @param data The data for the block, -1 for any data
*/
public Block(int block, int data) {
this.block = block;
this.data = data;
}
public int getBlock() {
return this.block;
}
public int getData() {
return this.data;
}
public static boolean inList(List<Block> types, int blockID) {
for (Block block : types) {
if (block.getBlock() == blockID) {
return true;
}
}
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,424 @@
package de.diddiz.util;
// Taken from maven-artifact at
// http://grepcode.com/file_/repo1.maven.org/maven2/org.apache.maven/maven-artifact/3.2.3/org/apache/maven/artifact/versioning/ComparableVersion.java/?v=source
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Properties;
import java.util.Stack;
/**
* Generic implementation of version comparison.
*
* <p>Features:
* <ul>
* <li>mixing of '<code>-</code>' (dash) and '<code>.</code>' (dot) separators,</li>
* <li>transition between characters and digits also constitutes a separator:
* <code>1.0alpha1 =&gt; [1, 0, alpha, 1]</code></li>
* <li>unlimited number of version components,</li>
* <li>version components in the text can be digits or strings,</li>
* <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
* Well-known qualifiers (case insensitive) are:<ul>
* <li><code>alpha</code> or <code>a</code></li>
* <li><code>beta</code> or <code>b</code></li>
* <li><code>milestone</code> or <code>m</code></li>
* <li><code>rc</code> or <code>cr</code></li>
* <li><code>snapshot</code></li>
* <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li>
* <li><code>sp</code></li>
* </ul>
* Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
* </li>
* <li>a dash usually precedes a qualifier, and is always less important than something preceded with a dot.</li>
* </ul></p>
*
* @see <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a>
* @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
* @author <a href="mailto:hboutemy@apache.org">Hervé Boutemy</a>
*/
public class ComparableVersion implements Comparable<ComparableVersion> {
private String value;
private String canonical;
private ListItem items;
private interface Item {
int INTEGER_ITEM = 0;
int STRING_ITEM = 1;
int LIST_ITEM = 2;
int compareTo(Item item);
int getType();
boolean isNull();
}
/**
* Represents a numeric item in the version item list.
*/
private static class IntegerItem implements Item {
private static final BigInteger BIG_INTEGER_ZERO = new BigInteger("0");
private final BigInteger value;
public static final IntegerItem ZERO = new IntegerItem();
private IntegerItem() {
this.value = BIG_INTEGER_ZERO;
}
public IntegerItem(String str) {
this.value = new BigInteger(str);
}
@Override
public int getType() {
return INTEGER_ITEM;
}
@Override
public boolean isNull() {
return BIG_INTEGER_ZERO.equals(value);
}
@Override
public int compareTo(Item item) {
if (item == null) {
return BIG_INTEGER_ZERO.equals(value) ? 0 : 1; // 1.0 == 1, 1.1 > 1
}
switch (item.getType()) {
case INTEGER_ITEM:
return value.compareTo(((IntegerItem) item).value);
case STRING_ITEM:
return 1; // 1.1 > 1-sp
case LIST_ITEM:
return 1; // 1.1 > 1-1
default:
throw new RuntimeException("invalid item: " + item.getClass());
}
}
@Override
public String toString() {
return value.toString();
}
}
/**
* Represents a string in the version item list, usually a qualifier.
*/
private static class StringItem implements Item {
private static final String[] QUALIFIERS = { "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" };
private static final List<String> _QUALIFIERS = Arrays.asList(QUALIFIERS);
private static final Properties ALIASES = new Properties();
static {
ALIASES.put("ga", "");
ALIASES.put("final", "");
ALIASES.put("cr", "rc");
}
/**
* A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
* the version older than one without a qualifier, or more recent.
*/
private static final String RELEASE_VERSION_INDEX = String.valueOf(_QUALIFIERS.indexOf(""));
private String value;
public StringItem(String value, boolean followedByDigit) {
if (followedByDigit && value.length() == 1) {
// a1 = alpha-1, b1 = beta-1, m1 = milestone-1
switch (value.charAt(0)) {
case 'a':
value = "alpha";
break;
case 'b':
value = "beta";
break;
case 'm':
value = "milestone";
break;
}
}
this.value = ALIASES.getProperty(value, value);
}
@Override
public int getType() {
return STRING_ITEM;
}
@Override
public boolean isNull() {
return (comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0);
}
/**
* Returns a comparable value for a qualifier.
*
* This method takes into account the ordering of known qualifiers then unknown qualifiers with lexical ordering.
*
* just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1
* or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character,
* so this is still fast. If more characters are needed then it requires a lexical sort anyway.
*
* @param qualifier
* @return an equivalent value that can be used with lexical comparison
*/
public static String comparableQualifier(String qualifier) {
int i = _QUALIFIERS.indexOf(qualifier);
return i == -1 ? (_QUALIFIERS.size() + "-" + qualifier) : String.valueOf(i);
}
@Override
public int compareTo(Item item) {
if (item == null) {
// 1-rc < 1, 1-ga > 1
return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX);
}
switch (item.getType()) {
case INTEGER_ITEM:
return -1; // 1.any < 1.1 ?
case STRING_ITEM:
return comparableQualifier(value).compareTo(comparableQualifier(((StringItem) item).value));
case LIST_ITEM:
return -1; // 1.any < 1-1
default:
throw new RuntimeException("invalid item: " + item.getClass());
}
}
@Override
public String toString() {
return value;
}
}
/**
* Represents a version list item. This class is used both for the global item list and for sub-lists (which start
* with '-(number)' in the version specification).
*/
private static class ListItem extends ArrayList<Item> implements Item {
private static final long serialVersionUID = 5914575811857700009L;
@Override
public int getType() {
return LIST_ITEM;
}
@Override
public boolean isNull() {
return (size() == 0);
}
void normalize() {
for (ListIterator<Item> iterator = listIterator(size()); iterator.hasPrevious();) {
Item item = iterator.previous();
if (item.isNull()) {
iterator.remove(); // remove null trailing items: 0, "", empty list
} else {
break;
}
}
}
@Override
public int compareTo(Item item) {
if (item == null) {
if (size() == 0) {
return 0; // 1-0 = 1- (normalize) = 1
}
Item first = get(0);
return first.compareTo(null);
}
switch (item.getType()) {
case INTEGER_ITEM:
return -1; // 1-1 < 1.0.x
case STRING_ITEM:
return 1; // 1-1 > 1-sp
case LIST_ITEM:
Iterator<Item> left = iterator();
Iterator<Item> right = ((ListItem) item).iterator();
while (left.hasNext() || right.hasNext()) {
Item l = left.hasNext() ? left.next() : null;
Item r = right.hasNext() ? right.next() : null;
// if this is shorter, then invert the compare and mul with -1
int result = l == null ? (r == null ? 0 : -1 * r.compareTo(l)) : l.compareTo(r);
if (result != 0) {
return result;
}
}
return 0;
default:
throw new RuntimeException("invalid item: " + item.getClass());
}
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder("(");
for (Iterator<Item> iter = iterator(); iter.hasNext();) {
buffer.append(iter.next());
if (iter.hasNext()) {
buffer.append(',');
}
}
buffer.append(')');
return buffer.toString();
}
}
public ComparableVersion(String version) {
parseVersion(version);
}
public final void parseVersion(String version) {
this.value = version;
items = new ListItem();
version = version.toLowerCase(Locale.ENGLISH);
ListItem list = items;
Stack<Item> stack = new Stack<>();
stack.push(list);
boolean isDigit = false;
int startIndex = 0;
for (int i = 0; i < version.length(); i++) {
char c = version.charAt(i);
if (c == '.') {
if (i == startIndex) {
list.add(IntegerItem.ZERO);
} else {
list.add(parseItem(isDigit, version.substring(startIndex, i)));
}
startIndex = i + 1;
} else if (c == '-') {
if (i == startIndex) {
list.add(IntegerItem.ZERO);
} else {
list.add(parseItem(isDigit, version.substring(startIndex, i)));
}
startIndex = i + 1;
if (isDigit) {
list.normalize(); // 1.0-* = 1-*
if ((i + 1 < version.length()) && Character.isDigit(version.charAt(i + 1))) {
// new ListItem only if previous were digits and new char is a digit,
// ie need to differentiate only 1.1 from 1-1
list.add(list = new ListItem());
stack.push(list);
}
}
} else if (Character.isDigit(c)) {
if (!isDigit && i > startIndex) {
list.add(new StringItem(version.substring(startIndex, i), true));
startIndex = i;
}
isDigit = true;
} else {
if (isDigit && i > startIndex) {
list.add(parseItem(true, version.substring(startIndex, i)));
startIndex = i;
}
isDigit = false;
}
}
if (version.length() > startIndex) {
list.add(parseItem(isDigit, version.substring(startIndex)));
}
while (!stack.isEmpty()) {
list = (ListItem) stack.pop();
list.normalize();
}
canonical = items.toString();
}
private static Item parseItem(boolean isDigit, String buf) {
return isDigit ? new IntegerItem(buf) : new StringItem(buf, false);
}
@Override
public int compareTo(ComparableVersion o) {
return items.compareTo(o.items);
}
public int compareTo(String version) {
return compareTo(new ComparableVersion(version));
}
@Override
public String toString() {
return value;
}
public String toCanonicalString() {
return canonical;
}
@Override
public boolean equals(Object o) {
return (o instanceof ComparableVersion) && canonical.equals(((ComparableVersion) o).canonical);
}
@Override
public int hashCode() {
return canonical.hashCode();
}
}

View File

@ -0,0 +1,58 @@
package de.diddiz.util;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.util.BlockVector;
public class CuboidRegion implements Cloneable {
private World world;
private BlockVector min = new BlockVector();
private BlockVector max = new BlockVector();
public CuboidRegion(World world, BlockVector first, BlockVector second) {
this.world = world;
this.min.setX(Math.min(first.getBlockX(), second.getBlockX()));
this.min.setY(Math.min(first.getBlockY(), second.getBlockY()));
this.min.setZ(Math.min(first.getBlockZ(), second.getBlockZ()));
this.max.setX(Math.max(first.getBlockX(), second.getBlockX()));
this.max.setY(Math.max(first.getBlockY(), second.getBlockY()));
this.max.setZ(Math.max(first.getBlockZ(), second.getBlockZ()));
}
public static CuboidRegion fromCorners(World world, Location first, Location second) {
return new CuboidRegion(world, new BlockVector(first.getBlockX(), first.getBlockY(), first.getBlockZ()), new BlockVector(second.getBlockX(), second.getBlockY(), second.getBlockZ()));
}
public World getWorld() {
return world;
}
public BlockVector getMinimumPoint() {
return min;
}
public BlockVector getMaximumPoint() {
return max;
}
public int getSizeX() {
return max.getBlockX() - min.getBlockX() + 1;
}
public int getSizeZ() {
return max.getBlockZ() - min.getBlockZ() + 1;
}
@Override
public CuboidRegion clone() {
try {
CuboidRegion clone = (CuboidRegion) super.clone();
clone.min = min.clone();
clone.max = max.clone();
return clone;
} catch (final CloneNotSupportedException ex) {
throw new Error("CuboidRegion should be cloneable", ex);
}
}
}

View File

@ -9,9 +9,15 @@ import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.material.*;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.type.Bell;
import org.bukkit.block.data.type.Bell.Attachment;
import org.bukkit.block.data.type.Lantern;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.projectiles.ProjectileSource;
import java.util.List;
import static de.diddiz.LogBlock.config.Config.getWorldConfig;
@ -19,6 +25,54 @@ import static de.diddiz.LogBlock.config.Config.mb4;
public class LoggingUtil {
public static void smartLogBlockPlace(Consumer consumer, Actor actor, BlockState replaced, BlockState placed) {
Location loc = replaced.getLocation();
Material placedType = placed.getType();
if (!placedType.hasGravity() || !BukkitUtils.canDirectlyFallIn(replaced.getBlock().getRelative(BlockFace.DOWN).getType())) {
if (placedType == Material.TWISTING_VINES) {
Block below = placed.getBlock().getRelative(BlockFace.DOWN);
if (below.getType() == Material.TWISTING_VINES) {
consumer.queueBlockReplace(actor, below.getState(), Material.TWISTING_VINES_PLANT.createBlockData());
}
}
if (placedType == Material.WEEPING_VINES) {
Block above = placed.getBlock().getRelative(BlockFace.UP);
if (above.getType() == Material.WEEPING_VINES) {
consumer.queueBlockReplace(actor, above.getState(), Material.WEEPING_VINES_PLANT.createBlockData());
}
}
if (BukkitUtils.isEmpty(replaced.getType())) {
consumer.queueBlockPlace(actor, placed);
} else {
consumer.queueBlockReplace(actor, replaced, placed);
}
return;
}
int x = loc.getBlockX();
int initialy = loc.getBlockY();
int y = initialy;
int z = loc.getBlockZ();
while (y > 0 && BukkitUtils.canFallIn(loc.getWorld(), x, (y - 1), z)) {
y--;
}
if (initialy != y && !BukkitUtils.isEmpty(replaced.getType())) {
// this is not the final location but the block got removed (vines etc)
consumer.queueBlockBreak(actor, replaced);
}
// If y is 0 then the block fell out of the world :(
if (y != 0) {
// Run this check to avoid false positives
Location finalLoc = new Location(loc.getWorld(), x, y, z);
if (y == initialy || !BukkitUtils.getFallingEntityKillers().contains(finalLoc.getBlock().getType())) {
if (BukkitUtils.isEmpty(finalLoc.getBlock().getType())) {
consumer.queueBlockPlace(actor, finalLoc, placed.getBlockData());
} else {
consumer.queueBlockReplace(actor, finalLoc.getBlock().getState(), placed.getBlockData());
}
}
}
}
public static void smartLogFallables(Consumer consumer, Actor actor, Block origin) {
WorldConfig wcfg = getWorldConfig(origin.getWorld());
@ -30,7 +84,7 @@ public class LoggingUtil {
Block checkBlock = origin.getRelative(BlockFace.UP);
int up = 0;
final int highestBlock = checkBlock.getWorld().getHighestBlockYAt(checkBlock.getLocation());
while (BukkitUtils.getRelativeTopFallables().contains(checkBlock.getType())) {
while (checkBlock.getType().hasGravity()) {
// Record this block as falling
consumer.queueBlockBreak(actor, checkBlock.getState());
@ -40,7 +94,7 @@ public class LoggingUtil {
int x = loc.getBlockX();
int y = loc.getBlockY();
int z = loc.getBlockZ();
while (y > 0 && BukkitUtils.canFall(loc.getWorld(), x, (y - 1), z)) {
while (y > 0 && BukkitUtils.canFallIn(loc.getWorld(), x, (y - 1), z)) {
y--;
}
// If y is 0 then the sand block fell out of the world :(
@ -49,10 +103,10 @@ public class LoggingUtil {
// Run this check to avoid false positives
if (!BukkitUtils.getFallingEntityKillers().contains(finalLoc.getBlock().getType())) {
finalLoc.add(0, up, 0); // Add this here after checking for block breakers
if (finalLoc.getBlock().getType() == Material.AIR || BukkitUtils.getRelativeTopFallables().contains(finalLoc.getBlock().getType())) {
consumer.queueBlockPlace(actor, finalLoc, checkBlock.getTypeId(), checkBlock.getData());
if (BukkitUtils.isEmpty(finalLoc.getBlock().getType())) {
consumer.queueBlockPlace(actor, finalLoc, checkBlock.getBlockData());
} else {
consumer.queueBlockReplace(actor, finalLoc, finalLoc.getBlock().getTypeId(), finalLoc.getBlock().getData(), checkBlock.getTypeId(), checkBlock.getData());
consumer.queueBlockReplace(actor, finalLoc, finalLoc.getBlock().getBlockData(), checkBlock.getBlockData());
}
up++;
}
@ -62,144 +116,165 @@ public class LoggingUtil {
}
checkBlock = checkBlock.getRelative(BlockFace.UP);
}
if (wcfg.isLogging(Logging.SCAFFOLDING) && checkBlock.getType() == Material.SCAFFOLDING && consumer.getLogblock().getScaffoldingLogging() != null) {
consumer.getLogblock().getScaffoldingLogging().addScaffoldingBreaker(actor, checkBlock);
}
}
public static void smartLogBlockBreak(Consumer consumer, Actor actor, Block origin) {
smartLogBlockReplace(consumer, actor, origin, null);
}
public static void smartLogBlockReplace(Consumer consumer, Actor actor, Block origin, BlockData replacedWith) {
WorldConfig wcfg = getWorldConfig(origin.getWorld());
if (wcfg == null) {
return;
}
Material replacedType = origin.getType();
if (replacedType == Material.TWISTING_VINES || replacedType == Material.TWISTING_VINES_PLANT) {
Block below = origin.getRelative(BlockFace.DOWN);
if (below.getType() == Material.TWISTING_VINES_PLANT) {
consumer.queueBlockReplace(actor, below.getState(), Material.TWISTING_VINES.createBlockData());
}
}
if (replacedType == Material.WEEPING_VINES || replacedType == Material.WEEPING_VINES_PLANT) {
Block above = origin.getRelative(BlockFace.UP);
if (above.getType() == Material.WEEPING_VINES_PLANT) {
consumer.queueBlockReplace(actor, above.getState(), Material.WEEPING_VINES.createBlockData());
}
}
Block checkBlock = origin.getRelative(BlockFace.UP);
if (BukkitUtils.getRelativeTopBreakabls().contains(checkBlock.getType())) {
if (wcfg.isLogging(Logging.SIGNTEXT) && checkBlock.getType() == Material.SIGN_POST) {
consumer.queueSignBreak(actor, (Sign) checkBlock.getState());
} else if (checkBlock.getType() == Material.IRON_DOOR_BLOCK || checkBlock.getType() == Material.WOODEN_DOOR) {
Material typeAbove = checkBlock.getType();
if (BukkitUtils.getRelativeTopBreakabls().contains(typeAbove)) {
if (typeAbove == Material.IRON_DOOR || BukkitUtils.isWoodenDoor(typeAbove)) {
Block doorBlock = checkBlock;
// If the doorBlock is the top half a door the player simply punched a door
// this will be handled later.
if (!BukkitUtils.isTop(doorBlock.getType(), doorBlock.getData())) {
if (!BukkitUtils.isTop(doorBlock.getBlockData())) {
doorBlock = doorBlock.getRelative(BlockFace.UP);
// Fall back check just in case the top half wasn't a door
if (doorBlock.getType() == Material.IRON_DOOR_BLOCK || doorBlock.getType() == Material.WOODEN_DOOR) {
if (doorBlock.getType() == Material.IRON_DOOR || BukkitUtils.isWoodenDoor(doorBlock.getType())) {
consumer.queueBlockBreak(actor, doorBlock.getState());
}
consumer.queueBlockBreak(actor, checkBlock.getState());
}
} else if (checkBlock.getType() == Material.DOUBLE_PLANT) {
} else if (BukkitUtils.isDoublePlant(typeAbove)) {
Block plantBlock = checkBlock;
// If the plantBlock is the top half of a double plant the player simply
// punched the plant this will be handled later.
if (!BukkitUtils.isTop(plantBlock.getType(), plantBlock.getData())) {
if (!BukkitUtils.isTop(plantBlock.getBlockData())) {
plantBlock = plantBlock.getRelative(BlockFace.UP);
// Fall back check just in case the top half wasn't a plant
if (plantBlock.getType() == Material.DOUBLE_PLANT) {
if (BukkitUtils.isDoublePlant(plantBlock.getType())) {
consumer.queueBlockBreak(actor, plantBlock.getState());
}
consumer.queueBlockBreak(actor, checkBlock.getState());
}
} else {
consumer.queueBlockBreak(actor, checkBlock.getState());
// check next blocks above
checkBlock = checkBlock.getRelative(BlockFace.UP);
typeAbove = checkBlock.getType();
while (BukkitUtils.getRelativeTopBreakabls().contains(typeAbove)) {
consumer.queueBlockBreak(actor, checkBlock.getState());
checkBlock = checkBlock.getRelative(BlockFace.UP);
typeAbove = checkBlock.getType();
}
}
} else if (typeAbove == Material.LANTERN) {
Lantern lantern = (Lantern) checkBlock.getBlockData();
if (!lantern.isHanging()) {
consumer.queueBlockBreak(actor, checkBlock.getState());
}
} else if (typeAbove == Material.BELL) {
Bell bell = (Bell) checkBlock.getBlockData();
if (bell.getAttachment() == Attachment.FLOOR) {
consumer.queueBlockBreak(actor, checkBlock.getState());
}
}
checkBlock = origin.getRelative(BlockFace.DOWN);
Material typeBelow = checkBlock.getType();
if (typeBelow == Material.LANTERN) {
Lantern lantern = (Lantern) checkBlock.getBlockData();
if (lantern.isHanging()) {
consumer.queueBlockBreak(actor, checkBlock.getState());
}
} else if (typeBelow == Material.BELL) {
Bell bell = (Bell) checkBlock.getBlockData();
if (bell.getAttachment() == Attachment.CEILING) {
consumer.queueBlockBreak(actor, checkBlock.getState());
}
} else if (typeBelow == Material.WEEPING_VINES || typeBelow == Material.WEEPING_VINES_PLANT) {
consumer.queueBlockBreak(actor, checkBlock.getState());
// check next blocks above
checkBlock = checkBlock.getRelative(BlockFace.DOWN);
typeBelow = checkBlock.getType();
while (typeBelow == Material.WEEPING_VINES || typeBelow == Material.WEEPING_VINES_PLANT) {
consumer.queueBlockBreak(actor, checkBlock.getState());
checkBlock = checkBlock.getRelative(BlockFace.DOWN);
typeBelow = checkBlock.getType();
}
}
List<Location> relativeBreakables = BukkitUtils.getBlocksNearby(origin, BukkitUtils.getRelativeBreakables());
if (relativeBreakables.size() != 0) {
for (Location location : relativeBreakables) {
final Material blockType = location.getBlock().getType();
final BlockState blockState = location.getBlock().getState();
final MaterialData data = blockState.getData();
switch (blockType) {
case REDSTONE_TORCH_ON:
case REDSTONE_TORCH_OFF:
if (blockState.getBlock().getRelative(((RedstoneTorch) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case TORCH:
if (blockState.getBlock().getRelative(((Torch) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case COCOA:
if (blockState.getBlock().getRelative(((CocoaPlant) data).getAttachedFace().getOppositeFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case LADDER:
if (blockState.getBlock().getRelative(((Ladder) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case LEVER:
if (blockState.getBlock().getRelative(((Lever) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case TRIPWIRE_HOOK:
if (blockState.getBlock().getRelative(((TripwireHook) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case WOOD_BUTTON:
case STONE_BUTTON:
if (blockState.getBlock().getRelative(((Button) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
}
break;
case WALL_SIGN:
if (blockState.getBlock().getRelative(((org.bukkit.material.Sign) data).getAttachedFace()).equals(origin)) {
if (wcfg.isLogging(Logging.SIGNTEXT)) {
consumer.queueSignBreak(actor, (Sign) blockState);
} else {
consumer.queueBlockBreak(actor, blockState);
Block block = location.getBlock();
BlockData blockData = block.getBlockData();
if (blockData instanceof Directional) {
if (blockData.getMaterial() == Material.BELL) {
if (((Bell) blockData).getAttachment() == Attachment.SINGLE_WALL) {
if (block.getRelative(((Bell) blockData).getFacing()).equals(origin)) {
consumer.queueBlockBreak(actor, block.getState());
}
}
break;
case TRAP_DOOR:
if (blockState.getBlock().getRelative(((TrapDoor) data).getAttachedFace()).equals(origin)) {
consumer.queueBlockBreak(actor, blockState);
} else {
if (block.getRelative(((Directional) blockData).getFacing().getOppositeFace()).equals(origin)) {
consumer.queueBlockBreak(actor, block.getState());
}
break;
default:
consumer.queueBlockBreak(actor, blockState);
break;
}
}
}
}
// Special door check
if (origin.getType() == Material.IRON_DOOR_BLOCK || origin.getType() == Material.WOODEN_DOOR) {
if (replacedType == Material.IRON_DOOR || BukkitUtils.isWoodenDoor(replacedType)) {
Block doorBlock = origin;
// Up or down?
if (!BukkitUtils.isTop(doorBlock.getType(), doorBlock.getData())) {
if (!BukkitUtils.isTop(doorBlock.getBlockData())) {
doorBlock = doorBlock.getRelative(BlockFace.UP);
} else {
doorBlock = doorBlock.getRelative(BlockFace.DOWN);
}
if (doorBlock.getType() == Material.IRON_DOOR_BLOCK || doorBlock.getType() == Material.WOODEN_DOOR) {
if (doorBlock.getType() == Material.IRON_DOOR || BukkitUtils.isWoodenDoor(doorBlock.getType())) {
consumer.queueBlockBreak(actor, doorBlock.getState());
}
} else if (origin.getType() == Material.DOUBLE_PLANT) { // Special double plant check
} else if (BukkitUtils.isDoublePlant(replacedType)) { // Special double plant check
Block plantBlock = origin;
// Up or down?
if (!BukkitUtils.isTop(origin.getType(), origin.getData())) {
if (!BukkitUtils.isTop(origin.getBlockData())) {
plantBlock = plantBlock.getRelative(BlockFace.UP);
} else {
plantBlock = plantBlock.getRelative(BlockFace.DOWN);
}
if (plantBlock.getType() == Material.DOUBLE_PLANT) {
if (BukkitUtils.isDoublePlant(plantBlock.getType())) {
consumer.queueBlockBreak(actor, plantBlock.getState());
}
}
// Do this down here so that the block is added after blocks sitting on it
consumer.queueBlockBreak(actor, origin.getState());
if (replacedWith == null) {
consumer.queueBlockBreak(actor, origin.getState());
} else {
consumer.queueBlockReplace(actor, origin.getState(), replacedWith);
}
}
public static String checkText(String text) {
@ -211,4 +286,20 @@ public class LoggingUtil {
}
return text.replaceAll("[^\\u0000-\\uFFFF]", "?");
}
public static Entity getRealDamager(Entity damager) {
if (damager instanceof Projectile) {
ProjectileSource realDamager = ((Projectile) damager).getShooter();
if (realDamager instanceof Entity) {
damager = (Entity) realDamager;
}
}
if (damager instanceof TNTPrimed) {
Entity realRemover = ((TNTPrimed) damager).getSource();
if (realRemover != null) {
damager = realRemover;
}
}
return damager;
}
}

View File

@ -1,277 +0,0 @@
package de.diddiz.util;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.material.MaterialData;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import static de.diddiz.util.Utils.isInt;
import static de.diddiz.util.Utils.isShort;
import static org.bukkit.Bukkit.getLogger;
public class MaterialName {
private static final String[] COLORS = {"white", "orange", "magenta", "light blue", "yellow", "lime", "pink", "gray", "silver", "cyan", "purple", "blue", "brown", "green", "red", "black"};
private static final Map<Integer, String> materialNames = new HashMap<Integer, String>();
private static final Map<Integer, Map<Short, String>> materialDataNames = new HashMap<Integer, Map<Short, String>>();
private static final Map<String, Integer> nameTypes = new HashMap<String, Integer>();
static {
// Add all known materials
for (final Material mat : Material.values()) {
materialNames.put(mat.getId(), mat.toString().replace('_', ' ').toLowerCase());
}
// Load config
final File file = new File("plugins/LogBlock/materials.yml");
final YamlConfiguration cfg = YamlConfiguration.loadConfiguration(file);
if (cfg.getKeys(false).isEmpty()) {
// Generate defaults
cfg.options().header("Add block or item names you want to be overridden or also names for custom blocks");
cfg.set("1.1", "granite");
cfg.set("1.2", "polished granite");
cfg.set("1.3", "diorite");
cfg.set("1.4", "polished diorite");
cfg.set("1.5", "andesite");
cfg.set("1.6", "polished andesite");
cfg.set("5.0", "oak wood");
cfg.set("5.1", "spruce wood");
cfg.set("5.2", "birch wood");
cfg.set("5.3", "jungle wood");
cfg.set("5.4", "acacia wood");
cfg.set("5.5", "dark oak wood");
cfg.set("3.1", "coarse dirt");
cfg.set("3.2", "podzol");
cfg.set("6.1", "redwood sapling");
cfg.set("6.2", "birch sapling");
cfg.set("6.3", "jungle sapling");
cfg.set("6.4", "acacia sapling");
cfg.set("6.5", "dark oak sapling");
cfg.set("9", "water");
cfg.set("11", "lava");
cfg.set("12.1", "red sand");
cfg.set("17.0", "oak log");
cfg.set("17.1", "spruce log");
cfg.set("17.2", "birch log");
cfg.set("17.3", "jungle log");
cfg.set("17.4", "oak log");
cfg.set("17.5", "spruce log");
cfg.set("17.6", "birch log");
cfg.set("17.7", "jungle log");
cfg.set("17.8", "oak log");
cfg.set("17.9", "spruce log");
cfg.set("17.10", "birch log");
cfg.set("17.11", "jungle log");
cfg.set("17.12", "oak log");
cfg.set("17.13", "spruce log");
cfg.set("17.14", "birch log");
cfg.set("17.15", "jungle log");
cfg.set("18.1", "spruce leaves");
cfg.set("18.2", "birch leaves");
cfg.set("18.3", "jungle leaves");
cfg.set("18.4", "oak leaves");
cfg.set("18.5", "spruce leaves");
cfg.set("18.6", "birch leaves");
cfg.set("18.7", "jungle leaves");
cfg.set("18.8", "oak leaves");
cfg.set("18.9", "spruce leaves");
cfg.set("18.10", "birch leaves");
cfg.set("18.11", "jungle leaves");
cfg.set("18.12", "oak leaves");
cfg.set("18.13", "spruce leaves");
cfg.set("18.14", "birch leaves");
cfg.set("18.15", "jungle leaves");
cfg.set("19.1", "wet sponge");
cfg.set("37.0", "dandelion");
cfg.set("38.0", "poppy");
cfg.set("38.1", "blue orchid");
cfg.set("38.2", "allium");
cfg.set("38.3", "azure bluet");
cfg.set("38.4", "red tulip");
cfg.set("38.5", "orange tulip");
cfg.set("38.6", "white tulip");
cfg.set("38.7", "pink tulip");
cfg.set("38.8", "oxeye daisy");
cfg.set("24.1", "chiseled sandstone");
cfg.set("24.2", "smooth sandstone");
cfg.set("31.0", "dead bush");
cfg.set("31.1", "tall grass");
cfg.set("31.2", "fern");
cfg.set("98.0", "stone brick");
cfg.set("98.1", "mossy stone brick");
cfg.set("98.2", "cracked stone brick");
cfg.set("98.3", "chiseled stone brick");
cfg.set("125.0", "oak double step");
cfg.set("125.1", "spruce double step");
cfg.set("125.2", "birch double step");
cfg.set("125.3", "jungle double step");
cfg.set("125.4", "acacia double step");
cfg.set("125.5", "dark oak double step");
cfg.set("126.0", "oak step");
cfg.set("126.1", "spruce step");
cfg.set("126.2", "birch step");
cfg.set("126.3", "jungle step");
cfg.set("126.4", "acacia step");
cfg.set("126.5", "dark oak step");
cfg.set("126.8", "oak step");
cfg.set("126.9", "spruce step");
cfg.set("126.10", "birch step");
cfg.set("126.11", "jungle step");
cfg.set("126.12", "acacia step");
cfg.set("126.13", "dark oak step");
cfg.set("139.1", "mossy cobble wall");
cfg.set("155.1", "chiseled quartz block");
cfg.set("155.2", "pillar quartz block");
cfg.set("155.3", "pillar quartz block");
cfg.set("155.4", "pillar quartz block");
cfg.set("161.0", "acacia leaves");
cfg.set("161.1", "dark oak leaves");
cfg.set("161.4", "acacia leaves");
cfg.set("161.5", "dark oak leaves");
cfg.set("161.8", "acacia leaves");
cfg.set("161.9", "dark oak leaves");
cfg.set("161.12", "acacia leaves");
cfg.set("161.13", "dark oak leaves");
cfg.set("162.0", "acacia log");
cfg.set("162.1", "dark oak log");
cfg.set("162.4", "acacia log");
cfg.set("162.5", "dark oak log");
cfg.set("162.8", "acacia log");
cfg.set("162.9", "dark oak log");
cfg.set("162.12", "acacia log");
cfg.set("162.13", "dark oak log");
cfg.set("168.1", "prismarine brick");
cfg.set("168.2", "dark prismarine");
cfg.set("181.0", "red sandstone double step");
cfg.set("181.8", "smooth red sandstone double step");
cfg.set("162.13", "dark oak log");
cfg.set("175.0", "sunflower");
cfg.set("175.1", "lilac");
cfg.set("175.2", "double tall grass");
cfg.set("175.3", "large fern");
cfg.set("175.4", "rose bush");
cfg.set("175.5", "peony");
cfg.set("175.8", "sunflower");
cfg.set("175.9", "lilac");
cfg.set("175.10", "double tall grass");
cfg.set("175.11", "large fern");
cfg.set("175.12", "rose bush");
cfg.set("175.13", "peony");
cfg.set("179.1", "chiseled sandstone");
cfg.set("179.2", "smooth sandstone");
cfg.set("263.1", "charcoal");
for (byte i = 0; i < 10; i++) {
cfg.set("43." + i, toReadable(Material.DOUBLE_STEP.getNewData(i)));
}
cfg.set("43.8", "stone double step");
cfg.set("43.9", "sandstone double step");
cfg.set("43.15", "quartz double step");
for (byte i = 0; i < 8; i++) {
cfg.set("44." + i, toReadable(Material.STEP.getNewData(i)));
// The second half of this data list should read the same as the first half
cfg.set("44." + (i + 7), toReadable(Material.STEP.getNewData(i)));
}
for (byte i = 0; i < 16; i++) {
cfg.set("351." + i, toReadable(Material.INK_SACK.getNewData(i)));
cfg.set("35." + i, COLORS[i] + " wool");
cfg.set("159." + i, COLORS[i] + " stained clay");
cfg.set("95." + i, COLORS[i] + " stained glass");
cfg.set("160." + i, COLORS[i] + " stained glass pane");
cfg.set("171." + i, COLORS[i] + " carpet");
}
for (byte i = 0; i < 6; i++) {
cfg.set("125." + i, toReadable(Material.WOOD_DOUBLE_STEP.getNewData(i)));
cfg.set("126." + i, toReadable(Material.WOOD_STEP.getNewData(i)));
cfg.set("126." + i + 8, toReadable(Material.WOOD_STEP.getNewData(i)));
}
try {
cfg.save(file);
} catch (final IOException ex) {
getLogger().log(Level.WARNING, "Unable to save material.yml: ", ex);
}
}
if (cfg.getString("263.1") == null) {
getLogger().info("[Logblock-names] Logblock's default materials.yml file has been updated with more names");
getLogger().info("[Logblock-names] Consider deleting your current materials.yml file to allow it to be recreated");
}
for (final String entry : cfg.getKeys(false)) {
if (isInt(entry)) {
if (cfg.isString(entry)) {
materialNames.put(Integer.valueOf(entry), cfg.getString(entry));
nameTypes.put(cfg.getString(entry), Integer.valueOf(entry));
} else if (cfg.isConfigurationSection(entry)) {
final Map<Short, String> dataNames = new HashMap<Short, String>();
materialDataNames.put(Integer.valueOf(entry), dataNames);
final ConfigurationSection sec = cfg.getConfigurationSection(entry);
for (final String data : sec.getKeys(false)) {
if (isShort(data)) {
if (sec.isString(data)) {
dataNames.put(Short.valueOf(data), sec.getString(data));
nameTypes.put(sec.getString(data), Integer.valueOf(entry));
} else {
getLogger().warning("Parsing materials.yml: '" + data + "' is not a string.");
}
} else {
getLogger().warning("Parsing materials.yml: '" + data + "' is no valid material data");
}
}
} else {
getLogger().warning("Parsing materials.yml: '" + entry + "' is neither a string nor a section.");
}
} else {
getLogger().warning("Parsing materials.yml: '" + entry + "' is no valid material id");
}
}
}
/**
* Returns the name of a material based on its id
*
* @param type The type of the material
* @return Name of the material, or if it's unknown, the id.
*/
public static String materialName(int type) {
return materialNames.containsKey(type) ? materialNames.get(type) : String.valueOf(type);
}
/**
* Returns the name of a material based on its id and data
*
* @param type The type of the material
* @param data The data of the material
* @return Name of the material regarding it's data, or if it's unknown, the basic name.
*/
public static String materialName(int type, short data) {
final Map<Short, String> dataNames = materialDataNames.get(type);
if (dataNames != null) {
if (dataNames.containsKey(data)) {
return dataNames.get(data);
}
}
return materialName(type);
}
public static Integer typeFromName(String name) {
Integer answer = nameTypes.get(toReadable(name));
if (answer != null) {
return answer;
}
final Material mat = Material.matchMaterial(name);
if (mat == null) {
throw new IllegalArgumentException("No material matching: '" + name + "'");
}
return mat.getId();
}
private static String toReadable(MaterialData matData) {
return matData.toString().toLowerCase().replace('_', ' ').replaceAll("[^a-z ]", "");
}
private static String toReadable(String matData) {
return matData.toLowerCase().replace('_', ' ').replaceAll("[^a-z ]", "");
}
}

View File

@ -0,0 +1,123 @@
package de.diddiz.util;
import static de.diddiz.util.ActionColor.CREATE;
import static de.diddiz.util.ActionColor.DESTROY;
import static de.diddiz.util.TypeColor.DEFAULT;
import static de.diddiz.util.Utils.spaces;
import de.diddiz.LogBlock.config.Config;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType;
public class MessagingUtil {
public static BaseComponent[] formatSummarizedChanges(int created, int destroyed, BaseComponent actor, int createdWidth, int destroyedWidth, float spaceFactor) {
TextComponent textCreated = createTextComponentWithColor(created + spaces((int) ((10 - String.valueOf(created).length()) / spaceFactor)), CREATE.getColor());
TextComponent textDestroyed = createTextComponentWithColor(destroyed + spaces((int) ((10 - String.valueOf(destroyed).length()) / spaceFactor)), DESTROY.getColor());
return new BaseComponent[] { textCreated, textDestroyed, actor };
}
public static TextComponent createTextComponentWithColor(String text, ChatColor color) {
TextComponent tc = new TextComponent(text);
tc.setColor(color);
return tc;
}
public static TextComponent brackets(BracketType type, BaseComponent... content) {
TextComponent tc = createTextComponentWithColor(type.getStarting(), TypeColor.BRACKETS.getColor());
for (BaseComponent c : content) {
tc.addExtra(c);
}
tc.addExtra(new TextComponent(type.getEnding()));
return tc;
}
public static TextComponent prettyDate(long date) {
TextComponent tc = brackets(BracketType.STANDARD, createTextComponentWithColor(Config.formatterShort.format(date), TypeColor.DATE.getColor()));
tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Config.formatter.format(date))));
return tc;
}
public static TextComponent prettyState(String stateName) {
return createTextComponentWithColor(stateName, TypeColor.STATE.getColor());
}
public static TextComponent prettyState(int stateValue) {
return prettyState(Integer.toString(stateValue));
}
public static <E extends Enum<E>> TextComponent prettyState(E enumerator) {
return prettyState(enumerator.toString());
}
public static TextComponent prettyMaterial(String materialName) {
return createTextComponentWithColor(materialName.toUpperCase(), TypeColor.MATERIAL.getColor());
}
public static TextComponent prettyMaterial(Material material) {
return prettyMaterial(material.name());
}
public static TextComponent prettyMaterial(BlockData material) {
TextComponent tc = prettyMaterial(material.getMaterial());
String bdString = material.getAsString();
int bracket = bdString.indexOf("[");
if (bracket >= 0) {
int bracket2 = bdString.indexOf("]", bracket);
if (bracket2 >= 0) {
String state = bdString.substring(bracket + 1, bracket2).replace(',', '\n');
tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(state)));
}
}
return tc;
}
public static TextComponent prettyEntityType(EntityType type) {
return prettyMaterial(type.name());
}
public static TextComponent prettyLocation(Location loc, int entryId) {
return prettyLocation(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), entryId);
}
public static TextComponent prettyLocation(int x, int y, int z, int entryId) {
TextComponent tc = createTextComponentWithColor("", DEFAULT.getColor());
tc.addExtra(createTextComponentWithColor(Integer.toString(x), TypeColor.COORDINATE.getColor()));
tc.addExtra(createTextComponentWithColor(", ", DEFAULT.getColor()));
tc.addExtra(createTextComponentWithColor(Integer.toString(y), TypeColor.COORDINATE.getColor()));
tc.addExtra(createTextComponentWithColor(", ", DEFAULT.getColor()));
tc.addExtra(createTextComponentWithColor(Integer.toString(z), TypeColor.COORDINATE.getColor()));
if (entryId > 0) {
tc.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/lb tp " + entryId));
tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Teleport here")));
}
return tc;
}
public enum BracketType {
STANDARD("[", "]"),
ANGLE("<", ">");
private String starting, ending;
BracketType(String starting, String ending) {
this.starting = starting;
this.ending = ending;
}
public String getStarting() {
return starting;
}
public String getEnding() {
return ending;
}
}
}

View File

@ -11,13 +11,14 @@ public class MySQLConnectionPool implements Closeable {
private final HikariDataSource ds;
public MySQLConnectionPool(String url, String user, String password) throws ClassNotFoundException {
public MySQLConnectionPool(String url, String user, String password, boolean useSSL, boolean requireSSL) {
this.ds = new HikariDataSource();
ds.setJdbcUrl(url);
ds.setUsername(user);
ds.setPassword(password);
ds.setMinimumIdle(2);
ds.setMaximumPoolSize(15);
ds.setPoolName("LogBlock-Connection-Pool");
ds.addDataSourceProperty("useUnicode", "true");
@ -27,6 +28,11 @@ public class MySQLConnectionPool implements Closeable {
ds.addDataSourceProperty("cachePrepStmts", "true");
ds.addDataSourceProperty("prepStmtCacheSize", "250");
ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
ds.addDataSourceProperty("useServerPrepStmts", "true");
ds.addDataSourceProperty("useSSL", Boolean.toString(useSSL));
ds.addDataSourceProperty("requireSSL", Boolean.toString(requireSSL));
ds.addDataSourceProperty("verifyServerCertificate", "false");
}
@Override

View File

@ -0,0 +1,24 @@
package de.diddiz.util;
public class SqlUtil {
public static String escapeString(String s) {
return escapeString(s, false);
}
public static String escapeString(String s, boolean escapeMatcher) {
s = s.replace("\u0000", "\\0");
s = s.replace("\u0026", "\\Z");
s = s.replace("\\", "\\\\");
s = s.replace("'", "\\'");
s = s.replace("\"", "\\\"");
s = s.replace("\b", "\\b");
s = s.replace("\n", "\\n");
s = s.replace("\r", "\\r");
s = s.replace("\t", "\\t");
if (escapeMatcher) {
s = s.replace("%", "\\%");
s = s.replace("_", "\\_");
}
return s;
}
}

View File

@ -0,0 +1,29 @@
package de.diddiz.util;
import net.md_5.bungee.api.ChatColor;
public enum TypeColor {
DEFAULT(ChatColor.YELLOW),
MATERIAL(ChatColor.BLUE),
STATE(ChatColor.BLUE),
DATE(ChatColor.DARK_AQUA),
BRACKETS(ChatColor.DARK_GRAY),
COORDINATE(ChatColor.WHITE),
HEADER(ChatColor.GOLD),
ERROR(ChatColor.RED);
private final ChatColor color;
TypeColor(ChatColor color) {
this.color = color;
}
public ChatColor getColor() {
return color;
}
@Override
public String toString() {
return color.toString();
}
}

View File

@ -1,14 +1,14 @@
package de.diddiz.util;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -19,19 +19,19 @@ import java.util.UUID;
public class UUIDFetcher {
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private static final JSONParser jsonParser = new JSONParser();
private static final Gson gson = new GsonBuilder().setLenient().create();
public static Map<String, UUID> getUUIDs(List<String> names) throws Exception {
Map<String, UUID> uuidMap = new HashMap<String, UUID>();
Map<String, UUID> uuidMap = new HashMap<>();
HttpURLConnection connection = createConnection();
String body = JSONArray.toJSONString(names);
String body = gson.toJson(names);
writeBody(connection, body);
JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
for (Object profile : array) {
JSONObject jsonProfile = (JSONObject) profile;
String id = (String) jsonProfile.get("id");
String name = (String) jsonProfile.get("name");
UUID uuid = UUIDFetcher.getUUID(id);
JsonArray array = gson.fromJson(new InputStreamReader(connection.getInputStream()), JsonArray.class);
for (JsonElement profile : array) {
JsonObject jsonProfile = (JsonObject) profile;
String id = jsonProfile.get("id").getAsString();
String name = jsonProfile.get("name").getAsString();
UUID uuid = getUUID(id);
uuidMap.put(name, uuid);
}
return uuidMap;
@ -58,22 +58,4 @@ public class UUIDFetcher {
private static UUID getUUID(String id) {
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32));
}
public static byte[] toBytes(UUID uuid) {
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
byteBuffer.putLong(uuid.getMostSignificantBits());
byteBuffer.putLong(uuid.getLeastSignificantBits());
return byteBuffer.array();
}
public static UUID fromBytes(byte[] array) {
if (array.length != 16) {
throw new IllegalArgumentException("Illegal byte array length: " + array.length);
}
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
long mostSignificant = byteBuffer.getLong();
long leastSignificant = byteBuffer.getLong();
return new UUID(mostSignificant, leastSignificant);
}
}

View File

@ -1,13 +1,27 @@
package de.diddiz.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipException;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.LogBlock;
public class Utils {
public static String newline = System.getProperty("line.separator");
@ -167,7 +181,7 @@ public class Utils {
* @return A new list with the quoted arguments parsed to single values
*/
public static List<String> parseQuotes(List<String> args) {
List<String> newArguments = new ArrayList<String>();
List<String> newArguments = new ArrayList<>();
String subjectString = join(args.toArray(new String[args.size()]), " ");
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
@ -183,7 +197,7 @@ public class Utils {
private final String ext;
public ExtensionFilenameFilter(String ext) {
this.ext = ext;
this.ext = "." + ext;
}
@Override
@ -191,4 +205,83 @@ public class Utils {
return name.toLowerCase().endsWith(ext);
}
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String mysqlEscapeBytes(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2 + 2];
hexChars[0] = '0';
hexChars[1] = 'x';
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2 + 2] = hexArray[v >>> 4];
hexChars[j * 2 + 3] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static String mysqlPrepareBytesForInsertAllowNull(byte[] bytes) {
if (bytes == null) {
return "null";
}
return "'" + mysqlEscapeBytes(bytes) + "'";
}
public static String mysqlTextEscape(String untrusted) {
return untrusted.replace("\\", "\\\\").replace("'", "\\'");
}
public static ItemStack loadItemStack(byte[] data) {
if (data == null || data.length == 0) {
return null;
}
YamlConfiguration conf = deserializeYamlConfiguration(data);
return conf == null ? null : conf.getItemStack("stack");
}
public static byte[] saveItemStack(ItemStack stack) {
if (stack == null || BukkitUtils.isEmpty(stack.getType())) {
return null;
}
YamlConfiguration conf = new YamlConfiguration();
conf.set("stack", stack);
return serializeYamlConfiguration(conf);
}
public static YamlConfiguration deserializeYamlConfiguration(byte[] data) {
if (data == null || data.length == 0) {
return null;
}
YamlConfiguration conf = new YamlConfiguration();
try {
InputStreamReader reader = new InputStreamReader(new GZIPInputStream(new ByteArrayInputStream(data)), "UTF-8");
conf.load(reader);
reader.close();
return conf;
} catch (ZipException | InvalidConfigurationException e) {
LogBlock.getInstance().getLogger().warning("Could not deserialize YamlConfiguration: " + e.getMessage());
return conf;
} catch (IOException e) {
throw new RuntimeException("IOException should be impossible for ByteArrayInputStream", e);
}
}
public static byte[] serializeYamlConfiguration(YamlConfiguration conf) {
if (conf == null || conf.getKeys(false).isEmpty()) {
return null;
}
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(new GZIPOutputStream(baos), "UTF-8");
writer.write(conf.saveToString());
writer.close();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("IOException should be impossible for ByteArrayOutputStream", e);
}
}
public static String serializeForSQL(YamlConfiguration conf) {
return mysqlPrepareBytesForInsertAllowNull(serializeYamlConfiguration(conf));
}
}

View File

@ -1,41 +0,0 @@
package de.diddiz.worldedit;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.selections.CuboidSelection;
import com.sk89q.worldedit.bukkit.selections.Selection;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class RegionContainer {
private Selection selection;
public RegionContainer(Selection sel) {
this.selection = sel;
}
public static RegionContainer fromPlayerSelection(Player player, Plugin plugin) {
final Selection selection = ((WorldEditPlugin) plugin).getSelection(player);
if (selection == null) {
throw new IllegalArgumentException("No selection defined");
}
if (!(selection instanceof CuboidSelection)) {
throw new IllegalArgumentException("You have to define a cuboid selection");
}
return new RegionContainer(selection);
}
public static RegionContainer fromCorners(World world, Location first, Location second) {
return new RegionContainer(new CuboidSelection(world, first, second));
}
public Selection getSelection() {
return selection;
}
public void setSelection(Selection selection) {
this.selection = selection;
}
}

View File

@ -0,0 +1,181 @@
package de.diddiz.worldedit;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.BlockVector;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.jnbt.NamedTag;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.util.CuboidRegion;
public class WorldEditHelper {
private static boolean checkedForWorldEdit;
private static boolean hasWorldEdit;
public static boolean hasWorldEdit() {
if (!checkedForWorldEdit) {
checkedForWorldEdit = true;
Plugin worldEdit = Bukkit.getPluginManager().getPlugin("WorldEdit");
hasWorldEdit = worldEdit != null;
if (worldEdit != null) {
Internal.setWorldEdit(worldEdit);
}
}
return hasWorldEdit;
}
public static boolean hasFullWorldEdit() {
return hasWorldEdit && Internal.hasBukkitImplAdapter();
}
public static byte[] serializeEntity(Entity entity) {
if (!hasWorldEdit()) {
return null;
}
return Internal.serializeEntity(entity);
}
public static Entity restoreEntity(Location location, EntityType type, byte[] serialized) {
if (!hasWorldEdit()) {
return null;
}
return Internal.restoreEntity(location, type, serialized);
}
public static CuboidRegion getSelectedRegion(Player player) throws IllegalArgumentException {
if (!hasWorldEdit()) {
throw new IllegalArgumentException("WorldEdit not found!");
}
return Internal.getSelectedRegion(player);
}
private static class Internal {
private static WorldEditPlugin worldEdit;
private static Method getBukkitImplAdapter;
public static void setWorldEdit(Plugin worldEdit) {
Internal.worldEdit = (WorldEditPlugin) worldEdit;
}
public static boolean hasBukkitImplAdapter() {
if (getBukkitImplAdapter == null) {
try {
getBukkitImplAdapter = WorldEditPlugin.class.getDeclaredMethod("getBukkitImplAdapter");
getBukkitImplAdapter.setAccessible(true);
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Exception while checking for BukkitImplAdapter", e);
return false;
}
}
try {
return getBukkitImplAdapter.invoke(worldEdit) != null;
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Exception while checking for BukkitImplAdapter", e);
return false;
}
}
public static Entity restoreEntity(Location location, EntityType type, byte[] serialized) {
com.sk89q.worldedit.world.entity.EntityType weType = BukkitAdapter.adapt(type);
com.sk89q.worldedit.util.Location weLocation = BukkitAdapter.adapt(location);
try {
NBTInputStream nbtis = new NBTInputStream(new ByteArrayInputStream(serialized));
NamedTag namedTag = nbtis.readNamedTag();
nbtis.close();
UUID newUUID = null;
if (namedTag.getName().equals("entity") && namedTag.getTag() instanceof CompoundTag) {
CompoundTag serializedState = (CompoundTag) namedTag.getTag();
BaseEntity state = new BaseEntity(weType, serializedState);
com.sk89q.worldedit.entity.Entity weEntity = weLocation.getExtent().createEntity(weLocation, state);
if (weEntity != null) {
CompoundTag newNbt = weEntity.getState().getNbtData();
int[] uuidInts = newNbt.getIntArray("UUID");
if (uuidInts != null && uuidInts.length >= 4) {
newUUID = new UUID(((long) uuidInts[0] << 32) | (uuidInts[1] & 0xFFFFFFFFL), ((long) uuidInts[2] << 32) | (uuidInts[3] & 0xFFFFFFFFL));
} else {
newUUID = new UUID(newNbt.getLong("UUIDMost"), newNbt.getLong("UUIDLeast")); // pre 1.16
}
}
}
return newUUID == null ? null : Bukkit.getEntity(newUUID);
} catch (IOException e) {
throw new RuntimeException("This IOException should be impossible", e);
}
}
public static byte[] serializeEntity(Entity entity) {
com.sk89q.worldedit.entity.Entity weEntity = BukkitAdapter.adapt(entity);
BaseEntity state = weEntity.getState();
if (state != null) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
NBTOutputStream nbtos = new NBTOutputStream(baos);
CompoundTag nbt = state.getNbtData();
LinkedHashMap<String, Tag> value = new LinkedHashMap<>(nbt.getValue());
value.put("Health", new FloatTag(20.0f));
value.put("Motion", new ListTag(DoubleTag.class, Arrays.asList(new DoubleTag[] { new DoubleTag(0), new DoubleTag(0), new DoubleTag(0) })));
value.put("Fire", new ShortTag((short) -20));
value.put("HurtTime", new ShortTag((short) 0));
nbtos.writeNamedTag("entity", new CompoundTag(value));
nbtos.close();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("This IOException should be impossible", e);
}
}
return null;
}
public static CuboidRegion getSelectedRegion(Player player) throws IllegalArgumentException {
LocalSession session = worldEdit.getSession(player);
World world = player.getWorld();
com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(world);
if (!weWorld.equals(session.getSelectionWorld())) {
throw new IllegalArgumentException("No selection defined");
}
Region selection;
try {
selection = session.getSelection(weWorld);
} catch (IncompleteRegionException e) {
throw new IllegalArgumentException("No selection defined");
}
if (selection == null) {
throw new IllegalArgumentException("No selection defined");
}
if (!(selection instanceof com.sk89q.worldedit.regions.CuboidRegion)) {
throw new IllegalArgumentException("You have to define a cuboid selection");
}
BlockVector3 min = selection.getMinimumPoint();
BlockVector3 max = selection.getMaximumPoint();
return new CuboidRegion(world, new BlockVector(min.getBlockX(), min.getBlockY(), min.getBlockZ()), new BlockVector(max.getBlockX(), max.getBlockY(), max.getBlockZ()));
}
}
}

View File

@ -1,24 +1,27 @@
package de.diddiz.worldedit;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.event.extent.EditSessionEvent;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.logging.AbstractLoggingExtent;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.config.Config;
import org.bukkit.Bukkit;
import de.diddiz.util.BukkitUtils;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import java.util.logging.Level;
@ -40,20 +43,6 @@ public class WorldEditLoggingHook {
return new de.diddiz.LogBlock.Actor(weActor.getName());
}
private World adapt(com.sk89q.worldedit.world.World weWorld) {
if (weWorld == null) {
throw new NullPointerException("[Logblock-Worldedit] The provided world was null.");
}
if (weWorld instanceof BukkitWorld) {
return ((BukkitWorld) weWorld).getWorld();
}
World world = Bukkit.getServer().getWorld(weWorld.getName());
if (world == null) {
throw new IllegalArgumentException("Can't find a Bukkit world for " + weWorld);
}
return world;
}
public void hook() {
WorldEdit.getInstance().getEventBus().register(new Object() {
@Subscribe
@ -66,9 +55,8 @@ public class WorldEditLoggingHook {
// Check to ensure the world should be logged
final World world;
final com.sk89q.worldedit.world.World k = event.getWorld();
try {
world = adapt(k);
world = BukkitAdapter.adapt(event.getWorld());
} catch (RuntimeException ex) {
plugin.getLogger().warning("Failed to register logging for WorldEdit!");
plugin.getLogger().log(Level.WARNING, ex.getMessage(), ex);
@ -80,38 +68,35 @@ public class WorldEditLoggingHook {
return;
}
event.setExtent(new AbstractLoggingExtent(event.getExtent()) {
event.setExtent(new AbstractDelegateExtent(event.getExtent()) {
@Override
protected void onBlockChange(Vector pt, BaseBlock block) {
public final <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block) throws WorldEditException {
onBlockChange(position, block);
return super.setBlock(position, block);
}
protected <B extends BlockStateHolder<B>> void onBlockChange(BlockVector3 pt, B block) {
if (event.getStage() != EditSession.Stage.BEFORE_CHANGE) {
return;
}
Location location = new Location(world, pt.getBlockX(), pt.getBlockY(), pt.getBlockZ());
Block origin = location.getBlock();
int typeBefore = origin.getTypeId();
byte dataBefore = origin.getData();
// If we're dealing with a sign, store the block state to read the text off
BlockState stateBefore = null;
if (typeBefore == Material.SIGN_POST.getId() || typeBefore == Material.SIGN.getId()) {
stateBefore = origin.getState();
}
Location location = BukkitAdapter.adapt(world, pt);
Block blockBefore = location.getBlock();
BlockData blockDataBefore = blockBefore.getBlockData();
Material typeBefore = blockDataBefore.getMaterial();
// Check to see if we've broken a sign
if (Config.isLogging(location.getWorld().getName(), Logging.SIGNTEXT) && (typeBefore == Material.SIGN_POST.getId() || typeBefore == Material.SIGN.getId())) {
plugin.getConsumer().queueSignBreak(lbActor, (Sign) stateBefore);
if (block.getType() != Material.AIR.getId()) {
plugin.getConsumer().queueBlockPlace(lbActor, location, block.getType(), (byte) block.getData());
BlockData blockDataNew = BukkitAdapter.adapt(block);
if (!blockDataBefore.equals(blockDataNew)) {
// Check to see if we've broken a sign
if (BlockStateCodecs.hasCodec(typeBefore)) {
plugin.getConsumer().queueBlockBreak(lbActor, blockBefore.getState());
} else if (!BukkitUtils.isEmpty(typeBefore)) {
plugin.getConsumer().queueBlockBreak(lbActor, location, blockDataBefore);
}
} else {
if (dataBefore != 0) {
plugin.getConsumer().queueBlockBreak(lbActor, location, typeBefore, dataBefore);
if (block.getType() != Material.AIR.getId()) {
plugin.getConsumer().queueBlockPlace(lbActor, location, block.getType(), (byte) block.getData());
}
} else {
plugin.getConsumer().queueBlock(lbActor, location, typeBefore, block.getType(), (byte) block.getData());
if (!BukkitUtils.isEmpty(blockDataNew.getMaterial())) {
plugin.getConsumer().queueBlockPlace(lbActor, location, blockDataNew);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,839 @@
0:0,minecraft:air
1:0,minecraft:stone
1:1,minecraft:granite
1:2,minecraft:polished_granite
1:3,minecraft:diorite
1:4,minecraft:polished_diorite
1:5,minecraft:andesite
1:6,minecraft:polished_andesite
2:0,minecraft:grass_block
3:0,minecraft:dirt
3:1,minecraft:coarse_dirt
3:2,minecraft:podzol
4:0,minecraft:cobblestone
5:0,minecraft:oak_planks
5:1,minecraft:spruce_planks
5:2,minecraft:birch_planks
5:3,minecraft:jungle_planks
5:4,minecraft:acacia_planks
5:5,minecraft:dark_oak_planks
6:0,minecraft:oak_sapling
6:1,minecraft:spruce_sapling
6:2,minecraft:birch_sapling
6:3,minecraft:jungle_sapling
6:4,minecraft:acacia_sapling
6:5,minecraft:dark_oak_sapling
6:9,minecraft:spruce_sapling
6:10,minecraft:birch_sapling
6:11,minecraft:jungle_sapling
6:12,minecraft:acacia_sapling
6:13,minecraft:dark_oak_sapling
7:0,minecraft:bedrock
8:0,minecraft:water
9:0,minecraft:water
10:0,minecraft:lava
11:0,minecraft:lava
12:0,minecraft:sand
12:1,minecraft:red_sand
13:0,minecraft:gravel
14:0,minecraft:gold_ore
15:0,minecraft:iron_ore
16:0,minecraft:coal_ore
17:0,minecraft:oak_log
17:1,minecraft:spruce_log
17:2,minecraft:birch_log
17:3,minecraft:jungle_log
17:5,minecraft:spruce_log
17:6,minecraft:birch_log
17:7,minecraft:jungle_log
17:9,minecraft:spruce_log
17:10,minecraft:birch_log
17:11,minecraft:jungle_log
18:0,minecraft:oak_leaves
18:1,minecraft:spruce_leaves
18:2,minecraft:birch_leaves
18:3,minecraft:jungle_leaves
18:5,minecraft:spruce_leaves
18:6,minecraft:birch_leaves
18:7,minecraft:jungle_leaves
18:9,minecraft:spruce_leaves
18:10,minecraft:birch_leaves
18:11,minecraft:jungle_leaves
18:13,minecraft:spruce_leaves
18:14,minecraft:birch_leaves
18:15,minecraft:jungle_leaves
19:0,minecraft:sponge
19:1,minecraft:wet_sponge
20:0,minecraft:glass
21:0,minecraft:lapis_ore
22:0,minecraft:lapis_block
23:0,minecraft:dispenser
24:0,minecraft:sandstone
24:1,minecraft:chiseled_sandstone
24:2,minecraft:cut_sandstone
25:0,minecraft:note_block
26:0,minecraft:red_bed
27:0,minecraft:powered_rail
28:0,minecraft:detector_rail
29:0,minecraft:sticky_piston
30:0,minecraft:cobweb
31:0,minecraft:dead_bush
31:1,minecraft:grass
31:2,minecraft:fern
32:0,minecraft:dead_bush
33:0,minecraft:piston
34:0,minecraft:piston_head
35:0,minecraft:white_wool
35:1,minecraft:orange_wool
35:2,minecraft:magenta_wool
35:3,minecraft:light_blue_wool
35:4,minecraft:yellow_wool
35:5,minecraft:lime_wool
35:6,minecraft:pink_wool
35:7,minecraft:gray_wool
35:8,minecraft:light_gray_wool
35:9,minecraft:cyan_wool
35:10,minecraft:purple_wool
35:11,minecraft:blue_wool
35:12,minecraft:brown_wool
35:13,minecraft:green_wool
35:14,minecraft:red_wool
35:15,minecraft:black_wool
36:0,minecraft:moving_piston
37:0,minecraft:dandelion
38:0,minecraft:poppy
38:1,minecraft:blue_orchid
38:2,minecraft:allium
38:3,minecraft:azure_bluet
38:4,minecraft:red_tulip
38:5,minecraft:orange_tulip
38:6,minecraft:white_tulip
38:7,minecraft:pink_tulip
38:8,minecraft:oxeye_daisy
39:0,minecraft:brown_mushroom
40:0,minecraft:red_mushroom
41:0,minecraft:gold_block
42:0,minecraft:iron_block
43:0,minecraft:smooth_stone_slab
43:1,minecraft:sandstone_slab
43:2,minecraft:petrified_oak_slab
43:3,minecraft:cobblestone_slab
43:4,minecraft:brick_slab
43:5,minecraft:stone_brick_slab
43:6,minecraft:nether_brick_slab
43:7,minecraft:quartz_slab
43:8,minecraft:smooth_stone
43:9,minecraft:smooth_sandstone
43:10,minecraft:petrified_oak_slab
43:11,minecraft:cobblestone_slab
43:12,minecraft:brick_slab
43:13,minecraft:stone_brick_slab
43:14,minecraft:nether_brick_slab
43:15,minecraft:smooth_quartz
44:0,minecraft:stone_slab
44:1,minecraft:sandstone_slab
44:2,minecraft:petrified_oak_slab
44:3,minecraft:cobblestone_slab
44:4,minecraft:brick_slab
44:5,minecraft:stone_brick_slab
44:6,minecraft:nether_brick_slab
44:7,minecraft:quartz_slab
44:9,minecraft:sandstone_slab
44:10,minecraft:petrified_oak_slab
44:11,minecraft:cobblestone_slab
44:12,minecraft:brick_slab
44:13,minecraft:stone_brick_slab
44:14,minecraft:nether_brick_slab
44:15,minecraft:quartz_slab
45:0,minecraft:bricks
46:0,minecraft:tnt
47:0,minecraft:bookshelf
48:0,minecraft:mossy_cobblestone
49:0,minecraft:obsidian
50:0,minecraft:torch
50:1,minecraft:wall_torch
50:2,minecraft:wall_torch
50:3,minecraft:wall_torch
50:4,minecraft:wall_torch
51:0,minecraft:fire
52:0,minecraft:spawner
53:0,minecraft:oak_stairs
54:0,minecraft:chest
55:0,minecraft:redstone_wire
56:0,minecraft:diamond_ore
57:0,minecraft:diamond_block
58:0,minecraft:crafting_table
59:0,minecraft:wheat
60:0,minecraft:farmland
61:0,minecraft:furnace
62:0,minecraft:air
62:2,minecraft:furnace
62:3,minecraft:furnace
62:4,minecraft:furnace
62:5,minecraft:furnace
63:0,minecraft:oak_sign
64:0,minecraft:oak_door
65:0,minecraft:ladder
66:0,minecraft:rail
67:0,minecraft:cobblestone_stairs
68:0,minecraft:air
68:2,minecraft:oak_wall_sign
68:3,minecraft:oak_wall_sign
68:4,minecraft:oak_wall_sign
68:5,minecraft:oak_wall_sign
69:0,minecraft:lever
70:0,minecraft:stone_pressure_plate
71:0,minecraft:iron_door
72:0,minecraft:oak_pressure_plate
73:0,minecraft:redstone_ore
74:0,minecraft:redstone_ore
75:0,minecraft:air
75:1,minecraft:redstone_wall_torch
75:2,minecraft:redstone_wall_torch
75:3,minecraft:redstone_wall_torch
75:4,minecraft:redstone_wall_torch
75:5,minecraft:redstone_torch
76:0,minecraft:redstone_torch
76:1,minecraft:redstone_wall_torch
76:2,minecraft:redstone_wall_torch
76:3,minecraft:redstone_wall_torch
76:4,minecraft:redstone_wall_torch
77:0,minecraft:stone_button
78:0,minecraft:snow
79:0,minecraft:ice
80:0,minecraft:snow_block
81:0,minecraft:cactus
82:0,minecraft:clay
83:0,minecraft:sugar_cane
84:0,minecraft:jukebox
85:0,minecraft:oak_fence
86:0,minecraft:carved_pumpkin
87:0,minecraft:netherrack
88:0,minecraft:soul_sand
89:0,minecraft:glowstone
90:0,minecraft:nether_portal
91:0,minecraft:jack_o_lantern
92:0,minecraft:cake
93:0,minecraft:repeater
94:0,minecraft:repeater
95:0,minecraft:white_stained_glass
95:1,minecraft:orange_stained_glass
95:2,minecraft:magenta_stained_glass
95:3,minecraft:light_blue_stained_glass
95:4,minecraft:yellow_stained_glass
95:5,minecraft:lime_stained_glass
95:6,minecraft:pink_stained_glass
95:7,minecraft:gray_stained_glass
95:8,minecraft:light_gray_stained_glass
95:9,minecraft:cyan_stained_glass
95:10,minecraft:purple_stained_glass
95:11,minecraft:blue_stained_glass
95:12,minecraft:brown_stained_glass
95:13,minecraft:green_stained_glass
95:14,minecraft:red_stained_glass
95:15,minecraft:black_stained_glass
96:0,minecraft:oak_trapdoor
97:0,minecraft:infested_stone
97:1,minecraft:infested_cobblestone
97:2,minecraft:infested_stone_bricks
97:3,minecraft:infested_mossy_stone_bricks
97:4,minecraft:infested_cracked_stone_bricks
97:5,minecraft:infested_chiseled_stone_bricks
98:0,minecraft:stone_bricks
98:1,minecraft:mossy_stone_bricks
98:2,minecraft:cracked_stone_bricks
98:3,minecraft:chiseled_stone_bricks
99:0,minecraft:brown_mushroom_block
99:10,minecraft:mushroom_stem
99:15,minecraft:mushroom_stem
100:0,minecraft:red_mushroom_block
100:10,minecraft:mushroom_stem
100:15,minecraft:mushroom_stem
101:0,minecraft:iron_bars
102:0,minecraft:glass_pane
103:0,minecraft:melon
104:0,minecraft:pumpkin_stem
105:0,minecraft:melon_stem
106:0,minecraft:vine
107:0,minecraft:oak_fence_gate
108:0,minecraft:brick_stairs
109:0,minecraft:stone_brick_stairs
110:0,minecraft:mycelium
111:0,minecraft:lily_pad
112:0,minecraft:nether_bricks
113:0,minecraft:nether_brick_fence
114:0,minecraft:nether_brick_stairs
115:0,minecraft:nether_wart
116:0,minecraft:enchanting_table
117:0,minecraft:brewing_stand
118:0,minecraft:cauldron
119:0,minecraft:end_portal
120:0,minecraft:end_portal_frame
121:0,minecraft:end_stone
122:0,minecraft:dragon_egg
123:0,minecraft:redstone_lamp
124:0,minecraft:redstone_lamp
125:0,minecraft:oak_slab
125:1,minecraft:spruce_slab
125:2,minecraft:birch_slab
125:3,minecraft:jungle_slab
125:4,minecraft:acacia_slab
125:5,minecraft:dark_oak_slab
126:0,minecraft:oak_slab
126:1,minecraft:spruce_slab
126:2,minecraft:birch_slab
126:3,minecraft:jungle_slab
126:4,minecraft:acacia_slab
126:5,minecraft:dark_oak_slab
126:9,minecraft:spruce_slab
126:10,minecraft:birch_slab
126:11,minecraft:jungle_slab
126:12,minecraft:acacia_slab
126:13,minecraft:dark_oak_slab
127:0,minecraft:cocoa
128:0,minecraft:sandstone_stairs
129:0,minecraft:emerald_ore
130:0,minecraft:ender_chest
131:0,minecraft:tripwire_hook
132:0,minecraft:tripwire
133:0,minecraft:emerald_block
134:0,minecraft:spruce_stairs
135:0,minecraft:birch_stairs
136:0,minecraft:jungle_stairs
137:0,minecraft:command_block
138:0,minecraft:beacon
139:0,minecraft:cobblestone_wall
139:1,minecraft:mossy_cobblestone_wall
140:0,minecraft:potted_cactus
141:0,minecraft:carrots
142:0,minecraft:potatoes
143:0,minecraft:oak_button
144:0,minecraft:air
145:0,minecraft:anvil
145:4,minecraft:chipped_anvil
145:5,minecraft:chipped_anvil
145:6,minecraft:chipped_anvil
145:7,minecraft:chipped_anvil
145:8,minecraft:damaged_anvil
145:9,minecraft:damaged_anvil
145:10,minecraft:damaged_anvil
145:11,minecraft:damaged_anvil
146:0,minecraft:trapped_chest
147:0,minecraft:light_weighted_pressure_plate
148:0,minecraft:heavy_weighted_pressure_plate
149:0,minecraft:comparator
150:0,minecraft:comparator
151:0,minecraft:daylight_detector
152:0,minecraft:redstone_block
153:0,minecraft:nether_quartz_ore
154:0,minecraft:hopper
155:0,minecraft:quartz_block
155:1,minecraft:chiseled_quartz_block
155:2,minecraft:quartz_pillar
155:3,minecraft:quartz_pillar
155:4,minecraft:quartz_pillar
156:0,minecraft:quartz_stairs
157:0,minecraft:activator_rail
158:0,minecraft:dropper
159:0,minecraft:white_terracotta
159:1,minecraft:orange_terracotta
159:2,minecraft:magenta_terracotta
159:3,minecraft:light_blue_terracotta
159:4,minecraft:yellow_terracotta
159:5,minecraft:lime_terracotta
159:6,minecraft:pink_terracotta
159:7,minecraft:gray_terracotta
159:8,minecraft:light_gray_terracotta
159:9,minecraft:cyan_terracotta
159:10,minecraft:purple_terracotta
159:11,minecraft:blue_terracotta
159:12,minecraft:brown_terracotta
159:13,minecraft:green_terracotta
159:14,minecraft:red_terracotta
159:15,minecraft:black_terracotta
160:0,minecraft:white_stained_glass_pane
160:1,minecraft:orange_stained_glass_pane
160:2,minecraft:magenta_stained_glass_pane
160:3,minecraft:light_blue_stained_glass_pane
160:4,minecraft:yellow_stained_glass_pane
160:5,minecraft:lime_stained_glass_pane
160:6,minecraft:pink_stained_glass_pane
160:7,minecraft:gray_stained_glass_pane
160:8,minecraft:light_gray_stained_glass_pane
160:9,minecraft:cyan_stained_glass_pane
160:10,minecraft:purple_stained_glass_pane
160:11,minecraft:blue_stained_glass_pane
160:12,minecraft:brown_stained_glass_pane
160:13,minecraft:green_stained_glass_pane
160:14,minecraft:red_stained_glass_pane
160:15,minecraft:black_stained_glass_pane
161:0,minecraft:acacia_leaves
161:1,minecraft:dark_oak_leaves
161:5,minecraft:dark_oak_leaves
161:9,minecraft:dark_oak_leaves
161:13,minecraft:dark_oak_leaves
162:0,minecraft:acacia_log
162:1,minecraft:dark_oak_log
162:5,minecraft:dark_oak_log
162:9,minecraft:dark_oak_log
163:0,minecraft:acacia_stairs
164:0,minecraft:dark_oak_stairs
165:0,minecraft:slime_block
166:0,minecraft:barrier
167:0,minecraft:iron_trapdoor
168:0,minecraft:prismarine
168:1,minecraft:prismarine_bricks
168:2,minecraft:dark_prismarine
169:0,minecraft:sea_lantern
170:0,minecraft:hay_block
171:0,minecraft:white_carpet
171:1,minecraft:orange_carpet
171:2,minecraft:magenta_carpet
171:3,minecraft:light_blue_carpet
171:4,minecraft:yellow_carpet
171:5,minecraft:lime_carpet
171:6,minecraft:pink_carpet
171:7,minecraft:gray_carpet
171:8,minecraft:light_gray_carpet
171:9,minecraft:cyan_carpet
171:10,minecraft:purple_carpet
171:11,minecraft:blue_carpet
171:12,minecraft:brown_carpet
171:13,minecraft:green_carpet
171:14,minecraft:red_carpet
171:15,minecraft:black_carpet
172:0,minecraft:terracotta
173:0,minecraft:coal_block
174:0,minecraft:packed_ice
175:0,minecraft:sunflower
175:1,minecraft:lilac
175:2,minecraft:tall_grass
175:3,minecraft:large_fern
175:4,minecraft:rose_bush
175:5,minecraft:peony
175:8,minecraft:peony
175:9,minecraft:peony
175:10,minecraft:peony
175:11,minecraft:peony
176:0,minecraft:white_banner
177:0,minecraft:air
177:2,minecraft:white_wall_banner
177:3,minecraft:white_wall_banner
177:4,minecraft:white_wall_banner
177:5,minecraft:white_wall_banner
178:0,minecraft:daylight_detector
179:0,minecraft:red_sandstone
179:1,minecraft:chiseled_red_sandstone
179:2,minecraft:cut_red_sandstone
180:0,minecraft:red_sandstone_stairs
181:0,minecraft:red_sandstone_slab
181:8,minecraft:smooth_red_sandstone
182:0,minecraft:red_sandstone_slab
183:0,minecraft:spruce_fence_gate
184:0,minecraft:birch_fence_gate
185:0,minecraft:jungle_fence_gate
186:0,minecraft:dark_oak_fence_gate
187:0,minecraft:acacia_fence_gate
188:0,minecraft:spruce_fence
189:0,minecraft:birch_fence
190:0,minecraft:jungle_fence
191:0,minecraft:dark_oak_fence
192:0,minecraft:acacia_fence
193:0,minecraft:spruce_door
194:0,minecraft:birch_door
195:0,minecraft:jungle_door
196:0,minecraft:acacia_door
197:0,minecraft:dark_oak_door
198:0,minecraft:end_rod
199:0,minecraft:chorus_plant
200:0,minecraft:chorus_flower
201:0,minecraft:purpur_block
202:0,minecraft:purpur_pillar
203:0,minecraft:purpur_stairs
204:0,minecraft:purpur_slab
205:0,minecraft:purpur_slab
206:0,minecraft:end_stone_bricks
207:0,minecraft:beetroots
208:0,minecraft:grass_path
209:0,minecraft:end_gateway
210:0,minecraft:repeating_command_block
211:0,minecraft:chain_command_block
212:0,minecraft:frosted_ice
213:0,minecraft:magma_block
214:0,minecraft:nether_wart_block
215:0,minecraft:red_nether_bricks
216:0,minecraft:bone_block
217:0,minecraft:structure_void
218:0,minecraft:observer
219:0,minecraft:white_shulker_box
220:0,minecraft:orange_shulker_box
221:0,minecraft:magenta_shulker_box
222:0,minecraft:light_blue_shulker_box
223:0,minecraft:yellow_shulker_box
224:0,minecraft:lime_shulker_box
225:0,minecraft:pink_shulker_box
226:0,minecraft:gray_shulker_box
227:0,minecraft:light_gray_shulker_box
228:0,minecraft:cyan_shulker_box
229:0,minecraft:purple_shulker_box
230:0,minecraft:blue_shulker_box
231:0,minecraft:brown_shulker_box
232:0,minecraft:green_shulker_box
233:0,minecraft:red_shulker_box
234:0,minecraft:black_shulker_box
235:0,minecraft:white_glazed_terracotta
236:0,minecraft:orange_glazed_terracotta
237:0,minecraft:magenta_glazed_terracotta
238:0,minecraft:light_blue_glazed_terracotta
239:0,minecraft:yellow_glazed_terracotta
240:0,minecraft:lime_glazed_terracotta
241:0,minecraft:pink_glazed_terracotta
242:0,minecraft:gray_glazed_terracotta
243:0,minecraft:light_gray_glazed_terracotta
244:0,minecraft:cyan_glazed_terracotta
245:0,minecraft:purple_glazed_terracotta
246:0,minecraft:blue_glazed_terracotta
247:0,minecraft:brown_glazed_terracotta
248:0,minecraft:green_glazed_terracotta
249:0,minecraft:red_glazed_terracotta
250:0,minecraft:black_glazed_terracotta
251:0,minecraft:white_concrete
251:1,minecraft:orange_concrete
251:2,minecraft:magenta_concrete
251:3,minecraft:light_blue_concrete
251:4,minecraft:yellow_concrete
251:5,minecraft:lime_concrete
251:6,minecraft:pink_concrete
251:7,minecraft:gray_concrete
251:8,minecraft:light_gray_concrete
251:9,minecraft:cyan_concrete
251:10,minecraft:purple_concrete
251:11,minecraft:blue_concrete
251:12,minecraft:brown_concrete
251:13,minecraft:green_concrete
251:14,minecraft:red_concrete
251:15,minecraft:black_concrete
252:0,minecraft:white_concrete_powder
252:1,minecraft:orange_concrete_powder
252:2,minecraft:magenta_concrete_powder
252:3,minecraft:light_blue_concrete_powder
252:4,minecraft:yellow_concrete_powder
252:5,minecraft:lime_concrete_powder
252:6,minecraft:pink_concrete_powder
252:7,minecraft:gray_concrete_powder
252:8,minecraft:light_gray_concrete_powder
252:9,minecraft:cyan_concrete_powder
252:10,minecraft:purple_concrete_powder
252:11,minecraft:blue_concrete_powder
252:12,minecraft:brown_concrete_powder
252:13,minecraft:green_concrete_powder
252:14,minecraft:red_concrete_powder
252:15,minecraft:black_concrete_powder
255:0,minecraft:structure_block
256:0,minecraft:iron_shovel
257:0,minecraft:iron_pickaxe
258:0,minecraft:iron_axe
259:0,minecraft:flint_and_steel
260:0,minecraft:apple
261:0,minecraft:bow
262:0,minecraft:arrow
263:0,minecraft:coal
263:1,minecraft:charcoal
264:0,minecraft:diamond
265:0,minecraft:iron_ingot
266:0,minecraft:gold_ingot
267:0,minecraft:iron_sword
268:0,minecraft:wooden_sword
269:0,minecraft:wooden_shovel
270:0,minecraft:wooden_pickaxe
271:0,minecraft:wooden_axe
272:0,minecraft:stone_sword
273:0,minecraft:stone_shovel
274:0,minecraft:stone_pickaxe
275:0,minecraft:stone_axe
276:0,minecraft:diamond_sword
277:0,minecraft:diamond_shovel
278:0,minecraft:diamond_pickaxe
279:0,minecraft:diamond_axe
280:0,minecraft:stick
281:0,minecraft:bowl
282:0,minecraft:mushroom_stew
283:0,minecraft:golden_sword
284:0,minecraft:golden_shovel
285:0,minecraft:golden_pickaxe
286:0,minecraft:golden_axe
287:0,minecraft:string
288:0,minecraft:feather
289:0,minecraft:gunpowder
290:0,minecraft:wooden_hoe
291:0,minecraft:stone_hoe
292:0,minecraft:iron_hoe
293:0,minecraft:diamond_hoe
294:0,minecraft:golden_hoe
295:0,minecraft:wheat_seeds
296:0,minecraft:wheat
297:0,minecraft:bread
298:0,minecraft:leather_helmet
299:0,minecraft:leather_chestplate
300:0,minecraft:leather_leggings
301:0,minecraft:leather_boots
302:0,minecraft:chainmail_helmet
303:0,minecraft:chainmail_chestplate
304:0,minecraft:chainmail_leggings
305:0,minecraft:chainmail_boots
306:0,minecraft:iron_helmet
307:0,minecraft:iron_chestplate
308:0,minecraft:iron_leggings
309:0,minecraft:iron_boots
310:0,minecraft:diamond_helmet
311:0,minecraft:diamond_chestplate
312:0,minecraft:diamond_leggings
313:0,minecraft:diamond_boots
314:0,minecraft:golden_helmet
315:0,minecraft:golden_chestplate
316:0,minecraft:golden_leggings
317:0,minecraft:golden_boots
318:0,minecraft:flint
319:0,minecraft:porkchop
320:0,minecraft:cooked_porkchop
321:0,minecraft:painting
322:0,minecraft:golden_apple
322:1,minecraft:enchanted_golden_apple
323:0,minecraft:oak_sign
324:0,minecraft:oak_door
325:0,minecraft:bucket
326:0,minecraft:water_bucket
327:0,minecraft:lava_bucket
328:0,minecraft:minecart
329:0,minecraft:saddle
330:0,minecraft:iron_door
331:0,minecraft:redstone
332:0,minecraft:snowball
333:0,minecraft:oak_boat
334:0,minecraft:leather
335:0,minecraft:milk_bucket
336:0,minecraft:brick
337:0,minecraft:clay_ball
338:0,minecraft:sugar_cane
339:0,minecraft:paper
340:0,minecraft:book
341:0,minecraft:slime_ball
342:0,minecraft:chest_minecart
343:0,minecraft:furnace_minecart
344:0,minecraft:egg
345:0,minecraft:compass
346:0,minecraft:fishing_rod
347:0,minecraft:clock
348:0,minecraft:glowstone_dust
349:0,minecraft:cod
349:1,minecraft:salmon
349:2,minecraft:tropical_fish
349:3,minecraft:pufferfish
350:0,minecraft:cooked_cod
350:1,minecraft:cooked_salmon
351:0,minecraft:ink_sac
351:1,minecraft:red_dye
351:2,minecraft:green_dye
351:3,minecraft:cocoa_beans
351:4,minecraft:lapis_lazuli
351:5,minecraft:purple_dye
351:6,minecraft:cyan_dye
351:7,minecraft:light_gray_dye
351:8,minecraft:gray_dye
351:9,minecraft:pink_dye
351:10,minecraft:lime_dye
351:11,minecraft:yellow_dye
351:12,minecraft:light_blue_dye
351:13,minecraft:magenta_dye
351:14,minecraft:orange_dye
351:15,minecraft:bone_meal
352:0,minecraft:bone
353:0,minecraft:sugar
354:0,minecraft:cake
355:0,minecraft:red_bed
355:1,minecraft:orange_bed
355:2,minecraft:magenta_bed
355:3,minecraft:light_blue_bed
355:4,minecraft:yellow_bed
355:5,minecraft:lime_bed
355:6,minecraft:pink_bed
355:7,minecraft:gray_bed
355:8,minecraft:light_gray_bed
355:9,minecraft:cyan_bed
355:10,minecraft:purple_bed
355:11,minecraft:blue_bed
355:12,minecraft:brown_bed
355:13,minecraft:green_bed
355:15,minecraft:black_bed
356:0,minecraft:repeater
357:0,minecraft:cookie
358:0,minecraft:filled_map
359:0,minecraft:shears
360:0,minecraft:melon_slice
361:0,minecraft:pumpkin_seeds
362:0,minecraft:melon_seeds
363:0,minecraft:beef
364:0,minecraft:cooked_beef
365:0,minecraft:chicken
366:0,minecraft:cooked_chicken
367:0,minecraft:rotten_flesh
368:0,minecraft:ender_pearl
369:0,minecraft:blaze_rod
370:0,minecraft:ghast_tear
371:0,minecraft:gold_nugget
372:0,minecraft:nether_wart
373:0,minecraft:potion
374:0,minecraft:glass_bottle
375:0,minecraft:spider_eye
376:0,minecraft:fermented_spider_eye
377:0,minecraft:blaze_powder
378:0,minecraft:magma_cream
379:0,minecraft:brewing_stand
380:0,minecraft:cauldron
381:0,minecraft:ender_eye
382:0,minecraft:glistering_melon_slice
383:0,minecraft:pig_spawn_egg
383:4,minecraft:elder_guardian_spawn_egg
383:5,minecraft:wither_skeleton_spawn_egg
383:6,minecraft:stray_spawn_egg
383:23,minecraft:husk_spawn_egg
383:27,minecraft:zombie_villager_spawn_egg
383:28,minecraft:skeleton_horse_spawn_egg
383:29,minecraft:zombie_horse_spawn_egg
383:31,minecraft:donkey_spawn_egg
383:32,minecraft:mule_spawn_egg
383:34,minecraft:evoker_spawn_egg
383:35,minecraft:vex_spawn_egg
383:36,minecraft:vindicator_spawn_egg
383:50,minecraft:creeper_spawn_egg
383:51,minecraft:skeleton_spawn_egg
383:52,minecraft:spider_spawn_egg
383:54,minecraft:zombie_spawn_egg
383:55,minecraft:slime_spawn_egg
383:56,minecraft:ghast_spawn_egg
383:57,minecraft:zombie_pigman_spawn_egg
383:58,minecraft:enderman_spawn_egg
383:59,minecraft:cave_spider_spawn_egg
383:60,minecraft:silverfish_spawn_egg
383:61,minecraft:blaze_spawn_egg
383:62,minecraft:magma_cube_spawn_egg
383:65,minecraft:bat_spawn_egg
383:66,minecraft:witch_spawn_egg
383:67,minecraft:endermite_spawn_egg
383:68,minecraft:guardian_spawn_egg
383:69,minecraft:shulker_spawn_egg
383:91,minecraft:sheep_spawn_egg
383:92,minecraft:cow_spawn_egg
383:93,minecraft:chicken_spawn_egg
383:94,minecraft:squid_spawn_egg
383:95,minecraft:wolf_spawn_egg
383:96,minecraft:mooshroom_spawn_egg
383:98,minecraft:ocelot_spawn_egg
383:100,minecraft:horse_spawn_egg
383:101,minecraft:rabbit_spawn_egg
383:102,minecraft:polar_bear_spawn_egg
383:103,minecraft:llama_spawn_egg
383:105,minecraft:parrot_spawn_egg
383:120,minecraft:villager_spawn_egg
383:255,minecraft:turtle_spawn_egg
384:0,minecraft:experience_bottle
385:0,minecraft:fire_charge
386:0,minecraft:writable_book
387:0,minecraft:written_book
388:0,minecraft:emerald
389:0,minecraft:item_frame
390:0,minecraft:flower_pot
391:0,minecraft:carrot
392:0,minecraft:potato
393:0,minecraft:baked_potato
394:0,minecraft:poisonous_potato
395:0,minecraft:map
396:0,minecraft:golden_carrot
397:0,minecraft:skeleton_skull
397:1,minecraft:wither_skeleton_skull
397:2,minecraft:zombie_head
397:3,minecraft:player_head
397:4,minecraft:creeper_head
397:5,minecraft:dragon_head
398:0,minecraft:carrot_on_a_stick
399:0,minecraft:nether_star
400:0,minecraft:pumpkin_pie
401:0,minecraft:firework_rocket
402:0,minecraft:firework_star
403:0,minecraft:enchanted_book
404:0,minecraft:comparator
405:0,minecraft:nether_brick
406:0,minecraft:quartz
407:0,minecraft:tnt_minecart
408:0,minecraft:hopper_minecart
409:0,minecraft:prismarine_shard
410:0,minecraft:prismarine_crystals
411:0,minecraft:rabbit
412:0,minecraft:cooked_rabbit
413:0,minecraft:rabbit_stew
414:0,minecraft:rabbit_foot
415:0,minecraft:rabbit_hide
416:0,minecraft:armor_stand
417:0,minecraft:iron_horse_armor
418:0,minecraft:golden_horse_armor
419:0,minecraft:diamond_horse_armor
420:0,minecraft:lead
421:0,minecraft:name_tag
422:0,minecraft:command_block_minecart
423:0,minecraft:mutton
424:0,minecraft:cooked_mutton
425:0,minecraft:black_banner
425:1,minecraft:red_banner
425:2,minecraft:green_banner
425:3,minecraft:brown_banner
425:4,minecraft:blue_banner
425:5,minecraft:purple_banner
425:6,minecraft:cyan_banner
425:7,minecraft:light_gray_banner
425:8,minecraft:gray_banner
425:9,minecraft:pink_banner
425:10,minecraft:lime_banner
425:11,minecraft:yellow_banner
425:12,minecraft:light_blue_banner
425:13,minecraft:magenta_banner
425:14,minecraft:orange_banner
425:15,minecraft:white_banner
426:0,minecraft:end_crystal
427:0,minecraft:spruce_door
428:0,minecraft:birch_door
429:0,minecraft:jungle_door
430:0,minecraft:acacia_door
431:0,minecraft:dark_oak_door
432:0,minecraft:chorus_fruit
433:0,minecraft:popped_chorus_fruit
434:0,minecraft:beetroot
435:0,minecraft:beetroot_seeds
436:0,minecraft:beetroot_soup
437:0,minecraft:dragon_breath
438:0,minecraft:splash_potion
439:0,minecraft:spectral_arrow
440:0,minecraft:tipped_arrow
441:0,minecraft:lingering_potion
442:0,minecraft:shield
443:0,minecraft:elytra
444:0,minecraft:spruce_boat
445:0,minecraft:birch_boat
446:0,minecraft:jungle_boat
447:0,minecraft:acacia_boat
448:0,minecraft:dark_oak_boat
449:0,minecraft:totem_of_undying
450:0,minecraft:shulker_shell
452:0,minecraft:iron_nugget
453:0,minecraft:knowledge_book
2256:0,minecraft:music_disc_13
2257:0,minecraft:music_disc_cat
2258:0,minecraft:music_disc_blocks
2259:0,minecraft:music_disc_chirp
2260:0,minecraft:music_disc_far
2261:0,minecraft:music_disc_mall
2262:0,minecraft:music_disc_mellohi
2263:0,minecraft:music_disc_stal
2264:0,minecraft:music_disc_strad
2265:0,minecraft:music_disc_ward
2266:0,minecraft:music_disc_11
2267:0,minecraft:music_disc_wait

View File

@ -5,7 +5,8 @@ authors: [md_5, ammar2, frymaster]
website: http://dev.bukkit.org/server-mods/logblock/
main: de.diddiz.LogBlock.LogBlock
description: ${project.description}
softdepend: [LogBlockQuestioner, WorldEdit]
softdepend: [WorldEdit]
api-version: 1.14
commands:
lb:
description: 'LogBlock plugin commands'

View File

@ -1,6 +1,5 @@
package de.diddiz.LogBlock;
import de.diddiz.util.Utils;
import org.junit.Assert;
import org.junit.Test;