753 Commits
Author SHA1 Message Date
DarkChromaandClaude Opus 5.5 dffa04fc4c Recover DuckDB entity UUID conflicts without dropping or stalling (#1039)
A DuckDB unique-index conflict on co_entity_spawn.uuid can happen when the
identity row was committed after the consumer transaction's snapshot began
(or a delete is still visible to an older reader).

- Entity removals: on the exact duplicate-UUID constraint error, roll back,
  start a fresh transaction and retry once; reuse the identity that is now
  visible. If it is still unresolved, drop only that removal and continue,
  instead of retrying the same event forever and blocking the queue.
- Entity interactions: same refresh-and-retry, loading the existing identity
  so the interaction is recorded instead of dropped (#1002 only dropped it).
- Match the conflict on the event's own UUID, so other constraint errors are
  still retried and reported.
- Reopen the entity_spawn prepared statement when DuckDB closed it after a
  constraint violation.
- Summarize skipped records in one console warning every 5 minutes instead of
  a stack trace per event.

Refs #998

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:10:24 -06:00
Intelli 2d484dffff Cleanup 2026-10-18 12:29:19 -06:00
Intelli 38b6d87220 Fix DuckDB lookup memory usage and failure recovery 2026-10-18 12:29:06 -06:00
Intelli bc46794bba Fix shutdown queue draining and lookup pause coordination 2026-10-14 14:39:00 -06:00
Intelli 9d93f9ca44 Fix v24 patch dispatch for migrated DuckDB databases 2026-10-14 13:17:23 -06:00
Intelli 0e3577da0d Widen MySQL art columns to TEXT 2026-10-14 13:06:53 -06:00
Antti Koponen 61f0282596 Fix ClickHouse queue stalling from per-entity UUID lookups (#1038)
* Reload entity spawn lookups in bulk after each database commit

* Trim consumed prefixes after successful commit
2026-10-14 12:38:14 -06:00
Antti Koponen 55576f9c3a Fix memory leaks in CacheHandler due to iteration failures (#1037)
* Fix memory leaks in CacheHandler due to iteration failures

* Revert type cleanup
2026-10-14 12:36:22 -06:00
CloudMinde 5407e582a1 fixed co near permission check (#1036) 2026-10-12 16:28:00 -06:00
Intelli d76b254af4 Close DuckDB recovery gaps for allocation failures 2026-10-12 16:13:17 -06:00
Intelli e24d36bc82 Restore legacy interaction queue overload for addon compatibility 2026-10-12 15:54:28 -06:00
Intelli 616ed04ca7 Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-10-12 15:48:04 -06:00
Intelli 2dc66e1464 Preserve LONGBLOB entity data during v25 migration 2026-10-12 15:47:40 -06:00
4b1e9ab334 Reduced entity kill data by skipping default attributes (#1028)
* Reduced entity kill data by skipping default attributes

EntityDeathListener stored every registered attribute of a killed mob,
about 23 entries on 1.21+, although almost all hold default values. The
attribute list made up about 88% of each kill blob.

Store an attribute only when its base value differs from the default or
it has modifiers. Kill data now always carries index 7 (UUID or null),
index 8 (kill location slot, null for mobs) and a format marker at index
9. When restore sees the marker, it first resets every attribute of the
respawned entity to its default base value and removes spawn-time
modifiers, then applies the stored attributes, so rollback reproduces
the logged entity exactly. Rows without the marker restore as before.

Capture and restore both use AttributeInstance#getDefaultValue. It is
deprecated since 26.1 for returning the server default rather than the
entity type default, but it exists on every supported version (1.16.5+)
and the same value is used on both sides.

Zombie kill blob: 2036 to 1027 bytes in the legacy format, and 250 to
141 bytes with EntityDataCodec (DuckDB and ClickHouse today, SQLite and
MySQL after the v26 migration). On SQLite that is 4096 to 1365 bytes per
kill on disk now, or 158 bytes after v26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Restored left-out attributes from a frozen baseline

Restoring sparse kill data reset every attribute of the respawned entity
to the restoring server's defaults. An attribute the logging server did
not have yet was reset as well: a horse logged before 1.20.5 and
restored on 26.1 got the global step height, safe fall distance and fall
damage multiplier instead of the horse values.

AttributeUtils now holds a frozen baseline of 40 attribute values, taken
from the 26.2 registry defaults. Kill data leaves out an attribute only
when it is in the baseline, has the baseline value and has no modifiers,
and sets that attribute's bit in a long stored after the format marker
at index 10. Restore sets exactly the attributes with a bit to their
baseline values and leaves every other attribute as spawned. Entries
are only ever appended, so stored bits keep their meaning, and the
restoring server's defaults are never read. Rows without the marker
restore as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: TonyJamesStark <tonyjamesstark1@gmail.com>
2026-10-05 17:48:37 -06:00
d5db22c23e Entity radius purge (#1030)
* Fixed world purges leaving orphaned entity data

co_entity has no world column, so /co purge r:#world deleted the kill
rows in co_block but kept every co_entity row. Those blobs became
orphans that no later purge removed.

- SQLite: copy only the co_entity rows that a retained kill row still
  references. This also drops orphans left by earlier purges.
- MySQL/DuckDB: delete the co_entity rows of the kill rows a world purge
  removes, before co_block is purged. MySQL uses a join so MariaDB and
  MySQL 5.7 do not run a dependent subquery. Global purges keep the
  cheaper time-based delete, which is equivalent. If MySQL stops between
  the two deletes, running the same purge again removes the remaining
  kill rows.

Player kills use the kill action with type 0 and a user id in data, so
they are never treated as co_entity references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Fixed SQLite purges with world and block filters purging other tables

On SQLite, /co purge r:#world i:<block> built the retain condition for
world-scoped tables without checking the block restriction, so it purged
co_container, co_chat and the other world-scoped tables in that world
although a block restriction should leave them untouched. The
entity_container/entity_interaction branch and the MySQL path already
check it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Fixed console purges with a radius purging all worlds

/co purge rejects a numeric radius, but the check only saw the radius
when the sender had a location. From the console, /co purge t:30d r:50
parsed no radius and no world, so it ran as a server-wide purge. The
radius is now also parsed against a placeholder location, so it is
rejected for every sender. r:#world is unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Added orphaned entity data cleanup to MySQL #optimize purges

MySQL deletes in place, so co_entity rows orphaned by earlier world
purges stay until something removes them. With #optimize, delete every
co_entity row that no kill row references, through a temporary table of
referenced ids (avoids an anti-join on the unindexed co_block.data), then
let OPTIMIZE reclaim the space. Failures are reported like the table
loop, so the entity_spawn link cleanup still runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Moved purge conditions into PurgeFilter

PurgeFilter now builds the purge condition for every table, and the
SQLite copy, the SQLite recovery path and the MySQL/DuckDB delete all use
it instead of three hand-built copies. Behavior is unchanged; the block
restriction check that the previous commit added to the SQLite copy is
part of the shared condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Added entity kill filters to purge command

/co purge rejected entity types and actions, so mob farm kill logs could
only be removed together with all other data. New arguments on SQLite,
MySQL and DuckDB:

- a:kill purges only entity kill rows.
- i:<entity> purges kills of those entity types; it can be combined with
  block types.
- e:<entity> keeps kills of those entity types while purging the rest.

Each purged kill also removes its co_entity row. a:kill with block types,
entity types in both i: and e:, e: with only block types in i:,
non-entity exclusions and entity filters on ClickHouse are rejected. Any
a: value other than a kill alias is rejected instead of falling through
to an unrestricted purge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Added radius support to purge command

/co purge rejected any numeric radius, so kills of one entity type could
only be purged across a whole world. A radius is now accepted when the
purge is restricted to co_block rows with i: or a:kill, for example
/co purge t:30d r:50 i:zombie. r:50x10 also limits the height.

The radius becomes an x/y/z range in the co_block condition. co_entity
rows follow the purged kill rows, so no entity data is orphaned, and the
(wid,x,z,time) index already covers the range. Other tables are left
untouched, as with any restricted purge.

Rejected: a radius without i: or a:kill, WorldEdit selections, radius
purges on ClickHouse, and a numeric radius from the console (it has no
location, as in the console radius fix merged into this branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

* Made MySQL entity data purges safe against concurrent writers

The #optimize orphan sweep snapshots the co_entity ids that kill rows
reference, then deletes every row missing from the snapshot. Another
installation sharing the database could commit a kill between the two
statements and lose its entity data. The sweep now only deletes rows
inside the purge time range. A kill always writes its co_entity row with
the current time, and a purge range ends at least 24 hours ago, so a
concurrent kill never matches.

A world purge deletes the entity data of the purged kills and
then the kill rows. On MySQL a failure or cancellation between the two
left kept kill rows without their rollback data. Both deletes now run in
one transaction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Limited SQLite entity data cleanup to the purge time range

A SQLite purge dropped every co_entity row no kill referenced, including
rows newer than the purge range, and counted them as deleted. It now only
removes unreferenced rows inside the range, like the MySQL #optimize sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rejected purge include and exclude lists without a value

/co purge t:30d e: passed validation, parsed to no exclusions, and ran
as an unrestricted time purge. e:, and e:,,, did the same, as did an
e: at the end of the command and an empty i:. The argument check now
follows each include or exclude list across its continuation tokens and
rejects a list that ends without a value. Space-separated lists such as
e:zombie, villager still work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Matched purge entity filters to kills logged under renamed types

Bukkit resolves a legacy name such as zombie_pigman to the renamed type,
so the filter only matched the id of the new name. e:zombie_pigman then
purged the old kills it was meant to keep, and i:zombie_pigman missed
them. The filter now covers every entity map id that resolves to the type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rejected purges with more than one radius argument

/co purge t:30d r:50 r:#world_nether i:zombie ignored the explicit world
and purged around the player in their current world. Two r: arguments
always conflict, so the second one is now rejected before the purge
starts, whether it is a world, #global or another radius.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Applied max-radius to radius purges

A radius purge accepted any value. A radius past the int range saturated,
the area overflowed, and the purge reported success with nothing deleted.
It now stops at max-radius like lookups and rollbacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Checked radius arguments inside purge include and exclude lists

/co purge t:30d i:zombie, r:50 r:#world_nether passed the second-radius
check: the validator took r:50 as a value of the include list, then saw
r:#world_nether as the only radius. The radius and world parsers read
both. A list now continues only with plain or minecraft: namespaced
values, so a radius after a trailing comma is counted and the second one
is rejected. This is the same change as 39deec3 on entity-kill-purge-filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFQuzYmYun57wJi2a5mqkM

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: TonyJamesStark <tonyjamesstark1@gmail.com>
2026-10-05 17:46:22 -06:00
Intelli 1490dc7c89 Reduce ClickHouse consumer payload retention 2026-10-04 11:05:16 -06:00
Intelli af78ea00d4 Added a:sign support for rollbacks/restores 2026-10-02 17:29:11 -06:00
Intelli 4c8038cd26 Fixed BatchUpdateException for 'meta' column on outdated MySQL schemas 2026-10-02 15:50:02 -06:00
tonyjamesstarkandClaude 29d22d5a58 Fixed console purges with a radius purging all worlds (#1026)
/co purge rejects a numeric radius, but the check only saw the radius
when the sender had a location. From the console, /co purge t:30d r:50
parsed no radius and no world, so it ran as a server-wide purge. The
radius is now also parsed against a placeholder location, so it is
rejected for every sender. r:#world is unaffected.


Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-02 13:58:53 -06:00
tonyjamesstarkandClaude 4e70d63978 Fixed SQLite purges with world and block filters purging other tables (#1025)
On SQLite, /co purge r:#world i:<block> built the retain condition for
world-scoped tables without checking the block restriction, so it purged
co_container, co_chat and the other world-scoped tables in that world
although a block restriction should leave them untouched. The
entity_container/entity_interaction branch and the MySQL path already
check it.


Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-02 13:58:17 -06:00
CloudMindeandIntelli 00e7b3819b Add #sign block-group (#1024)
* Add #sign block-group

* Fix #sign group: use singular name, version-aware detection, remove TRACK_ANY

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2026-10-02 13:55:11 -06:00
Sage Kummer 079b97405b Stop threads and subscribers that outlive a plugin disable (#1020)
* Stop threads and subscribers that outlive a plugin disable

The WorldEdit subscriber, the cache sweeper thread, per-click inspector threads and the bStats scheduler all kept running after CoreProtect was disabled, and the Folia shutdown checkpoint could wait 30 seconds on tasks that never run. Shutdown now unregisters and stops each of them, inspector lookups use a small named pool, and the checkpoint waits 5 seconds.

* Recreate inspector executor on enable and account for shutdown leftovers

The inspector executor was a static final that stayed shut down after a disable, so a same-instance enable could never run another lookup. It is now created on each enable. At shutdown, lookups still queued after the drain window release their throttle slot, since shutdownNow() returns them without running their finally block, and running lookups that survive the interrupt are logged by thread and player after a bounded wait. The Folia checkpoint timeout change is reverted to keep this change to thread cleanup.
2026-10-02 13:34:34 -06:00
Sage Kummer 48c9bf3c0f Apply Folia inventory rollbacks on each player's scheduler (#1018)
* Apply Folia inventory rollbacks on each player's scheduler

Inventory rollbacks run in the region that owns chunk 0,0 and changed every player's inventory from there, which Folia does not allow. On Folia the rows are now grouped per player and applied on that player's entity scheduler, the batch waits for those tasks, and the rollback counters are updated atomically. Paper and Spigot are unchanged.

* Run Folia inventory tasks as mutations of the rollback context

The per-player inventory tasks now go through the rollback's entity context. A task checks for cancellation before it changes anything, runs as an active mutation so cleanup waits for it to finish, and adds its item count to that context instead of the shared counter for the sender, so a late task cannot change a rollback that already ended or a newer one from the same sender. The counter helpers go back to their upstream form since player tasks no longer write to them.
2026-10-02 13:34:22 -06:00
Sage Kummer 804e398eb1 Read bisected halves through the WorldEdit edit, not the live world (#1016)
* Read bisected halves through the WorldEdit edit, not the live world

When an edit removes a door or tall plant, the other half was read from the live world on the edit thread, which does not own the chunk and can see blocks the edit already changed. The half is now read through the edit extent, or the FAWE processor's chunk arrays, and carried on WorldEditBlockState. The type list Queue checked inline moves to BlockGroup.LOGGED_BY_LOWER_HALF so both sides use the same one.

* Capture the other bisected half before the WorldEdit change applies

CoreProtect's extent sits at BEFORE_CHANGE, below WorldEdit's batching, so its reads reach the live world, and both the lower half and the removed partner were read after setBlock(). Read the other half before setBlock() instead, and only use it when it is the same material with the opposite half, so an unrelated block next to a removed half is no longer logged from block data alone. A door or tall flower is already logged by its lower half, so its partner no longer gets a second, duplicate row. The FAWE lower half is validated the same way.
2026-10-02 13:34:11 -06:00
Sage Kummer 18643de5a1 Make /co reload swap world configs and leave block groups alone (#1015)
* Make /co reload swap world configs and leave block groups alone

The per-world config map was a plain HashMap that getConfig wrote into on a miss, and reload cleared it before refilling, so a listener hitting that window could leave a world on the global config until the next reload. The map is now concurrent and read-only for listeners, and reload swaps new configs in without a gap. Reload also stops rebuilding the version adapters and block groups, which only depend on the server version, while listeners read them.

* Rebuild adapters and block groups on /co reload again

Several block groups are filled from datapack tags (buttons, pressure
plates, doors, saplings, flowers, carpets, logs and more), so they are
not fixed by the server version. Running the adapters and
BlockGroup.initialize() only at startup would leave those groups stale
after a datapack reload until the server restarts. Restore the previous
behaviour and keep this change to the world config map.
2026-10-02 13:34:00 -06:00
Sage Kummer 2863de7f30 Fix races on the pending item transaction lists (#1013)
* Fix races on the pending item transaction lists

Item listeners appended to the shared per-player lists with get, add and put while the consumer read and then cleared them, so items could be logged twice or not at all, and the anvil path replaced pending items outright. Listeners now append inside ConcurrentHashMap.compute and the consumer takes each list with remove. The prepared copy is kept for every database type so a retried relational batch still writes its rows.

* Close discard and clone-failure gaps in pending item transactions

Listeners now append their item and register its generation in one
Queue.addPendingItems call that holds the Queue lock, the same lock
ItemTransactionProcess.discard takes, so discard can no longer clear an
item whose transaction has not been queued yet. ItemLogger.prepare now
only detaches the pending lists; cloning and merging move into
PreparedTransaction.log, which runs after the prepared transaction is
stored for the batch, so a cloning failure leaves the detached items
available to the retried transaction.
2026-10-02 13:32:56 -06:00
Sage Kummer 80caab5938 Restore paintings and item frames on Folia without teleporting (#1011)
* Restore paintings and item frames on Folia without teleporting

Rollback spawns a painting or item frame and then teleports it into place, but Folia always throws from Entity#teleport, so paintings are lost and frames never get their recorded facing or item. On Folia the entity is now spawned directly at its recorded block with its facing, art or item set in the pre-spawn consumer. Paper and Spigot are unchanged.

* Spawn Folia hangings through the adapter on 1.19.4 and 1.20.1

Compiling against the current API binds World#spawn(Location, Class,
Consumer) to the java.util.function.Consumer overload, which only exists
from Bukkit 1.20.2. Folia 1.19.4 and 1.20.1 only have the
org.bukkit.util.Consumer overload, so restoring a painting or item frame
there threw NoSuchMethodError. The spawn now goes through
BukkitAdapter.spawn, which calls the java.util.function.Consumer overload
when World has it and otherwise invokes the org.bukkit.util.Consumer
overload by reflection.
2026-10-02 13:32:46 -06:00
Sage Kummer 4274f88d0a Log armor stand breaks from the retired callback on Folia (#1010)
* Log armor stand breaks from the retired callback on Folia

When an armor stand dies from non-player damage, the break and its equipment are logged from a task on the stand's scheduler. On Folia the stand is removed in the same tick, so the task is retired instead of run and nothing is logged. The same runnable is now passed as the retired callback, so exactly one of them logs the break.

* Log armor stand breaks from EntityDeathEvent instead of isDead

The damage listeners now only capture the attacker and the stand's
contents, and EntityDeathEvent logs the break when the stand actually
dies. The scheduled task and Folia's retired callback just discard a
capture that was never used, so a stand that survived the hit and was
unloaded or otherwise removed before the callback ran no longer
produces break and container rows, and the retired callback never
touches the world.
2026-10-02 13:32:30 -06:00
Sage Kummer 2f50109bdd Materialize the MySQL sign text filter as a derived table (#1008)
* Materialize the MySQL sign text filter as a derived table

MySQL cannot semi-join the rowid IN (UNION ...) subquery the sign text filter builds, so it runs it as a dependent subquery, re-probing all eight branches for every candidate row and never using the line_N prefix indexes. Wrapping the union in a derived table lets MySQL materialize it once through those indexes. Other databases build the same query as before.

* Inline the MySQL sign text filter so restricted lookups stay indexed

The derived table materialized every sign row matching the text across the whole table before the location, time and user restrictions were applied, so a common prefix at one sign location scanned the full table. MySQL now gets the same inline line predicates DuckDB already uses: restricted lookups filter the rows found through the wid, time or user index, and unrestricted lookups still reach the line prefix indexes through an index merge.

* Split the sign filter comment into two sentences
2026-10-02 13:32:05 -06:00
Intelli 6e9c8302fa Name DuckDB workers to avoid Spark profile misattribution 2026-10-02 13:17:27 -06:00
Intelli 7582ab4d8e Added logging for cushions (fixes #1023) 2026-09-28 22:27:54 -06:00
Intelli ac94648185 Fixed copper golem transaction logging on newer Paper versions (fixes #1033) 2026-09-28 18:52:32 -06:00
Intelli 6a27fbfaba Fixed ClickHouse 26.9+ initialization failure caused by server response compression (fixes #1032) 2026-09-28 18:29:18 -06:00
Intelli d5cad31d6c Optimize ClickHouse player lookups with a metadata index (fixes #1031) 2026-09-27 00:18:18 -06:00
Intelli e518b2ae02 Optimized ClickHouse lookups with safe PREWHERE filters (fixes #1031) 2026-09-25 20:51:18 -06:00
Intelli 0d96edbf2b Changed required ClickHouse version to v26.1 2026-09-25 15:40:28 -06:00
Sage Kummer bdc53a1250 Key the explosion scan by packed block position (#1021)
The explosion scan cloned a Location for every block and built five more per block to find attached neighbours, hashing each one on the region thread. It now keys the map by a packed long position and walks the neighbours from an offset table. The same blocks are logged.
2026-09-25 15:12:42 -06:00
Sage Kummer 35f64f09a4 Close lookup ResultSets on exception paths (#1019)
Lookup.countLookupRows and LookupRaw.performLookupRaw closed their ResultSet as the last statement of the try, so any error while reading rows left it, and the statement behind it, open until the connection was returned. Both now close it on every path.
2026-09-25 15:11:38 -06:00
Sage Kummer fc5a12c0b7 Key populated chunks by world and drop them after 240 seconds (#1017)
populatedChunks had no world in its key, so a chunk generated in one world suppressed water, lava and vine rows at the same chunk coordinates in every other world, and entries were only removed when a later fluid row happened to land there. It is now kept per world and swept after 240 seconds, the longest window BlockPlaceLogger checks.
2026-09-25 15:10:55 -06:00
Sage Kummer 335fec2036 Skip a null tamed owner name when restoring an entity (#1012)
A tamed animal whose owner has no known name is stored with a null owner, and restoring it throws a NullPointerException at the owner step, which drops the rest of the restore. A null owner name is now skipped like an empty one.
2026-09-25 15:10:27 -06:00
Sage Kummer 72d5655ee9 Skip matchMaterial's regex for enum names and cache blockdata encodings (#1022)
The consumer ran Material.matchMaterial's two regex passes on names they cannot change, and re-encoded the same blockdata strings into ids on every row. Plain enum names now go straight to getMaterial, and stringToByteData caches its results per string, bounded, reset when the id maps reload and skipped on ClickHouse.
2026-09-25 15:08:11 -06:00
Sage Kummer 97fced4cb6 Allocate world, material, blockdata, art and entity ids under one lock (#1009)
The id allocators read the counter, add one and store it without a lock, so two threads that see new names at the same time (region threads on Folia, lookup and API threads everywhere) can give the same id to two different names. Allocation now holds the ConfigHandler monitor reloadAndGetId already uses, with a re-check, and queues the insert after releasing it.
2026-09-25 14:56:30 -06:00
Sage Kummer 96eec2b768 Skip block entity snapshots on the dispenser logging path (#1007)
Every dispense copied the dispenser's block entity twice on the region thread (block.getState() in the dispense listeners and inventory.getHolder() in onInventoryInteract), and both copies were only used for their type, location and live inventory. Paper can hand out non-snapshot states for this, so the dispense path now asks for those through a new PaperInterface.getBlockState(Block, boolean), falling back to a normal snapshot on Spigot.
2026-09-25 14:12:34 -06:00
Intelli bba6f9f522 Improved DuckDB startup exception 2026-09-25 13:22:08 -06:00
Intelli 3314d993a6 Bump API documentation to v13 2026-09-24 15:57:17 -06:00
Intelli 68739a2596 Target Paper 26.3 2026-09-23 22:37:10 -06:00
Intelli 4e5ddd2334 Improve rollback diagnostics and abort failed database initialization 2026-09-23 20:12:06 -06:00
Intelli bbe5917ec5 Fix entity container rollbacks aborting on missing items or full inventories 2026-09-23 17:42:44 -06:00
Intelli 78fbf12d9e CoreProtect Community Edition v24.1 2026-09-23 14:37:50 -06:00
Intelli 56ba90b58b Align ClickHouse lookup pagination with DuckDB 2026-09-23 13:59:32 -06:00
Intelli 91f08590d0 Align ClickHouse entity inspection pruning with DuckDB 2026-09-23 13:39:58 -06:00
Intelli 3af1079b51 v13 API documentation improvements 2026-09-21 13:15:49 -06:00
Intelli 37be5ebf15 Updated v13 API documentation 2026-09-21 12:59:07 -06:00
Mykhailo Alipa 5db696103a feat(api): add message prefix filters to typed lookups (#1001)
* feat(api): add message prefix filters to typed lookups

* refactor(api): reuse existing message filter helper directly

* chore: remove test file from message filter PR

* feat(api): make message prefix exclusions explicit

* refactor(api): name text prefix filters by their predicates
2026-09-21 12:35:23 -06:00
Intelli 2f719054eb Fixed BatchUpdateException / sign data truncation for MySQL databases 2026-09-19 12:52:18 -06:00
Intelli 46b56d577a Fix DuckDB entity interaction failure recovery 2026-09-19 12:49:46 -06:00
DarkChroma 0ec76561dc Prevent DuckDB entity interaction queue stalls (#1002) 2026-09-19 12:37:10 -06:00
Intelli 9cf12ee5bf Prevent DuckDB entity UUID conflicts with active readers 2026-09-18 14:14:40 -06:00
Intelli 5318bd2031 Fix duplicate right-click inspections triggering database busy messages (#1000) 2026-09-18 13:33:49 -06:00
Intelli 94ecdb3427 Prevent duplicate UUID inserts during delayed entity spawn logging 2026-09-17 17:14:27 -06:00
Intelli d664ece2a8 Fixed UUID cache gap 2026-09-17 14:07:47 -06:00
Intelli 11b4795bf3 Updated documentation 2026-09-16 15:36:25 -06:00
Mykhailo Alipa 45da5eee0c feat(api): extend typed block lookups and add entity lookup (#995)
* feat(api): add material filters to typed block lookup

* feat(api): support scoped typed block lookups

* refactor(api): simplify block material predicates

* feat(api): add typed entity lookup and filters

* fix(api): separate block history and optimize entity scopes

* fix(api): cast ClickHouse entity tracking IDs safely
2026-09-15 13:13:52 -06:00
Intelli 645bdb52d8 config.yml cleanup 2026-09-14 19:17:47 -06:00
Thoria 0ac4761a3a Add dispensers config option to disable dispenser logging (fixes #727) (#989)
* Added dispensers config option to disable dispenser logging (fixes #727)

* Only suppress dispenser-caused bone meal and ignition

* Only track bone meal applied by a dispenser

* Identify dispenser fireball ignition by its shooter

* Clarify the dispensers config header
2026-09-14 19:14:57 -06:00
DHEnry-FMCRAFT 678b27c714 fix: 修复CoreProtect与Slimefun物品meta空指针报错 (#886) 2026-09-14 17:24:28 -06:00
Intelli 5e036528a9 Extend big_dripleaf fix to redstone ores 2026-09-14 16:25:54 -06:00
Intelli dbcdbd853d Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-09-14 16:14:08 -06:00
Intelli 9e3a1ec546 Hardened legacy metadata decoding 2026-09-14 16:13:35 -06:00
Mykhailo Alipa 58587ed671 feat(api): add block and session action filters (#985) 2026-09-14 15:14:15 -06:00
Intelli 79ba793626 Fixed #summary not always returning correct row counts (fixes #991) 2026-09-14 13:07:01 -06:00
Intelli 852574db6b Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-09-14 12:35:27 -06:00
Intelli 2765bf502a Don't require time value if radius is specified (fixes #992) 2026-09-14 12:35:15 -06:00
Splosh 1db3a39711 Recognize modern Paper platform marker (#990) 2026-09-12 21:27:03 -06:00
Intelli 6b1de1b947 Improved shutdown logic for container transaction logging 2026-09-12 21:21:14 -06:00
Intelli 2251c758ac Additional attribute metadata improvements 2026-09-11 15:06:05 -06:00
Intelli 9dd653c72b Improved legacy attribute metadata handling (fixes #986) 2026-09-10 16:51:50 -06:00
Intelli ac20d00d62 Avoid getState calls in PlayerInteractListener to improve performance (fixes #984) 2026-09-10 16:16:21 -06:00
Emir a27026c7a6 Avoid unnecessary regex compilation in hot paths (#951) 2026-09-10 12:41:34 -06:00
Intelli 3e04b389f2 Updated en.yml 2026-09-09 19:11:56 -06:00
Intelli d93827f184 Added exclusion support to lookup filters 2026-09-09 18:40:49 -06:00
Intelli ce556227c0 WorldEdit logging improvements 2026-09-09 18:08:15 -06:00
Intelli 6a40286a59 FAWE performance improvements 2026-09-09 17:57:30 -06:00
Intelli f55bb32cbf Fixed a:+item not returning lookup results (fixes #962) 2026-09-09 17:00:05 -06:00
Intelli 5b0e4c3f7a Disabled unnecessary logging of big_dripleaf state changes (fixes #982) 2026-09-09 16:49:13 -06:00
Intelli 17a9cb5a4a Fixed hopper minecarts logging incorrect item amounts 2026-09-09 16:32:27 -06:00
Intelli d58bf0732d Fixed left-click inspector lookups on boats/minecarts 2026-09-09 15:42:41 -06:00
Intelli 9e1b74a49c Fixed entity name formatting in lookups 2026-09-09 15:22:51 -06:00
Intelli 8f6de4d2af Updated en.yml 2026-09-09 15:16:14 -06:00
dependabot[bot] af4296c1e6 Bump org.apache.logging.log4j:log4j-api from 2.25.4 to 2.25.5 (#955)
Bumps org.apache.logging.log4j:log4j-api from 2.25.4 to 2.25.5.

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-api
  dependency-version: 2.25.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-09 15:15:39 -06:00
Mykhailo Alipa 06863f197c feat(api): add action filters to typed transaction lookups (#983)
* feat(api): add typed transaction action filters

* docs(api): clarify action filters for API consumers
2026-09-09 14:08:54 -06:00
Intelli e263556f70 Additional item_name improvements 2026-09-08 23:37:20 -06:00
Daniel Škopek 867fb3d0f3 Added support for item_name (#963) 2026-09-08 23:30:01 -06:00
Intelli 37a67f2594 Fixed ClickHouse location query pruning while preserving NULL values 2026-09-08 23:07:14 -06:00
Intelli 36b30bc5f3 Improved lookup performance on empty DDB pages 2026-09-08 22:47:22 -06:00
Intelli 5b012504a5 Improved metadata conversion handling 2026-09-08 22:46:43 -06:00
Intelli 0944d31357 Fixed command throttling logic 2026-09-08 20:47:09 -06:00
Intelli 464aabbd1e Improved custom database filename logic 2026-09-08 20:33:04 -06:00
Dainsleif f0f355f1ba Add hidden sqlite-database config option to customize the SQLite database filename. (#956) 2026-09-08 20:24:26 -06:00
Intelli 7dd70c45d5 Fixed ClickHouse user ownership and consumer failure handling 2026-09-08 20:23:41 -06:00
Intelli 548acc16dd Fixed ClickHouse event retention and identity reuse, and DuckDB indexing 2026-09-08 17:54:59 -06:00
Intelli 6a5c46673c Fixed mixed-case UUID intersections in username API lookups 2026-09-08 17:13:11 -06:00
Mykhailo Alipa edea5f105b feat(api): add user filters to typed lookups (#980)
* feat(api): add user filters to typed lookups

* docs(api): consolidate lookup filter options

* refactor(api): extract username inclusion filter resolution
2026-09-08 17:07:12 -06:00
Mykhailo Alipa e97878927e feat(api): add material filters to typed lookups (#979)
* feat(api): add typed material lookup filters

* fix(api): match normalized lookup materials

* fix(api): share stored material name resolution
2026-09-08 14:22:39 -06:00
Linwendong83 9b57624726 Fix 30-second shutdown delay on Folia (#978) 2026-09-08 14:15:33 -06:00
Intelli d9f728d917 Additional lookup optimizations 2026-09-08 13:38:15 -06:00
Intelli 6138dffe7a Fixed inspector not preventing damage to boats/minecarts 2026-09-08 13:19:56 -06:00
Intelli 2405e7cf76 Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-09-08 13:18:43 -06:00
Intelli d2fb0bd092 Looking performance improvements 2026-09-08 13:18:32 -06:00
Mitra b95fb65dde chore(build.yml): update actions/checkout and actions/setup-java to latest versions (#975) 2026-09-05 07:39:09 -06:00
Intelli 335e1deca1 Fixed FAWE bulk logging compatibility and ClickHouse NULL locations 2026-09-03 16:09:35 -06:00
CheekyNox 6d74208f6a Fix ClickHouse lookup pagination (#958) 2026-09-03 15:56:14 -06:00
Telesphoreo fa7ed2a397 Reduce ClickHouse lookup memory use and scan time (#966)
* Optimize ClickHouse lookup queries

* Separate ClickHouse inspector queries

* Keep inspector queries on compatibility views

* Fix WorldEdit bulk change logging
2026-09-03 15:54:51 -06:00
Intelli aa788e454c Added logging for zombie-nautilus variant (fixes #968) 2026-09-03 14:48:45 -06:00
Intelli eb3866cc28 Harden DuckDB indexing and recovery 2026-09-02 23:34:45 -06:00
Intelli 834281d5f3 Fixed ClickHouse sign write failure (fixes #961) 2026-08-25 16:34:09 -06:00
Intelli b1edbbbf02 Fixed SQLite rowid issues 2026-08-20 15:58:26 -06:00
Ben e3be1520fa Set prefer_column_name_to_alias on ClickHouse connections (#948)
ClickHouse resolves identifiers to select-list aliases before table
columns by default, so lookup queries that reuse a column name as an
alias (e.g. 'data as metadata, 0 as data' in the item lookup) return
the alias literal instead of the column. Item lookups then return
metadata as UInt8 0, mixed lookup unions produce
Variant(Array(Int8), UInt8), and the JDBC client fails every /co
lookup with 'Column is not of array type'.

Setting prefer_column_name_to_alias=1 restores the alias semantics
these queries rely on under SQLite and MySQL.

Fixes #947
2026-08-18 13:46:57 -06:00
Intelli 29f25cf1de Fixed CraftLegacy initialization when using CraftEngine (fixes #957) 2026-08-18 13:23:54 -06:00
Intelli 8adde82882 Ensure user UUID data is never outdated 2026-08-17 13:33:46 -06:00
Intelli f2eb0b0331 Improved multi-server support for columnar storage 2026-08-16 14:46:15 -06:00
Intelli 0bb1d5dcb7 Cleanup 2026-08-11 13:44:55 -06:00
Intelli d47457fa90 Custom block metadata serialization 2026-08-11 13:17:42 -06:00
Intelli 078adf535f Improved entity serialization 2026-08-10 19:18:57 -06:00
Intelli 49262d9e28 Custom entity serialization (WIP) 2026-08-06 21:41:44 -06:00
Intelli 5ff8072ddc Fixed NoSuchMethodError on PurPur servers 2026-08-05 19:33:24 -06:00
Intelli a34e56e9d6 Lookup performance improvements 2026-08-01 10:25:58 -06:00
Intelli b745a6099a Added logging for chicken, cow, and pig visual variants 2026-07-31 15:57:29 -06:00
Intelli 2a9f1234b3 Cap villager level at 5 during rollbacks 2026-07-28 16:27:48 -06:00
Intelli eefdd95776 Fixed race when processing container transactions 2026-07-28 15:46:31 -06:00
Intelli b0b14cd270 Fixed NoClassDefFoundError on Spigot 26.2 servers 2026-07-20 17:16:28 -06:00
Intelli a95c0f93b0 Improved shutdown handling 2026-07-19 23:09:57 -06:00
Intelli ccb7d5d85c Fixed entity lifecycle race 2026-07-19 17:59:08 -06:00
Intelli 79d904af88 Additional entity identity persistence improvements 2026-07-19 16:45:24 -06:00
Intelli 43a8986ca8 Fixed race condition and shutdown hang 2026-07-19 15:37:43 -06:00
Intelli 6d1a4ac520 Harden entity interaction persistence and identity tracking 2026-07-19 09:02:05 -06:00
Intelli 438d6680a8 Fix entity interaction persistence and identity tracking 2026-07-18 14:43:30 -06:00
Intelli 3cb2f205d9 Fixed dispenser logging creating false block-break logs 2026-07-17 14:43:07 -06:00
Intelli fda960a18c Improved internal logic for migration handling 2026-07-16 20:21:57 -06:00
Intelli 6731831283 Added columnar storage options 2026-07-16 14:11:40 -06:00
purpurcofandIntelli 130bb803e3 Update and polish Russian localization (#929)
* i18n(ru): update and polish Russian localization

* i18n(ru): fix typography and restore standard hyphens

* Reorder lookup header in Russian translations

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2026-07-12 22:48:32 -06:00
Intelli bca5fe5305 Fixed #count being accepted for rollbacks/restores 2026-07-12 22:46:23 -06:00
Intelli aa2d0a1e46 Added rollback-state lookup filters 2026-07-12 22:26:35 -06:00
Intelli 197e5b34a4 Additional hopper transaction logging hardening 2026-07-12 18:57:11 -06:00
Intelli 3d1594e116 Added entity interaction logging 2026-07-12 17:32:35 -06:00
Intelli c25621622e Dropped unnecessary guards for #count 2026-07-12 14:13:23 -06:00
Intelli 7ad4a77149 Hardened hopper transaction logging 2026-07-12 13:05:52 -06:00
Intelli e4c0ea88df Added filtering support for a:sign 2026-07-12 13:04:14 -06:00
Intelli 39f66412d6 Enhanced #count to display summary information (implements #926) 2026-07-12 01:09:18 -06:00
Intelli bab8dca58a Fixed missing parameters in tab completion 2026-07-11 19:31:22 -06:00
Intelli d95e70eebb Fixed risk of NPE when logging container transactions 2026-07-11 19:00:24 -06:00
Intelli 3151a25f3e Added entity spawn, movement, & transaction logging 2026-07-11 16:21:01 -06:00
Intelli 52a0e45c61 Minor improvements to filter logic 2026-07-09 17:04:02 -06:00
Intelli 956828b5d0 Added message filtering 2026-07-09 15:48:12 -06:00
Intelli b5f534fd2c CoreProtect Community Edition v24.0 2026-07-07 13:22:19 -06:00
Intelli 4ecab0b101 Added extended logging support for sulfur spikes 2026-07-06 20:38:41 -06:00
Intelli 2f196fb385 Fixed dripstone logging duplicate #gravity break on attached block break 2026-07-06 19:07:05 -06:00
Intelli 32e074666d Always set air blocks with physics false 2026-07-06 17:13:37 -06:00
Intelli 6f86e08849 Apply blocks with physics updates in second pass 2026-07-06 17:02:39 -06:00
Intelli 3a95881ea4 Log all entity explosions by name rather than #explosion
E.g. #sulfur_cube
2026-07-06 16:44:12 -06:00
Intelli 04575029c9 Target Paper 26.2 2026-07-05 16:01:55 -06:00
Intelli 9d04c025be Optimize rollback chunk batching and prefetching 2026-07-05 15:31:17 -06:00
Intelli a0ae242dac Fixed incorrect logging of typed block events (II) (fixes #917) 2026-07-03 16:37:08 -06:00
Intelli 4c2c304329 Fixed compatibility issues with older MC versions (fixes #921) 2026-07-03 13:23:43 -06:00
Intelli 943d471269 Fixed FallingBlock being logged when block-movement: false (fixes #918) 2026-07-03 12:41:00 -06:00
Intelli 948ba32e77 Force transaction snapshots on item-type boundary 2026-07-03 12:11:26 -06:00
Intelli 7d47684afb Improve container transaction logging performance 2026-07-03 11:35:00 -06:00
Intelli 3da2bc554d Improved hopper logging logic 2026-07-03 10:37:46 -06:00
Intelli ec699a5a34 Stop bundling OSHI 2026-07-02 16:05:25 -06:00
Intelli 9a37a0d33a Added additional validation for armor stand damage events 2026-07-01 14:46:39 -06:00
Intelli 5ed0863a87 Improve rollback performance + Folia hardening 2026-06-29 20:09:37 -06:00
Intelli 2f4eeeffca Update supported version in README 2026-06-23 14:16:35 -06:00
Intelli b3f5959af4 Fixed incorrect logging of typed block events (fixes #917) 2026-06-09 12:24:33 -06:00
Intelli eee8d1c39b Update blacklist version requirements in documentation 2026-06-01 20:16:55 -06:00
Intelli 15aaaf8051 Harden v24 patch script 2026-05-30 14:43:09 -06:00
Intelli 2429967b79 Added logging for all Silverfish infestations 2026-05-27 20:33:34 -06:00
Intelli 68d3f19d93 Added Ghast fireball attribution 2026-05-27 19:30:18 -06:00
Intelli e2f8da9070 Mention snowballs, potions, ender pearls, tridents, and wind charges in README 2026-05-27 18:04:50 -06:00
Intelli b7a662d137 Remove bucket usage line from README 2026-05-27 17:56:27 -06:00
Intelli cddbc398a6 README cleanup 2026-05-27 17:54:39 -06:00
Intelli 1893eac47a Extended features + logged actions info sections in README 2026-05-27 17:33:19 -06:00
Intelli 305e6de3ac Update README.md 2026-05-27 14:06:50 -06:00
Intelli 5a8b0f93d3 Unify README.md 2026-05-27 13:58:29 -06:00
Intelli 1a9b542517 Link to other versions 2026-05-27 12:11:38 -06:00
Intelli 8d5b0a20b6 Changed minimum MC version to 1.16.5 2026-05-27 11:04:13 -06:00
1wairesd 39e82953b4 feat: add & color code support in Chat and Language (#907) 2026-05-27 10:53:35 -06:00
TWME bdfd6b1576 Log pattern setBlocks for block sets (#908)
Add handling for setBlocks calls that use block sets and generic BlockStateHolder patterns. Imported java.util.Set and added an override for the generic <B extends BlockStateHolder<B>> setBlocks to delegate to the Pattern variant, plus a new setBlocks(Set<BlockVector3>, Pattern) override that checks the WORLDEDIT config, processes each target position to record previous block state and container contents (unless FAWE), and posts WorldEditLogger events before delegating to eventExtent. Includes a new helper processPatternToBlocks and documents limitations around reading container/NBT data from the WorldEdit API.
2026-05-27 10:51:11 -06:00
2291e0d9c7 #810 Add option to give looked up items into inventory (#811)
* Add givable items to ItemUtils

* Improve performance of givable items methods

* Add ItemUtils#getItemStack

* Add ChatUtils#createGiveItemComponent

* Fix ChatUtils#createGiveItemComponent not returning empty String when no id is given

* Add 'give item' components to StandardLookupThread

* Add GivableItemIdParser

* Add CommandParser#parseGivableItemId

* Add Phrase#INVALID_ITEM_ID

* Add GiveCommand

* Update permissions.md

* Fix itemId parsing

* Improve performance of givable items methods

* Update coreprotect.give permission to be false by default

* Only show give component if player has permission to use the command

---------

Co-authored-by: Leonard Steppy <leonard.steppy@outlook.de>
Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2026-05-27 10:47:57 -06:00
Intelli a8aeeca62c Bump version info 2026-05-26 13:04:51 -06:00
Intelli e7197123aa Fixed update notice issue 2026-05-26 13:02:29 -06:00
Intelli 6e6f8d203b Added logging for custom skull textures (fixes #70) 2026-05-21 14:59:52 -06:00
Intelli cb09ddeb0b Added automatic error reporting 2026-05-21 13:54:39 -06:00
Intelli b7a452bf03 Added logging for villagers killed by lightning (fixes #674) 2026-05-20 19:25:50 -06:00
Intelli 7f7de03f54 Fixed MysqlDataTruncation error when logging large bundles (fixes #799) 2026-05-20 18:40:37 -06:00
Intelli ec96019bc2 Fixed custom block rollbacks (fixes #895) 2026-05-20 13:52:52 -06:00
Intelli 5b2e8883ea Improve auto purge documentation 2026-05-20 13:01:12 -06:00
Intelli 6571e91c56 Formatting improvements 2026-05-20 12:56:31 -06:00
Intelli 434d42b547 Added auto-purge documentation 2026-05-20 12:45:54 -06:00
Intelli ba96dd1d19 Added auto-purge-time config value 2026-05-20 11:54:02 -06:00
Intelli 7a730a76fc Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-05-19 17:12:21 -06:00
Intelli 6994ec2ac4 Bump supported MC version to 26.1.2 2026-05-19 17:12:14 -06:00
Intelli 3914e5e989 Update API doc plugin version to v24.0 2026-05-15 13:15:03 -06:00
Intelli ab939cfa6f Update API version from 11 to 12 in documentation 2026-05-15 11:39:36 -06:00
Intelli 39005ee978 UI improvements 2026-05-14 12:51:02 -06:00
Intelli acadd24c70 Share PURGE_TABLES outside of manual purges 2026-05-14 12:35:22 -06:00
Intelli ab4c405dd2 Add auto purge config + status information 2026-05-14 12:30:30 -06:00
Intelli fe0274b069 Add BackgroundService extension 2026-05-13 18:23:39 -06:00
Intelli 1bd0a0f8b6 Fixed jukebox rollback cast issue 2026-05-13 13:29:48 -06:00
Intelli b6acf109d4 Fix Folia falling block entity change handling 2026-05-13 13:29:36 -06:00
Intelli 6d1ddb8580 Added refreshBrain reflection (fixes #905) 2026-05-13 12:36:09 -06:00
Intelli 6d60951cb9 Properly handle physics update exceptions 2026-05-13 11:55:52 -06:00
Intelli 3f7c892153 Drop "specialPrice" from villager rollbacks/restores 2026-05-12 20:30:37 -06:00
Intelli 962ab485ee Properly log villager trade and gossip data (fixes #904) 2026-05-12 16:06:42 -06:00
Intelli 559ee73edf Fixed BlockResult not handling entity kill rows properly 2026-05-12 12:28:19 -06:00
Intelli 5c561c866d Replace magic action numbers with constants 2026-05-12 10:38:27 -06:00
Intelli 78d8301751 Utilize internal model for shared APIs 2026-05-11 15:32:22 -06:00
Intelli 688b4b930d Added inventoryLookup API method 2026-05-11 13:28:08 -06:00
Intelli cf04a42d3e Added itemLookup API method 2026-05-11 12:56:05 -06:00
Intelli bd5c1f33b7 API cleanup 2026-05-07 16:17:38 -06:00
Intelli a658c32ce4 Added usernameLookup API method 2026-05-06 15:49:02 -06:00
Intelli 592a4f25e4 Added typed block lookup methods 2026-05-06 15:27:14 -06:00
Intelli 29256b47e6 Added typed session lookups 2026-05-06 15:04:45 -06:00
Intelli 9924a9376e Added typed API helpers 2026-05-06 13:41:46 -06:00
Intelli cebcc8655a Added signLookup API methods 2026-05-06 13:31:39 -06:00
Intelli 3be769ed96 Added Tatar language file 2026-05-06 13:09:18 -06:00
Intelli 3ffece26a3 Added chatLookup and commandLookup API methods 2026-05-05 16:01:14 -06:00
Intelli 7eaa88eb7f Added radius support for container lookup API 2026-05-05 13:58:24 -06:00
RestitutorandIntelli c4d3dda0ad Added Container Lookup API (#854)
* Add ContainerResult and BoxAPI

* Added area container lookup and "v12" docs

* Revert v11.md

* Simplify container API

---------

Co-authored-by: Intelli <contact@intelli.software>
2026-05-05 13:00:55 -06:00
Intelli 8b0e735313 Fixed NullPointerException on MC 26 servers 2026-05-04 19:10:15 -06:00
Intelli 34ada262c2 Restore villager job-site memory on rollback (fixes #753) 2026-05-04 18:41:51 -06:00
Intelli d7aa696c28 Added noteblock normalization (fixes #712) 2026-04-24 20:46:56 -06:00
Intelli 67033ac08d Improve WorldEdit / FAWE version parsing (fixes #678) 2026-04-24 20:18:44 -06:00
Intelli 2c30ba626a Fixed Folia death logging crash (fixes #750) 2026-04-24 19:51:10 -06:00
Intelli 4ae0affe4b Next-generation sand/gravel logging (fixes #762)
- Sand/gravel that breaks (e.g. landing on a torch) no longer logs a phantom placement.
- Falls now log at the actual landing coord, even when blocks like torches or grass are in the column.
- Naturally-triggered falls (mobs, world gen, plugins, explosions) are now logged.
- Falls caused by a player breaking the support are now attributed to the breaker at the real landing coord.
- Falls without a player cause are now attributed to a new #gravity tag.
- Rollback and restore of sand/gravel falls are now symmetric, leaving no phantom blocks in either direction.
2026-04-24 16:08:45 -06:00
Intelli 0d47338038 Fixed beds not always rolling back in correct state (fixes #856) 2026-04-24 14:33:04 -06:00
Intelli afbbbd5aab Fixed armor stand and end crystal breaks logging as air 2026-04-24 14:21:32 -06:00
Intelli cce8c9eb51 Improved painting variant handling 2026-04-24 14:10:02 -06:00
Intelli eaae2afaf8 Fixed duplicate TNT prime events being logged 2026-04-24 13:15:55 -06:00
Intelli a1458a38be Fixed ConcurrentModificationException when using queueLookup in API (fixes #481) 2026-04-24 12:14:49 -06:00
Intelli 7ccb3eff34 Fixed fire being logged as air 2026-04-24 12:05:10 -06:00
Intelli def5a99096 Added logging for removal of primed TNT & fire fade (implements #824) 2026-04-24 12:04:54 -06:00
Intelli a92a050d2f Extended CoreProtectPreLogEvent with Action, actionId, material, entityType, and message metadata (implements #846) 2026-04-24 11:03:38 -06:00
Intelli 14b0a712de Add v12.md template 2026-04-24 10:31:06 -06:00
Intelli cdc8082c3e Added logging for dropping items into a hopper 2026-04-24 09:50:00 -06:00
Intelli 829bcc530d Fixed books placed in lecterns not being logged (fixes #761) 2026-04-24 09:31:16 -06:00
Intelli aa2a3b9e8e Fixed incorrect string value error on legacy MySQL databases (fixes #826) 2026-04-23 22:05:22 -06:00
Intelli eee96fb5f9 Added BlockType support for custom block support (implements #801) 2026-04-23 18:28:10 -06:00
Intelli fc91bc558c Further improved attribute handling 2026-04-23 17:39:45 -06:00
Intelli 127170d7b6 Fixed NullPointerException when performing a preview with invalid data (fixes #862) 2026-04-23 15:31:16 -06:00
Intelli 7caa617a34 Added argument validation to purge command (fixes #848) 2026-04-23 15:10:06 -06:00
Intelli 7c7013f49f Added logging for flowers being placed in flower pots (fixes #509) 2026-04-23 14:22:54 -06:00
Intelli 4d311f5248 Improved Folia support (fixes #892) 2026-04-23 13:45:30 -06:00
Intelli f5ac148b57 Added logging for Zombie's breaking doors (fixes #865) 2026-04-23 13:10:14 -06:00
Intelli e852d75264 Fixed IllegalArgumentException for lava/water in inventory rollbacks (fixes #802) 2026-04-23 13:00:42 -06:00
Intelli 383caedff5 Changed blacklist.txt comment character to ';' (fixes #891) 2026-04-23 12:36:52 -06:00
Intelli bd449d1b89 Extended de-dupe logic to include droppers/dispensers/fluids/kills 2026-04-23 12:36:14 -06:00
Intelli c706cf5b18 Added #bundle tag (implements #855) 2026-04-23 10:25:49 -06:00
Intelli d69fffbce0 Improved MySQL index handling (fixes #867) 2026-04-23 09:42:21 -06:00
Intelli ff36b6e0e6 Fixed AdvancedChests causing circular load order (fixes #831) 2026-04-22 23:00:49 -06:00
Intelli a90b0ea18a Fixed NoSuchMethodError on transaction events when using Arclight (fixes #864) 2026-04-22 20:41:54 -06:00
Intelli 7dbd928edc Fixed Crimson/Warped fungus growth not respecting mushroom-growth (fixes #866) 2026-04-22 20:21:25 -06:00
Intelli 99130fdcdb Improve metadata sanitization 2026-04-22 20:04:01 -06:00
Intelli 42a2965cd1 Fixed water incorrectly being logged in Nether (fixes #857) 2026-04-22 19:46:44 -06:00
Intelli 971ff57496 Added byte data sanitization to resolve NoSuchElementException (fixes #890) 2026-04-22 18:31:19 -06:00
Intelli 6c04867eb8 Fixed MySQL warnings when logging paintings on MC 1.21+ (fixes #829) 2026-04-22 17:47:32 -06:00
Intelli b83b39feae Formatting fixes 2026-04-22 16:52:53 -06:00
Intelli 80a0f24e10 Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-04-22 16:50:06 -06:00
Intelli 9cadca9acb Fix NullPointerException on PurPur servers when destination hopper has moved (fixes #885) 2026-04-22 16:49:59 -06:00
Warrior 0c389e92f3 Fix wind charge/add new egg throw logging (#884) 2026-04-22 16:40:19 -06:00
Alisa GusmeroliandIntelli db94ad70eb Blacklist improvements (#847)
* add new blacklist features

* update documentation to include new blacklist additions

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2026-04-22 16:36:15 -06:00
Alisa Gusmeroli eff43b7d6f fix waterlogged bucket incorrect logging (#860) 2026-04-22 16:15:51 -06:00
Intelli f0aad273f1 Update excludes 2026-04-22 15:46:45 -06:00
Intelli bf0e9cf661 CoreProtect Community Edition v23.2 2026-04-22 15:07:18 -06:00
Intelli 71b673756f Remove duplicate section in build.yml 2026-04-22 15:00:31 -06:00
Intelli 2f794d6398 Build with Java 25 2026-04-22 14:57:12 -06:00
Intelli f3a1d95b77 Merge branch 'master' of github.com:PlayPro/CoreProtect 2026-04-22 14:52:01 -06:00
Intelli 8c79b3a867 Update dependencies & bump supported MC version to 26.1 2026-04-22 14:51:54 -06:00
Alisa Gusmeroli d87ecaee42 fix issue so coloured bundles are rolled back properly (#872) 2026-03-23 14:59:07 -06:00
Alisa Gusmeroli dc4e9f8d60 Prevent logging of wind burst explosions and improve BlockExplodeListener (fixes #830) 2026-03-23 14:58:12 -06:00
Intelli c145fca68c Fix UnsupportedOperationException in Folia (fixes #842) 2026-02-27 11:01:29 -07:00
Intelli b0856b51a5 Update documentation 2026-01-22 16:58:23 -07:00
Alberto GusmeroliandIntelli 7386f4bd50 Add support for Shelf item logging (#833)
* add shelf logging

* bump supported version to 1.21.11

* cleanup

* Update pom.xml

Remove project branch definition from properties.

* fix incorrect instanceof check

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2025-12-17 13:30:50 -07:00
Intelli 0f0ceccb7b Fix copper golem logging not working with double chests
Works around Paper bug where the entity is null when a golem closes a double chest
2025-12-15 16:11:03 -07:00
Intelli 893ce9af46 Update API docs 2025-12-15 13:50:50 -07:00
Intelli 609990e344 Finalized copper golem logging 2025-12-14 17:56:43 -07:00
Intelli 38166fd381 Re-enable experimental copper golem logging (WIP) 2025-12-14 14:56:30 -07:00
Intelli 2979af360c CoreProtect Community Edition v23.1 release 2025-12-14 14:00:11 -07:00
Intelli 0712545345 Bump supported MC version to 1.21.11 2025-12-14 13:44:01 -07:00
Intelli d3091f9401 Fix missed import removal 2025-11-26 15:23:09 -07:00
Intelli de5a064c8d Fixes for copper golem logging (WIP) 2025-11-20 15:05:12 -07:00
Intelli 1f60dcc3cf Improve synthetic username handling for golem logging (WIP) 2025-11-19 15:12:33 -07:00
Intelli 5276dc57d2 Added logging support for copper golem chest transactions 2025-11-18 17:17:59 -07:00
Intelli 49c0f16934 Whoops 2025-10-10 11:26:59 -06:00
Intelli 322c630af4 Bump supported MC version to 1.21.10 2025-10-10 11:26:15 -06:00
Intelli 251b4c56cd Fixed NoSuchFieldError in MC 1.20.5 and earlier 2025-10-02 11:22:59 -06:00
Intelli 9de1d16836 Bump supported MC version to 1.21.9 2025-10-02 10:38:36 -06:00
Intelli f5109122ac Added edge version handling in status command 2025-10-01 20:07:18 -06:00
Intelli 4a8dd716f4 Fixed UnsupportedOperationException 2025-10-01 20:07:03 -06:00
Intelli 6572946b3d Added support for copper chests 2025-10-01 19:06:00 -06:00
Intelli 51d4bf27cd Added MultiPaper support 2025-09-26 14:12:00 -06:00
Artem Dmitriev d30f6cf8d5 Update ru.yml (#798)
"Выберете" instead of "Выберите"
2025-09-12 18:04:05 -06:00
Stuffy f96556a61f Crafter Slot Enable/Disable Support (fixes #766) (#767)
* Crafter Slot Enable/Disable Support (fixes #766)

* Exclude negative slot values
2025-09-09 10:34:43 -06:00
JoMiNovi 813fee1017 Update de.yml (#609)
* Update de.yml

First version of the Update.

* Update de.yml again

* Adding a comma

* Added missing translations of en.yml to de.yml
2025-09-09 10:32:04 -06:00
Sihu HwangandIntelli 3b58f69263 Update Korean Translation and some English strings (#620)
* Translation update

* Update en.yml

* Update ko.yml

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2025-09-09 10:30:53 -06:00
Tomasz CzajkowskiandIntelli 3158fe5268 Update Polish Translation (#626)
* Update Polish Translation

- Changed a bunch of translations to be as natural as possible
- Made translations more consistent
- Changed special quotation mark to normal escaped quotation marks, to be consistent with the English translations
- Added missing translations

* Update pl.yml

fixed translation for entities in 2 places

* Added missing translation keys and better time-frames

In addition to new translation keys, I added an "optional" suffix to time-frames, for cases when numbers are between 2 and 4 (inclusive). Unfortunately couldn't make it pretty for months, since more of the word changes ("miesiące" vs "miesięcy"), not just the suffix, so I opted to just leave the word translation for the cases of 5+

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2025-09-09 10:27:26 -06:00
Artem Dmitriev 150bd4b0ce Improve Russian translation (#793)
Fixed typos, logically improved messages, removed an extra period
2025-09-09 10:23:25 -06:00
ShotadftandIntelli 5ee33c7790 Fix Japanese Translation (#784)
* Fix Japanese Translation

* Update Japanese Translation

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2025-09-09 10:22:57 -06:00
Intelli d49a8bee73 Add default TIME_UNITS to all language files 2025-09-09 10:16:47 -06:00
HaloglyandIntelli abb0ece3e5 Optimize Chinese Localization, Add Time Units, and Fix Italic Bug (#796)
* add time unit to language file

* Fixed a bug where the font following a blocktype name was incorrectly rendered in italics.

* Collected time unit abbreviations

* Update pom.xml

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2025-09-09 10:13:41 -06:00
Intelli 8b3e37774a Added location to CoreProtectPreLogEvent (implements #709) 2025-09-02 18:05:30 -06:00
Intelli 6155d4e864 Update database-migration.md 2025-09-02 15:03:00 -06:00
Intelli 252de2f233 Update database-migration.md 2025-09-02 14:59:07 -06:00
Intelli cec40e384c Added database migration documentation 2025-09-02 14:52:27 -06:00
Intelli 86d164e4b0 Remove dependencyManagement from pom.xml 2025-09-02 13:03:25 -06:00
Intelli b3abb70744 Update pom.xml 2025-09-02 12:53:45 -06:00
Intelli 2373a9c19f Update stale.yml 2025-09-02 09:29:17 -06:00
Intelli 105d14f11f Update "Third-Party Plugins & Tools" doc 2025-08-14 19:21:07 -06:00
Intelli 942430c11c Update "Third-Party Plugins & Tools" doc 2025-08-14 18:31:48 -06:00
Intelli dcb0864013 Fix parsing for Minecraft versions without a minor version 2025-08-13 12:53:10 -06:00
Intelli a4ffbb642c Target API version 1.16 2025-08-08 18:45:31 -06:00
Intelli df41d1446e Fixed CoreProtect Edge update notice displaying for Community Edition builds 2025-08-08 17:01:44 -06:00
Intelli 78701dee96 CoreProtect Community Edition v23.0 release 2025-08-08 15:34:49 -06:00
Intelli 4f41c19284 Improve build validation (partial implementation) 2025-07-18 18:42:18 -06:00
Intelli dff6526537 Added support for targeting minor versions 2025-07-18 18:15:02 -06:00
Intelli 22fa0dbc47 Fixed inconsistent rollback states 2025-07-16 20:00:29 -06:00
Intelli 90a2e481f2 Added CPU handling for Apple Silicon 2025-04-11 18:33:33 -06:00
Intelli bb86b1b555 Fixed users such as #tnt being incorrectly parsed 2025-04-11 17:18:04 -06:00
Intelli 8f8a263278 Fixed CoreProtect API ParseResult backwards compatibility 2025-04-09 20:16:52 -06:00
Intelli 898bcf2465 Fixed backwards compatibility 2025-04-09 19:58:24 -06:00
Intelli 068542223d Fixed NullPointerException when processing unknown entity types 2025-04-09 18:52:54 -06:00
Intelli 620449f2f2 ... Actual formatting fix 2025-04-04 12:14:46 -06:00
Intelli cc7761b9a6 Formatting fix 2025-04-04 12:13:04 -06:00
Intelli 99bdbf79cd Added CoreProtect Time-Lapse 2025-04-04 12:12:00 -06:00
Intelli e4db0e9c61 Allow forcing SQLite prefix when generating indexes 2025-03-22 15:15:24 -06:00
Intelli 16b7d35fac Added logging for wolf variants and cat sit states 2025-03-21 16:49:50 -06:00
Intelli 6ff24ad347 Fixed fence gates and trap doors not logging interactions on all types 2025-03-21 16:28:11 -06:00
Intelli 6cdc139772 Fixed SQLiteException when performing purges 2025-03-20 13:21:43 -06:00
Intelli 5ebb9f72fc Month / Year param support 2025-03-17 13:51:32 -06:00
Intelli 06f63459ac Build against Paper 1.21.4 2025-03-12 14:29:45 -06:00
Intelli 484614f71e Improved performance of BlockPreDispenseEvent 2025-03-10 19:39:33 -06:00
Intelli 4803b1af23 Don't log drowning glow squids (fixes #646) 2025-03-10 18:38:23 -06:00
Intelli 5211374e14 Include HopperPullListener performance fixes 2025-03-10 18:37:16 -06:00
Intelli 38ba3abc9a Significantly improved performance of hopper logging (fixes #636) 2025-03-10 17:42:43 -06:00
Intelli 3038e96c6e Fixed broken entity rollbacks 2025-03-10 17:15:59 -06:00
Intelli afb13e6677 Refactored Bukkit adapters 2025-03-10 16:46:55 -06:00
Intelli 83302f59f7 Refactor remaining API classes 2025-03-10 14:06:31 -06:00
Intelli 5c965b8a68 Refactored CoreProtectAPI 2025-03-10 13:41:42 -06:00
Quintin Dunn d6c4755fba Fix logging caused by wind charges (#701)
* Fixed blocks broken by wind charges not being logged

* Fixed wind charges still logging invalid block breaks when they're a 'breeze_wind_charge'
2025-03-09 18:39:51 -06:00
Intelli 67336168fb Minor cleanup 2025-03-09 14:55:05 -06:00
Intelli 5b93123ed1 Database and TabHandler refactoring 2025-03-07 20:18:23 -07:00
Intelli a2076468ad Split up Lookup class 2025-03-07 20:05:06 -07:00
Intelli 1e32b20f86 Split up PlayerInteractListener class 2025-03-07 19:53:19 -07:00
Intelli d060a6354f Split up LookupCommand class 2025-03-07 19:39:02 -07:00
Intelli baf61099b8 Update Metrics 2025-03-07 19:09:13 -07:00
Intelli 778be07e51 Remove build.gradle (unsupported) 2025-03-07 17:14:54 -07:00
Intelli 56f5c04619 Fixed rollback item metadata handling 2025-03-07 15:06:13 -07:00
Intelli bdbc465998 Fix anvil transaction logging (fixes #616) 2025-03-07 14:47:29 -07:00
Intelli 6d3df412a7 Updated gradle 2025-03-04 10:25:49 -07:00
Intelli ecebbd11db Fixed build errors 2025-03-04 10:24:19 -07:00
DeadSilenceIV 170b520b58 Support for AdvancedChests was implemented. (#726) 2025-03-04 07:13:55 -07:00
Intelli 1d294c51c4 Refactor CoreProtect.java 2025-02-26 14:05:32 -07:00
Intelli 779b1acbff Split up CommandHandler class 2025-02-26 13:21:38 -07:00
Intelli 5bb0080ca3 Split up Rollback class 2025-02-26 11:10:05 -07:00
Intelli e2c2505b2b Split up utility class 2025-02-25 19:30:28 -07:00
Intelli 828af8443f Update .gitignore 2025-02-25 16:54:40 -07:00
Intelli b3db65d07d Fixed NotSerializableException when logging items containing attribute data 2024-12-09 16:37:29 -07:00
Intelli f729c8a126 Fixed NullPointerException when breaking/placing certain skulls on Paper servers (fixes #643) 2024-12-05 17:01:57 -07:00
Intelli e5ec7ede72 Disable processor information when running on Android 2024-11-28 19:41:52 -07:00
Intelli 65705508c5 Build against 1.21.1 to resolve IncompatibleClassChangeError (upstream issue) 2024-11-05 11:56:57 -07:00
Intelli 2654562585 Changed minimum supported MC version to 1.16 2024-11-05 11:06:33 -07:00
Intelli 011865de56 Fixed IncompatibleClassChangeError on entity kills for MC 1.21.1 and earlier 2024-11-05 10:34:13 -07:00
Intelli 9958d372e3 Fixed IncompatibleClassChangeError when breaking paintings in 1.21.1 and earlier 2024-11-05 10:17:55 -07:00
Intelli 2780a9af27 Fixed NotSerializableException when sacrificing entities (fixes #666) 2024-11-04 14:31:00 -07:00
Intelli ad4b0ddf24 Fixed NoSuchMethodError when killing entities on Spigot servers 2024-11-04 13:59:18 -07:00
Shotadft(承太劇場)andIntelli 25cef3e5c5 Change to Japanese (#660)
* Update ja.yml

* Update ja.yml

* Update ja.yml

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2024-10-28 17:57:32 -06:00
Intelli 2327dbcf78 Fixed SQLiteException when performing purges on SQLite databases (fixes #654) 2024-10-07 19:59:46 -06:00
Intelli eaa8c5c5c1 Fixed v23.1 patch script not triggering on v23.0 installs when using SQLite 2024-10-03 18:37:13 -06:00
Intelli 70ffeacbc0 Fixed NoClassDefFoundError when attempting to load plugin on 32-bit Windows 2024-10-03 15:56:29 -06:00
Intelli 385c11a3aa Finalized extension system 2024-10-03 15:51:18 -06:00
Intelli 88b19efc89 Improve Extension handling 2024-09-29 18:56:07 -06:00
Intelli c7406a01a7 Fix compilation error 2024-09-29 18:47:30 -06:00
Intelli 0f0c89845c Added separate version check for CoreProtect Edge 2024-09-29 18:40:05 -06:00
Intelli de978fce2c Added base changes for database migration command (unfinished) 2024-09-29 18:35:37 -06:00
DarkSnakeX fc0c8ff9ac Update es.yml (#633)
Added new lines that were not covered on the old translation file.
2024-09-03 12:55:47 -06:00
Intelli edadebf8c2 Run v23.1 patch script on v23.0 builds 2024-08-20 14:16:27 -06:00
Intelli ea1b86c034 Only start CoreProtect on supported Minecraft versions 2024-08-20 14:13:44 -06:00
Intelli c493a0dece Added support for Community Edition builds 2024-08-19 20:55:43 -06:00
Wang Siu Kao cff6be6d90 Update Traditional Chinese translation (#618) 2024-08-19 16:17:23 -06:00
Intelli 7ab0704002 Improved how CPU information is displayed under /co status 2024-08-15 20:48:42 -06:00
Intelli cae29f257d Fixed NullPointerException when attempting to roll back skulls with texture data 2024-08-15 20:48:21 -06:00
Intelli c6f8be9043 Fixed NoClassDefFoundError if JNA is missing on system 2024-08-15 19:55:21 -06:00
Intelli 1cf940e201 Improved MySQL index handling on radius lookups (fixes #599) 2024-08-15 19:23:39 -06:00
Intelli d2bffb2103 Fixed errors pertaining the CPU lib on startup on some systems (fixes #591) 2024-08-15 19:04:31 -06:00
Intelli 64eedb0aa3 Added "include" parameter to purge tab completion 2024-08-15 18:28:02 -06:00
Intelli 8ab582d6d1 Fixed old turtle scute item data displaying as air 2024-08-15 17:51:03 -06:00
Intelli 3194c35bb9 Fixed purge include parameter not functioning correctly for SQLite databases 2024-08-13 20:11:16 -06:00
Intelli 888df067f9 Fixed Villager type not being rolled back correctly in MC 1.21+ 2024-07-25 16:40:36 -06:00
Intelli fda87e2f6a Updated language file 2024-07-24 14:27:49 -06:00
Intelli 9aea18749d Fixed backwards compatibility with entity serialization 2024-07-24 14:04:46 -06:00
Intelli 06208a69b0 Added logging for skull skin textures on Paper servers 2024-07-23 20:09:05 -06:00
Intelli a93bf2f047 Fixed NotSerializableException when killing certain entities (fixes #589) 2024-07-23 20:06:18 -06:00
Intelli 77a4a9476a Hide all errors regarding CPU info (fixes #591) 2024-07-23 13:21:46 -06:00
ac0792b9ef Update de.yml (#518)
* Update de.yml

some changes for the lang file

* Improve /co help inspect translations

Co-authored-by: Daniel Scherf <35780068+dlsf@users.noreply.github.com>

* Formatting fixes

Co-authored-by: Daniel Scherf <35780068+dlsf@users.noreply.github.com>

* Update lang/de.yml

Co-authored-by: Daniel Scherf <35780068+dlsf@users.noreply.github.com>

* Update lang/de.yml

Co-authored-by: Daniel Scherf <35780068+dlsf@users.noreply.github.com>

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
Co-authored-by: Daniel Scherf <35780068+dlsf@users.noreply.github.com>
2024-07-23 13:05:05 -06:00
Intelli da227afe33 Added primary thread error when running a rollback via API (implements #456) 2024-07-09 18:59:16 -06:00
Intelli 4bf4f4eb92 Prepped logging for skull skin textures on Paper servers 2024-07-09 18:51:46 -06:00
Intelli dbd8723752 Added CPU & RAM information to /co status 2024-07-09 18:02:49 -06:00
Intelli e093970992 Fixed inventory sorting mods generating unnecessary threads 2024-07-08 19:54:06 -06:00
jd07159 63074715d3 Improve Korean translation (#566) 2024-07-08 13:32:41 -06:00
Intelli 24a09b49d2 Added base Korean translation file 2024-07-08 11:42:08 -06:00
Intelli d4cc7ec729 Hide item serialization errors unless using development branch 2024-07-06 12:07:31 -06:00
Intelli 4af95632d5 Fix inventory drag event not properly checking if a player is dragging within their own inventory 2024-07-06 12:03:54 -06:00
Intelli c67e69755d Fixed IllegalStateException error on some servers during server shutdowns. 2024-07-06 11:45:32 -06:00
Intelli ce8c194e0d Fixed IncompatibleClassChangeError on inventory transactions (fixes #571) 2024-06-24 17:02:01 -06:00
Intelli 40e7f04774 Added support for logging Crafter transactions (#565) 2024-06-18 16:10:09 -06:00
Intelli fa1de08c0d Use proper adapter for EntityType.END_CRYSTAL 2024-06-18 16:04:35 -06:00
Intelli 49eb176ee6 Fixed invalid method name from previous commit 2024-06-17 18:52:25 -06:00
Intelli aaa7a3536a Hide item deserialization errors unless using development branch (fixes #550) 2024-06-17 18:49:43 -06:00
Intelli 957bf70c26 Hide entity deserialization errors unless using development branch (fixes #552) 2024-06-17 18:44:46 -06:00
Intelli 3d32320a33 Added support for MC 1.21 block/entity types 2024-06-17 18:31:33 -06:00
Intelli 3e8083becf Fixed wind charges incorrectly logging false #explosion data (fixes #555) 2024-06-17 18:06:04 -06:00
Intelli 057a635030 Added logging for dispensers removing blocks from the world 2024-06-17 17:51:01 -06:00
Intelli aa2ee7225c Added logging for droppers adding items to other containers 2024-06-17 17:34:18 -06:00
Intelli 37fc9af300 Added MC 1.21 support 2024-06-17 17:30:18 -06:00
Intelli 371e496351 Changed "#endercrystal" to now log as "#end_crystal" 2024-06-17 15:56:58 -06:00
Intelli e781d9ef01 Skip namespace remapping on Paper 1.20.5+ 2024-06-17 15:00:33 -06:00
Preva1l d2edaceae7 Change Folia check to be compatible with ShreddedPaper (#551)
ShreddedPaper is fully compatible with all folia plugins if don't check if its folia, BUT we have an empty class for RegionizedServer as MOST plugins will check for this, please be like everyone else!
2024-06-08 11:16:06 -06:00
Intelli ca59ff25df Changed "#endercrystal" to now log as "#ender_crystal" 2024-05-27 13:46:59 -06:00
Intelli cfb53f766e Fixed NullPointerException on Mohist servers when processing inventory transactions 2024-05-19 12:36:38 -06:00
lukyn76 e7c5078035 Fix typo (#543) 2024-05-15 10:15:18 -06:00
Intelli 5242973fe8 Fixed NullPointerException when hopper destination block is missing 2024-05-14 16:26:34 -06:00
Intelli e99a4a86d0 Update build version 2024-05-13 17:47:50 -06:00
Intelli 48b1fe22d3 Set Maven to build against Java 21 2024-05-13 17:46:16 -06:00
Intelli 29225a4a59 Set current API documentation version as v10 2024-05-13 17:43:49 -06:00
Intelli e6557ab5f3 Added #button, #container, #door, and #pressure_plate tags 2024-05-13 17:21:14 -06:00
Intelli 5859958ec2 Simplified tag handling 2024-05-13 17:16:17 -06:00
Intelli 5bc28b3e05 Added ability to specify specific block types in the purge command 2024-05-13 16:13:55 -06:00
Intelli b41e40acc0 Minor refactoring on Rollback class 2024-05-12 12:16:48 -06:00
Intelli 5a49ef8447 Cleanup 2024-05-12 11:47:25 -06:00
TelesphoreoandIntelli 42af5dc7a1 Fix FAWE logging on newer builds (#528)
* Fix FAWE logging on newer builds

* fix regular WE logging

* Fix registering of FAWE for event bus

* Remove jcenter

* Add missing `replaceBlocks` method

* Remove FAWE toggle entirely

* oops, i can't do it that way

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2024-05-12 11:43:36 -06:00
Intelli dbb44ab5b9 Only validate skull owner name length if using MySQL 2024-05-11 15:09:50 -06:00
Intelli d18a023e73 Fixed MysqlDataTruncation error when logging custom skull data 2024-05-11 15:08:16 -06:00
Intelli 377a6f55b9 Added #shulker_box tag to target all shulker box types (#493) 2024-05-11 13:16:58 -06:00
Intelli e61b8a4d9b Fixed unnecessary database transactions when consumer is empty (fixes #511) 2024-05-11 13:06:29 -06:00
Intelli 3b9d8726d9 Fixed obfuscation in item names making tooltips unreadable (fixes #487) 2024-05-09 22:44:29 -06:00
Intelli 48d2030766 Fixed NoSuchMethodError for arrows on MC 1.20/1.20.1 2024-05-09 12:25:57 -06:00
Intelli 86ace6c326 Added required version for disabling block logging in documentation 2024-05-09 12:08:43 -06:00
Intelli 346b4b8b20 Fixed occasional NullPointerException while processing hopper transactions (fixes #539) 2024-05-08 13:18:42 -06:00
Intelli 704f0aaf9b Update build version 2024-05-07 17:51:02 -06:00
Intelli 4daaf6cc02 Fixed lookups on legacy data not matching against legacy materials 2024-05-07 14:18:43 -06:00
Intelli 5426e69fb8 Improved hopper logging to prevent infinite attempts on failed pushes
E.g. when a hopper is repeatedly trying to push into a smoker, but no items are actually being transferred
2024-05-07 14:08:59 -06:00
Intelli 6023c21ba9 Fixed hopper pulls performing container validation on source block 2024-05-06 19:19:48 -06:00
Intelli 3e3496ad12 Corrected version references in API v10 documentation 2024-05-06 18:18:56 -06:00
Intelli f3c631bbf5 Added thread-safe logPlacement and logRemoval API methods 2024-05-03 12:57:27 -06:00
Intelli d8ad48beb8 Fixed entity deaths not always being logged in MC 1.19 and earlier (fixes #515) 2024-05-02 13:03:53 -06:00
Intelli 053f7d5c7d Fixed NoSuchMethodError when shooting or dropping arrows (fixes #535) 2024-05-02 12:52:51 -06:00
Nahuel Dolores a4c37da8ee Ditch weak map world references and use names instead (#533) 2024-04-25 14:05:48 -06:00
Intelli b1598a022a Fixed broken commit 32688c5 2024-04-21 14:08:37 -06:00
Intelli 32688c55cf Fixed block spread cache not always being properly utilized 2024-04-19 19:17:35 -06:00
Intelli a239b51ed1 Fixed teleports not working on some systems (fixes #491) 2024-02-07 12:49:36 -07:00
Intelli af1d4402eb Fixed hopper transactions not always being logged (fixes #490) 2024-01-24 17:44:18 -07:00
Intelli f7fea2b298 Added more detailed entity death reasons (such as suffocation). 2024-01-04 20:57:35 -07:00
Intelli 15e8cd5b88 Fixed zombies killed by wither roses not being logged when skip-generic-data is enabled (fixes #309) 2024-01-04 20:56:21 -07:00
Intelli c91f460e42 Fixed ConcurrentModificationException while using queueLookup API method (fixes #481) 2024-01-04 20:23:05 -07:00
Intelli 7424037795 Fixed teleport shortcut not properly handling extremely large numbers 2024-01-04 20:09:38 -07:00
Intelli ba6a55fff6 Fixed NullPointerException when rolling back player heads (fixes #473) 2024-01-04 19:48:31 -07:00
Intelli 1c57ba52aa Fixed item frames logging incorrect item amounts (fixes #477) 2024-01-04 18:23:53 -07:00
Intelli 4308d4afca Updated documentation for blacklist.txt 2024-01-04 18:09:20 -07:00
Intelli 7c61a33571 Added ability to exclude specific block types in blacklist (implements #444) 2024-01-04 17:56:30 -07:00
Intelli 172c6440db Added container transaction logging for decorated pots 2024-01-04 17:44:46 -07:00
Intelli d8a669ed6c Fixed legacy short grass data rolling back as air in MC 1.20.3+ 2024-01-04 16:45:36 -07:00
Intelli 66b77ee75a Improved safety block handling during rollback teleports (#328) 2023-10-12 11:28:53 -06:00
Intelli 488392cdbc Added missing new method to WorldEditBlockState 2023-10-12 10:50:16 -06:00
Intelli b48dcec589 CoreProtect v22.2 release 2023-09-25 14:36:37 -06:00
Intelli e49913bc09 Fixed item names containing vertical bars displaying incorrectly (fixes #450) 2023-09-25 14:27:02 -06:00
Intelli d944c2bd89 Fixed SQLFeatureNotSupportedException when logging entity data (fixes #455) 2023-09-25 14:04:50 -06:00
Intelli f76b0d45bd Clear entityBlockMapper cache after 5 seconds 2023-09-22 15:51:38 -06:00
Intelli 9f3196dc97 Fixed rollbacks not checking world when teleporting players. (Fixes #453) 2023-09-18 15:46:33 -06:00
Intelli 69c163468f CoreProtect v22.1 release 2023-08-29 15:34:24 -06:00
Intelli 34421b64f7 Check that using CampfireStartEvent before comparing campfire types 2023-08-23 17:08:20 -06:00
Intelli 0edf160f43 Added hidden "enable-ssl" database option (default: false) 2023-08-23 16:31:40 -06:00
Intelli 45d4642086 Fixed tree growth still being logged when disabled via config 2023-08-23 16:21:37 -06:00
Take-John af35a98b1d Fix CoreProtect API hasPlaced() and hasRemoved() (#434) (#435) 2023-08-22 17:54:42 -06:00
Take-John 950cf9460b Add clause "implements Cancellable" (#431) 2023-08-22 17:52:41 -06:00
Intelli 1b6d2ec667 Fixed dropper transactions not being logged when using Paper 2023-08-08 16:57:51 -06:00
Intelli d7910262be Added support for displaying lore in item tooltips (#420) 2023-08-08 15:46:45 -06:00
Intelli 8459181f77 Fixed IndexOutOfBoundsException when modifying signs on Paper 1.17-1.19 2023-08-01 17:49:55 -06:00
Intelli 58f4a76d75 CoreProtect v22.0 release 2023-07-31 18:39:57 -06:00
Intelli a46deeb6ba Fixed typo in API documentation 2023-07-28 15:42:40 -06:00
Jeff P 7fd50a0507 Add method override for FAWE logging (#417) 2023-07-24 13:48:42 -06:00
Intelli 9e1d5af97d Fixed NullPointerException while logging container transactions 2023-07-19 17:12:00 -06:00
ianiiaannn 4cce566e37 Update Traditional Chinese translation (#392)
* Update Phrase to make strings correctly split by fullwidth colons (U+FF1A)

* Fix Traditional Chinese translation

* Replace U+FF1A with `: `
2023-07-19 14:35:25 -06:00
Intelli 703c36f356 Added automatic forward compatibility for extended logging of new wood types 2023-07-17 18:41:05 -06:00
Intelli 1dd293c5a4 Renamed "mysql-maximum-pool-size" to "maximum-pool-size" 2023-07-17 17:33:57 -06:00
1331b8d0c5 Added hidden "unknown-user" logging option (default: false) (#397)
* Allow unknown user logging (attempt to patch a bug of tnt splashing blocks and it not being logged)

* Changed "unknown-logging" to be hidden (default: false)

* Update BlockFormListener.java

---------

Co-authored-by: Radiant <i.like.using.discord@gmail.com>
Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2023-07-17 17:25:35 -06:00
violetcandIntelli 2476aa2ea8 Utilize internal cache in StructureGrowthProcess (#371)
* Use whoPlacedCache

* Still perform database lookup if cache is empty

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2023-07-17 16:55:16 -06:00
Keno MedenbachandIntelli 6045799fea Make MySQL maximumPoolSize configurable (#411)
* Make MySQL maximumPoolSize configurable

* Correctly assign default value to mysql-maximum-pool-size

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2023-07-17 16:47:52 -06:00
Intelli 342fadd0f6 Fixed invalid config values defaulting to zero rather than default value 2023-07-17 16:42:12 -06:00
Intelli e81c84671c Update CONTRIBUTING.md 2023-07-15 12:45:59 -06:00
Intelli e501046073 Fixed sign edits being logged even if no changes were made 2023-07-14 15:27:30 -06:00
Intelli c6a0f20fff Improved logic for logging brushing of suspicious blocks 2023-07-14 14:59:18 -06:00
Intelli 05aebe7544 Added logging for brushing suspicious blocks (implement #409) 2023-07-14 14:29:36 -06:00
Intelli 36ad62007f Added missing break keywords in BukkitAdapter 2023-07-13 19:44:04 -06:00
Intelli bdde2d36be Fixed signs using SpigotAdapter instead of BukkitAdapter 2023-07-13 19:41:51 -06:00
Intelli f32a3e7c5e Fixed Folia teleportation not using adapter 2023-07-13 19:27:53 -06:00
Intelli c7cf035564 Check that CampfireStartEvent exists before registering 2023-07-13 19:27:03 -06:00
Intelli d4997cca60 Added extended support for new cherry/bamboo blocks 2023-07-13 19:10:48 -06:00
Intelli 355eea1cf9 @kugge: Fixed Folia teleportation in unloaded chunks (#368) 2023-07-13 18:35:49 -06:00
Intelli d7b76135c8 Reduced MySQL connection lifetime from 5m to 1m (fix #362) 2023-07-13 18:07:15 -06:00
Intelli 1a06e6a6bf Made PreLogEvent cancellable (implements #325) 2023-07-13 17:55:00 -06:00
Intelli e7a6f21d88 Added inventory transaction logging for placing items on a campfire (#357) 2023-07-13 17:28:14 -06:00
Intelli 5973206bb3 Added interaction logging for chiseled bookshelves 2023-07-13 15:51:04 -06:00
Intelli 1e007c98a4 Cleanup 2023-07-13 14:43:09 -06:00
Intelli df9b478be7 Split up MySQL upgrade query to resolve issues from interrupted queries 2023-07-13 13:56:27 -06:00
Intelli 054beb700b Fixed ConcurrentModificationException in ProjectileLaunchEvent 2023-07-13 13:49:13 -06:00
Intelli 47a000a57f Added support for hanging signs 2023-07-13 13:06:47 -06:00
Intelli cde18dc529 Update formatting 2023-07-12 16:33:50 -06:00
asdanjer d200616fe1 Improved performance of ItemStack merging (fix #385) 2023-07-12 16:29:22 -06:00
Intelli e0d65453b6 Added logging for destruction of suspicious sand/gravel 2023-07-12 16:26:38 -06:00
Intelli d414db1c0b Added item mapping for torchflower seeds 2023-07-12 15:58:47 -06:00
Intelli b0469924a7 Build project against Folia 1.20.1 2023-07-12 15:19:47 -06:00
Intelli 4e43a42662 Added logging and rollback support for double sided signs 2023-07-12 15:17:14 -06:00
Intelli 7e97e12969 Added inventory transaction logging for chiseled bookshelves 2023-07-08 14:55:24 -06:00
Intelli d7d53bf9e2 Fixed NullPointerException while performing rollbacks 2023-07-08 13:57:36 -06:00
Intelli b9309482cf Fix config typo 2023-07-08 13:52:00 -06:00
Intelli 410eba5559 Fixed Folia detection 2023-06-19 18:59:58 -06:00
ByteExceptionMandIntelli 814ae4d9d5 Fixed NullPointerException when breaking/placing a spawner (#380)
* fix: Make Util#getEntityId parameter nullable

* Removed JetBrains Nullable

* Updated to follow contribution guidelines

---------

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2023-06-13 17:14:09 -06:00
Intelli d64ec8d911 Updated HikariCP dependency from 4.0.3 to 5.0.1 2023-05-21 15:42:28 -06:00
Intelli 5d90f25211 Fixed logging and lookup issues after performing "/co reload" (fix #366) 2023-05-05 16:47:56 -06:00
Intelli 7825f61e45 Fixed NullPointerException when processing transactions for dyed armor 2023-04-09 13:19:02 -06:00
Intelli 7e49c95832 Fixed InventoryClickEvent exception when using Magma 2023-04-08 17:38:31 -06:00
Intelli 4b9e69c40f Fixed tree/mushroom growth not respecting config file when using bonemeal (...)
Fixed mushroom growth logging invalid data when using bonemeal
2023-04-06 13:55:57 -06:00
Sofiane H. Djerbi f2934fd876 Fix Folia Teleportation (#349) 2023-04-03 18:15:16 -06:00
Intelli f706bcf38e Added repeating task support to Scheduler 2023-03-31 16:43:27 -06:00
Intelli fe6023450c Added missing methods to WorldEditBlockState 2023-03-29 19:11:48 -06:00
Intelli ace870b930 Fixed item frame & armor stand logging under Folia 2023-03-24 18:10:11 -06:00
Intelli 7170e29452 Added support for Folia 2023-03-23 19:45:44 -06:00
Intelli 29345e2fb1 Use HumanEntity instead of Player when logging item crafting (fix #336) 2023-03-13 13:32:22 -06:00
Amir a22db638fa Updated examples in commands.md (#323)
When I first looked at the documentation, I did not recognize that you can enter such values ​​in the <user> field. Thought I needed to clarify that this is possible.
2023-02-01 12:42:00 -07:00
Intelli de3c326203 Corrected Spanish translation 2023-01-12 12:51:42 -07:00
Intelli acb71a83a2 Fixed performLookup in API not returning valid block data (#308) 2023-01-03 12:17:09 -07:00
Intelli 3ffed30b1e Updated exclusions (Maven/Gradle) 2022-12-21 19:56:32 -07:00
Intelli 5d6fd60d40 Added tooltips to lookups that display item names and enchantments (#67) 2022-12-21 19:11:34 -07:00
Intelli 20710a00de Added legacy component serialization support for signs in Paper 1.17+ 2022-12-20 14:37:22 -07:00
Intelli 5eabee6504 Added logging for dragon egg teleportation (#303) 2022-12-19 16:05:02 -07:00
Intelli 97eb0d777b Fixed incorrect amounts when dispensing items in Paper 1.19.3+ 2022-12-19 14:57:46 -07:00
Intelli 61847f2f8d Changed minimum required version to Spigot 1.15 2022-12-15 16:45:24 -07:00
Intelli 12f2000085 Fixed sign lookups displaying incorrectly when containing hex colors 2022-12-15 16:06:41 -07:00
Intelli 66e32c02bf Added player interaction logging for jukeboxes 2022-12-15 15:36:19 -07:00
Intelli 9da1343f7f Add logging and rollback support for jukebox transactions 2022-12-15 15:25:53 -07:00
Intelli 272ecae858 Fixed networking API connections being reset when reloading config
Credit: https://github.com/PlayPro/CoreProtect/pull/211/files#diff-1342ad937bb4d40012db93a64c02a41e92f2d6f35b069435f464af03aca69b00 (@vacla)
2022-12-10 14:12:33 -07:00
Intelli 3db2159292 Updated versions listed in README 2022-12-09 18:51:04 -07:00
Intelli 78ef45d049 CoreProtect v21.3 release 2022-12-09 14:55:54 -07:00
Intelli 2f28183261 Fixed NoSuchMethodErrorr when stopping server using Paper 1.14 2022-12-08 17:55:30 -07:00
Intelli d82f92e06b Fixed tab completion not fully respecting player permissions (#226) 2022-12-08 17:35:03 -07:00
Intelli ce5c1c50bf Added logging for player inventory transactions with allays 2022-12-08 16:24:53 -07:00
Intelli 026b9ce322 Fixed IllegalStateException when modifying containers using FAWE 2022-12-08 13:56:14 -07:00
Intelli 3cedc69205 Added extended support for mangrove blocks 2022-12-07 19:52:54 -07:00
Intelli f4fe410141 Added logging and rollback support for tadpole age data 2022-12-07 19:14:44 -07:00
Intelli e3b82858b4 Updated config description regarding donation keys 2022-12-07 18:56:18 -07:00
Intelli 990f90bab9 Rename #sculk to #sculk_catalyst 2022-12-07 18:38:40 -07:00
Ryan HustonandIntelli 3b349c9d07 Add support for 1.19 specific features. (#239)
* Added support for 1.19 specific features.

* Removed leftover import from testing

* Added missing sculk materials for the catalyst and changed user field to #sculk

* Reverted version to v21.2

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2022-12-07 18:26:45 -07:00
Intelli 319d104622 Fixed NullPointerException when killing NPC (#261) 2022-12-07 17:49:50 -07:00
Intelli 9f8c2e8a2f Set Hikari maxLifetime to 5m, keepaliveTime to 1m (fixes #199) 2022-09-26 15:22:07 -06:00
Intelli fcd789ef5b Bump mkdocs from 1.2.3 to 1.3.1 2022-09-09 16:01:48 -06:00
dkim19375 b450f4d05a Make version comparisons by long - Support WorldEdit 7.2.11 Beta 1 (#240) (Fixes #245) 2022-06-27 20:15:23 -06:00
Intelli 606c043bf0 Fixed NullPointerException when processing hopper transactions 2022-04-19 20:07:56 -06:00
Intelli 4b056d9474 Added default Turkish language file 2022-04-19 20:00:53 -06:00
Intelli 1cca7bc573 Updated de.yml (fixes #203) 2022-04-13 19:41:18 -06:00
Intelli 204456d6c5 Fixed queueLookup API method returning data in wrong order 2022-03-25 16:50:05 -06:00
Intelli d34b60dc8f Improved database index handling 2022-03-24 19:16:57 -06:00
Intelli 070c5139e1 Fixed IllegalArgumentException when processing corrupt data 2022-03-24 17:11:58 -06:00
流光溢彩Last 1974838f74 Update v21.0 Simplified Chinese translation. (#182)
* Update v21.0 Simplified Chinese translation.

* 完善翻译
2022-03-23 19:08:47 -06:00
Intelli acdfb3515b Fixed NullPointerException on disable if plugin failed to load 2022-03-23 18:56:53 -06:00
vaclaandIntelli 8cdf99429d Added Networking API (#180)
* sent data to client

* send more data + debug + add docs + switch to handshake

* add diff between container and normal changes

* fix docs

* changes according guidelines

* make requested changes + cleanup

* correct documentation

* Improved Networking API documentation

* Moved networking API channel registration out of main class

* Cleanup

* Removed debug on chat/command lookups

* Optimized array merging

* Fixed ClassCastException when running console commands

* Skip preparing networking data if not a channel player

* Renamed "networking-debug" to "network-debug"

* Fixed failed handshake still registering players

* Adjusted networking phrases

* Renamed pluginchannel to channel

Co-authored-by: Intelli <contact@intelli.software>
2022-03-23 18:49:46 -06:00
Intelli e848d65b93 Added missing command/permissions to documentation 2022-03-22 17:24:54 -06:00
Intelli 85ebd53e5f Minor Spanish tweaks 2022-03-21 18:48:40 -06:00
KrazyxWolfandIntelli 209c63b009 Improved Spanish translation (#179)
* Not too literal spanish

* Fixes and adjustments

* Fix

* Minor fixes

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2022-03-21 18:21:08 -06:00
Intelli 8fd41ac231 CoreProtect v21.2 release 2022-03-21 16:45:42 -06:00
Intelli 703d52bcc7 Changed inventory lookups to always require a username 2022-03-21 16:42:30 -06:00
Intelli aea0c82fd7 Fixed typos in API documentation 2022-03-21 15:19:16 -06:00
Intelli 01ed2e9f97 Fixed NullPointerException when logging player projectile data 2022-03-21 15:12:58 -06:00
Intelli 5861570f8a Added post-rollback sorting for player inventories 2022-03-20 17:34:24 -06:00
Intelli 840eb3b4ee Added hidden "disable-wal" database option (default: false) 2022-03-20 16:37:20 -06:00
Intelli 92e030f3e8 Fixed WAL checkpointing not running on some systems 2022-03-20 16:29:51 -06:00
Intelli d97f5482ee Fixed rollbacks/restores not correctly updating all blocks (#175) 2022-03-20 16:03:54 -06:00
Intelli 2763952dc6 Fixed deprecated display width warnings for MySQL databases 2022-03-11 18:16:16 -07:00
Intelli 3011aece46 Fixed inventory rollbacks not modifying player equipment slots (#170) 2022-03-11 17:20:52 -07:00
Intelli dedba3c57f Added command example to blacklist documentation 2022-03-10 16:42:13 -07:00
Intelli 75f66bf212 CoreProtect v21.1 release 2022-03-10 16:41:54 -07:00
Intelli 64106e2d64 Fixed "user not found" error when performing inventory lookups/rollbacks 2022-03-09 15:32:18 -07:00
Intelli 975a030ebe Fixed NullPointerException when trading with wandering traders 2022-03-08 13:59:19 -07:00
Intelli 829f4e95d5 Fixed exploited end crystals logging phantom fire data (#164) 2022-03-08 11:59:39 -07:00
Intelli 96292c82c3 Added example inventory lookup/rollback commands to docs 2022-03-08 11:10:36 -07:00
Intelli dad51fc900 Removed deprecated item durability and art ID method usage 2022-03-08 08:42:06 -07:00
Intelli 40ee11f644 CoreProtect v21.0 release 2022-03-08 08:39:38 -07:00
Intelli 02dcc873d6 Added hidden "exclude-tnt" rollback option to exclude TNT blocks (default: false) 2022-03-07 16:39:21 -07:00
Intelli 2b96daa422 Change items crafted at crafting tables to log at table location 2022-03-07 15:25:27 -07:00
Intelli d472879dec Added interaction logging for crafting tables, enchanting tables, smithing tables, and stonecutters 2022-03-07 15:16:03 -07:00
EEstarandIntelli dbcf79cd46 Add CoreProtectTNT to tools-integrations.md (#163)
* Update tools-integrations.md

* Updated CoreProtect TNT description

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2022-03-07 14:26:09 -07:00
Intelli c69ea79329 Fixed rollbacks not skipping processing for identical blocks 2022-03-06 17:05:18 -07:00
Intelli 83fc221bf2 Added logging for items traded with villagers (MC 1.18+) 2022-03-03 15:22:37 -07:00
Intelli 7bc95931d2 Added hidden "enable-awe" option for AWE logging (default: false) 2022-03-02 16:24:56 -07:00
Intelli 2fa5af64ca Updated Gradle build file to match Maven 2022-03-02 13:14:30 -07:00
Intelli 1be82111ed Changed entities killed via /kill command to log under #command 2022-03-01 20:34:12 -07:00
Intelli a6869176be Minor refactoring in EntityDeathListener 2022-03-01 20:17:12 -07:00
Intelli 7c7fd200d2 Fixed all listener classes not being final 2022-03-01 20:05:13 -07:00
Intelli 6714ded600 Improved logging to skip unnecessary data from newly generated chunks 2022-03-01 17:41:46 -07:00
Intelli 6a22903240 Re-enabled BlocksHub & disabled logging integration with AWE 2022-03-01 16:49:22 -07:00
Intelli 0bbeee65e8 Added support for time ranges in the time parameter 2022-03-01 15:58:15 -07:00
Intelli a596dd66a9 Blocked access to BlocksHub to prevent duplicate data from being logged 2022-02-28 18:49:20 -07:00
Intelli 1aaff4e2b0 Enabled write-ahead logging for SQLite 2022-02-28 17:47:07 -07:00
Intelli 1c476408e9 Fixed rare instances of lookups displaying negative time values 2022-02-28 17:11:24 -07:00
Intelli 5530a89189 Improved index handling when targeting a specific location 2022-02-27 17:19:37 -07:00
Intelli c69a5a58c0 Fixed instances of NullPointerException in CraftItemEvent 2022-02-27 16:29:17 -07:00
Intelli f0702339f2 Migrated language code documentation to docs.coreprotect.net 2022-02-27 16:17:44 -07:00
Intelli 3774e6748d Improved Spanish translation (implements #161) 2022-02-27 15:59:04 -07:00
Intelli 596a39c965 Fixed all firework metadata not being correctly logged 2022-02-25 19:34:23 -07:00
Intelli 42c6deff8a Added logging and inventory rollback support for throwing snowballs and lingering potions 2022-02-25 19:23:53 -07:00
Intelli f5d13f8818 Fixed backwards compatibility with older server software/versions 2022-02-25 19:16:06 -07:00
Intelli 659fe649ed Changed minimum required Java version to Java 11 2022-02-25 16:53:05 -07:00
Intelli 9bdac6db4d Added entity counts to #preview confirmation page 2022-02-25 16:33:41 -07:00
Intelli 42233f1f72 Fixed phantom fire blocks being logged adjacent to lava 2022-02-25 16:14:35 -07:00
Intelli 900cad643f Fixed inventory lookups not returning correct item for planted seeds 2022-02-24 20:52:30 -07:00
Intelli ddb804ce99 Fixed farmland displaying in inventory rollbacks 2022-02-24 20:15:48 -07:00
Intelli 45f9f4316e Added logging and inventory rollback support for players breaking items 2022-02-24 20:03:28 -07:00
Intelli 5494bce0fc Added logging and rollback support for players crafting items 2022-02-24 19:52:19 -07:00
Intelli 30c1921968 Fixed inventory rollbacks not restoring shulker boxes with items 2022-02-23 19:36:08 -07:00
Intelli 262f27da3e Changed doors and beds to no longer log data as two separate blocks 2022-02-23 18:56:00 -07:00
Intelli f9083053b2 Fixed rolling back double blocks dropping an item in the world 2022-02-23 17:27:47 -07:00
Intelli 3b93046731 Fixed ClassCastException when rolling back banner inventory transactions 2022-02-22 21:41:11 -07:00
Intelli 4445c3c4a7 Fixed inventory rollbacks not always returning correct item for placed blocks 2022-02-22 21:15:45 -07:00
Intelli c95bf3cc30 Fixed fire/water displaying in inventory lookups 2022-02-22 20:10:55 -07:00
Intelli 2a8e4f9797 Fixed "/co undo" not functioning when targeting a container 2022-02-22 19:49:02 -07:00
Intelli f80c6ab5cd Fixed IllegalArgumentException when performing rollbacks on armor stand contents 2022-02-22 19:40:39 -07:00
Intelli b96f47cbac Added logging and inventory rollback support for thrown/shot items 2022-02-22 19:26:17 -07:00
Intelli 0d7fa2dd59 Added logging for players picking up arrows and tridents 2022-02-21 18:54:50 -07:00
Intelli a238045287 Moved BlockLookupAPI to net.coreprotect.api.BlockAPI 2022-02-21 18:22:55 -07:00
Intelli 167b8dfabd Display correct error message if a user isn't specified on an inventory rollback 2022-02-21 18:12:43 -07:00
Intelli d365eb498b Re-sort language file after upgrading 2022-02-18 20:47:18 -07:00
Intelli 585d8b275a Fixed inventory lookups/rollbacks missing block placement actions 2022-02-18 20:29:45 -07:00
Intelli 527be90249 Fixed item frame transactions not logging BlockFace data 2022-02-18 17:31:35 -07:00
Intelli c652ce852f Added interaction logging for players clicking item frames 2022-02-17 21:10:49 -07:00
Intelli fb79071ee7 Added container transaction support for item frames (...)
Fixed ClassCastException when targeting an armor stand with #container
2022-02-17 20:56:20 -07:00
Intelli e462e68806 Fixed item frames and paintings not logging directional data 2022-02-16 19:16:56 -07:00
Intelli 7398f7dfe6 Added "/co consumer <pause|resume>" console command 2022-02-16 16:09:43 -07:00
Intelli eb5b3b5bcc Added "inspector" as alias for inspect command 2022-02-16 15:01:20 -07:00
Intelli abfecda8d0 Added queueLookup method to API 2022-02-15 21:29:33 -07:00
Intelli c5d5308753 Minor cleanup in SessionLookup class 2022-02-15 20:20:46 -07:00
Intelli 50689604dd Renamed api/versions to api/version 2022-02-15 19:59:56 -07:00
Intelli 25b03511c2 Added sessionLookup method to API 2022-02-15 19:59:25 -07:00
Intelli d69d762bf4 Added API v7 documentation 2022-02-15 17:45:32 -07:00
Intelli d9eea32b19 Improved WorldEdit logging and added compatibility with FAWE 2022-02-15 16:47:50 -07:00
Intelli a2353afd0a Added inventory rollback support for online players 2022-02-11 21:45:33 -07:00
Intelli 29d55e6ba9 Fixed blocks placing when using shift+click to inspect trapdoors (#134) 2022-02-10 21:08:11 -07:00
Intelli 21eefff53f Added hidden "log-cancelled-chat" option (default: true) (#154) 2022-02-10 20:49:10 -07:00
Intelli b1c2795289 Fixed "coreprotect.inspect" permission not being checked at the time of inspection (fixes #133) 2022-02-10 20:36:38 -07:00
Intelli fdb98bd329 Fixed missing safety checks when inspecting certain blocks 2022-02-10 20:33:12 -07:00
Intelli dc359a1be3 Added "+container" and "-container" to tab-complete 2022-02-10 19:59:44 -07:00
Intelli d5c2b908d3 Fixed a:+/-inventory displaying mixed results 2022-02-10 19:57:30 -07:00
Intelli 54da739c1d Inverted action tags for deposits/withdraws 2022-02-10 19:36:23 -07:00
Intelli 01e6351194 Fixed "drop/pickup/deposit/withdraw" actions being incorrectly mapped 2022-02-10 19:26:27 -07:00
Intelli 855a2e5ae2 Renamed "a:inventory" to "a:item" in lookups 2022-02-10 19:21:30 -07:00
Intelli 971de8c078 Changed a:inventory to only display player inventory transactions 2022-02-10 19:18:32 -07:00
Intelli cd8ef0d273 Improved permission handling for "inventory" and "item" actions 2022-02-10 17:32:11 -07:00
Intelli 02afc6c530 Changed a:item to no longer include container transactions 2022-02-10 17:19:29 -07:00
Intelli 748490b8f0 Added "logins", "logouts", "blocks", and "signs" as new action aliases 2022-02-09 19:43:29 -07:00
Intelli 40561e41c0 Fixed player disconnections not being logged on server shutdown 2022-02-09 19:34:06 -07:00
Intelli 6a978128af Improved performance of rollback/restore previews 2022-02-09 18:14:46 -07:00
Intelli 635a2b0d95 Fixed IllegalArgumentException when performing a rollback preview containing an end crystal 2022-02-09 17:28:49 -07:00
Intelli ea69bf0dc5 UI improvements & action colours 2022-02-08 20:17:40 -07:00
Intelli 19efdeff89 Added clickable page numbers to lookup pagination 2022-02-08 19:25:07 -07:00
Intelli 673529df54 Fixed coordinates in lookups not always being properly aligned 2022-02-07 18:03:53 -07:00
Intelli 3a0c86a614 Fixed some lookups being slow when specifying an action 2022-02-07 17:39:33 -07:00
Intelli 70f74ced0f Added tooltips for clickable commands in lookup results 2022-02-03 20:36:27 -07:00
Intelli 2c8e289c43 Added "/co page" alias command for page lookups 2022-02-03 20:18:17 -07:00
Intelli b0d14f4d2b Fixed SQLiteException on lookups 2022-02-03 18:16:35 -07:00
Intelli 29d01a158a Added internal caching of table names & table counts 2022-02-02 21:19:06 -07:00
Intelli 67d9f2f832 Improved rollback speeds, now up to 2,000% faster 2022-01-31 21:49:44 -07:00
Intelli 591110cbb2 Improved lookup query performance 2022-01-31 20:03:07 -07:00
Intelli 286cf2afb9 Manually load SQLite JDBC driver 2022-01-19 15:11:11 -07:00
Intelli d9f5411e87 CoreProtect v20.4 release 2022-01-04 19:26:11 -07:00
Intelli 19ed0162e5 Fixed MySQL purges not being gracefully interrupted on shutdown 2022-01-04 19:19:08 -07:00
Intelli 03bd19480e Fixed SQLite purges not successfully completing on some systems 2022-01-04 17:55:52 -07:00
Intelli 947c59882e Fixed MySQL connections not being released when performing a reload 2022-01-04 16:49:57 -07:00
Intelli 8ed4216c64 Fixed SQLException when MySQL driver isn't automatically detected (#117) 2022-01-04 16:32:30 -07:00
Intelli 64188f5a75 Fixed the top block of bamboo growth not being logged (#129) 2022-01-04 15:58:09 -07:00
Intelli 55fec1d092 Moved entity & teleport methods out of Util class 2022-01-04 15:20:01 -07:00
Intelli c1eecefa6f Fixed radius selections not supporting negative heights 2021-12-21 15:31:08 -07:00
Intelli 204d6c8d5f Added base Japanese language file 2021-12-20 15:24:49 -07:00
Intelli fffe75f1f4 CoreProtect v20.3 release 2021-12-18 11:35:55 -07:00
Intelli 3201a18b16 Fixed PoolInitializationException when initializing HikariCP 2021-12-17 17:00:06 -07:00
Intelli 085e410ab1 Fixed NoClassDefFoundError when using Paper 1.16 (#120) 2021-12-17 11:31:04 -07:00
Intelli 4f977d394c Minor description update to documentation 2021-12-17 11:25:05 -07:00
Intelli a4c21ec8f3 Added MC 1.18 to adapters 2021-12-17 11:24:29 -07:00
Intelli d7ee5257dd Changed additional timestamp values to long types 2021-12-16 18:08:56 -07:00
Intelli 08aeb5d5aa Updated GitHub workflow to use Java 17 2021-12-16 15:25:38 -07:00
Intelli c1a4323ce4 Added base Ukrainian language file (#119) 2021-12-16 15:10:34 -07:00
Intelli a5d063dced CoreProtect v20.2 release 2021-12-16 14:01:43 -07:00
Intelli f0779d288c Incremented API version to 8 2021-12-16 14:01:23 -07:00
Intelli 550c3038b8 Added support for Paper AsyncChatEvent (fixes #75) 2021-11-30 19:11:19 -07:00
Intelli d864bbe233 Replaced commons-lang with Guava 2021-11-22 15:39:57 -07:00
Jan Erik Petersen fc99c24a0a Add HikariCP connection pool for MySQL (#86)
* Add HikariCP connection pool for MySQL

* Make code more compatible with connection pooling

- Remove connection caching in Process/Consumer
- Use try-with-resources to make sure Connections always get closed, even in
  when an Exception occurs.

* Disable SSL for MySQL
2021-11-15 17:57:33 -07:00
Intelli 2d90f9cc08 Fixed moving pistons logged during explosions not rolling back correctly (#92) 2021-11-09 16:42:48 -07:00
dependabot[bot] ba0ad6c15c Bump mkdocs from 1.2.1 to 1.2.3 (#108)
Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.2.1 to 1.2.3.
- [Release notes](https://github.com/mkdocs/mkdocs/releases)
- [Commits](https://github.com/mkdocs/mkdocs/compare/1.2.1...1.2.3)

---
updated-dependencies:
- dependency-name: mkdocs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-09 15:27:12 -07:00
Intelli 370558cec5 Fixed piston heads not being logged when the piston base is broken 2021-11-09 14:53:42 -07:00
Intelli 69184302c9 Fixed ClassCastException when logging chat and command messages 2021-11-04 13:59:48 -06:00
Intelli a61df070e6 Fixed InventoryMoveItemEvent still processing if both hopper & item transactions are disabled 2021-10-16 16:05:39 -06:00
Intelli 3742e018c4 Minor readability improvements in EntityDamageByBlockListener 2021-10-04 16:09:08 -06:00
Intelli 554e17df02 Use isAlive for monitoring consumer thread 2021-10-04 15:55:54 -06:00
Intelli bc39bc03d6 Fixed NullPointerException when logging player death item drops (fixes #71) 2021-08-22 14:25:26 -06:00
bermudalocketandIntelli da718de252 Make user mutable via CoreProtectPreLogEvent (#66)
* Make user mutable via CoreProtectPreLogEvent

* Cleanup & added missing toLowerCase in getId

* Docs: add Event category; add CoreProtectPreLogEvent

* Merged event documentation into API documentation

* Removed event.getUser() equality check

* Added IllegalArgumentException check within PreLogEvent

* Reverted docs/index.md

Co-authored-by: Intelli <contact@intelli.software>
2021-08-06 18:40:21 -06:00
Ghost_chu 39cd888440 Fixed sign message phrase in zh-cn.yml (#63)
It shouldn't translate to `Signature Message`, this commit fixed it to `Sign Message`
2021-08-03 13:07:25 -06:00
Intelli 697ad1b3ce Minor cleanup in adapters 2021-08-01 16:53:14 -06:00
Intelli 922e4478dc Improved hopper logging performance when using Paper
Disables snapshot creation when getting inventory holders
2021-08-01 16:42:07 -06:00
Intelli 2fd366389b Changed base dependency to Paper 2021-08-01 16:36:17 -06:00
Intelli 5e3a19f208 Added base Vietnamese language file 2021-07-27 18:45:52 -06:00
Intelli 37d49a0900 Added stale.yml configuration file 2021-07-27 15:10:14 -06:00
Intelli adef057590 CoreProtect v20.1 release 2021-07-27 14:16:20 -06:00
Intelli ac1726a1a5 Added base Polish language file 2021-07-27 13:20:29 -06:00
Intelli aeba1918f3 Changed name of "coreprotect.lookup.item" permission to "coreprotect.lookup.inventory" 2021-07-26 18:49:39 -06:00
Intelli 490b5eb0ee Removed unnecessary array length check 2021-07-26 18:09:17 -06:00
Intelli 40b52fa321 Changed all timestamp values utilized by the API to long types 2021-07-26 18:00:21 -06:00
Intelli 3c82dc337a Simplified regex when parsing server version 2021-07-26 17:56:30 -06:00
Intelli f6f9c27f08 Added private constructors to utility classes 2021-07-26 17:34:45 -06:00
a7eb6a48c9 Fix Traditional Chinese translation (#54)
* Fix Traditional Chinese translation 

Fixed some wrong usages and punctuations.

* Update zh-tw.yml

* Apply suggestions from code review

Co-authored-by: KID <jyhsu2000@yahoo.com.tw>

* Reverted colon punctuation marks to english characters

* Fixed spacing in zh-tw.yml

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
Co-authored-by: KID <jyhsu2000@yahoo.com.tw>
2021-07-26 14:32:02 -06:00
Intelli 40ff1e7778 Additional cleanup in ChestTool 2021-07-25 21:15:53 -06:00
Intelli e178ed91ca Fixed double chests not rolling back correctly when each half is in a separate chunk 2021-07-25 21:08:53 -06:00
div2005andIntelli 5d6388fff7 Improved Russian translation (#55)
* Update ru.yml

* Fixed ru.yml regressions

Co-authored-by: Intelli <6790859+Intelli@users.noreply.github.com>
2021-07-25 17:17:57 -06:00
Intelli d2614ed6dc Fixed risk of NullPointerException when logging container transactions 2021-07-25 16:53:58 -06:00
Intelli 130614bfdd Minor refactoring in PlayerInteractListener and BlockExplodeListener 2021-07-22 18:06:24 -06:00
Intelli ae3768695f Skip extra checks on furnaces when processing hopper transactions
This more aggressively aborts transaction logging to improve performance
2021-07-22 14:11:45 -06:00
Intelli 2f1c45cae1 Moved inventory checkTasks to directly before onInventoryInteract
This improves performance (allows unused threads to return faster), and resolves issues with pass-through transactions
2021-07-22 13:31:08 -06:00
Intelli 6fce87386d Fixed hopper transactions not always detecting invalid events 2021-07-22 13:17:40 -06:00
Intelli 3f0291e0d5 Fixed AssertionError when logging hopper transactions 2021-07-21 22:49:44 -06:00
Intelli 718d7d7a80 Fixed NullPointerException when logging container transactions 2021-07-21 19:01:32 -06:00
Intelli 45e1fee9ff Fixed hopper transaction logging not always logging all items 2021-07-21 18:48:04 -06:00
Intelli aa55472c1a Utilize item caching on invalid hopper events 2021-07-21 18:47:23 -06:00
Intelli bd98bdfe05 Added Gradle build folder to .gitignore 2021-07-21 14:31:22 -06:00
Intelli 38f1e304e0 Fixed entities not being excluded when using the "exclude" parameter 2021-07-20 15:00:10 -06:00
Intelli e68daef7db Added FUNDING file 2021-07-20 13:17:56 -06:00
流光溢彩 72bcadea55 Update Simplified Chinese Translation (#49)
* Update Simplified Chinese Translation

Changes:
- Replace all "." char to "。"
- Modify some strings to conform to Chinese usage habits

* Add spaces and units where necessary

* Replace all "您" chat to "你"

* Change "PREVIEW_CONTAINER" translation
2021-07-20 13:10:51 -06:00
Intelli b5b6acaa9d Minor refactoring in Util class 2021-07-19 17:19:35 -06:00
Intelli fae4c9e690 Fixed blocks broken by portal events not being logged (#45) 2021-07-19 16:29:43 -06:00
Intelli 03bac937c3 Minor refactoring in PortalCreateListener 2021-07-19 16:28:40 -06:00
Andrew 1d828e3b68 Improved ru translation (#51) 2021-07-19 13:59:28 -06:00
YuanYuanOwO c71ea224a4 Fix Simplified Chinese translation (#48)
* Fix Simplified Chinese translation

* Update zh-cn.yml
2021-07-17 18:25:28 -06:00
NatsuHana 7ea46fb585 improve(trans/zh-CN): optimize zh-CN translation (#44)
correct some sentences cause ambiguity
2021-07-17 12:39:37 -06:00
Intelli 2bc8a00657 Updated badges in README 2021-07-16 16:57:00 -06:00
Intelli b696abd655 Set workflow Java version back to 8 2021-07-16 16:54:53 -06:00
Intelli 5e2a99b2a8 Updated workflow & removed SonarCloud 2021-07-16 16:38:55 -06:00
HeroCC 79ad6de362 Update WorldEdit Maven Repo & use Java 11 (#43)
* Use HTTPS for sk89q maven repo

Newer Maven releases don't allow use of http endpoints. Bump this to the secure https endpoint.

* Migrate sk89q maven repo to enginehub

Both URLs point to the same place, but according to https://worldguard.enginehub.org/en/latest/developer/dependency/ enginehub is the preferred source.

* Bump Java to Adoptium Hotspot JDK 11
2021-07-16 16:25:24 -06:00
Intelli 3a1e5a356b Updated PR guidelines & added GitHub workflow build file 2021-07-16 15:25:59 -06:00
Intelli db817e603e Fixed French translation 2021-07-16 13:06:44 -06:00
441 changed files with 68878 additions and 13712 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
patreon: coreprotect
+15
View File
@@ -0,0 +1,15 @@
daysUntilStale: 60
daysUntilClose: 7
exemptLabels:
- 'accepted'
- 'bug'
- 'confirmed bug (tested)'
- 'potential bug (needs testing)'
- 'suggestion'
- 'help wanted'
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: false
+23
View File
@@ -0,0 +1,23 @@
name: Build
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.1
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 25
uses: actions/setup-java@v6
with:
java-version: '25'
distribution: 'temurin'
cache: 'maven'
- name: Build
run: mvn -B verify
+38 -2
View File
@@ -16,6 +16,7 @@ local.properties
.settings/
.loadpath
.gradle/
.vscode/
# External tool builders
.externalToolBuilders/
@@ -52,7 +53,8 @@ nbproject
target
dependency-reduced-pom.xml
test/
src/test
maven_output.txt
# Wrapper files
mvnw
@@ -62,6 +64,7 @@ mvnw.cmd
## Gradle
#################
build
gradle
# Wrapper files
@@ -170,6 +173,24 @@ Desktop.ini
## Misc.
#############
.claude
AGENTS.md
CLAUDE.md
.cursorrules
.windsurfrules
.clinerules
context.json
context.yml
issues.md
run.sh
run_*.sh
no-log4j2.xml
database.db
*tests.txt
test*.db
nullnull
refactor.txt
# Compiled class file
*.class
@@ -194,4 +215,19 @@ Desktop.ini
hs_err_pid*
# Documentation dev folder
docs/dev/
docs/dev/
# symlinks
CoreProtect-TestAPI
CoreProtect-Fabric
CoreProtect-Hytale
publish/
# Extensions
/src/main/java/net/coreprotect/utility/extensions
!/src/main/java/net/coreprotect/utility/extensions/Extensions.java
/.apt_generated/
/.apt_generated_tests/
# Server side files
server/*
+3 -2
View File
@@ -10,7 +10,8 @@ Please note we have a code of conduct, please follow it in all your interactions
- For consistency, please only use spaces for indentation.
- Use descriptiveCamelCase for variable/method names. Names containing underscore or abbreviations should be changed.
- Restrict changes in a commit to a single event or section of code (e.g. refactor BlockBreakEvent and BlockPlaceEvent in separate commits)
- Attempt to keep the amount of code modified in a single commit small and readable.
- Attempt to keep the amount of code modified in a single pull request small and readable.
- Please utilize [SonarLint](https://www.sonarlint.org) in your IDE. Contributions are automatically analyzed.
- When adding new functionality, please also be sure to update the documentation as well.
- Use common sense. If you have a suggestion for a contribution guideline, post it on Discord.
@@ -147,4 +148,4 @@ at [https://www.contributor-covenant.org/translations][translations].
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
[translations]: https://www.contributor-covenant.org/translations
+170 -17
View File
@@ -1,53 +1,206 @@
![CoreProtect](https://userfolio.com/uploads/coreprotect-banner-v19.png)
[![Artistic License 2.0](https://img.shields.io/github/license/PlayPro/CoreProtect?&logo=github)](LICENSE)
[![Artistic License 2.0](https://img.shields.io/github/license/PlayPro/CoreProtect?&logo=github)](https://github.com/PlayPro/CoreProtect/blob/master/LICENSE)
[![GitHub Workflows](https://github.com/PlayPro/CoreProtect/actions/workflows/build.yml/badge.svg)](https://github.com/PlayPro/CoreProtect/actions)
[![Netlify Status](https://img.shields.io/netlify/c1d26a0f-65c5-4e4b-95d7-e08af671ab67)](https://app.netlify.com/sites/coreprotect/deploys)
[![CodeFactor](https://www.codefactor.io/repository/github/playpro/coreprotect/badge)](https://www.codefactor.io/repository/github/playpro/coreprotect)
[![Join us on Discord](https://img.shields.io/discord/348680641560313868.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/b4DZ4jy)
[![Netlify Status](https://api.netlify.com/api/v1/badges/c1d26a0f-65c5-4e4b-95d7-e08af671ab67/deploy-status)](https://app.netlify.com/sites/coreprotect/deploys)
CoreProtect
===========
# CoreProtect
CoreProtect is a blazing fast data logging and anti-griefing tool for Minecraft servers.
CoreProtect is a fast, efficient data logging and anti-griefing tool. Inspect, lookup, rollback, and restore any amount of damage without impacting your server performance.
For a detailed description of the plugin, please visit [spigotmc.org/resources/coreprotect.8631](https://www.spigotmc.org/resources/coreprotect.8631/).
CoreProtect is the [#1 anti-griefing plugin](https://bstats.org/plugin/bukkit/CoreProtect), and has been actively developed since early 2012.
| Quick Links | |
| --- | --- |
| CoreProtect Discord: | [discord.gg/b4DZ4jy](https://discord.gg/b4DZ4jy) |
| CoreProtect Patreon: | [patreon.com/coreprotect](https://www.patreon.com/coreprotect) |
| CoreProtect Documentation: | [docs.coreprotect.net](https://docs.coreprotect.net) |
| Downloads for MC 1.14 - 1.17: | [spigotmc.org/resources/coreprotect.8631](https://www.spigotmc.org/resources/coreprotect.8631/) |
| Downloads for MC 1.14 - 26.2: | [coreprotect.net/latest](https://coreprotect.net/latest/) |
| Downloads for MC 1.8 - 1.12: | [coreprotect.net/legacy](https://coreprotect.net/legacy/) |
| Downloads for Fabric: | [coreprotect.net/fabric](https://coreprotect.net/fabric) |
| Downloads for Hytale: | [coreprotect.net/hytale](https://coreprotect.net/hytale) |
bStats
------
[![bStats Graph Data](https://bstats.org/signatures/bukkit/CoreProtect.svg)](https://bstats.org/plugin/bukkit/CoreProtect)
## API
API
------
### [API Documentation](https://docs.coreprotect.net/api/)
### Dependency Information
Maven
Maven:
```xml
<repository>
<id>playpro-repo</id>
<url>https://maven.playpro.com</url>
</repository>
```
```xml
<dependency>
<groupId>net.coreprotect</groupId>
<artifactId>coreprotect</artifactId>
<version>20.0</version>
<version>24.1</version>
<scope>provided</scope>
</dependency>
```
Contributing
------
## Donation Keys
To support the project and obtain a donation key, visit [coreprotect.net/donate](https://coreprotect.net/donate/).
## Other Plugins
* DarkerNights: [spigotmc.org/resources/darkernights.87814](https://www.spigotmc.org/resources/darkernights.87814/)
* TransitTubes: [patreon.com/posts/75731668](https://www.patreon.com/posts/75731668)
## Video Tutorial
[![CoreProtect video tutorial](https://www.userfolio.com/uploads/coreprotect-video-tutorial.png)](https://youtu.be/JwijCiueZ3Y)
## Core Features
* Fast, efficient data logging.
* Rollbacks and restores with lag-free processing.
* Multi-threaded to ensure server performance is never impacted.
* No configuration required. Install the plugin, and you're good to go.
* Embedded DuckDB columnar storage by default, with optional ClickHouse, SQLite, and MySQL support.
* Supports Bukkit, Spigot, Paper, Folia, MultiPaper, and more.
* Permission system support and advanced lookup permissions.
* Easy-to-use commands and inspector.
* Advanced lookup filters and operation limits by radius, world, or WorldEdit selection.
* Paginated results with clickable pagination.
* Preview rollbacks and restores, and undo rollbacks by restoring the same data.
* Rollback or restore specific users, multiple users, or global activity.
* Inventory rollback support.
* Purge old data by time, world, and block type.
* Multi-world support.
* Configurable global and per-world logging settings.
* Safe default parameters.
* Include or exclude specific blocks, items, entities, or users, plus logging blacklists.
* WorldEdit and supported FAWE logging.
* Enhanced item lookup tooltips.
* Localization and translation support.
* Automatic update checking and error reporting.
* Developer and networking API support.
* *...and much more!*
## What does it log?
### Blocks and World Changes
* Blocks placed or broken by players.
* Natural block breakage, such as attached blocks breaking when their support is removed.
* Block movement, including falling sand, gravel, and piston movement.
* Water and lava placement, removal, and flow.
* Liquid tracking, associating flowing liquids with the player who placed the source.
* Fire ignition from flint and steel or fire charges, block burning, and fire fade.
* Optional natural fire extinguishing.
* TNT, Creeper, Ghast, and other explosions, plus primed TNT removal.
* Entities changing blocks, including Endermen, Ender Dragons, and zombies breaking doors.
* Leaf decay.
* Tree growth linked back to the player who planted the sapling.
* Mushroom, vine, amethyst, chorus, and bamboo growth.
* Sculk spread from sculk catalysts.
* Nether portal and other natural portal generation.
* Crops trampled by players and turtle eggs trampled by players or entities.
* Snow generated by snow golems.
* Liquid-formed blocks such as obsidian, cobblestone, and concrete.
* Flowers placed in flower pots.
* Dragon egg teleportation.
* Suspicious sand and gravel brushing or destruction.
* Custom blocks, such as CraftEngine blocks.
### Containers, Inventories, and Items
* Items taken from or placed into vanilla or supported third-party containers.
* Hopper, dropper, and dispenser transactions, including container transfers.
* Player inventory transactions, including items dropped, thrown, shot, or picked up.
* Items broken, created, destroyed, deposited, or withdrawn by players.
* Items placed on campfires and lectern book transactions.
* Chiseled bookshelf, decorated pot, and shelf inventory transactions.
* Copper Golem chest and jukebox transactions, plus crafter slot changes.
* Bundle actions through the `#bundle` tag.
* Items crafted or traded with villagers.
* Allay item exchanges.
* Paintings and item frames.
### Entities and Player Activity
* Entity deaths and detailed death reasons.
* Player-caused entity kills, including animals and monsters.
* Armor stands and End Crystals.
* Villagers killed by lightning, gossip data, and job-site memories.
* Thrown eggs, snowballs, potions, ender pearls, tridents, and wind charges.
* Player interactions with doors, buttons, levers, containers, bookshelves, and jukeboxes.
* Sign text, color, glow, wax, and modern double-sided signs.
* Player chat messages and commands.
* Player sessions including logins and logouts, player deaths, and username changes.
* Skull skin texture data on Paper servers and custom skull textures.
* WorldEdit changes and supported FAWE clipboard pastes.
* *...and more!*
## How to use the inspector
Once you have the inspector enabled with `/core inspect` or `/co i`, you can do the following:
* Left-click a block to see who placed that block.
* Right-click a block to see what adjacent block was removed.
* Right-click while placing a block in a location to see what block was removed at that location.
* Right-click while placing a block in another block to see who placed it. For example, place dirt in water to see who placed the water.
* Right-click a door, button, lever, container, or similar block to see who last used it.
## A Few Reviews
* *"It's easy to use and lightning fast when it comes to rollbacks."*
* *"I've been running a MC server since before Bukkit, so I've used just about every block logger out there. CoreProtect is the best and the fastest."*
* *"Go with CoreProtect. It's fast, updated frequently, and logs WorldEdit."*
* *"Overall it felt more natural, simpler, and much faster to use CoreProtect than Prism, which just doesn't compare."*
* *"CoreProtect brings complex logging and rollbacks, whilst at the same time keeping a low profile."*
* *Featured on [Linus Tech Tips](https://www.userfolio.com/uploads/coreprotect-linus.png).*
## Useful Links
**Documentation**
https://docs.coreprotect.net
**Commands**
https://docs.coreprotect.net/commands/
**Permissions**
https://docs.coreprotect.net/permissions/
**API Documentation**
https://docs.coreprotect.net/api/
**Feedback & Support**
https://github.com/PlayPro/CoreProtect/issues
**Discord**
Join us on Discord: https://discord.gg/b4DZ4jy
**Donate**
Enjoy using CoreProtect and want to show your support? [Join our Patreon!](https://www.patreon.com/coreprotect)
**Sponsors**
Thanks to [HostHorde](https://www.hosthorde.com) for sponsoring CoreProtect!
## Bug Reports
For any bug reports, please submit a ticket here:
https://github.com/PlayPro/CoreProtect/issues
Thanks for your support!
## bStats
[![bStats Graph Data](https://bstats.org/signatures/bukkit/CoreProtect.svg)](https://bstats.org/plugin/bukkit/CoreProtect)
## Contributing
CoreProtect is an open source project, and gladly accepts community contributions.
If you'd like to contribute, please read our contributing guidelines here: [CONTRIBUTING.md](CONTRIBUTING.md)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CONTRIBUTING.md#code-of-conduct)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CONTRIBUTING.md#code-of-conduct)
-87
View File
@@ -1,87 +0,0 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '6.0.0'
id 'com.palantir.git-version' version '0.12.3'
}
group = 'net.coreprotect'
String projectVersion = '20.0'
String projectBranch = ''
version = projectVersion // `version` might be modified, we don't always want that (e.g. plugin.yml)
description = 'Provides block protection for your server.'
sourceCompatibility = '1.8'
if (System.getenv("BUILD_NUMBER") != null) {
// Being built in Jenkins, append Build ID
version += "-${System.getenv("BUILD_NUMBER")}"
} else if (!(version ==~ '^[^.]*\\.[^.]*\\.[^.]*$')) { // Thanks https://stackoverflow.com/a/9949200/1709894
// Append the Git hash if 'version' has less than two periods
version += "-${gitVersion()}"
}
logger.info("Building version $version")
repositories {
jcenter()
maven { url = 'https://hub.spigotmc.org/nexus/content/groups/public/' }
maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url = 'https://papermc.io/repo/repository/maven-public/' }
maven { url = 'https://repo.codemc.org/repository/maven-public/' }
maven { url = 'https://maven.sk89q.com/repo/' }
}
dependencies {
compileOnly('com.sk89q.worldedit:worldedit-bukkit:7.0.0-SNAPSHOT') {
exclude group: 'org.bukkit'
}
compileOnly 'org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT'
implementation 'org.bstats:bstats-bukkit-lite:1.7'
}
jar {
archiveClassifier.set("original")
}
artifacts {
archives shadowJar
}
shadowJar {
dependencies {
// #toString because #getGroup technically returns an Object
relocate('org.bstats', project.group.toString())
}
archiveClassifier.set(null)
}
ext {
author = 'Intelli'
resourceTokens = [
'project.version': projectVersion,
'project.branch': projectBranch,
]
}
processResources {
include 'plugin.yml'
// Whole lotta boilerplate to get the same functionality as Maven here.
// Replace this if Gradle ever lets us configure the filter before filtering.
filter(new Transformer<String, String>() {
@Override
String transform(String s) {
ReplaceTokens replaceTokens = new ReplaceTokens(new StringReader(s))
replaceTokens.setBeginToken('${')
replaceTokens.setEndToken('}')
resourceTokens.forEach { key, val ->
def token = new ReplaceTokens.Token()
token.setKey(key.toString())
token.setValue(val.toString())
replaceTokens.addConfiguredToken(token)
}
return replaceTokens.readLines().join('\n')
}
})
}
+3 -438
View File
@@ -4,443 +4,8 @@ The CoreProtect API enables you to log your own block changes, perform lookups,
| API Details | |
| --- | --- |
| **API Version:** | 7 |
| **Plugin Version:** | v20.0+ |
| **API Version:** | 13 |
| **Plugin Version:** | v25.0+ |
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
*Documentation for the previous API version can be found [here](https://www.minerealm.com/community/viewtopic.php?f=32&t=16687).*
---
## Upgrading from API v6
The changes from the previous API version are as follows:
- The following methods have been added:
```java
parseResult(String[] result).getTimestamp()
```
---
- The following methods have been deprecated:
```java
parseResult(String[] result).getTime()
```
---
- The following methods have been removed:
```java
parseResult(String[] result).getTypeId()
```
---
## Getting Started
Ensure you're using CoreProtect 20.0 or higher. Add it as an external jar to your plugin in your IDE.
Alternatively, if using Maven, you can add it via the repository [https://maven.playpro.com](https://maven.playpro.com) (net.coreprotect, 20.0).
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
```java
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
// Check that CoreProtect is loaded
if (plugin == null || !(plugin instanceof CoreProtect)) {
return null;
}
// Check that the API is enabled
CoreProtectAPI CoreProtect = ((CoreProtect) plugin).getAPI();
if (CoreProtect.isEnabled() == false) {
return null;
}
// Check that a compatible version of the API is loaded
if (CoreProtect.APIVersion() < 7) {
return null;
}
return CoreProtect;
}
```
With this code, you can then access the API with a call like the following:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null){ // Ensure we have access to the API
api.testAPI(); // Will print out "[CoreProtect] API test successful." in the console.
}
```
Yay, you're now using the CoreProtect API!
---
## API Overview
### Available Methods
```java
boolean isEnabled()
void testAPI()
List<String[]> performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> blockLookup(Block block, int time)
ParseResult parseResult(String[] result)
boolean logChat(Player player, String message)
boolean logCommand(Player player, String command)
boolean logPlacement(String user, Location location, Material type, BlockData blockData)
boolean logRemoval(String user, Location location, Material type, BlockData blockData)
boolean logContainerTransaction(String user, Location location)
boolean logInteraction(String user, Location location)
boolean hasPlaced(String user, Block block, int time, int offset)
boolean hasRemoved(String user, Block block, int time, int offset)
void performPurge(int time)
```
### Usage
`isEnabled()`
Calling this will return true if the server has the CoreProtect API enabled, and false if it does not.
---
`testAPI()`
Running this will print out "[CoreProtect] API Test Successful." in the server console.
---
`performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a lookup.
* **time:** Specify the amount of time to search back. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the lookup on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the lookup. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the search to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the search. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the search to. Can be set to "null"
* **radius:** Specify a radius to restrict the search to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to search around. Can be set to "null" if no radius is specified, and a user is specified.
---
`performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a rollback. Method must be called async.
* **time:** Specify the amount of time to rollback. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the rollback on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the rollback. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the rollback to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the rollback. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the rollback to. Can be set to "null"
* **radius:** Specify a radius to restrict the rollback to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to rollback around. Can be set to "null" if no radius is specified, and a user is specified.
---
`performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a restore.
* **time:** Specify the amount of time to restore. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the restore on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the restore. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the restore to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the restore. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the restore to. Can be set to "null"
* **radius:** Specify a radius to restrict the restore to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to restore around. Can be set to "null" if no radius is specified, and a user is specified.
---
`blockLookup(Block block, int time)`
This will perform a full lookup on a single block.
* **block:** The block to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
`ParseResult parseResult(String[] result)`
This will parse results from a lookup. You'll then be able to view the following:
* **getX():** Get the X coordinate of the block.
* **getY():** Get the Y coordinate of the block.
* **getZ():** Get the Z coordinate of the block.
* **getType():** Get the Material of the block.
* **getBlockData():** Get the BlockData of the block.
* **getPlayer():** Get the username as a string..
* **getTimestamp():** Get the time of the action.
* **getActionId():** Get the action ID. (0=removed, 1=placed, 2=interaction)
* **getActionString():** Get the action as a string. (Removal, Placement, Interaction)
* **isRolledBack():** If the block is currently rolled back or not.
* **worldName():** The name of the world the block is located in.
---
`logPlacement(String user, Location location, Material type, BlockData blockData)`
This will log a block as being placed.
* **user:** Specify the username to log as having placed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
`logRemoval(String user, Location location, Material type, BlockData blockData)`
This will log a block as being removed/broken, and will log the block's inventory (if applicable).
* **user:** Specify the username to log as having removed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
`logContainerTransaction(String user, Location location)`
This will log any transactions made to a block's inventory immediately after calling the method.
* **user:** Specify the username to log as having added/removed the items.
* **location:** Specify the location of the block inventory you're logging.
---
`logInteraction(String user, Location location)`
This will log a block as having been interacted with.
* **user:** Specify the username to log as having caused the interaction.
* **location:** Specify the location of the interaction you're logging.
---
`hasPlaced(String user, Block block, int time, int offset)`
This will return true if a user has already placed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've placed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0=no offset)
---
`hasRemoved(String user, Block block, int time, int offset)`
This will return true if a user has already removed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've removed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0=no offset)
---
`performPurge(int time)`
This will perform a purge on the CoreProtect database.
* **time:** Purge any data earlier than this. "120" would purge any data older than 120 seconds (2 minutes).
---
### Examples
- Get the last 60 seconds of block data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup!=null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
//...
}
}
}
```
---
- Get the last 60 seconds of block data for the user "Notch", excluding dirt and grass blocks.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<Object> exclude = Arrays.asList(Material.DIRT, Material.GRASS);
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, exclude, null, 0, null);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
//...
}
}
}
```
---
- Get the last 60 seconds of block data within 5 blocks of a location.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, null, null, null, null, null, 5, location);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
//...
}
}
}
```
---
- Rollbacks / restores use the same code structure as the above examples. For example:
```java
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performRollback(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
//...
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
- Check if the user "Notch" has already placed a block at a location within the last 60 seconds.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean hasPlaced = CoreProtect.HasPlaced("Notch", block, 60, 0);
}
```
---
- Get the last 60 seconds of block data for a specific block.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.blockLookup(block, 60);
if (lookup!=null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(value);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
//...
}
}
}
```
---
- Log the placement of a block at a location by the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean success = CoreProtect.logPlacement("Notch", block.getLocation(), block.getType(), block.getData());
}
```
---
- Log adding/remove items in a chest (or some other block inventory).
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean success = CoreProtect.logContainerTransaction("Notch", inventory.getLocation());
// modify your container contents immediately after (e.g. [i]inventory.addItem(itemStack);[/i])
}
```
---
- Perform a multi-threaded placement check to see if the user "Notch" has already placed a block at a location within the last 60 seconds. This ignores the most recent 1 second of logged data, to account for the fact that that new block data may have already been logged, depending on your code.
```java
final Block block = null; //Should be an actual block
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 1);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
*Documentation for the API version 13 can be found [here](/api/version/v13/).*
+99
View File
@@ -0,0 +1,99 @@
# Networking API
The CoreProtect Networking API allows clients to receive data using packets.
| Networking Details | |
|-------------------------|--------|
| **Networking Version:** | 1 |
| **Plugin Version:** | v21.3+ |
---
## Packets
The server will not respond unless the player has the correct permission, which is `coreprotect.networking`.
---
## Server to Client
### Data Packet
Sends data from the database.
* Channel: `coreprotect:data`
| Type: `Int` | 1 | 2 | 3 | 4 |
|-------------|------------------------|------------------------|--------------------|--------------------|
| | Time: `long` | Time: `long` | Time: `long` | Time: `long` |
| | Phrase selector: `UTF` | Phrase selector: `UTF` | Result User: `UTF` | Result User: `UTF` |
| | Result User: `UTF` | Result User: `UTF` | Message: `UTF` | Target: `UTF` |
| | Target: `UTF` | Amount: `Int` | Sign: `Boolean` | |
| | Amount: `Int` | X: `Int` | X: `Int` | |
| | X: `Int` | Y: `Int` | Y: `Int` | |
| | Y: `Int` | Z: `Int` | Z: `Int` | |
| | Z: `Int` | World name: `UTF` | World name: `UTF` | |
| | World name: `UTF` | | | |
| | Rolledback: `Boolean` | | | |
| | isContainer: `Boolean` | | | |
| | Added: `Boolean` | | | |
Example (Fabric):
```
ByteArrayInputStream in = new ByteArrayInputStream(buf.getWrittenBytes());
DataInputStream dis = new DataInputStream(in);
int type = dis.readInt();
long time = dis.readLong();
String selector = dis.readUTF();
String resultUser = dis.readUTF();
String target = dis.readUTF();
int amount = dis.readInt();
int x = dis.readInt();
int y = dis.readInt();
int z = dis.readInt();
String worldName = dis.readUTF();
boolean rolledback = dis.readBoolean();
boolean isContainer = dis.readBoolean();
boolean added = dis.readBoolean();
```
### Handshake Packet
Sends handshake if player is registered.
* Channel: `coreprotect:handshake`
* Registered: `Boolean`
---
## Client to Server
### Handshake Packet
Sends handshake to register
* Channel: `coreprotect:handshake`
* Mod Version: `UTF`
* Mod Id: `UTF`
* CoreProtect Protocol: `Int`
Example (Fabric):
```
PacketByteBuf packetByteBuf = new PacketByteBuf(Unpooled.buffer());
ByteArrayOutputStream msgBytes = new ByteArrayOutputStream();
DataOutputStream msgOut = new DataOutputStream(msgBytes);
msgOut.writeUTF(modVersion);
msgOut.writeUTF(modId);
msgOut.writeInt(coreprotectProtocol);
packetByteBuf.writeBytes(msgBytes.toByteArray());
```
---
## Debugging
### /co network-debug
Allows you to debug the networking API if you are registered and have correct permissions.
To utilize the command, `network-debug: true` must be set in the CoreProtect `config.yml`.
**Example**
`/co network-debug <type>`
___
+526
View File
@@ -0,0 +1,526 @@
# API Version 10
The CoreProtect API enables you to log your own block changes, perform lookups, rollbacks, restores, and more.
| API Details | |
| --- | --- |
| **API Version:** | 10 |
| **Plugin Version:** | v22.4+ |
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
---
## Upgrading from API v9
The changes from the previous API version are as follows:
- The following methods have been added:
```java
logPlacement(String user, BlockState blockState)
logRemoval(String user, BlockState blockState)
```
---
## Getting Started
Ensure you're using CoreProtect 22.4 or higher. Add it as an external jar to your plugin in your IDE.
Alternatively, if using Maven, you can add it via the repository [https://maven.playpro.com](https://maven.playpro.com) (net.coreprotect, 22.4).
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
```java
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
// Check that CoreProtect is loaded
if (plugin == null || !(plugin instanceof CoreProtect)) {
return null;
}
// Check that the API is enabled
CoreProtectAPI CoreProtect = ((CoreProtect) plugin).getAPI();
if (CoreProtect.isEnabled() == false) {
return null;
}
// Check that a compatible version of the API is loaded
if (CoreProtect.APIVersion() < 10) {
return null;
}
return CoreProtect;
}
```
With this code, you can then access the API with a call like the following:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null){ // Ensure we have access to the API
api.testAPI(); // Will print out "[CoreProtect] API test successful." in the console.
}
```
Yay, you're now using the CoreProtect API!
---
## API Overview
### Available Methods
```java
boolean isEnabled()
void testAPI()
List<String[]> performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> blockLookup(Block block, int time)
List<String[]> sessionLookup(String user, int time)
List<String[]> queueLookup(Block block)
ParseResult parseResult(String[] result)
boolean logChat(Player player, String message)
boolean logCommand(Player player, String command)
boolean logPlacement(String user, BlockState blockState)
boolean logPlacement(String user, Location location, Material type, BlockData blockData)
boolean logRemoval(String user, BlockState blockState)
boolean logRemoval(String user, Location location, Material type, BlockData blockData)
boolean logContainerTransaction(String user, Location location)
boolean logInteraction(String user, Location location)
boolean hasPlaced(String user, Block block, int time, int offset)
boolean hasRemoved(String user, Block block, int time, int offset)
void performPurge(int time)
```
---
### Available Events
*The following events are emitted by CoreProtect.*
#### CoreProtectPreLogEvent
Fired when a CoreProtect logger is about to log an action. Cancellable.
| Property | Description | Mutable |
| --- | --- | --- |
| User | The name of the user under which this action will be logged. | Yes |
| Cancelled | If cancelled, the action won't be logged to the database. | Yes |
---
### Method Usage
*Detailed method information is listed below.*
#### `isEnabled()`
Calling this will return true if the server has the CoreProtect API enabled, and false if it does not.
---
#### `testAPI()`
Running this will print out "[CoreProtect] API Test Successful." in the server console.
---
#### `performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a lookup.
* **time:** Specify the amount of time to search back. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the lookup on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the lookup. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the search to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the search. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the search to. Can be set to "null"
* **radius:** Specify a radius to restrict the search to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to search around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a rollback. Method must be called async.
* **time:** Specify the amount of time to rollback. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the rollback on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the rollback. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the rollback to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the rollback. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the rollback to. Can be set to "null"
* **radius:** Specify a radius to restrict the rollback to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to rollback around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a restore.
* **time:** Specify the amount of time to restore. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the restore on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the restore. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the restore to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the restore. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the restore to. Can be set to "null"
* **radius:** Specify a radius to restrict the restore to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to restore around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `blockLookup(Block block, int time)`
This will perform a full lookup on a single block.
* **block:** The block to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `queueLookup(Block block)`
This will search the consumer queue for changes on a block not yet saved in the database.
* **block:** The block to perform the lookup on.
---
#### `sessionLookup(String user, int time)`
This will perform a session lookup on a single player.
* **user:** The username to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `ParseResult parseResult(String[] result)`
This will parse results from a lookup. You'll then be able to view the following:
* **getX():** Get the X coordinate of the block.
* **getY():** Get the Y coordinate of the block.
* **getZ():** Get the Z coordinate of the block.
* **getType():** Get the Material of the block.
* **getBlockData():** Get the BlockData of the block.
* **getPlayer():** Get the username as a string.
* **getTimestamp():** Get the time of the action.
* **getActionId():** Get the action ID. (0=removed, 1=placed, 2=interaction)
* **getActionString():** Get the action as a string. (Removal, Placement, Interaction)
* **isRolledBack():** If the block is currently rolled back or not.
* **worldName():** The name of the world the block is located in.
---
#### `logPlacement(String user, BlockState blockState)`
This will log a block as being placed. *(Thread safe)*
* **user:** Specify the username to log as having placed the block.
* **blockState:** Specify the BlockState of the block you're logging.
---
#### `logPlacement(String user, Location location, Material type, BlockData blockData)`
This will log a block as being placed.
* **user:** Specify the username to log as having placed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logRemoval(String user, BlockState blockState)`
This will log a block as being removed/broken. *(Thread safe)*
* **user:** Specify the username to log as having removed the block.
* **blockState:** Specify the BlockState of the block you're logging.
---
#### `logRemoval(String user, Location location, Material type, BlockData blockData)`
This will log a block as being removed/broken, and will log the block's inventory (if applicable).
* **user:** Specify the username to log as having removed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logContainerTransaction(String user, Location location)`
This will log any transactions made to a block's inventory immediately after calling the method.
* **user:** Specify the username to log as having added/removed the items.
* **location:** Specify the location of the block inventory you're logging.
---
#### `logInteraction(String user, Location location)`
This will log a block as having been interacted with.
* **user:** Specify the username to log as having caused the interaction.
* **location:** Specify the location of the interaction you're logging.
---
#### `hasPlaced(String user, Block block, int time, int offset)`
This will return true if a user has already placed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've placed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0 = no offset)
---
#### `hasRemoved(String user, Block block, int time, int offset)`
This will return true if a user has already removed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've removed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0 = no offset)
---
#### `performPurge(int time)`
This will perform a purge on the CoreProtect database.
* **time:** Purge any data earlier than this. "120" would purge any data older than 120 seconds (2 minutes).
---
### Examples
- Get the last 60 seconds of block data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data for the user "Notch", excluding dirt and grass blocks.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<Object> exclude = Arrays.asList(Material.DIRT, Material.GRASS);
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, exclude, null, 0, null);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data within 5 blocks of a location.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, null, null, null, null, null, 5, location);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Rollbacks / restores use the same code structure as the above examples. For example:
```java
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performRollback(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
- Check if the user "Notch" has already placed a block at a location within the last 60 seconds.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 0);
// Search queue for pending changes
if (!hasPlaced){
List<String[]> lookup = CoreProtect.queueLookup(block);
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
if (parseResult.getActionId()==1 && parseResult.getPlayer().equals("Notch")){
hasPlaced = true;
break;
}
}
}
}
```
---
- Get the last 60 seconds of block data for a specific block.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.blockLookup(block, 60);
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 1 day of session data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.sessionLookup("Notch", (24 * 60 * 60));
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
int action = parseResult.getActionId(); // 0 = logout, 1 = login
// ...
}
}
}
```
---
- Log the placement of a block at a location by the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean success = CoreProtect.logPlacement("Notch", block.getLocation(), block.getType(), block.getData());
}
```
---
- Log adding/remove items in a chest (or some other block inventory).
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean success = CoreProtect.logContainerTransaction("Notch", inventory.getLocation());
// modify your container contents immediately after (e.g. [i]inventory.addItem(itemStack);[/i])
}
```
---
- Perform a multi-threaded placement check to see if the user "Notch" has already placed a block at a location within the last 60 seconds. This ignores the most recent 1 second of logged data, to account for the fact that that new block data may have already been logged, depending on your code.
```java
final Block block = null; //Should be an actual block
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 1);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
+530
View File
@@ -0,0 +1,530 @@
# API Version 11
The CoreProtect API enables you to log your own block changes, perform lookups, rollbacks, restores, and more.
| API Details | |
| --- | --- |
| **API Version:** | 11 |
| **Plugin Version:** | v23.1+ |
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
---
## Upgrading from API v10
The changes from the previous API version are as follows:
- The following event was extended to include Location (mutable):
```java
CoreProtectPreLogEvent(String user, Location location)
```
- The old constructor remains for backward compatibility and is deprecated:
```java
@Deprecated
CoreProtectPreLogEvent(String user)
```
---
## Getting Started
Ensure you're using CoreProtect 23.1 or higher. Add it as an external jar to your plugin in your IDE.
Alternatively, if using Maven, you can add it via the repository https://maven.playpro.com (net.coreprotect, 23.1).
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
```java
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
// Check that CoreProtect is loaded
if (plugin == null || !(plugin instanceof CoreProtect)) {
return null;
}
// Check that the API is enabled
CoreProtectAPI CoreProtect = ((CoreProtect) plugin).getAPI();
if (CoreProtect.isEnabled() == false) {
return null;
}
// Check that a compatible version of the API is loaded
if (CoreProtect.APIVersion() < 11) {
return null;
}
return CoreProtect;
}
```
With this code, you can then access the API with a call like the following:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null){ // Ensure we have access to the API
api.testAPI(); // Will print out "[CoreProtect] API test successful." in the console.
}
```
Yay, you're now using the CoreProtect API!
---
## API Overview
### Available Methods
```java
boolean isEnabled()
void testAPI()
List<String[]> performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> blockLookup(Block block, int time)
List<String[]> sessionLookup(String user, int time)
List<String[]> queueLookup(Block block)
ParseResult parseResult(String[] result)
boolean logChat(Player player, String message)
boolean logCommand(Player player, String command)
boolean logPlacement(String user, BlockState blockState)
boolean logPlacement(String user, Location location, Material type, BlockData blockData)
boolean logRemoval(String user, BlockState blockState)
boolean logRemoval(String user, Location location, Material type, BlockData blockData)
boolean logContainerTransaction(String user, Location location)
boolean logInteraction(String user, Location location)
boolean hasPlaced(String user, Block block, int time, int offset)
boolean hasRemoved(String user, Block block, int time, int offset)
void performPurge(int time)
```
---
### Available Events
The following events are emitted by CoreProtect.
#### CoreProtectPreLogEvent
Fired when a CoreProtect logger is about to log an action. Cancellable.
| Property | Description | Mutable |
| --- | --- | --- |
| User | The name of the user under which this action will be logged. | Yes |
| Location | The location where the action will be logged. | Yes |
| Cancelled | If cancelled, the action won't be logged to the database. | Yes |
---
### Method Usage
*Detailed method information is listed below.*
#### `isEnabled()`
Calling this will return true if the server has the CoreProtect API enabled, and false if it does not.
---
#### `testAPI()`
Running this will print out "[CoreProtect] API Test Successful." in the server console.
---
#### `performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a lookup.
* **time:** Specify the amount of time to search back. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the lookup on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the lookup. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the search to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the search. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the search to. Can be set to "null"
* **radius:** Specify a radius to restrict the search to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to search around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a rollback. Method must be called async.
* **time:** Specify the amount of time to rollback. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the rollback on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the rollback. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the rollback to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the rollback. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the rollback to. Can be set to "null"
* **radius:** Specify a radius to restrict the rollback to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to rollback around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a restore.
* **time:** Specify the amount of time to restore. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the restore on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the restore. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the restore to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the restore. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the restore to. Can be set to "null"
* **radius:** Specify a radius to restrict the restore to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to restore around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `blockLookup(Block block, int time)`
This will perform a full lookup on a single block.
* **block:** The block to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `queueLookup(Block block)`
This will search the consumer queue for changes on a block not yet saved in the database.
* **block:** The block to perform the lookup on.
---
#### `sessionLookup(String user, int time)`
This will perform a session lookup on a single player.
* **user:** The username to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `ParseResult parseResult(String[] result)`
This will parse results from a lookup. You'll then be able to view the following:
* **getX():** Get the X coordinate of the block.
* **getY():** Get the Y coordinate of the block.
* **getZ():** Get the Z coordinate of the block.
* **getType():** Get the Material of the block.
* **getBlockData():** Get the BlockData of the block.
* **getPlayer():** Get the username as a string.
* **getTimestamp():** Get the time of the action.
* **getActionId():** Get the action ID. (0=removed, 1=placed, 2=interaction)
* **getActionString():** Get the action as a string. (Removal, Placement, Interaction)
* **isRolledBack():** If the block is currently rolled back or not.
* **worldName():** The name of the world the block is located in.
---
#### `logPlacement(String user, BlockState blockState)`
This will log a block as being placed. *(Thread safe)*
* **user:** Specify the username to log as having placed the block.
* **blockState:** Specify the BlockState of the block you're logging.
---
#### `logPlacement(String user, Location location, Material type, BlockData blockData)`
This will log a block as being placed.
* **user:** Specify the username to log as having placed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logRemoval(String user, BlockState blockState)`
This will log a block as being removed/broken. *(Thread safe)*
* **user:** Specify the username to log as having removed the block.
* **blockState:** Specify the BlockState of the block you're logging.
---
#### `logRemoval(String user, Location location, Material type, BlockData blockData)`
This will log a block as being removed/broken, and will log the block's inventory (if applicable).
* **user:** Specify the username to log as having removed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logContainerTransaction(String user, Location location)`
This will log any transactions made to a block's inventory immediately after calling the method.
* **user:** Specify the username to log as having added/removed the items.
* **location:** Specify the location of the block inventory you're logging.
---
#### `logInteraction(String user, Location location)`
This will log a block as having been interacted with.
* **user:** Specify the username to log as having caused the interaction.
* **location:** Specify the location of the interaction you're logging.
---
#### `hasPlaced(String user, Block block, int time, int offset)`
This will return true if a user has already placed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've placed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0 = no offset)
---
#### `hasRemoved(String user, Block block, int time, int offset)`
This will return true if a user has already removed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've removed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0 = no offset)
---
#### `performPurge(int time)`
This will perform a purge on the CoreProtect database.
* **time:** Purge any data earlier than this. "120" would purge any data older than 120 seconds (2 minutes).
---
### Examples
- Get the last 60 seconds of block data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data for the user "Notch", excluding dirt and grass blocks.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<Object> exclude = Arrays.asList(Material.DIRT, Material.GRASS);
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, exclude, null, 0, null);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data within 5 blocks of a location.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, null, null, null, null, null, 5, location);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Rollbacks / restores use the same code structure as the above examples. For example:
```java
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performRollback(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
- Check if the user "Notch" has already placed a block at a location within the last 60 seconds.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 0);
// Search queue for pending changes
if (!hasPlaced){
List<String[]> lookup = CoreProtect.queueLookup(block);
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
if (parseResult.getActionId()==1 && parseResult.getPlayer().equals("Notch")){
hasPlaced = true;
break;
}
}
}
}
```
---
- Get the last 60 seconds of block data for a specific block.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.blockLookup(block, 60);
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 1 day of session data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.sessionLookup("Notch", (24 * 60 * 60));
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
int action = parseResult.getActionId(); // 0 = logout, 1 = login
// ...
}
}
}
```
---
- Log the placement of a block at a location by the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean success = CoreProtect.logPlacement("Notch", block.getLocation(), block.getType(), block.getData());
}
```
---
- Log adding/remove items in a chest (or some other block inventory).
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean success = CoreProtect.logContainerTransaction("Notch", inventory.getLocation());
// modify your container contents immediately after (e.g. [i]inventory.addItem(itemStack);[/i])
}
```
---
- Perform a multi-threaded placement check to see if the user "Notch" has already placed a block at a location within the last 60 seconds. This ignores the most recent 1 second of logged data, to account for the fact that that new block data may have already been logged, depending on your code.
```java
final Block block = null; //Should be an actual block
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 1);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
File diff suppressed because it is too large. Load diff
+964
View File
@@ -0,0 +1,964 @@
# API Version 13
The CoreProtect API enables plugins to log changes, look up recorded activity, and perform rollbacks and restores.
| API Details | |
| --- | --- |
| **API Version** | 13 |
| **Plugin Version** | v25.0+ |
| **Maven** | [maven.playpro.com](https://maven.playpro.com) |
## Upgrading from API v12
All API v12 methods remain available. API v13 adds the following:
| Addition | Description |
| --- | --- |
| `blockLookup(LookupOptions)` | Searches block breaks, placements, and interactions at an exact location, within a radius, in a world, or across all worlds. |
| `entityLookup(LookupOptions)` and `EntityResult` | Searches entity spawns and kills, with action and entity-type filters. |
| `LookupOptions.world(World)` | Selects a whole world for spatial typed lookups. |
| User and material lists | Adds `users`, `excludeUsers`, `includeMaterials`, and `excludeMaterials` to typed lookup options. |
| Typed action filters | Adds `blockActions`, `entityActions`, `containerActions`, `itemActions`, `inventoryActions`, and `sessionActions`. |
| Text prefix filters | Adds `textStartsWithAny` and `textStartsWithNone` for chat, command, and sign lookups. |
| Entity-spawn action | Adds `CoreProtectAction.ENTITY_SPAWN` with ID `13`. Legacy lookup, rollback, and restore methods accept this action explicitly. |
| Entity result parsing | `BlockResult` and `ParseResult` expose `getEntityType()` for entity kills and spawns. Their material and block-data getters return `null` for entity rows. |
| Tracked containers | Container and inventory lookups include transactions in tracked boats and minecarts. |
| Pre-log events | Adds `CoreProtectPreLogEvent.Action.ENTITY_SPAWN` and `ENTITY_INTERACTION`. |
The existing `blockLookup(Block, LookupOptions)` overload retains entity events for compatibility. The new `blockLookup(LookupOptions)` overload returns only block events; use `entityLookup` for entities.
Entity and tracked-container searches can match persisted current/final positions as well as original event positions. See [entity lookups](#entity-lookups) and [container lookups](#container-lookups) for the coordinates returned by each method.
## Getting Started
CoreProtect 25.0 or higher is required to use API v13.
### Add the dependency
Compile against a CoreProtect build that provides API v13. You can add the CoreProtect jar to your IDE or use the Maven repository:
```xml
<repositories>
<repository>
<id>playpro</id>
<url>https://maven.playpro.com</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.coreprotect</groupId>
<artifactId>CoreProtect</artifactId>
<version>25.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
Use the published version matching the CoreProtect build you target. For prerelease builds, use that build's published version instead of `25.0`. CoreProtect runs as a separate server plugin; do not package its classes inside your plugin.
Add CoreProtect to your plugin's `plugin.yml` so it loads first:
```yaml
depend: [CoreProtect]
```
Use `softdepend: [CoreProtect]` instead if your plugin also works without CoreProtect, and only initialize the integration when CoreProtect is present.
### Obtain the API
The following helper belongs in your `JavaPlugin` class:
```java
import org.bukkit.plugin.Plugin;
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
if (!(plugin instanceof CoreProtect)) {
return null;
}
CoreProtectAPI api = ((CoreProtect) plugin).getAPI();
if (!api.isEnabled() || api.APIVersion() < 13) {
return null;
}
return api;
}
```
Call it during your plugin's initialization:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null) {
api.testAPI();
}
```
Options and action enums are in `net.coreprotect.api`. Typed result classes are in `net.coreprotect.api.result`. Events are in `net.coreprotect.event`.
### Threading and return values
Database lookups run synchronously on the calling thread. Run database searches asynchronously to avoid blocking the server thread. Both `performRollback` and `performRestore` **must** be called asynchronously; CoreProtect schedules the required world updates internally.
Capture locations and any required Bukkit state on the appropriate server thread before starting database work. Logging methods that inspect live blocks, players, or inventories should run on that server thread. A `BlockState` overload does not make every logging operation safe to call asynchronously.
Typed lookups and the legacy `blockLookup`, `performLookup`, `performPartialLookup`, `performRollback`, and `performRestore` methods return `null` when the API is disabled. `queueLookup` and the legacy `sessionLookup(String, int)` instead return an empty list. Legacy searches can also return `null` for invalid search parameters. An empty list can mean no matching records or an operation that could not proceed; it is not a separate success status. Logging methods return a boolean indicating whether the request was accepted, not whether it has already been written to the database.
API rollback and restore methods perform no changes when database maintenance or another conflicting operation prevents them from starting. This includes an active database purge.
## API Overview
### Available methods
The table below lists the supported methods on `CoreProtectAPI`. Click a method to jump to its parameters and behavior.
| Method | Return type | Purpose |
| --- | --- | --- |
| [`APIVersion()`](#apiversion) | `int` | Returns `13` for this API version. |
| [`isEnabled()`](#isenabled) | `boolean` | Checks whether the API is enabled in CoreProtect's configuration. |
| [`testAPI()`](#testapi) | `void` | Prints an API test message to the console. |
| [`blockLookup(Block, int)`](#legacy-block-session-and-queue-lookups) | `List<String[]>` | Legacy lookup at a block. |
| [`blockLookup(Block, LookupOptions)`](#block-lookups) | `List<BlockResult>` | Typed lookup at a block, retaining legacy entity events. |
| [`blockLookup(LookupOptions)`](#block-lookups) | `List<BlockResult>` | Typed block-only lookup. |
| [`entityLookup(LookupOptions)`](#entity-lookups) | `List<EntityResult>` | Entity spawn and kill history. |
| [`containerLookup(Location, int)`](#container-lookups) | `List<ContainerResult>` | Container history at a location. |
| [`containerLookup(LookupOptions)`](#container-lookups) | `List<ContainerResult>` | Container history with shared filters. |
| [`itemLookup(LookupOptions)`](#item-lookups) | `List<ItemResult>` | World item transactions. |
| [`inventoryLookup(LookupOptions)`](#inventory-lookups) | `List<InventoryResult>` | Transactions affecting player inventories. |
| [`chatLookup(String, int)`](#chat-and-command-lookups) / [`chatLookup(LookupOptions)`](#chat-and-command-lookups) | `List<MessageResult>` | Chat history. |
| [`commandLookup(String, int)`](#chat-and-command-lookups) / [`commandLookup(LookupOptions)`](#chat-and-command-lookups) | `List<MessageResult>` | Command history. |
| [`signLookup(Location, int)`](#sign-lookups) / [`signLookup(LookupOptions)`](#sign-lookups) | `List<SignResult>` | Written sign text. |
| [`sessionLookup(String, int)`](#legacy-block-session-and-queue-lookups) | `List<String[]>` | Legacy login/logout history. |
| [`sessionLookup(LookupOptions)`](#session-lookups) | `List<SessionResult>` | Typed login/logout history. |
| [`usernameLookup(String, int)`](#username-lookups) / [`usernameLookup(LookupOptions)`](#username-lookups) | `List<UsernameResult>` | Username history. |
| [`queueLookup(Block)`](#legacy-block-session-and-queue-lookups) | `List<String[]>` | Pending block changes in the consumer queue. |
| [`parseResult(String[])`](#parsing-legacy-rows) | `CoreProtectAPI.ParseResult` | Parses a legacy result row. |
| [`performLookup(...)`](#full-and-partial-searches) / [`performPartialLookup(...)`](#full-and-partial-searches) | `List<String[]>` | Legacy block/entity searches, with optional pagination. |
| [`performRollback(...)`](#rollback-and-restore-behavior) / [`performRestore(...)`](#rollback-and-restore-behavior) | `List<String[]>` | Rolls back or restores matching changes. |
| [`hasPlaced(String, Block, int, int)`](#placement-and-removal-checks) / [`hasRemoved(String, Block, int, int)`](#placement-and-removal-checks) | `boolean` | Checks a player's persisted block history. |
| [`logChat(Player, String)`](#chat-and-commands) / [`logCommand(Player, String)`](#chat-and-commands) | `boolean` | Queues player messages or commands. |
| [`logPlacement(...)`](#block-placement-and-removal) / [`logRemoval(...)`](#block-placement-and-removal) | `boolean` | Queues block changes. |
| [`logContainerTransaction(String, Location)`](#container-transactions) | `boolean` | Captures a block inventory before your changes. |
| [`logInteraction(String, Location)`](#block-interactions) | `boolean` | Queues a block interaction. |
| [`performPurge(int)`](#purging-history) | `void` | Dispatches a database purge command. |
### Shared typed lookup options
Create options with `LookupOptions.builder()` and finish with `build()`:
```java
LookupOptions options = LookupOptions.builder()
.user("Notch")
.time(3600)
.radius(location, 5)
.limit(0, 100)
.build();
```
| Builder method | Behavior |
| --- | --- |
| `user(String user)` | Restricts the search to one user. `null`, an empty string, or `#global` imposes no single-user restriction. |
| `users(List<String> users)` | Includes any matching user in the list. Combines with `user` as an additional restriction. |
| `excludeUsers(List<String> users)` | Excludes matching users, including users selected by either inclusion option. |
| `time(int seconds)` | Searches back this many seconds. Omitted, zero, or negative values impose no time restriction for typed lookups. |
| `location(Location location)` | Searches the exact block coordinates and world. |
| `radius(Location location, int radius)` | A positive radius searches an inclusive X/Z square around the location, at all heights. Zero or a negative radius searches the exact block coordinates. |
| `world(World world)` | Searches the entire selected world. |
| `limit(int offset, int count)` | Skips `offset` matching rows and returns at most `count`. The offset is zero-based. Both values must be nonnegative for the limit to apply; a count of zero returns no rows. |
| `includeMaterials(List<Material> materials)` | Includes any matching logged block/item material in supported lookup types. |
| `excludeMaterials(List<Material> materials)` | Excludes matching logged block/item materials. |
| `includeEntities(List<EntityType> entities)` | Includes any matching entity type in `entityLookup`. |
| `excludeEntities(List<EntityType> entities)` | Excludes matching entity types in `entityLookup`. |
| `blockActions(List<BlockAction> actions)` | Selects block actions. |
| `entityActions(List<EntityAction> actions)` | Selects entity actions. |
| `containerActions(List<ContainerAction> actions)` | Selects container transaction actions. |
| `itemActions(List<ItemAction> actions)` | Selects world item transaction actions. |
| `inventoryActions(List<InventoryAction> actions)` | Selects source-specific inventory transactions. |
| `sessionActions(List<SessionAction> actions)` | Selects login/logout actions. |
| `textStartsWithAny(List<String> prefixes)` | Requires text to begin with at least one supplied prefix. |
| `textStartsWithNone(List<String> prefixes)` | Rejects text beginning with any supplied prefix. |
The last call to `world`, `location`, or `radius` determines the search area. With no area specified, spatial typed lookups search all worlds. The `blockLookup(Block, LookupOptions)` overload always uses the supplied block's location instead.
All filters that apply to a lookup are combined. Empty filter lists retain the default behavior. List setters copy their inputs and reject null lists or null entries. A built options object's lists cannot be modified. Passing `null` instead of a `LookupOptions` object uses the defaults.
Within `users`, an empty string or `#global` removes that list's user restriction. Within `excludeUsers`, either value excludes everyone. Use an empty list when you want no exclusion.
Results are returned in descending event order. Limits apply after filtering; omitting a limit permits all matching rows. Use a bounded time range and pagination for large histories. Offset pagination does not hold a snapshot across separate API calls, so new records can shift later pages.
| Lookup | User/time/limit | Spatial filters | Material filters | Additional filters |
| --- | --- | --- | --- | --- |
| Block | Yes | Yes; supplied `Block` overrides options | Yes | `blockActions` |
| Entity | Yes | Yes | No | `entityActions`, `includeEntities`, `excludeEntities` |
| Container | Yes | Yes | Yes | `containerActions` |
| Item | Yes | Yes | Yes | `itemActions` |
| Inventory | Yes | Yes | Yes | `inventoryActions` |
| Chat / command | Yes | Yes | No | Text prefixes |
| Sign | Yes | Yes | No | Text prefixes |
| Session | Yes | Yes | No | `sessionActions` |
| Username | Yes | No | No | None |
Filters outside a lookup's scope are ignored. Username history has no location, radius, or world.
### Text prefix filters
`textStartsWithAny` matches any included prefix. `textStartsWithNone` rejects any excluded prefix, so exclusions take precedence. Either option can be used alone.
The API uses the same SQL matching and escaping as the in-game `f:` / `filter:` option, including the database's case-handling rules. Each API list entry is already one literal prefix: commas, leading `-`, and leading/trailing spaces are preserved. `%`, `_`, `~`, and `*` are not wildcards. The API does not parse command-style comma separators or exclusion markers.
Prefixes must contain at least three Unicode code points. Shorter prefixes throw `IllegalArgumentException` when the builder method is called. Empty lists impose no restriction.
Sign filters check each line on the logged face. An inclusion match on any line includes the entry; an exclusion match on any line excludes the whole entry. Text on the opposite face does not affect the match.
### Action filters
Use the enum associated with the lookup. Multiple values match any listed action; an omitted or empty list retains that lookup's defaults.
| Lookup | Enum values and IDs | Default selection |
| --- | --- | --- |
| Block | `BlockAction.BREAK` = `0`, `PLACE` = `1`, `INTERACTION` = `2` | All three for `blockLookup(options)`; the supplied-block overload also retains entity events unless narrowed. |
| Entity | `EntityAction.KILL` = `3`, `SPAWN` = `13` | Both. |
| Container | `ContainerAction.REMOVE` = `0`, `ADD` = `1` | Both. |
| Session | `SessionAction.LOGOUT` = `0`, `LOGIN` = `1` | Both. |
| Item | See below. | `DROP`, `PICKUP`, `REMOVE_ENDER`, `ADD_ENDER`, `THROW`, `SHOOT`. |
| Inventory | See below. | Block placements, container transactions, and all supported item transactions. |
| `ItemAction` | ID | Result action string |
| --- | --- | --- |
| `DROP` | 2 | `drop` |
| `PICKUP` | 3 | `pickup` |
| `REMOVE_ENDER` | 4 | `withdraw` |
| `ADD_ENDER` | 5 | `deposit` |
| `THROW` | 6 | `throw` |
| `SHOOT` | 7 | `shoot` |
| `BREAK` | 8 | `break` |
| `DESTROY` | 9 | `destroy` |
| `CREATE` | 10 | `create` |
| `SELL` | 11 | `sell` |
| `BUY` | 12 | `buy` |
`BREAK`, `DESTROY`, `CREATE`, `SELL`, and `BUY` require explicit selection in `itemLookup`. They are included by default in `inventoryLookup`.
`InventoryAction` selects both a source and its underlying action:
| Source | `InventoryAction` values |
| --- | --- |
| Block | `BLOCK_PLACE` |
| Container | `CONTAINER_REMOVE`, `CONTAINER_ADD` |
| Item | `ITEM_DROP`, `ITEM_PICKUP`, `ITEM_REMOVE_ENDER`, `ITEM_ADD_ENDER`, `ITEM_THROW`, `ITEM_SHOOT`, `ITEM_BREAK`, `ITEM_DESTROY`, `ITEM_CREATE`, `ITEM_SELL`, `ITEM_BUY` |
Each action enum exposes `id()`. `InventoryAction` also exposes `sourceId()`. Container remove/add describes the container's contents: taking an item out of a container adds it to the player's inventory, and depositing an item removes it from the player's inventory.
### CoreProtectAction
`CoreProtectAction` names the general lookup categories. It exposes `id()`, `actionString()`, and `fromId(int)`; an unrecognized ID maps to `UNKNOWN`.
| Constant | ID | Action string |
| --- | --- | --- |
| `BLOCK_BREAK` | 0 | `break` |
| `BLOCK_PLACE` | 1 | `place` |
| `INTERACTION` | 2 | `click` |
| `ENTITY_KILL` | 3 | `kill` |
| `CONTAINER` | 4 | `container` |
| `CHAT` | 6 | `chat` |
| `COMMAND` | 7 | `command` |
| `SESSION` | 8 | `session` |
| `USERNAME` | 9 | `username` |
| `SIGN` | 10 | `sign` |
| `ITEM` | 11 | `item` |
| `ENTITY_SPAWN` | 13 | `spawn` |
| `UNKNOWN` | -1 | `unknown` |
The legacy `performLookup`, `performPartialLookup`, `performRollback`, and `performRestore` methods accept only block/entity action IDs `0`, `1`, `2`, `3`, and `13`. Use the dedicated typed methods for the other categories.
A result's `getActionId()` uses that result type's action values. For example, `SessionResult` uses `0`/`1` for logout/login, and `InventoryResult` uses `0`/`1` for removal/addition to the player's inventory. Do not interpret every result ID through `CoreProtectAction.fromId()`.
## API Status Methods
### APIVersion()
`int APIVersion()` returns the API version supported by the installed CoreProtect plugin. This version returns `13`. Check for `13` or higher before using API v13 methods; the API version is separate from the plugin's release version, such as `25.0`.
### isEnabled()
`boolean isEnabled()` returns `true` when the server has the CoreProtect API enabled in its configuration, and `false` when it is disabled. Check this before using the API.
### testAPI()
`void testAPI()` prints an API test message to the server console. With the default English language configuration, the message is:
```text
[CoreProtect] API test successful.
```
The message follows CoreProtect's language configuration. This method returns no value and does not test database reads or writes; use it to confirm that your plugin can call the API.
## Typed Lookup Methods
For convenience overloads taking `int time`, the argument is how many seconds to search back. As with `LookupOptions.time`, zero or negative values impose no time restriction.
### Block lookups
| Signature | Returns |
| --- | --- |
| `blockLookup(LookupOptions options)` | `List<BlockResult>` |
| `blockLookup(Block block, LookupOptions options)` | `List<BlockResult>` |
The options-only method searches logged block breaks, placements, and interactions. It supports exact locations, X/Z radius searches, whole-world searches, and searches across all worlds.
The supplied-block method always searches that block's coordinates and world. Location, radius, and world options are ignored. It retains entity kills and spawns stored at those coordinates unless an explicit `blockActions` filter narrows the search.
Material filters compare the logged material, regardless of the block currently at the location. Including materials selects matching block events. Excluding materials preserves entity events in the supplied-block overload. Use `getEntityType()` to read entity rows; `getType()` and `getBlockData()` return `null` for them.
### Entity lookups
| Signature | Returns |
| --- | --- |
| `entityLookup(LookupOptions options)` | `List<EntityResult>` |
Returns entity spawns and kills. Use `entityActions` to select `SPAWN` or `KILL`, and `includeEntities` / `excludeEntities` to filter Bukkit `EntityType` values. Empty lists select both actions and all entity types.
For spawn events, location/radius filters match either the original spawn location or the persisted current/final location. Whole-world filters similarly match either world. Kill events match the location where the kill was logged. Results always expose the **original event coordinates and world**, even when a tracked position matched the filter.
These synchronous lookups use persisted tracking data and do not wait for a live-entity scan. Material filters and other lookup-specific action filters do not affect entity lookups.
The in-game `a:block` filters present placed and destroyed boats/minecarts as block changes. That command/display alias does not change API action IDs: their spawn and kill rows remain `ENTITY_SPAWN` (`13`) and `ENTITY_KILL` (`3`).
### Container lookups
| Signature | Returns |
| --- | --- |
| `containerLookup(Location location, int time)` | `List<ContainerResult>` |
| `containerLookup(LookupOptions options)` | `List<ContainerResult>` |
Searches item additions to and removals from containers. The location/time overload searches an exact location. The options overload also supports radius, world, user, material, action, and pagination filters.
Both methods include tracked boat and minecart container transactions. Their location filters match either the transaction's original location or the entity's persisted current/final location. Typed results for these tracked containers expose the **persisted current/final coordinates and world**. Ordinary block containers expose the transaction location.
Tracked positions are checkpointed when transactions are logged and during relevant entity lifecycle changes. Lookups do not wait for a live-entity scan. `containerActions` applies to both block containers and tracked entity containers.
### Item lookups
| Signature | Returns |
| --- | --- |
| `itemLookup(LookupOptions options)` | `List<ItemResult>` |
Searches world item transactions, including drops, pickups, ender-chest transfers, throws, and shots by default. Use `itemActions` to select specific actions, including item breaks, destruction, creation, sales, and purchases. Those last five categories are not included in the default item lookup.
### Inventory lookups
| Signature | Returns |
| --- | --- |
| `inventoryLookup(LookupOptions options)` | `List<InventoryResult>` |
Searches transactions affecting player inventories. The default combines block placements, container transactions, and all supported world item transactions. Use `inventoryActions` to select source-specific events.
Results normalize the action to removal from (`0`) or addition to (`1`) the player's inventory. Use `getTransactionActionId()` and `getSourceId()` together to identify the underlying event. For example, a container removal is an inventory addition.
Tracked boat and minecart transactions follow the location rules described under container lookups and remain part of the public `container` source.
### Chat and command lookups
| Signature | Returns |
| --- | --- |
| `chatLookup(String user, int time)` | `List<MessageResult>` |
| `chatLookup(LookupOptions options)` | `List<MessageResult>` |
| `commandLookup(String user, int time)` | `List<MessageResult>` |
| `commandLookup(LookupOptions options)` | `List<MessageResult>` |
Searches logged chat or commands. The user/time overloads accept `null` or `#global` for all users. The options overloads also support spatial filters, user lists, pagination, and [text prefix filters](#text-prefix-filters).
Commands include their leading `/`. `MessageResult.getActionId()` is `6` for chat and `7` for commands.
### Sign lookups
| Signature | Returns |
| --- | --- |
| `signLookup(Location location, int time)` | `List<SignResult>` |
| `signLookup(LookupOptions options)` | `List<SignResult>` |
Searches logged written sign text. Returned rows use the placed/written action (`1`) and contain nonempty text; this method is not a listing of sign breaks or all sign state changes.
The location/time overload searches an exact location. The options overload supports user, spatial, text-prefix, and pagination filters. Results contain both sides' saved lines and identify which face was logged.
### Session lookups
| Signature | Returns |
| --- | --- |
| `sessionLookup(LookupOptions options)` | `List<SessionResult>` |
Searches player logins and logouts. Both are included by default. Use `sessionActions` to select `LOGIN` or `LOGOUT`, and shared options for user, time, spatial, and pagination filters.
The older `sessionLookup(String user, int time)` overload returns raw `String[]` rows; see [legacy block, session, and queue lookups](#legacy-block-session-and-queue-lookups).
### Username lookups
| Signature | Returns |
| --- | --- |
| `usernameLookup(String user, int time)` | `List<UsernameResult>` |
| `usernameLookup(LookupOptions options)` | `List<UsernameResult>` |
Searches the usernames recorded for player UUIDs. User filters accept current usernames, historical usernames, or UUID strings and resolve the associated username history. `null` or `#global` selects all users.
Use `getUsername()` for the historical logged name and `getUuid()` for its UUID. `getPlayer()` returns the current known username when available, with the logged name as a fallback.
Only user, time, and limit filters apply. Location, radius, world, material, entity, action, and text filters are ignored.
## Reading Results
### Shared fields
All typed result classes and `ParseResult` implement `CoreProtectResult`:
| Method | Return type | Meaning |
| --- | --- | --- |
| `getPlayer()` | `String` | Username associated with the event. See username history for its current-name behavior. |
| `getTimestamp()` | `long` | Unix timestamp in **milliseconds**. Lookup `time` parameters are in **seconds**. |
| `getX()`, `getY()`, `getZ()` | `int` | Block coordinates, with the tracked-location behavior described above. |
| `worldName()` | `String` | World name. Username results return an empty string and zero coordinates. |
| `getActionId()` | `int` | Action ID in the result type's action family. |
| `getActionString()` | `String` | Action name, such as `break`, `spawn`, `add`, or `login`. |
### BlockResult
| Additional method | Return type | Meaning |
| --- | --- | --- |
| `getType()` | `Material` | Logged block material; `null` for entity rows or an unresolved material. |
| `getBlockData()` | `BlockData` | Logged block state; `null` for entity rows. |
| `getEntityType()` | `EntityType` | Entity type for kill/spawn rows; `null` for block rows. |
| `getData()` | `int` | Deprecated legacy block data value. Prefer `getBlockData()`. |
| `isRolledBack()` | `boolean` | Whether the block/entity change is currently rolled back. |
Action strings are `break`, `place`, `click`, `kill`, or `spawn` as applicable.
### EntityResult
| Additional method | Return type | Meaning |
| --- | --- | --- |
| `getEntityType()` | `EntityType` | Logged entity type, including `PLAYER` for player-kill records. |
| `isRolledBack()` | `boolean` | Whether the entity change is currently rolled back. |
Actions are `KILL` (`3`, `kill`) and `SPAWN` (`13`, `spawn`).
### ContainerResult and ItemResult
| Additional method | Return type | Meaning |
| --- | --- | --- |
| `getType()` | `Material` | Logged item material, or `null` if unresolved. |
| `getAmount()` | `int` | Number of items. |
| `getMetadata()` | `byte[]` | Copy of the raw item metadata, or `null` when absent. |
| `isRolledBack()` | `boolean` | Whether the transaction is currently rolled back. |
| `ContainerResult.getData()` | `int` | Stored item data value. |
Container actions are `0` / `remove` and `1` / `add`. Item actions use the IDs and strings in the [item action table](#action-filters).
### InventoryResult
| Additional method | Return type | Meaning |
| --- | --- | --- |
| `getType()` | `Material` | Item material, with block sources normalized to an inventory item. |
| `getAmount()` | `int` | Number of items. |
| `getMetadata()` | `byte[]` | Copy of raw item metadata, or `null`. |
| `getData()` | `int` | Deprecated legacy item data value. |
| `getTransactionActionId()` | `int` | Underlying transaction action. Interpret together with its source. |
| `getTransactionActionString()` | `String` | Transaction action string, such as `remove`, `drop`, or `buy`. |
| `getSourceId()` | `int` | Public source: `0` = block, `1` = container, `2` = item. |
| `getSource()` | `String` | `block`, `container`, or `item`. |
| `isRolledBack()` | `boolean` | Whether the inventory change is currently rolled back. |
The shared `getActionId()` / `getActionString()` report `0` / `remove` or `1` / `add` from the player's inventory perspective. Tracked entity-container transactions use source `1` / `container`.
### MessageResult
`getMessage()` returns the chat message or command as a `String`. Actions are `6` / `chat` and `7` / `command`.
### SignResult
| Additional method | Return type | Meaning |
| --- | --- | --- |
| `getLine(int line)` | `String` | Zero-based saved line: `0`–`3` front, `4`–`7` back. An out-of-range index returns an empty string. |
| `getLines()` | `String[]` | Copy of all eight saved lines. |
| `getMessage()` | `String` | Nonempty lines on the logged face joined with spaces. Nonempty messages end with a space; a face with no text returns an empty string. |
| `isFront()` | `boolean` | Whether the logged face is the front. |
| `getColor()` | `int` | Stored front text color. |
| `getColorSecondary()` | `int` | Stored back text color. |
| `getData()` | `int` | Raw sign-state flags. |
| `isFrontGlowing()` / `isBackGlowing()` | `boolean` | Whether the corresponding side glows. |
| `isWaxed()` | `boolean` | Whether the sign is waxed. |
The returned action is `1` / `place`.
### SessionResult
Uses the shared fields. Actions are `0` / `logout` and `1` / `login`.
### UsernameResult
`getUsername()` returns the logged name and `getUuid()` returns the UUID as a `String`. The shared action is `9` / `username`. These results have no world or coordinates.
## Legacy Lookups, Rollbacks, and Restores
### Full and partial searches
`performLookup` searches persisted block and entity history using the supplied filters. `performPartialLookup` performs the same search with an offset and maximum result count for pagination. Use `parseResult` to read each returned row.
The following methods return `List<String[]>`:
```text
performLookup(time, restrictUsers, excludeUsers, restrictBlocks, excludeBlocks, actionList, radius, radiusLocation)
performPartialLookup(time, restrictUsers, excludeUsers, restrictBlocks, excludeBlocks, actionList, radius, radiusLocation, limitOffset, limitCount)
performRollback(time, restrictUsers, excludeUsers, restrictBlocks, excludeBlocks, actionList, radius, radiusLocation)
performRestore(time, restrictUsers, excludeUsers, restrictBlocks, excludeBlocks, actionList, radius, radiusLocation)
```
| Parameter | Type | Behavior |
| --- | --- | --- |
| `time` | `int` | How far back to search in seconds. Use a positive value; these legacy methods do not share the typed options' zero-means-all-time convention. |
| `restrictUsers` | `List<String>` | Users to include. `null` or an empty list selects all users and requires a positive radius and location. |
| `excludeUsers` | `List<String>` | Users to exclude; `null` imposes no exclusion. |
| `restrictBlocks` | `List<Object>` | Bukkit `Material` or `EntityType` values to include; `null` imposes no type restriction. |
| `excludeBlocks` | `List<Object>` | Bukkit `Material` or `EntityType` values to exclude; `null` imposes no type exclusion. |
| `actionList` | `List<Integer>` | Block/entity action IDs. Accepted IDs are `0`, `1`, `2`, `3`, and `13`. |
| `radius` | `int` | Positive X/Z radius at all heights. Zero or a negative value disables the radius. |
| `radiusLocation` | `Location` | Center of a positive radius. May be `null` for user-restricted searches without a radius. |
| `limitOffset` | `int` | Zero-based matching-row offset for `performPartialLookup`. |
| `limitCount` | `int` | Maximum rows for `performPartialLookup`. Use nonnegative offset/count values. |
With no explicit actions or type restrictions, the action list defaults to block breaks and placements. Material restrictions infer block breaks/placements; entity-type restrictions infer entity kills. To include entity spawns, explicitly select `CoreProtectAction.ENTITY_SPAWN.id()`.
Unsupported explicit action IDs are ignored. If none remain, the method returns an empty list. An unrestricted global search without a positive radius, or a positive radius without a location, returns `null`.
For legacy lookups, a supplied location also restricts the world even when the radius is disabled. Use typed options for a clear whole-world or exact-location search.
### Rollback and restore behavior
`performRollback` reverses the selected changes. `performRestore` reapplies previously rolled-back changes. Both require an asynchronous caller and return raw result rows. They do not accept `LookupOptions`.
Entity spawns are opt-in through action ID `13`. This preserves legacy default block behavior; in-game rollbacks/restores with no action filter can include entity spawns when `rollback-entities` is enabled. Spawn lookup selection can match original or tracked positions, while rollback/restore world selection uses the tracked current/final world.
An empty result can also indicate that CoreProtect could not start the operation, including during a purge, a conflicting database operation, or when called from the primary server thread. Do not treat it as a guaranteed successful rollback of zero rows.
### Legacy block, session, and queue lookups
| Signature | Returns | Behavior |
| --- | --- | --- |
| `blockLookup(Block block, int time)` | `List<String[]>` | Searches persisted block-table history at the supplied block coordinates, including entity rows. `time` is seconds to look back. |
| `sessionLookup(String user, int time)` | `List<String[]>` | Searches persisted login/logout history for the user. `time` is seconds to look back. |
| `queueLookup(Block block)` | `List<String[]>` | Searches pending block breaks and placements at the block's location that have not yet been persisted. |
The legacy block and session overloads impose no time restriction when `time` is zero or negative. The legacy session overload requires a concrete username; `null`, an empty string, or `#global` returns no results. Use typed session options to search all users.
Queue lookups cover pending block changes, not every logging category. A newly queued event might not yet appear in a database lookup. Queue result timestamps reflect the time of the queue lookup, not the original event time, so they cannot reliably enforce an event-age filter.
### Parsing legacy rows
Call `api.parseResult(row)` for rows returned by these legacy methods. It returns `CoreProtectAPI.ParseResult`, which extends `net.coreprotect.api.result.ParseResult` and preserves the existing nested-class import.
`ParseResult` implements the [shared result fields](#shared-fields) and adds:
| Method | Return type | Meaning |
| --- | --- | --- |
| `getType()` | `Material` | Logged block material; `null` for entity or session rows. |
| `getBlockData()` | `BlockData` | Logged block data; `null` for entity or session rows. |
| `getEntityType()` | `EntityType` | Entity type for entity rows; `null` for block or session rows. |
| `isRolledBack()` | `boolean` | Whether the block/entity change is rolled back; false for session rows. |
| `getData()` | `int` | Deprecated legacy data value. |
| `getTime()` | `int` | Deprecated Unix timestamp in seconds. Prefer `getTimestamp()` for milliseconds. |
Block/entity action strings are lowercase `break`, `place`, `click`, `kill`, and `spawn`. Session rows use `logout` and `login`. Use the typed result classes for their corresponding lookup methods instead of passing typed results to `parseResult`.
## Logging and Utility Methods
### Block placement and removal
All of these overloads return `boolean`:
| Signature | Data recorded |
| --- | --- |
| `logPlacement(String user, BlockState blockState)` | Placement using the supplied block-state snapshot. |
| `logPlacement(String user, Location location, Material type, BlockData blockData)` | Placement of the supplied material and optional block data. |
| `logPlacement(String user, Location location, BlockData blockData)` | Placement using the current block's material and supplied non-null block data. |
| `logRemoval(String user, BlockState blockState)` | Removal using the supplied block-state snapshot. |
| `logRemoval(String user, Location location, Material type, BlockData blockData)` | Removal of the supplied material and optional block data, with container-break handling. |
| `logRemoval(String user, Location location, BlockData blockData)` | Removal using the current block's material and supplied non-null block data, with container-break handling. |
`user` identifies the player or plugin responsible for the change. Capture removal data **before** changing the block. For placements, supply the state/material being placed. The explicit-material overloads allow `null` block data; the `Location, BlockData` overloads require non-null data.
Location-based removal checks the existing block for container contents. The `BlockState` overload does not perform that separate container-break check. Call methods that inspect live blocks on the appropriate server thread.
Deprecated placement/removal overloads taking `(String user, Location location, Material type, byte data)` remain for older integrations. Use `BlockData` for new code.
### Chat and commands
| Signature | Behavior |
| --- | --- |
| `logChat(Player player, String message)` | Queues a nonempty chat message. Messages beginning with `/` are rejected. |
| `logCommand(Player player, String command)` | Queues a nonempty command, which must begin with `/`. |
Both return `boolean`, require the API to be enabled, and honor the world's corresponding player-message or player-command logging setting. Supply a valid player with a world.
### Container transactions
`boolean logContainerTransaction(String user, Location location)` captures a block inventory so CoreProtect can detect your subsequent changes. Call it **immediately before** modifying the inventory, on the appropriate server thread.
`user` is the username or plugin actor credited with adding or removing the items. `location` is the block container's location.
This method is for a block container. Tracked entity-container lookup support does not add an entity overload to this logging method.
### Block interactions
`boolean logInteraction(String user, Location location)` records an interaction with the block currently at the location. It reads the block's material and block data.
`user` is the username or plugin actor credited with the interaction. `location` identifies the affected block.
### Placement and removal checks
| Signature | Behavior |
| --- | --- |
| `hasPlaced(String user, Block block, int time, int offset)` | Checks whether the user placed a block at these coordinates during the requested window. |
| `hasRemoved(String user, Block block, int time, int offset)` | Checks whether the user removed a block at these coordinates during the requested window. |
Both return `boolean` and compare usernames without case sensitivity. `time` is how many seconds to search back; `offset` excludes the most recent seconds from that window. Use `offset = 0` to include the latest persisted records. These methods search persisted history; use `queueLookup` separately for pending block changes.
### Purging history
`void performPurge(int time)` dispatches the console command `co purge t:<time>s`. The argument is the age in seconds beyond which history should be purged: `120` selects records older than two minutes.
Call this command-dispatching method on the appropriate server thread. It returns no completion result and uses the normal command's purge behavior and restrictions.
## Available Events
### CoreProtectPreLogEvent
`net.coreprotect.event.CoreProtectPreLogEvent` is a cancellable **asynchronous** event emitted by participating loggers before they write a record. Keep listeners safe for asynchronous execution and avoid accessing live Bukkit world state from the listener.
| Getter | Meaning | Setter |
| --- | --- | --- |
| `getUser()` | User under which the action will be logged. | `setUser(String)` |
| `getLocation()` | Location associated with the record. | `setLocation(Location)` |
| `getAction()` | `CoreProtectPreLogEvent.Action` category. | None |
| `getActionId()` | Logger-specific action value, or `-1` when not applicable. | None |
| `getMaterial()` | Material when applicable; otherwise `null`. | None |
| `getEntityType()` | Entity type when applicable; otherwise `null`. | None |
| `getMessage()` | Message metadata when applicable; otherwise `null`. | None |
| `isCancelled()` | Whether this record has been cancelled. | `setCancelled(boolean)` |
`setUser` rejects null or empty names. `setLocation` rejects null. Cancelling prevents the record from being logged; it does not undo the gameplay action.
The action enum contains:
| Category | Meaning |
| --- | --- |
| `UNKNOWN` | Unspecified action, including compatibility constructors. |
| `BLOCK_BREAK`, `BLOCK_PLACE` | Block removal or placement. |
| `PLAYER_INTERACTION` | Player interaction with a block. |
| `ENTITY_KILL`, `PLAYER_KILL` | Entity or player death. |
| `ENTITY_SPAWN` | Entity spawn. |
| `ENTITY_INTERACTION` | Interaction with a tracked entity. |
| `SIGN_TEXT` | Sign text logging. |
| `CONTAINER_TRANSACTION` | Container item transaction. |
| `ITEM_TRANSACTION` | World item transaction. |
| `PLAYER_COMMAND` | Player command. |
Use `getAction()` to identify the category before interpreting the action ID. Container, item, and sign events use their own stored action values; player-command events use `-1`. Command events expose their command as the message. Entity-interaction events expose the interaction action name. Sign-text events do not provide their lines through `getMessage()`.
The event is not emitted for every logging category; for example, chat and session logging do not emit it.
The metadata constructor is:
```text
CoreProtectPreLogEvent(String user, Location location, Action action, int actionId, Material material, EntityType entityType, String message)
```
`CoreProtectPreLogEvent(String user, Location location)` remains available. The older `CoreProtectPreLogEvent(String user)` constructor is deprecated and supplies a null location. Listeners should tolerate absent optional metadata.
## Examples
These examples use an enabled API v13 instance named `api` and your plugin instance named `plugin`. Import `java.util.List`, the required Bukkit types, and the relevant classes from `net.coreprotect.api` and `net.coreprotect.api.result`.
Capture `location`, `world`, and other live Bukkit values before starting asynchronous work. In the examples that show only a lookup body, run that body asynchronously as demonstrated below. Database queries return lists directly; they do not create a background task themselves.
### Search blocks asynchronously
Find the first 100 block changes by Notch in the last minute, excluding dirt and grass blocks:
```java
LookupOptions options = LookupOptions.builder()
.user("Notch")
.time(60)
.excludeMaterials(List.of(Material.DIRT, Material.GRASS_BLOCK))
.limit(0, 100)
.build();
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
List<BlockResult> results = api.blockLookup(options);
if (results != null) {
for (BlockResult result : results) {
plugin.getLogger().info(result.getPlayer() + " " + result.getActionString()
+ " " + result.getType() + " at " + result.worldName()
+ " " + result.getX() + "," + result.getY() + "," + result.getZ());
}
}
});
```
Use `.radius(location, 5)` to restrict this search to an X/Z area, or `.world(world)` for a whole world. To use the exact-block compatibility overload:
```java
List<BlockResult> results = api.blockLookup(block, LookupOptions.builder()
.time(60)
.blockActions(List.of(BlockAction.BREAK, BlockAction.PLACE))
.limit(0, 100)
.build());
```
### Find entity spawns
Find zombie and skeleton spawns within 50 blocks of a captured location:
```java
LookupOptions options = LookupOptions.builder()
.time(3600)
.radius(location, 50)
.entityActions(List.of(EntityAction.SPAWN))
.includeEntities(List.of(EntityType.ZOMBIE, EntityType.SKELETON))
.limit(0, 100)
.build();
List<EntityResult> results = api.entityLookup(options);
```
The search can match a tracked position, but each result still reports its original spawn coordinates.
### Find container deposits
Find diamonds added to containers within five blocks of a location:
```java
LookupOptions options = LookupOptions.builder()
.time(3600)
.radius(location, 5)
.includeMaterials(List.of(Material.DIAMOND))
.containerActions(List.of(ContainerAction.ADD))
.limit(0, 100)
.build();
List<ContainerResult> results = api.containerLookup(options);
```
For an exact-location lookup without additional filters, use `api.containerLookup(location, 60)`.
### Find item and inventory transactions
Look up dropped and picked-up items:
```java
LookupOptions options = LookupOptions.builder()
.user("Notch")
.time(3600)
.itemActions(List.of(ItemAction.DROP, ItemAction.PICKUP))
.limit(0, 100)
.build();
List<ItemResult> results = api.itemLookup(options);
```
Find inventory removals caused by container deposits or block placements:
```java
LookupOptions options = LookupOptions.builder()
.user("Notch")
.time(3600)
.inventoryActions(List.of(InventoryAction.CONTAINER_ADD, InventoryAction.BLOCK_PLACE))
.limit(0, 100)
.build();
List<InventoryResult> results = api.inventoryLookup(options);
if (results != null) {
for (InventoryResult result : results) {
plugin.getLogger().info(result.getActionString() + " " + result.getAmount()
+ " " + result.getType() + " via " + result.getSource()
+ ":" + result.getTransactionActionString());
}
}
```
### Filter commands by prefix
Find `/shop ...` and `/market ...` commands from the last hour, excluding commands beginning with `/shop admin`:
```java
LookupOptions options = LookupOptions.builder()
.time(3600)
.textStartsWithAny(List.of("/shop ", "/market "))
.textStartsWithNone(List.of("/shop admin"))
.limit(0, 100)
.build();
List<MessageResult> results = api.commandLookup(options);
```
The trailing spaces in the inclusion prefixes are significant: bare `/shop` and `/market` commands do not match. Use `chatLookup(options)` to apply the same kind of filters to chat, or `chatLookup("Notch", 60)` for an unfiltered user/time search.
### Find sign text
Find signs whose logged face has a line beginning with `Diamond Shop`:
```java
LookupOptions options = LookupOptions.builder()
.time(3600)
.radius(location, 10)
.textStartsWithAny(List.of("Diamond Shop"))
.limit(0, 100)
.build();
List<SignResult> results = api.signLookup(options);
if (results != null) {
for (SignResult result : results) {
plugin.getLogger().info((result.isFront() ? "Front: " : "Back: ") + result.getMessage());
}
}
```
Use `api.signLookup(location, 60)` for an exact-location search without additional filters.
### Read sessions and username history
Find logins for Notch in the last day:
```java
LookupOptions options = LookupOptions.builder()
.user("Notch")
.time(24 * 60 * 60)
.sessionActions(List.of(SessionAction.LOGIN))
.limit(0, 100)
.build();
List<SessionResult> results = api.sessionLookup(options);
```
Read username history for the same period:
```java
List<UsernameResult> results = api.usernameLookup("Notch", 24 * 60 * 60);
if (results != null) {
for (UsernameResult result : results) {
plugin.getLogger().info(result.getUuid() + " used " + result.getUsername());
}
}
```
### Parse a legacy lookup
Read the last minute of Notch's block history and parse its coordinates:
```java
List<String[]> rows = api.performLookup(60, List.of("Notch"), null,
null, null, null, 0, null);
if (rows != null) {
for (String[] row : rows) {
CoreProtectAPI.ParseResult result = api.parseResult(row);
plugin.getLogger().info(result.getActionString() + " at "
+ result.getX() + "," + result.getY() + "," + result.getZ());
}
}
```
For a page of results within five blocks of a location:
```java
List<String[]> rows = api.performPartialLookup(60, null, null,
null, null, List.of(CoreProtectAction.BLOCK_BREAK.id(), CoreProtectAction.BLOCK_PLACE.id()),
5, location, 0, 100);
```
The deprecated single-user overloads of `performLookup`, `performPartialLookup`, `performRollback`, and `performRestore` remain available. Prefer the list-based signatures above for new integrations.
### Roll back changes asynchronously
Reverse Notch's block changes and entity spawns from the last minute within five blocks of a captured location:
```java
List<Integer> actions = List.of(CoreProtectAction.BLOCK_BREAK.id(),
CoreProtectAction.BLOCK_PLACE.id(), CoreProtectAction.ENTITY_SPAWN.id());
Location center = location.clone();
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
List<String[]> rows = api.performRollback(60, List.of("Notch"), null,
null, null, actions, 5, center);
if (rows != null) {
plugin.getLogger().info("Rollback returned " + rows.size() + " rows.");
}
});
```
To restore changes, use `performRestore` with the same parameter structure in an asynchronous task. An empty result alone does not distinguish no matches from an operation that could not start.
### Include pending block changes in a placement check
```java
boolean placed = api.hasPlaced("Notch", block, 60, 0);
if (!placed) {
List<String[]> pending = api.queueLookup(block);
if (pending != null) {
for (String[] row : pending) {
CoreProtectAPI.ParseResult result = api.parseResult(row);
if (result.getActionId() == CoreProtectAction.BLOCK_PLACE.id()
&& result.getPlayer().equalsIgnoreCase("Notch")) {
placed = true;
break;
}
}
}
}
plugin.getLogger().info("Recent placement found: " + placed);
```
The persisted check covers the last 60 seconds; the pending check cannot enforce that age because queue rows use the lookup time as their timestamp. Use `hasRemoved` and `CoreProtectAction.BLOCK_BREAK.id()` for removal checks. A nonzero `offset` on the persisted check can exclude the most recent seconds when needed by your integration.
### Log a block replacement
Run this on the appropriate server thread, while the original block still exists:
```java
api.logRemoval("Notch", block.getLocation(), block.getType(), block.getBlockData());
block.setType(Material.STONE);
api.logPlacement("Notch", block.getState());
```
Use the actual responsible username or a plugin-specific actor name. If CoreProtect already records the gameplay event, additional API logging can create duplicate history.
### Log inventory changes
For a block-backed `inventory`, capture its contents immediately before changing them:
```java
api.logContainerTransaction("Notch", inventory.getLocation());
inventory.addItem(itemStack);
```
### Listen for pre-log events
This listener cancels block-placement records attributed to a plugin's preview actor:
```java
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import net.coreprotect.event.CoreProtectPreLogEvent;
public final class PreviewLogListener implements Listener {
@EventHandler
public void onPreLog(CoreProtectPreLogEvent event) {
if (event.getAction() == CoreProtectPreLogEvent.Action.BLOCK_PLACE
&& "MyPluginPreview".equals(event.getUser())) {
event.setCancelled(true);
}
}
}
```
Register it during plugin initialization:
```java
plugin.getServer().getPluginManager().registerEvents(new PreviewLogListener(), plugin);
```
The listener executes asynchronously. Cancelling it suppresses the log record, not the original block placement.
+450
View File
@@ -0,0 +1,450 @@
# API Version 7
The CoreProtect API enables you to log your own block changes, perform lookups, rollbacks, restores, and more.
| API Details | |
| --- | --- |
| **API Version:** | 7 |
| **Plugin Version:** | v20.0+ |
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
<span style="color:red;">*Documentation for the latest API version can be found [here](/api/).*</span>
---
## Upgrading from API v6
The changes from the previous API version are as follows:
- The following methods have been added:
```java
parseResult(String[] result).getTimestamp()
```
---
- The following methods have been deprecated:
```java
parseResult(String[] result).getTime()
```
---
- The following methods have been removed:
```java
parseResult(String[] result).getTypeId()
```
---
## Getting Started
Ensure you're using CoreProtect 20.0 or higher. Add it as an external jar to your plugin in your IDE.
Alternatively, if using Maven, you can add it via the repository [https://maven.playpro.com](https://maven.playpro.com) (net.coreprotect, 20.0).
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
```java
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
// Check that CoreProtect is loaded
if (plugin == null || !(plugin instanceof CoreProtect)) {
return null;
}
// Check that the API is enabled
CoreProtectAPI CoreProtect = ((CoreProtect) plugin).getAPI();
if (CoreProtect.isEnabled() == false) {
return null;
}
// Check that a compatible version of the API is loaded
if (CoreProtect.APIVersion() < 7) {
return null;
}
return CoreProtect;
}
```
With this code, you can then access the API with a call like the following:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null){ // Ensure we have access to the API
api.testAPI(); // Will print out "[CoreProtect] API test successful." in the console.
}
```
Yay, you're now using the CoreProtect API!
---
## API Overview
### Available Methods
```java
boolean isEnabled()
void testAPI()
List<String[]> performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> blockLookup(Block block, int time)
ParseResult parseResult(String[] result)
boolean logChat(Player player, String message)
boolean logCommand(Player player, String command)
boolean logPlacement(String user, Location location, Material type, BlockData blockData)
boolean logRemoval(String user, Location location, Material type, BlockData blockData)
boolean logContainerTransaction(String user, Location location)
boolean logInteraction(String user, Location location)
boolean hasPlaced(String user, Block block, int time, int offset)
boolean hasRemoved(String user, Block block, int time, int offset)
void performPurge(int time)
```
---
### Method Usage
*Detailed method information is listed below.*
#### `isEnabled()`
Calling this will return true if the server has the CoreProtect API enabled, and false if it does not.
---
#### `testAPI()`
Running this will print out "[CoreProtect] API Test Successful." in the server console.
---
#### `performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a lookup.
* **time:** Specify the amount of time to search back. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the lookup on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the lookup. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the search to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the search. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the search to. Can be set to "null"
* **radius:** Specify a radius to restrict the search to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to search around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a rollback. Method must be called async.
* **time:** Specify the amount of time to rollback. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the rollback on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the rollback. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the rollback to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the rollback. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the rollback to. Can be set to "null"
* **radius:** Specify a radius to restrict the rollback to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to rollback around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a restore.
* **time:** Specify the amount of time to restore. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the restore on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the restore. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the restore to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the restore. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the restore to. Can be set to "null"
* **radius:** Specify a radius to restrict the restore to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to restore around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `blockLookup(Block block, int time)`
This will perform a full lookup on a single block.
* **block:** The block to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `ParseResult parseResult(String[] result)`
This will parse results from a lookup. You'll then be able to view the following:
* **getX():** Get the X coordinate of the block.
* **getY():** Get the Y coordinate of the block.
* **getZ():** Get the Z coordinate of the block.
* **getType():** Get the Material of the block.
* **getBlockData():** Get the BlockData of the block.
* **getPlayer():** Get the username as a string.
* **getTimestamp():** Get the time of the action.
* **getActionId():** Get the action ID. (0=removed, 1=placed, 2=interaction)
* **getActionString():** Get the action as a string. (Removal, Placement, Interaction)
* **isRolledBack():** If the block is currently rolled back or not.
* **worldName():** The name of the world the block is located in.
---
#### `logPlacement(String user, Location location, Material type, BlockData blockData)`
This will log a block as being placed.
* **user:** Specify the username to log as having placed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logRemoval(String user, Location location, Material type, BlockData blockData)`
This will log a block as being removed/broken, and will log the block's inventory (if applicable).
* **user:** Specify the username to log as having removed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logContainerTransaction(String user, Location location)`
This will log any transactions made to a block's inventory immediately after calling the method.
* **user:** Specify the username to log as having added/removed the items.
* **location:** Specify the location of the block inventory you're logging.
---
#### `logInteraction(String user, Location location)`
This will log a block as having been interacted with.
* **user:** Specify the username to log as having caused the interaction.
* **location:** Specify the location of the interaction you're logging.
---
#### `hasPlaced(String user, Block block, int time, int offset)`
This will return true if a user has already placed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've placed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0=no offset)
---
#### `hasRemoved(String user, Block block, int time, int offset)`
This will return true if a user has already removed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've removed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0=no offset)
---
#### `performPurge(int time)`
This will perform a purge on the CoreProtect database.
* **time:** Purge any data earlier than this. "120" would purge any data older than 120 seconds (2 minutes).
---
### Examples
- Get the last 60 seconds of block data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup!=null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data for the user "Notch", excluding dirt and grass blocks.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<Object> exclude = Arrays.asList(Material.DIRT, Material.GRASS);
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, exclude, null, 0, null);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data within 5 blocks of a location.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, null, null, null, null, null, 5, location);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Rollbacks / restores use the same code structure as the above examples. For example:
```java
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performRollback(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
- Check if the user "Notch" has already placed a block at a location within the last 60 seconds.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean hasPlaced = CoreProtect.HasPlaced("Notch", block, 60, 0);
}
```
---
- Get the last 60 seconds of block data for a specific block.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.blockLookup(block, 60);
if (lookup!=null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Log the placement of a block at a location by the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean success = CoreProtect.logPlacement("Notch", block.getLocation(), block.getType(), block.getData());
}
```
---
- Log adding/remove items in a chest (or some other block inventory).
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean success = CoreProtect.logContainerTransaction("Notch", inventory.getLocation());
// modify your container contents immediately after (e.g. [i]inventory.addItem(itemStack);[/i])
}
```
---
- Perform a multi-threaded placement check to see if the user "Notch" has already placed a block at a location within the last 60 seconds. This ignores the most recent 1 second of logged data, to account for the fact that that new block data may have already been logged, depending on your code.
```java
final Block block = null; //Should be an actual block
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 1);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
+450
View File
@@ -0,0 +1,450 @@
# API Version 8
The CoreProtect API enables you to log your own block changes, perform lookups, rollbacks, restores, and more.
| API Details | |
| --- | --- |
| **API Version:** | 8 |
| **Plugin Version:** | v20.2+ |
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
<span style="color:red;">*Documentation for the latest API version can be found [here](/api/).*</span>
---
## Upgrading from API v7
The changes from the previous API version are as follows:
- The following events have been added:
```java
CoreProtectPreLogEvent
```
---
## Getting Started
Ensure you're using CoreProtect 20.2 or higher. Add it as an external jar to your plugin in your IDE.
Alternatively, if using Maven, you can add it via the repository [https://maven.playpro.com](https://maven.playpro.com) (net.coreprotect, 20.2).
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
```java
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
// Check that CoreProtect is loaded
if (plugin == null || !(plugin instanceof CoreProtect)) {
return null;
}
// Check that the API is enabled
CoreProtectAPI CoreProtect = ((CoreProtect) plugin).getAPI();
if (CoreProtect.isEnabled() == false) {
return null;
}
// Check that a compatible version of the API is loaded
if (CoreProtect.APIVersion() < 8) {
return null;
}
return CoreProtect;
}
```
With this code, you can then access the API with a call like the following:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null){ // Ensure we have access to the API
api.testAPI(); // Will print out "[CoreProtect] API test successful." in the console.
}
```
Yay, you're now using the CoreProtect API!
---
## API Overview
### Available Methods
```java
boolean isEnabled()
void testAPI()
List<String[]> performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> blockLookup(Block block, int time)
ParseResult parseResult(String[] result)
boolean logChat(Player player, String message)
boolean logCommand(Player player, String command)
boolean logPlacement(String user, Location location, Material type, BlockData blockData)
boolean logRemoval(String user, Location location, Material type, BlockData blockData)
boolean logContainerTransaction(String user, Location location)
boolean logInteraction(String user, Location location)
boolean hasPlaced(String user, Block block, int time, int offset)
boolean hasRemoved(String user, Block block, int time, int offset)
void performPurge(int time)
```
---
### Available Events
*The following events are emitted by CoreProtect.*
#### CoreProtectPreLogEvent
Fired when a CoreProtect logger is about to log an action. Not cancellable.
| Property | Description | Mutable |
| --- | --- | --- |
| User | The name of the user under which this action will be logged. | Yes |
---
### Method Usage
*Detailed method information is listed below.*
#### `isEnabled()`
Calling this will return true if the server has the CoreProtect API enabled, and false if it does not.
---
#### `testAPI()`
Running this will print out "[CoreProtect] API Test Successful." in the server console.
---
#### `performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a lookup.
* **time:** Specify the amount of time to search back. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the lookup on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the lookup. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the search to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the search. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the search to. Can be set to "null"
* **radius:** Specify a radius to restrict the search to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to search around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a rollback. Method must be called async.
* **time:** Specify the amount of time to rollback. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the rollback on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the rollback. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the rollback to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the rollback. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the rollback to. Can be set to "null"
* **radius:** Specify a radius to restrict the rollback to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to rollback around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a restore.
* **time:** Specify the amount of time to restore. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the restore on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the restore. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the restore to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the restore. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the restore to. Can be set to "null"
* **radius:** Specify a radius to restrict the restore to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to restore around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `blockLookup(Block block, int time)`
This will perform a full lookup on a single block.
* **block:** The block to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `ParseResult parseResult(String[] result)`
This will parse results from a lookup. You'll then be able to view the following:
* **getX():** Get the X coordinate of the block.
* **getY():** Get the Y coordinate of the block.
* **getZ():** Get the Z coordinate of the block.
* **getType():** Get the Material of the block.
* **getBlockData():** Get the BlockData of the block.
* **getPlayer():** Get the username as a string.
* **getTimestamp():** Get the time of the action.
* **getActionId():** Get the action ID. (0=removed, 1=placed, 2=interaction)
* **getActionString():** Get the action as a string. (Removal, Placement, Interaction)
* **isRolledBack():** If the block is currently rolled back or not.
* **worldName():** The name of the world the block is located in.
---
#### `logPlacement(String user, Location location, Material type, BlockData blockData)`
This will log a block as being placed.
* **user:** Specify the username to log as having placed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logRemoval(String user, Location location, Material type, BlockData blockData)`
This will log a block as being removed/broken, and will log the block's inventory (if applicable).
* **user:** Specify the username to log as having removed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logContainerTransaction(String user, Location location)`
This will log any transactions made to a block's inventory immediately after calling the method.
* **user:** Specify the username to log as having added/removed the items.
* **location:** Specify the location of the block inventory you're logging.
---
#### `logInteraction(String user, Location location)`
This will log a block as having been interacted with.
* **user:** Specify the username to log as having caused the interaction.
* **location:** Specify the location of the interaction you're logging.
---
#### `hasPlaced(String user, Block block, int time, int offset)`
This will return true if a user has already placed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've placed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0=no offset)
---
#### `hasRemoved(String user, Block block, int time, int offset)`
This will return true if a user has already removed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've removed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0=no offset)
---
#### `performPurge(int time)`
This will perform a purge on the CoreProtect database.
* **time:** Purge any data earlier than this. "120" would purge any data older than 120 seconds (2 minutes).
---
### Examples
- Get the last 60 seconds of block data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup!=null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data for the user "Notch", excluding dirt and grass blocks.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<Object> exclude = Arrays.asList(Material.DIRT, Material.GRASS);
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, exclude, null, 0, null);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data within 5 blocks of a location.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, null, null, null, null, null, 5, location);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Rollbacks / restores use the same code structure as the above examples. For example:
```java
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.performRollback(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup!=null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
- Check if the user "Notch" has already placed a block at a location within the last 60 seconds.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean hasPlaced = CoreProtect.HasPlaced("Notch", block, 60, 0);
}
```
---
- Get the last 60 seconds of block data for a specific block.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
List<String[]> lookup = CoreProtect.blockLookup(block, 60);
if (lookup!=null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Log the placement of a block at a location by the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean success = CoreProtect.logPlacement("Notch", block.getLocation(), block.getType(), block.getData());
}
```
---
- Log adding/remove items in a chest (or some other block inventory).
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean success = CoreProtect.logContainerTransaction("Notch", inventory.getLocation());
// modify your container contents immediately after (e.g. [i]inventory.addItem(itemStack);[/i])
}
```
---
- Perform a multi-threaded placement check to see if the user "Notch" has already placed a block at a location within the last 60 seconds. This ignores the most recent 1 second of logged data, to account for the fact that that new block data may have already been logged, depending on your code.
```java
final Block block = null; //Should be an actual block
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect!=null){ //Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 1);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
+504
View File
@@ -0,0 +1,504 @@
# API Version 9
The CoreProtect API enables you to log your own block changes, perform lookups, rollbacks, restores, and more.
| API Details | |
| --- | --- |
| **API Version:** | 9 |
| **Plugin Version:** | v21.0+ |
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
---
## Upgrading from API v8
The changes from the previous API version are as follows:
- The following methods have been added:
```java
sessionLookup(String user, int time)
queueLookup(Block block)
```
---
## Getting Started
Ensure you're using CoreProtect 21.0 or higher. Add it as an external jar to your plugin in your IDE.
Alternatively, if using Maven, you can add it via the repository [https://maven.playpro.com](https://maven.playpro.com) (net.coreprotect, 21.0).
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
```java
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
private CoreProtectAPI getCoreProtect() {
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
// Check that CoreProtect is loaded
if (plugin == null || !(plugin instanceof CoreProtect)) {
return null;
}
// Check that the API is enabled
CoreProtectAPI CoreProtect = ((CoreProtect) plugin).getAPI();
if (CoreProtect.isEnabled() == false) {
return null;
}
// Check that a compatible version of the API is loaded
if (CoreProtect.APIVersion() < 9) {
return null;
}
return CoreProtect;
}
```
With this code, you can then access the API with a call like the following:
```java
CoreProtectAPI api = getCoreProtect();
if (api != null){ // Ensure we have access to the API
api.testAPI(); // Will print out "[CoreProtect] API test successful." in the console.
}
```
Yay, you're now using the CoreProtect API!
---
## API Overview
### Available Methods
```java
boolean isEnabled()
void testAPI()
List<String[]> performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)
List<String[]> blockLookup(Block block, int time)
List<String[]> sessionLookup(String user, int time)
List<String[]> queueLookup(Block block)
ParseResult parseResult(String[] result)
boolean logChat(Player player, String message)
boolean logCommand(Player player, String command)
boolean logPlacement(String user, Location location, Material type, BlockData blockData)
boolean logRemoval(String user, Location location, Material type, BlockData blockData)
boolean logContainerTransaction(String user, Location location)
boolean logInteraction(String user, Location location)
boolean hasPlaced(String user, Block block, int time, int offset)
boolean hasRemoved(String user, Block block, int time, int offset)
void performPurge(int time)
```
---
### Available Events
*The following events are emitted by CoreProtect.*
#### CoreProtectPreLogEvent
Fired when a CoreProtect logger is about to log an action. Cancellable.
| Property | Description | Mutable |
| --- | --- | --- |
| User | The name of the user under which this action will be logged. | Yes |
| Cancelled | If cancelled, the action won't be logged to the database. | Yes |
---
### Method Usage
*Detailed method information is listed below.*
#### `isEnabled()`
Calling this will return true if the server has the CoreProtect API enabled, and false if it does not.
---
#### `testAPI()`
Running this will print out "[CoreProtect] API Test Successful." in the server console.
---
#### `performLookup(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a lookup.
* **time:** Specify the amount of time to search back. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the lookup on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the lookup. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the search to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the search. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the search to. Can be set to "null"
* **radius:** Specify a radius to restrict the search to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to search around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRollback(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a rollback. Method must be called async.
* **time:** Specify the amount of time to rollback. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the rollback on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the rollback. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the rollback to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the rollback. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the rollback to. Can be set to "null"
* **radius:** Specify a radius to restrict the rollback to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to rollback around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `performRestore(int time, List<String> restrict_users, List<String> exclude_users, List<Object> restrict_blocks, List<Object> exclude_blocks, List<Integer> action_list, int radius, Location radius_location)`
This will perform a restore.
* **time:** Specify the amount of time to restore. "5" would return results from the last 5 seconds.
* **restrict_users:** Specify any usernames to perform the restore on. Can be set to "null" if both a radius and a location are specified.
* **exclude_users:** Specify any usernames to exclude from the restore. Can be set to "null".
* **restrict_blocks:** Specify a list of EntityType's or Material's to restrict the restore to. Can be set to "null".
* **exclude_blocks:** Specify a list of EntityType's or Material's to exclude from the restore. Can be set to "null".
* **action_list:** Specify a list of action types to restrict the restore to. Can be set to "null"
* **radius:** Specify a radius to restrict the restore to. A location must be specified if using this. Set to "0" to disable.
* **radius_location:** Specify a location to restore around. Can be set to "null" if no radius is specified, and a user is specified.
---
#### `blockLookup(Block block, int time)`
This will perform a full lookup on a single block.
* **block:** The block to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `queueLookup(Block block)`
This will search the consumer queue for changes on a block not yet saved in the database.
* **block:** The block to perform the lookup on.
---
#### `sessionLookup(String user, int time)`
This will perform a session lookup on a single player.
* **user:** The username to perform the lookup on.
* **time:** Specify the amount of time to lookup. "5" would return results from the last 5 seconds.
---
#### `ParseResult parseResult(String[] result)`
This will parse results from a lookup. You'll then be able to view the following:
* **getX():** Get the X coordinate of the block.
* **getY():** Get the Y coordinate of the block.
* **getZ():** Get the Z coordinate of the block.
* **getType():** Get the Material of the block.
* **getBlockData():** Get the BlockData of the block.
* **getPlayer():** Get the username as a string.
* **getTimestamp():** Get the time of the action.
* **getActionId():** Get the action ID. (0=removed, 1=placed, 2=interaction)
* **getActionString():** Get the action as a string. (Removal, Placement, Interaction)
* **isRolledBack():** If the block is currently rolled back or not.
* **worldName():** The name of the world the block is located in.
---
#### `logPlacement(String user, Location location, Material type, BlockData blockData)`
This will log a block as being placed.
* **user:** Specify the username to log as having placed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logRemoval(String user, Location location, Material type, BlockData blockData)`
This will log a block as being removed/broken, and will log the block's inventory (if applicable).
* **user:** Specify the username to log as having removed the block.
* **location:** Specify the location of the block you're logging.
* **type:** Specify the Material of the block you're logging.
* **blockData:** Specify the BlockData of the block you're logging. Can be set to "null".
---
#### `logContainerTransaction(String user, Location location)`
This will log any transactions made to a block's inventory immediately after calling the method.
* **user:** Specify the username to log as having added/removed the items.
* **location:** Specify the location of the block inventory you're logging.
---
#### `logInteraction(String user, Location location)`
This will log a block as having been interacted with.
* **user:** Specify the username to log as having caused the interaction.
* **location:** Specify the location of the interaction you're logging.
---
#### `hasPlaced(String user, Block block, int time, int offset)`
This will return true if a user has already placed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've placed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0 = no offset)
---
#### `hasRemoved(String user, Block block, int time, int offset)`
This will return true if a user has already removed a block at the location within the specified time limit.
* **user:** The username you're checking to see if they've removed a block already.
* **block:** The block you're checking.
* **time:** How far back to check. "5" would only check through the last 5 seconds of logged blocks.
* **offset:** A time offset. "2" would ignore the last 2 seconds of most recently ignored data. (0 = no offset)
---
#### `performPurge(int time)`
This will perform a purge on the CoreProtect database.
* **time:** Purge any data earlier than this. "120" would purge any data older than 120 seconds (2 minutes).
---
### Examples
- Get the last 60 seconds of block data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data for the user "Notch", excluding dirt and grass blocks.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<Object> exclude = Arrays.asList(Material.DIRT, Material.GRASS);
List<String[]> lookup = CoreProtect.performLookup(60, Arrays.asList("Notch"), null, null, exclude, null, 0, null);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Get the last 60 seconds of block data within 5 blocks of a location.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performLookup(60, null, null, null, null, null, 5, location);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
```
---
- Rollbacks / restores use the same code structure as the above examples. For example:
```java
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.performRollback(60, Arrays.asList("Notch"), null, null, null, null, 0, null);
if (lookup != null){
for (String[] value : lookup){
ParseResult result = CoreProtect.parseResult(value);
int x = result.getX();
int y = result.getY();
int z = result.getZ();
// ...
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
- Check if the user "Notch" has already placed a block at a location within the last 60 seconds.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 0);
// Search queue for pending changes
if (!hasPlaced){
List<String[]> lookup = CoreProtect.queueLookup(block);
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
if (parseResult.getActionId()==1 && parseResult.getPlayer().equals("Notch")){
hasPlaced = true;
break;
}
}
}
}
```
---
- Get the last 60 seconds of block data for a specific block.
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.blockLookup(block, 60);
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
// ...
}
}
}
```
---
- Get the last 1 day of session data for the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
List<String[]> lookup = CoreProtect.sessionLookup("Notch", (24 * 60 * 60));
if (lookup != null){
for (String[] result : lookup){
ParseResult parseResult = CoreProtect.parseResult(result);
int x = parseResult.getX();
int y = parseResult.getY();
int z = parseResult.getZ();
int action = parseResult.getActionId(); // 0 = logout, 1 = login
// ...
}
}
}
```
---
- Log the placement of a block at a location by the user "Notch".
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean success = CoreProtect.logPlacement("Notch", block.getLocation(), block.getType(), block.getData());
}
```
---
- Log adding/remove items in a chest (or some other block inventory).
```java
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean success = CoreProtect.logContainerTransaction("Notch", inventory.getLocation());
// modify your container contents immediately after (e.g. [i]inventory.addItem(itemStack);[/i])
}
```
---
- Perform a multi-threaded placement check to see if the user "Notch" has already placed a block at a location within the last 60 seconds. This ignores the most recent 1 second of logged data, to account for the fact that that new block data may have already been logged, depending on your code.
```java
final Block block = null; //Should be an actual block
class BasicThread implements Runnable {
@Override
public void run() {
try {
CoreProtectAPI CoreProtect = getCoreProtect();
if (CoreProtect != null){ // Ensure we have access to the API
boolean hasPlaced = CoreProtect.hasPlaced("Notch", block, 60, 1);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
```
---
+76
View File
@@ -0,0 +1,76 @@
# Automatic Purging
Automatic purging removes old CoreProtect data on a daily schedule, helping keep database growth under control without requiring manual `/co purge` runs.
> **Note:** Automatic purging is not enabled by default. This feature is exclusive to CoreProtect 24.0+ [Patreon builds](https://patreon.com/coreprotect).
## Configuration
To enable automatic purging, set `auto-purge` in `config.yml` to the amount of data you want to keep:
```yaml
auto-purge: 180d
```
This example keeps the most recent 180 days of CoreProtect data and automatically removes older data.
Supported values use the same style as CoreProtect command times, such as `30d`, `12w`, or `6mo`. The minimum automatic purge value is `30d`. Set `auto-purge: false` to disable automatic purging.
## Schedule
Automatic purging runs once per day using your server's local time. By default, it runs at midnight.
To change the daily runtime, manually add `auto-purge-time: 03:30` to `config.yml`.<br>
Use 24-hour `HH:mm` server time.
After changing `auto-purge` or `auto-purge-time`, use `/co reload` or restart the server. Changes apply to the next scheduled run.
When automatic purging is enabled, CoreProtect logs the next scheduled run when the server starts and again after an automatic purge completes.
## How It Works
Automatic purging runs in the background. SQLite, MySQL, and DuckDB remove old rows incrementally in small chunks with short pauses between database work. ClickHouse uses its columnar retention path, dropping fully covered monthly partitions for an unfiltered time purge and synchronously removing rows from any partially covered partition.
Automatic purging does not rebuild SQLite, optimize MySQL, or run a ClickHouse `OPTIMIZE FINAL`. DuckDB performs a checkpoint after cleanup. Deleting rows helps control future growth, but it may not immediately reduce the database file or table size on disk.
Only one automatic purge can run per CoreProtect instance at a time. If the server shuts down, a manual purge starts, a database migration or conversion starts, or the consumer is manually paused, the automatic purge stops safely and can continue during the next scheduled run.
ClickHouse rejects automatic and manual purges while `database-lock` is disabled. For a shared namespace, stop every installation, enable `database-lock` on one installation, restart or reload it, and finish the purge before restoring shared-writer mode.
## Important Notes
* **Patreon exclusive:** Only available in CoreProtect 24.0+ [Patreon builds](http://patreon.com/coreprotect)
* **Not enabled by default:** Set `auto-purge` in `config.yml` to enable automatic purging
* **Background cleanup:** Automatic purging is designed to run while the server remains usable
## Existing Databases
If enabling automatic purging on a new database, no additional action is required.
If enabling automatic purging on an existing database, it is recommended to run a manual purge first using the same time value:
```text
/co purge t:180d
```
For MySQL, add `#optimize` if you want to reclaim disk space during the initial manual purge:
```text
/co purge t:180d #optimize
```
This lets the manual purge reduce the existing database size, while automatic purging helps keep the database from growing beyond the configured retention period afterward.
ClickHouse normally reclaims complete old partitions without `#optimize`. A manual ClickHouse purge accepts `#optimize`, but it runs the expensive `OPTIMIZE TABLE ... FINAL` operation and is generally unnecessary. DuckDB checkpoints automatically after a manual or automatic purge; `#optimize` has no additional effect for DuckDB.
## Status
Use `/co status` to see how many rows have been automatically purged since the last server restart.
## Troubleshooting
**Automatic purging won't start:**
* Verify you're using a CoreProtect 24.0+ [Patreon build](http://patreon.com/coreprotect)
* Verify `auto-purge` is set to a valid value of at least `30d`
* Check the server console for automatic purge scheduling messages
+88 -17
View File
@@ -15,6 +15,8 @@ ___
| [/co purge](#co-purge) | Delete old block data |
| [/co reload](#co-reload) | Reload the configuration file |
| [/co status](#co-status) | View the plugin status |
| [/co consumer](#co-consumer) | Toggle consumer processing |
| [/co migrate-db](#co-migrate-db) | Migrate between database backends |
### Alias Commands
@@ -54,6 +56,7 @@ Perform a lookup. Nearly all of the parameters are optional.
| [`a:<action>`](#aaction) | Restrict the lookup to a certain action. |
| [`i:<include>`](#iinclude) | Include specific blocks/entities in the lookup. |
| [`e:<exclude>`](#eexclude) | Exclude blocks/entities from the lookup. |
| [`f:<filter>`](#ffilter) | Include or exclude chat, command, or sign text by prefix. |
| [`#<hashtag>`](#hashtag) | Add a hashtag to perform additional actions. |
#### Pagination
@@ -92,7 +95,7 @@ Purge old block data. Useful for freeing up space on your HDD if you don't need
| Command | Parameters |
| --- | --- |
| /co purge | `t:<time> r:<world>` |
| /co purge | `t:<time> r:<world/radius> i:<include> e:<exclude> a:kill` |
For example, `/co purge t:30d` will delete all data older than one month, and only keep the last 30 days of data.
@@ -100,14 +103,37 @@ For example, `/co purge t:30d` will delete all data older than one month, and on
> If used from the console, only data older than 24 hours can be purged.
**Purging Worlds**
You can also optionally specify a world in CoreProtect v19+.
For example, `/co purge t:30d r:#world_nether` will delete all data older than one month in the Nether, without deleting data in any other worlds.
You can optionally specify a world in CoreProtect v19+.
For example, `/co purge t:30d r:#world_nether` will delete all data older than one month in the Nether, without removing data in any other worlds.
**MySQL Optimization**
In CoreProtect v2.15+, adding "#optimize" to the end of the command (e.g. `/co purge t:30d #optimize`) will also optimize your tables and reclaim disk space.
This option is only available when using MySQL, as SQLite purges do this by default.
**Purging by Radius**
A radius can limit a purge of specific block types or entity kills to the area around you. It only applies to block and entity kill data, so it must be combined with `i:` or `a:kill`, and it can only be used in-game. A radius always applies to the world you are in, so it cannot be combined with a world such as `r:#world_nether`. The radius is limited by `max-radius` in the config, like other commands.
For example, `/co purge t:30d r:50 i:zombie` will delete zombie kills older than one month within 50 blocks of you, without removing any other data. Use `r:50x10` to also limit the height to 10 blocks above and below you.
*Please note adding the #optimize option will significantly slow down your purge, and is generally unnecessary.*
**Purging Blocks**
You can optionally specify block types in CoreProtect v23+.
For example, `/co purge t:30d i:stone,dirt` will delete all stone and dirt data older than one month, without removing other block data.
**Purging Entity Kills**
Entity kill logs can be purged separately from other data on SQLite, MySQL, and DuckDB. Each kill also removes the saved entity data used to restore the mob on rollback.
* `/co purge t:30d a:kill` deletes all entity kills older than one month, and keeps all other data.
* `/co purge t:30d i:zombie,skeleton` deletes zombie and skeleton kills older than one month. Entity and block types can be combined in `i:`.
* `/co purge t:30d e:villager,wolf` deletes all data older than one month, but keeps villager and wolf kills.
* `/co purge t:30d a:kill e:villager r:#world` deletes entity kills older than one month in the overworld, except villager kills.
`e:` only accepts entity types. It cannot be combined with entity types in `i:`, or with only block types in `i:`, because a block restriction already keeps every kill. `a:kill` cannot be combined with block types in `i:`.
**Database Optimization**
In CoreProtect v2.15+, adding `#optimize` to the end of the command (for example, `/co purge t:30d #optimize`) will also optimize supported database tables and reclaim unused disk space. How this option is handled depends on the database backend:
* SQLite already rebuilds the database from retained data and reclaims unused file space as part of a manual purge, so `#optimize` is not needed.
* MySQL normally deletes matching rows. Adding `#optimize` also removes saved entity data in the purged time range that no entity kill references anymore (this requires the `CREATE TEMPORARY TABLES` privilege), then optimizes its tables to reclaim unused space.
* DuckDB deletes matching rows in one transaction and checkpoints afterward. `#optimize` has no additional effect.
* ClickHouse drops fully covered monthly partitions for an unfiltered time purge and synchronously removes rows from partial or filtered partitions. Adding `#optimize` also runs `OPTIMIZE TABLE ... FINAL`.
`#optimize` can significantly slow MySQL and ClickHouse purges and is generally unnecessary.
___
@@ -119,6 +145,27 @@ ___
Displays the plugin status and version information.
___
### /co consumer
Console command to pause or resume consumer queue processing.
___
### /co migrate-db
Migrate data from the active database backend to a different backend. This is a console-only command.
| Command | Parameters |
| --- | --- |
| /co migrate-db | `<sqlite|mysql|duckdb|clickhouse> [--full-validation]` |
By default, migration validates all table statistics and compares approximately 1% of each large history table, with full comparisons for small and reference tables. Add `--full-validation` to compare every copied row.
The target namespace must contain no CoreProtect data; a DuckDB target must use a new database file, and `database-lock` must remain enabled. After a successful migration, CoreProtect automatically updates `database-type` in `config.yml` before queued writes resume.
> **Note:** Migrations between SQLite and MySQL require a CoreProtect 23.0+ Patreon build. Any migration involving DuckDB or ClickHouse requires CoreProtect 25.0+.
For complete migration instructions, safety guidelines, and troubleshooting information, see the [Database Migration documentation](/database-migration/).
___
## Parameter Details
@@ -128,6 +175,7 @@ ___
* Example: `u:Notch`
* Example: `u:Notch,Intelli`
* Example: `u:#fire,#tnt,#creeper,#explosion`
---
@@ -138,7 +186,8 @@ ___
* Example: `t:2w,5d,7h,2m,10s`
* Example: `t:5d2h`
* Example: `t:2.50h` *(2 and a half hours)*
* Example: `t:1h-2h` *(between one to two hours)*
* Example: `t:2.50h` *(two and a half hours)*
---
@@ -171,14 +220,15 @@ ___
| `a:container` | items taken from or put in chests |
| `a:+container` | items put in chests |
| `a:-container` | items taken from chests |
| `a:inventory` | items dropped, picked up, deposited, or withdrawn by players |
| `a:+inventory` | items picked up or withdrawn by players |
| `a:-inventory` | items dropped or deposited by players |
| `a:item` | merges `a:container` and `a:inventory` |
| `a:+item` | merges `a:+container` and `a:+inventory` |
| `a:-item` | merges `a:-container` and `a:-inventory` |
| `a:kill` | mobs/animals killed |
| `a:session` | player logins/logouts |
| `a:inventory` | items added or removed from player inventories |
| `a:+inventory` | items added to player inventories |
| `a:-inventory` | items removed from player inventories |
| `a:item` | items dropped, thrown, picked up, deposited, or withdrawn by players |
| `a:+item` | items picked up or withdrawn by players |
| `a:-item` | items dropped, thrown, or deposited by players |
| `a:kill` | mobs/animals killed |
| `a:spawn` | entities placed or spawned by players |
| `a:session` | player logins/logouts |
| `a:+session` | player logins |
| `a:-session` | player logouts |
| `a:sign` | messages written on signs |
@@ -206,6 +256,20 @@ ___
---
### `f:<filter>`
*Filter message prefixes when using `a:chat`, `a:command`, or `a:sign`.*
* Example: `a:command f:/ban` *(commands starting with "/ban")*
* Example: `a:command f:/ban,/kick` *(commands starting with either prefix)*
* Example: `a:command f:/ban,-/banlist` *(include "/ban" and exclude "/banlist")*
* Example: `a:chat f:-hello` *(exclude messages starting with "hello")*
* Example: `a:sign f:Diamond Shop,-Diamond Shop Closed` *(filter prefixes on the recorded sign face)*
> Separate filters with commas. Included prefixes are alternatives; any matching excluded prefix removes the result. Exclusions can be used on their own. Each prefix requires at least three characters, excluding the leading `-` marker. Spaces are allowed, and `*`, `%`, `_`, and `~` are matched literally.
---
### `#<hashtag>`
Add a hashtag to the end of your command to perform additional actions.
@@ -217,6 +281,7 @@ Add a hashtag to the end of your command to perform additional actions.
| --- | --- |
| `#preview` | Preview a rollback/restore |
| `#count` | Return the number of rows found in a lookup query |
| `#summary` | Return the row count and per-user material totals |
| `#verbose` | Display additional information during a rollback/restore |
| `#silent` | Display minimal information during a rollback/restore |
@@ -244,6 +309,8 @@ By default, if no radius is specified, a radius of 10 will be applied, restricti
*(rollback griefing Notch did in the last hour that is within 20 blocks of you)*
* `/co rollback u:Notch t:1h r:#nether`
*(rollback griefing Notch did in the last hour ONLY in the Nether)*
* `/co rollback u:Notch t:5m a:inventory`
*(rollback inventory transactions by Notch in the last 5 minutes)*
* `/co rollback t:15m r:30`
*(rollback everything done in the last 15 minutes by anyone within 30 blocks of you)*
* `/co rollback t:15m r:#worldedit`
@@ -259,11 +326,15 @@ Lookup commands are generally the same as rollback commands. The primary differe
*(lookup all diamond ore mined in the last hour)*
* `/co lookup u:Notch t:30m a:chat`
*(lookup chat messages sent by Notch in the last 30 minutes)*
* `/co lookup u:Notch t:3d a:inventory`
*(lookup inventory transactions by Notch in the last 3 days)*
* `/co lookup u:Notch a:login`
*(lookup all logins ever done by Notch)*
* `/co lookup u:Notch a:login`
*(lookup all logins ever done by Notch)*
* `/co lookup u:Notch a:username`
*(lookup previous usernames used by Notch)*
* `/co lookup r:50 t:7d a:block #summary`
*(show the matching row count and material totals for each user within the same area and time range)*
___
___
+79 -6
View File
@@ -2,6 +2,59 @@
The CoreProtect configuration file can be found within the CoreProtect folder, at `config.yml`.
## Database Storage
New installations use embedded DuckDB by default. Upgrading an existing SQLite or MySQL installation does not switch it to DuckDB: if `database-type` is absent, CoreProtect derives it from the existing legacy `use-mysql` setting and automatically writes the matching `sqlite` or `mysql` value. No manual change is required. Once present, `database-type` takes precedence over `use-mysql`, and changes take effect after `/co reload` or a restart.
| Value | Storage | Requirements |
| --- | --- | --- |
| `duckdb` | Embedded columnar database; the default for new installations | CoreProtect 25.0+ |
| `clickhouse` | External columnar database | CoreProtect 25.0+ and a reachable ClickHouse 26.1+ server |
| `sqlite` | Embedded legacy relational database | None beyond CoreProtect |
| `mysql` | External legacy relational database | A reachable MySQL server |
The server downloads the DuckDB JDBC driver automatically as a plugin library, while the ClickHouse JDBC driver is packaged inside CoreProtect; neither requires manual driver installation. DuckDB includes its database engine, while ClickHouse requires a separate server.
Changing `database-type` selects a separate dataset; it does not copy existing data. CoreProtect 23.0+ Patreon builds can migrate between SQLite and MySQL; any migration involving DuckDB or ClickHouse requires CoreProtect 25.0+. The target namespace must contain no CoreProtect data; DuckDB requires a new target file, and `database-lock` must remain enabled. See the [database migration guide](/database-migration/) for preparation and switchover instructions.
### DuckDB
The default settings are suitable for most servers:
```yaml
database-type: duckdb
duckdb-memory-limit: 512MB
duckdb-threads: 3
duckdb-max-temp-directory-size: 10GB
```
DuckDB stores its data in `plugins/CoreProtect/database.duckdb`; CoreProtect manages it directly, so no DuckDB service is needed. `duckdb-memory-limit` controls the buffer manager, although total native memory can be higher, and `duckdb-threads` limits query threads. `duckdb-max-temp-directory-size` caps spill data without preallocating space or limiting the database file itself. Queries that exceed both memory and available spill space can fail.
On a pristine installation where the CoreProtect data folder contains no existing files, CoreProtect verifies that DuckDB's native library can run before creating the database file. If the operating system, architecture, or native runtime is incompatible, CoreProtect records `database-type: sqlite` in the generated `config.yml`, logs a warning, and starts with SQLite instead. Existing or preconfigured installations and explicitly configured DuckDB databases never fall back automatically; an initialization error stops database startup so CoreProtect cannot silently open a separate empty history.
### ClickHouse
Configure the ClickHouse HTTP endpoint and credentials before selecting it:
```yaml
database-type: clickhouse
table-prefix: co_
clickhouse-host: 127.0.0.1
clickhouse-port: 8123
clickhouse-database: default
clickhouse-username: default
clickhouse-password: ""
clickhouse-tls: false
```
Create the configured database first with a database engine that provides persistent UUID-backed table identities. `Atomic` is the normal choice for self-hosted ClickHouse and is the default there; for example, `CREATE DATABASE coreprotect ENGINE = Atomic`. CoreProtect checks this capability before creating any tables, then creates and validates its prefixed tables and views. The account needs permission to read `system.databases` and `system.tables`, create and read/write the CoreProtect objects, run mutations and partition drops, and drop or truncate purge-target tables. Server-wide database-creation permission is not required; `#optimize` additionally requires table optimization permission.
To share a ClickHouse database and prefix, disable `database-lock` on every installation. Use the same CoreProtect version and direct endpoint to one physical ClickHouse server, with separate CoreProtect data directories. Load balancing across independent nodes, replicated tables, and distributed tables are unsupported. Synchronize system clocks; events from different installations within the same second have no guaranteed order. Use globally unique world names unless their coordinate histories should be combined.
Players must log in with a UUID before UUID-less activity under a changed or reassigned name. UUIDs preserve account IDs across name changes and distinguish successive owners of a reused name. Name lookups use the latest UUID-bearing observation; ownership is unspecified when observations conflict within one second. Simultaneous name changes are unsupported. Cached names from other servers may remain until the player is observed locally or CoreProtect reloads.
Purges require `database-lock`. Stop all installations sharing the database and prefix, enable the lock on the installation performing the purge, then restart or reload it before purging. Also stop all installations before migration. Migration updates only the initiating installation's configuration; update the others before restarting them.
## Per-World Configuration
If you'd like to modify the logging settings for a specific world, simply do the following:
@@ -18,14 +71,34 @@ Secondary configuration files override the value specified in config.yml. If you
## Disabling Logging
To disable logging for specific users or commands, simply do the following:
To disable logging for specific users, blocks or commands, simply do the following:
1. In the CoreProtect plugin directory, create a file named `blacklist.txt`.
2. Enter the names of the users (or commands) you'd like to disable logging for (each username on a new line).
2. Enter the names of the users, commands, blocks, or entities you'd like to disable logging for (each entry on a new line).
3. Either restart your server, or type "/co reload" in-game.
This can be used to disable logging for non-player users, such as "#tnt". For example, if you'd like to disable logging for the user "Notch" and for TNT explosions, the blacklist.txt file would look like this:
The blacklist supports disabling logs for:
- Users, which includes Players and non-player users, such as "#creeper"
- Commands, such as `/help`
- Blocks, such as minecraft:stone. Only `block` actions are affected.
- Entities, such as minecraft:creeper, which will disable logging the death for that entity. *Note: renamed entities will be logged even if blacklisted.*
- Filters can also be specified for a particular user, by using the `@` symbol after the specific item, block, or entity namespaced ID. The format is `id@user`. This will filter all `block`, `kill`, `item` and `container` actions involving that particular block, item or mob, only when caused by the specified player or non-player user.
- Items and container actions are only affected by filtered blacklist entries, not by generic item or block IDs.
*Please note that you must include the namespace (e.g. minecraft:) for blocks, entities and items.*
An example blacklist.txt file would look like this:
```text
Notch
#tnt
```
Notch ; User
#tnt ; TNT explosions
/help ; Help command
minecraft:stone ; Stone blocks
minecraft:creeper ; Creeper entity
minecraft:shears@#dispenser ; Shears being dispensed
```
*Please note that to disable logging for blocks, CoreProtect v23+ is required.*
*To disable logging for entities or to use filtering, CoreProtect v24+ is required.*
+114
View File
@@ -0,0 +1,114 @@
# Database Migration
CoreProtect 23.0+ can migrate data between SQLite and MySQL while retaining the source history. CoreProtect 25.0+ adds DuckDB and ClickHouse as migration sources and destinations. Changing `database-type` by itself does not migrate any data.
> **Note:** Database migration is available only in [Patreon builds](https://patreon.com/coreprotect) for Patron supporters.
## Overview
Database migration can be used to move from SQLite or DuckDB to an external database, return to an embedded database for simpler operation, or switch between any of the supported storage engines. The active backend is always the source, and the command argument selects the destination.
## Command Usage
| Command | Parameters | Description |
| --- | --- | --- |
| `/co migrate-db` | `<sqlite|mysql|duckdb|clickhouse> [--full-validation]` | Migrate the active database to the selected backend |
The command must be run from the server console, and the destination must differ from the active source.
For example:
```text
co migrate-db duckdb
co migrate-db clickhouse
co migrate-db duckdb --full-validation
```
## Before You Start
1. Stop the server and make a normal backup of the source database and `config.yml`.
2. Leave `database-type` set to the source backend and keep `database-lock: true`.
3. Configure the target while the server is stopped, then start the server with the source backend still selected. MySQL and ClickHouse connection settings must be loaded before the migration starts.
4. Use a target namespace that contains no CoreProtect data. Unrelated tables in the same external database are not affected.
5. Verify that the target has enough storage and that its account has permission to create and modify the required tables.
6. Run the migration during a quiet maintenance period, with no purge, rollback, reload, or other database operation in progress.
Prepare the target as follows:
| Target | Preparation |
| --- | --- |
| SQLite | Move or archive an existing `plugins/CoreProtect/database.db` if it contains CoreProtect data. CoreProtect creates the file and schema when needed. |
| DuckDB | `plugins/CoreProtect/database.duckdb` must be a new database file so its row ID sequences can be initialized from the source. |
| MySQL | Configure the `mysql-*` options and create the configured database and account. The selected table-prefix namespace must contain no CoreProtect data. |
| ClickHouse | Use ClickHouse 26.1 or newer, create the configured database with persistent UUID-backed table identities (`Atomic` is the normal self-hosted choice), create the account, configure the `clickhouse-*` options, and use a table-prefix namespace containing no CoreProtect data. |
`table-prefix` is shared by MySQL and ClickHouse. If the source is MySQL or ClickHouse, leave its prefix unchanged; an external target will use that same prefix. If the source is SQLite or DuckDB, you may select the external target prefix before starting the migration session. Embedded SQLite and DuckDB targets always use `co_`.
## During Migration
CoreProtect pauses database persistence, waits for active database work to finish, and copies data in bounded batches. The console shows a throttled copy progress bar with the number of successfully written records, overall percentage, current table, transfer rate, and estimated copy time remaining. It also reports each non-empty table as its copy completes. Verification, activation, and the automatic `config.yml` update follow the copy, so the ETA does not include those final stages.
New events logged while migration is running remain in the in-memory consumer queue. After successful activation, they are written to the target; if migration fails and the source is restored, persistence resumes there instead. Queued events are not stored for recovery after a process restart, and database-backed commands may report that the database is busy. Large migrations can take hours and use significant CPU, memory, disk, and network bandwidth, so keep server activity low and do not stop or restart the server until the command reports success or failure.
Do not run `/co reload`, start a purge or rollback, or otherwise change database configuration while migration is running. CoreProtect normally rejects these conflicting operations, but they should not be used as part of the migration procedure.
## Data Integrity
The migration copies all logical CoreProtect history and reference data, including rollback state and entity tracking. It preserves copied row IDs. MySQL, DuckDB, and ClickHouse targets also preserve allocator high-water marks; SQLite continues after the largest surviving row ID, matching its existing allocation behavior.
CoreProtect checks every table's row count, minimum and maximum row IDs, and applicable allocator high-water marks before activation. By default, it also compares approximately 1% of rows in each large history table, including their complete metadata. Tables with at most 100,000 rows and reference tables (`*_map`, `user`, `world`, and `version`) receive full comparisons.
Samples are random contiguous ranges distributed across each million rows actually copied, rather than across the numeric row-ID space. Deleted-ID gaps therefore do not bias selection. Only selected ranges are reread for content verification, and the console reports the validation mode and progress against the number of rows selected for comparison. A small temporary file containing sample boundaries is removed after success or failure.
Sampled validation can miss differences outside the selected ranges. Add `--full-validation` to compare every copied row instead. Any detected mismatch prevents activation in either mode. Sampling reduces content comparisons, but storage-engine scans and statistics checks can still make verification expensive.
Database-lock metadata is initialized separately; ClickHouse targets also initialize their own current version row. For a ClickHouse source, the current logical version of each row is copied; superseded internal ClickHouse revisions are storage-engine history rather than separate CoreProtect records.
Source history rows are not deleted or replaced. Migration may update operational metadata such as database-lock heartbeats and allocator state.
## After a Successful Migration
1. CoreProtect atomically changes `database-type` in `config.yml` to the target before queued writes resume. If the legacy `use-mysql` option exists, it is updated at the same time (`true` only for MySQL). Comments and unrelated settings are preserved.
2. The running server immediately begins persisting queued and new events to the target. Keep the server in maintenance mode until the restart check below succeeds.
3. Review the completion message and server log, then test `/co status` and a representative lookup.
4. Restart once and verify that CoreProtect reconnects to the same target and can still read old and new records. No manual database selector edit is required.
5. Resume normal activity and test a newly logged change.
6. Keep the source backup until the migrated database has been verified. Archive or remove it only when you are satisfied that the migration is complete.
When a ClickHouse source or target namespace is shared, stop every peer first and run the migration from exactly one installation with `database-lock` enabled. Before restarting any peer that will use the migrated target namespace, update its database selector, connection, and prefix to match the migrated installation.
## Failure and Recovery
The command does not resume a partial migration. A failed or interrupted attempt can leave rows, schema objects, or allocator state in the target, so clean only the failed target namespace before retrying. For DuckDB, stop the server and remove the failed target file before starting over. Never clean or replace the source database as part of retrying.
If target activation or the atomic `config.yml` update fails after verification, CoreProtect restores the source when possible and does not resume writes on the target. The selector is changed only after the target has initialized successfully, so an interruption before that change restarts on the source, while an interruption after it restarts on the verified target. If neither the target nor source can be initialized, persistence is halted to avoid writing to an unknown database state. Correct the connection or configuration problem and restart the server; any events still queued only in memory cannot survive that restart.
## Troubleshooting
### Migration Does Not Start
* Confirm that the command is being run from the console on a CoreProtect 23.0+ Patreon build, or 25.0+ if DuckDB or ClickHouse is involved.
* Confirm that the selected target differs from the active source backend.
* Keep `database-lock` enabled and wait for any purge, rollback, reload, or previous migration to finish.
* Check that the target namespace contains no CoreProtect data. DuckDB must use a new database file.
### Connection or Schema Failure
* Confirm that the target settings were loaded while `database-type` still selected the source.
* Verify the target host, port, database, credentials, table prefix, and available storage.
* Confirm that the target account can create, read, insert, update or mutate, and delete or drop the required objects.
* For ClickHouse, confirm the server is version 26.1 or newer, no other process is using the same CoreProtect data directory, and every other CoreProtect installation sharing the source or target namespace is stopped.
### Verification Failure or Interruption
* Review the server log for the table and row reported by the failure.
* Keep the source database and backup intact.
* Clean the partial target namespace, use a new DuckDB file when applicable, and retry during a quieter maintenance period.
### Automatic Configuration Update Fails
Check that `plugins/CoreProtect/config.yml` is a regular, writable file on a filesystem that supports atomic replacement. CoreProtect reports the migration as failed and restores the source before persistence resumes. The verified target still contains the copied data, so clean only that failed target namespace before retrying; do not manually select it or delete the source.
## Getting Help
If a migration fails, keep both databases intact and review the complete error and surrounding server log. When asking for help in the [CoreProtect Discord](https://discord.gg/b4DZ4jy), include your CoreProtect version, source and destination database types, the complete migration error, and the relevant log excerpt.
+15 -5
View File
@@ -2,10 +2,11 @@
For more information about CoreProtect, visit [coreprotect.net](http://coreprotect.net).
Looking for another platform? Visit [CoreProtect for Fabric](https://coreprotect.net/fabric) or [CoreProtect for Hytale](https://coreprotect.net/hytale).
## Getting Started
These documents are still a work in progress.
Please be sure to visit the following links for more information.
Please visit one of the following links for more information.
[**CoreProtect Commands**](/commands/)
A list of all commands that can be used within CoreProtect.
@@ -13,14 +14,23 @@ A list of all commands that can be used within CoreProtect.
[**CoreProtect Permissions**](/permissions/)
A list of all permissions that can be used with CoreProtect.
[**CoreProtect Language Codes**](/languages/)
A list of all language codes that can be used with CoreProtect.
[**CoreProtect API Documentation**](/api/)
Log your own block changes, perform lookups, rollbacks, restores, and more.
[**Database Migration**](/database-migration/)
Migrate your CoreProtect database between supported backends.
[**Automatic Purging**](/auto-purge/)
Automatically remove old CoreProtect data on a daily schedule.
[**Compatible Third-Party Plugins & Tools**](/tools-integrations/)
A list of plugins and tools that are compatible with CoreProtect.
[**Per-World Configuration Files**](/config/#per-world-configuration)
Log your own block changes, perform lookups, rollbacks, restores, and more.
Modify the logging settings for a specific world on your server.
[**Disable Logging for Specific Users or Commands**](/config/#disabling-logging)
Log your own block changes, perform lookups, rollbacks, restores, and more.
[**Disable Logging With a Blacklist**](/config/#disabling-logging)
Disable logging for specific users, blocks or commands.
+123
View File
@@ -0,0 +1,123 @@
# Languages
Language codes can be entered in `config.yml` for automatic online translation.
---
## CoreProtect Language Codes
| Language | Code |
| --- | --- |
| Afrikaans | `af` |
| Albanian | `sq` |
| Amharic | `am` |
| Arabic | `ar` |
| Armenian | `hy` |
| Azerbaijani | `az` |
| Basque | `eu` |
| Belarusian | `be` |
| Bengali | `bn` |
| Bosnian | `bs` |
| Bulgarian | `bg` |
| Catalan | `ca` |
| Cebuano | `ceb` |
| Chinese (Simplified) | `zh-CN` or `zh` |
| Chinese (Traditional) | `zh-TW` |
| Corsican | `co` |
| Croatian | `hr` |
| Czech | `cs` |
| Danish | `da` |
| Dutch | `nl` |
| English | `en` |
| Esperanto | `eo` |
| Estonian | `et` |
| Finnish | `fi` |
| French | `fr` |
| Frisian | `fy` |
| Galician | `gl` |
| Georgian | `ka` |
| German | `de` |
| Greek | `el` |
| Gujarati | `gu` |
| Haitian Creole | `ht` |
| Hausa | `ha` |
| Hawaiian | `haw` |
| Hebrew | `he` or `iw` |
| Hindi | `hi` |
| Hmong | `hmn` |
| Hungarian | `hu` |
| Icelandic | `is` |
| Igbo | `ig` |
| Indonesian | `id` |
| Irish | `ga` |
| Italian | `it` |
| Japanese | `ja` |
| Javanese | `jv` |
| Kannada | `kn` |
| Kazakh | `kk` |
| Khmer | `km` |
| Kinyarwanda | `rw` |
| Korean | `ko` |
| Kurdish | `ku` |
| Kyrgyz | `ky` |
| Lao | `lo` |
| Latin | `la` |
| Latvian | `lv` |
| Lithuanian | `lt` |
| Luxembourgish | `lb` |
| Macedonian | `mk` |
| Malagasy | `mg` |
| Malay | `ms` |
| Malayalam | `ml` |
| Maltese | `mt` |
| Maori | `mi` |
| Marathi | `mr` |
| Mongolian | `mn` |
| Myanmar (Burmese) | `my` |
| Nepali | `ne` |
| Norwegian | `no` |
| Nyanja (Chichewa) | `ny` |
| Odia (Oriya) | `or` |
| Pashto | `ps` |
| Persian | `fa` |
| Polish | `pl` |
| Portuguese (Portugal, Brazil) | `pt` |
| Punjabi | `pa` |
| Romanian | `ro` |
| Russian | `ru` |
| Samoan | `sm` |
| Scots Gaelic | `gd` |
| Serbian | `sr` |
| Sesotho | `st` |
| Shona | `sn` |
| Sindhi | `sd` |
| Sinhala (Sinhalese) | `si` |
| Slovak | `sk` |
| Slovenian | `sl` |
| Somali | `so` |
| Spanish | `es` |
| Sundanese | `su` |
| Swahili | `sw` |
| Swedish | `sv` |
| Tagalog (Filipino) | `tl` |
| Tajik | `tg` |
| Tamil | `ta` |
| Tatar | `tt` |
| Telugu | `te` |
| Thai | `th` |
| Turkish | `tr` |
| Turkmen | `tk` |
| Ukrainian | `uk` |
| Urdu | `ur` |
| Uyghur | `ug` |
| Uzbek | `uz` |
| Vietnamese | `vi` |
| Welsh | `cy` |
| Xhosa | `xh` |
| Yiddish | `yi` |
| Yoruba | `yo` |
| Zulu | `zu` |
*Source: [cloud.google.com/translate](https://cloud.google.com/translate/docs/languages)*
---
+18 -1
View File
@@ -35,11 +35,22 @@ The following permissions can be used to restrict functionality within the plugi
&nbsp;
* **coreprotect.status** *(default: op)*
Allows access to the CoreProtect status command.
&nbsp;
* **coreprotect.consumer** *(default: op)*
Allows access to the CoreProtect consumer command.
&nbsp;
* **coreprotect.give** *(default: false)*
Allows access to the CoreProtect give command.
&nbsp;
* **coreprotect.networking** *(default: op)*
Allows access to the CoreProtect networking API.
---
## Child Permissions
> **Note:** Child permissions are not presently supported by [CoreProtect for Hytale](https://coreprotect.net/hytale).
* **coreprotect.lookup.block** *(default: op)*
Can be optionally used as a negative permission to prevent block lookups.
&nbsp;
@@ -55,12 +66,18 @@ The following permissions can be used to restrict functionality within the plugi
* **coreprotect.lookup.container** *(default: op)*
Can be optionally used as a negative permission to prevent container lookups.
&nbsp;
* **coreprotect.lookup.inventory** *(default: op)*
Can be optionally used as a negative permission to prevent inventory lookups.
&nbsp;
* **coreprotect.lookup.item** *(default: op)*
Can be optionally used as a negative permission to prevent item lookups.
&nbsp;
* **coreprotect.lookup.kill** *(default: op)*
Can be optionally used as a negative permission to prevent entity kill lookups.
&nbsp;
* **coreprotect.lookup.spawn** *(default: op)*
Can be optionally used as a negative permission to prevent entity spawn lookups.
&nbsp;
* **coreprotect.lookup.near** *(default: op)*
Can be optionally used as a negative permission to prevent lookups via the near command.
&nbsp;
@@ -84,4 +101,4 @@ The following permissions can be used to restrict functionality within the plugi
Allows access to the CoreProtect "/core" command.
&nbsp;
* **coreprotect.coreprotect** *(default: false)*
Allows access to the CoreProtect "/coreprotect" command.
Allows access to the CoreProtect "/coreprotect" command.
+90 -4
View File
@@ -9,7 +9,7 @@ These plugins/tools are maintained by third parties, and may have issues outside
## Plugins
* **WorldEdit**
Use WorldEdit selections as a radius, and log your WorldEdit changes.
Use WorldEdit selections as a radius, and log your WorldEdit changes.
[https://dev.bukkit.org/projects/worldedit](https://dev.bukkit.org/projects/worldedit)
&nbsp;
@@ -18,22 +18,108 @@ Displays how many ores a player has destroyed in a specific time span.
[https://dev.bukkit.org/bukkit-plugins/coreprotect-anti-xray/](https://dev.bukkit.org/bukkit-plugins/coreprotect-anti-xray/)
&nbsp;
* **CoreProtect TNT**
The CoreProtect TNT extension adds logging for TNT and creeper explosion sources.
[https://www.spigotmc.org/resources/coreprotect-tnt-log-tnt-creeper-explode-source.69609/](https://www.spigotmc.org/resources/coreprotect-tnt-log-tnt-creeper-explode-source.69609/)
&nbsp;
* **CoreProtect Time-Lapse**
CPTL is an add-on tool for CoreProtect used to create a time-lapse from historical data.
[https://modrinth.com/plugin/cptl](https://modrinth.com/plugin/cptl)
&nbsp;
* **LightUp**
Conveniently place temporary lighting with rollback safety via CoreProtect integration.
[https://modrinth.com/plugin/lightup](https://modrinth.com/plugin/lightup)
&nbsp;
* **Lumen**
Custom (virtual) lighting with undo/redo and built-in CoreProtect & FAWE integration.
[https://modrinth.com/plugin/lumen](https://modrinth.com/plugin/lumen)
&nbsp;
* **FRTrustSystem**
Simple trust/friends system that uses the CoreProtect API to determine block ownership for protections/notifications.
[https://modrinth.com/plugin/frtrustsystem](https://modrinth.com/plugin/frtrustsystem)
&nbsp;
* **Movecraft-CoreProtect**
Logs Movecraft “craft” movement into CoreProtect for auditing/rollback.
[https://hangar.papermc.io/Airship-Pirates/Movecraft-CoreProtect](https://hangar.papermc.io/Airship-Pirates/Movecraft-CoreProtect)
&nbsp;
* **M0-CoreCord**
Discord bridge for CoreProtect lookups and alerts (SQLite/MySQL supported).
[https://www.spigotmc.org/resources/m0-corecord-1-13-1-21-8.91863/](https://www.spigotmc.org/resources/m0-corecord-1-13-1-21-8.91863/)
&nbsp;
* **XRayHunter**
Hunt potential xrayers by analyzing ore-mining stats pulled from CoreProtect data.
[https://modrinth.com/plugin/xrayhunter](https://modrinth.com/plugin/xrayhunter)
&nbsp;
* **ShadowTrace (Server Plugin)**
Backend for the ShadowTrace client mod; queries CoreProtect to visualize players’ mined blocks in-game.
[https://modrinth.com/plugin/shadowtraceplugin](https://modrinth.com/plugin/shadowtraceplugin)
&nbsp;
* **WildInspect**
Custom inspect-mode powered by CoreProtect (with history/claim limits and more).
[https://www.spigotmc.org/resources/%E2%9A%A1%EF%B8%8F-wildinspect-%E2%9A%A1%EF%B8%8F-coreprotect-addon-history-limits-claim-limits-%E2%9A%A1%EF%B8%8F-1-21-5-support.87405/](https://www.spigotmc.org/resources/%E2%9A%A1%EF%B8%8F-wildinspect-%E2%9A%A1%EF%B8%8F-coreprotect-addon-history-limits-claim-limits-%E2%9A%A1%EF%B8%8F-1-21-5-support.87405/)
&nbsp;
* **ExplosionProtector**
Uses the CoreProtect API to prevent explosions from destroying player-placed blocks.
[https://www.spigotmc.org/resources/explosionprotector.124838/](https://www.spigotmc.org/resources/explosionprotector.124838/)
&nbsp;
* **Axiom Paper Plugin**
Server component for the Axiom editor; includes CoreProtect integration to log Axiom edits.
[https://modrinth.com/plugin/axiom-paper-plugin](https://modrinth.com/plugin/axiom-paper-plugin)
&nbsp;
* **BlocksHub**
Bridge API used by world-editing plugins (e.g., AWE/FAWE) to send edits to loggers like CoreProtect.
[https://www.spigotmc.org/resources/blockshub.331/](https://www.spigotmc.org/resources/blockshub.331/)
&nbsp;
* **SpitSTIK**
Fun projectile plugin with CoreProtect integration for item-frame logging.
[https://modrinth.com/plugin/spitstik](https://modrinth.com/plugin/spitstik)
&nbsp;
* **XRay Informer**
Ore-ratio checker that supports CoreProtect. *(last known MC: 1.12)*
[https://dev.bukkit.org/projects/xray-informer](https://dev.bukkit.org/projects/xray-informer)
&nbsp;
* **DesirePaths**
Logs automatically generated paths into CoreProtect for moderation/rollback.
[https://www.spigotmc.org/resources/desirepaths-dynamic-player-made-trails.109043/](https://www.spigotmc.org/resources/desirepaths-dynamic-player-made-trails.109043/)
&nbsp;
## Tools
* **CoreProtect Lookup Web Interface**
Search through your CoreProtect history with an easy-to-use interface.
Search through your CoreProtect history with an easy-to-use web UI.
[https://www.spigotmc.org/resources/coreprotect-lookup-web-interface.28033/](https://www.spigotmc.org/resources/coreprotect-lookup-web-interface.28033/)
&nbsp;
* **Watson**
Watson is a client side Minecraft mod that displays CoreProtect logs in 3D.
Client-side mod that displays CoreProtect logs in 3D (optional server helper plugin available).
[https://github.com/vacla/Watson](https://github.com/vacla/Watson)
&nbsp;
* **ShadowTrace (Client Mod)**
Client-side visualizer to view mined-block locations supplied by the ShadowTrace server plugin via CoreProtect.
[https://modrinth.com/mod/shadowtrace](https://modrinth.com/mod/shadowtrace)
&nbsp;
---
Have you created a plugin that's compatible with CoreProtect?
Let us know to have it added to this list!
---
---
+94 -83
View File
@@ -1,50 +1,54 @@
# CoreProtect Language File (de)
ACTION_NOT_SUPPORTED: "Diese Aktion wird nicht unterstützt."
AMOUNT_BLOCK: "{0} {block|blöcke}"
AMOUNT_CHUNK: "{0} {Brocken|Brocken}"
AMOUNT_BLOCK: "{0} {Block|Blöcke}"
AMOUNT_CHUNK: "{0} {Chunk|Chunks}"
AMOUNT_ENTITY: "{0} {Entität|Entitäten}"
AMOUNT_ITEM: "{0} {Artikel|Artikel}"
AMOUNT_ITEM: "{0} {Item|Items}"
API_TEST: "API-Test erfolgreich."
CACHE_ERROR: "WARNUNG: Fehler beim Validieren {0} Zwischenspeicher."
CACHE_RELOAD: "Erzwingen des Nachladens von {kartierung|welt} Caches aus der Datenbank."
CHECK_CONFIG: "Bitte überprüfen Sie config.yml"
CACHE_ERROR: "WARNUNG: Fehler beim Überprüfen des {0}-Zwischenspeichers."
CACHE_RELOAD: "Erzwingen des Nachladens von {Kartierungs|Welt}caches aus der Datenbank."
CHECK_CONFIG: "Bitte überprüfe die config.yml"
COMMAND_CONSOLE: "Bitte führen Sie den Befehl von der Konsole aus."
COMMAND_NOT_FOUND: "Befehl \"{0}\" nicht gefunden."
COMMAND_THROTTLED: "Bitte warten Sie einen Moment und versuchen Sie es erneut."
CONSUMER_ERROR: "Die Verarbeitung der Verbraucherwarteschlange wurde bereits {angehalten|fortgesetzt}."
CONSUMER_TOGGLED: "Die Verarbeitung der Verbraucherwarteschlange wurde {angehalten|fortgesetzt}."
CONTAINER_HEADER: "Containertransaktionen"
CPU_CORES: "CPU-Kerne."
DATABASE_BUSY: "Datenbank beschäftigt. Bitte versuchen Sie es später erneut."
DATABASE_INDEX_ERROR: "Datenbankindizes können nicht validiert werden."
DATABASE_INDEX_ERROR: "Datenbankindizes können nicht geprüft werden."
DATABASE_LOCKED_1: "Datenbank gesperrt. Warte bis zu 15 Sekunden..."
DATABASE_LOCKED_2: "Datenbank wird bereits verwendet. Bitte versuche es erneut."
DATABASE_LOCKED_2: "Datenbank wird bereits verwendet. Bitte versuche es später erneut."
DATABASE_LOCKED_3: "Um die Datenbanksperre zu deaktivieren, setzen Sie \"database-lock: false\"."
DATABASE_LOCKED_4: "Das Deaktivieren der Datenbanksperre kann zu Datenbeschädigungen führen."
DATABASE_LOCKED_4: "Deaktivieren der Datenbanksperre kann zu Datenbeschädigungen führen."
DATABASE_UNREACHABLE: "Datenbank ist nicht erreichbar. Daten verwerfen und herunterfahren."
DEVELOPMENT_BRANCH: "Entwicklungszweig erkannt, Patch-Skripte werden übersprungen."
DIRT_BLOCK: "Platziere einen Schmutzblock unter dir."
DISABLE_SUCCESS: "Erfolg! Deaktiviert {0}"
DIRT_BLOCK: "Ein temporärer Sicherheitsblock wurde unter ihnen platziert."
DISABLE_SUCCESS: "{0} erfolgreich deaktiviert!"
ENABLE_FAILED: "{0} war nicht in der Lage zu starten."
ENABLE_SUCCESS: "{0} ist jetzt aktiviert!"
ENJOY_COREPROTECT: "Genießen {0}? Tritt unser... bei Discord!"
FINISHING_CONVERSION: "Abschluss der Datenkonvertierung. Warten Sie mal..."
FINISHING_LOGGING: "Abschluss der Datenprotokollierung. Warten Sie mal..."
ENJOY_COREPROTECT: "Dir gefällt {0}? Tritt unserem Discord bei!"
FINISHING_CONVERSION: "Abschluss der Datenkonvertierung. Bitte warten..."
FINISHING_LOGGING: "Abschluss der Datenprotokollierung. Bitte warten..."
FIRST_VERSION: "Anfangs-DB: {0}"
GLOBAL_LOOKUP: "Geben Sie keinen Radius an, um eine globale Suche durchzuführen."
GLOBAL_ROLLBACK: "Benutzen \"{0}\" um eine globale {Rollback|Wiederherstellung}"
GLOBAL_ROLLBACK: "Benutze \"{0}\" für einen globalen {Rollback|Restore}"
HELP_ACTION_1: "Beschränken Sie die Suche auf eine Aktion."
HELP_ACTION_2: "Beispiele: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Zusätzliche Informationen anzeigen."
HELP_EXCLUDE_1: "Blöcke/Benutzer ausschließen."
HELP_EXCLUDE_1: "Bestimmte Blöcke/Benutzer ausschließen."
HELP_EXCLUDE_2: "Beispiele: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Hilfe"
HELP_INCLUDE_1: "Fügen Sie Blöcke/Entitäten ein."
HELP_INCLUDE_2: "Beispiele: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Wenn der Inspektor aktiviert ist, können Sie:"
HELP_INSPECT_2: "Klicken Sie mit der linken Maustaste, um die Platzierung anzuzeigen."
HELP_INSPECT_3: "Klicken Sie mit der rechten Maustaste, um benachbarte Entfernungen anzuzeigen."
HELP_INSPECT_4: "Platzieren Sie einen Block, um die Entfernung zu sehen."
HELP_INSPECT_5: "Legen Sie einen Block in Flüssigkeit, um die Platzierung zu sehen."
HELP_INSPECT_6: "Klicken Sie mit der rechten Maustaste, um Truhentransaktionen anzuzeigen."
HELP_INSPECT_7: "Tipp: Sie können einfach \"/co i\" verwenden."
HELP_INSPECT_1: "Mit dem Inspector kannst du Folgendes tun:"
HELP_INSPECT_2: "Linksklicke einen Block, um zu sehen, wer ihn platziert hat"
HELP_INSPECT_3: "Rechtsklick auf einen Block zeigt Änderungen an Nachbarn an"
HELP_INSPECT_4: "Platziere einen Block, um Änderungen an seinem Ort zu sehen"
HELP_INSPECT_5: "So kann man auch Änderungen an Flüssigkeiten etc. sehen"
HELP_INSPECT_6: "Rechtsklicke Türen, Truhen, etc., um Zugriffe zu sehen"
HELP_INSPECT_7: "Tipp: Benutze \"/co i\" für schnelleren Zugriff"
HELP_INSPECT_COMMAND: "Schaltet den Blockinspektor um."
HELP_LIST: "Zeigt eine Liste aller Befehle an."
HELP_LOOKUP_1: "Befehlsverknüpfung."
@@ -52,13 +56,13 @@ HELP_LOOKUP_2: "Weitere Seiten anzeigen."
HELP_LOOKUP_COMMAND: "Blockdaten nachschlagen."
HELP_NO_INFO: "Informationen zum Befehl \"{0}\" nicht gefunden."
HELP_PARAMETER: "Bitte sehen Sie \"{0}\" für detaillierte Parameterinformationen."
HELP_PARAMS_1: "Führen Sie die {Nachschlagen|Rollback|Wiederherstellen}."
HELP_PARAMS_2: "Geben Sie die Benutzer an {Nachschlagen|Rollback|Wiederherstellen}."
HELP_PARAMS_3: "Geben Sie die Zeit an, um {Nachschlagen|Rollback|Wiederherstellen}."
HELP_PARAMS_4: "Beschränken Sie die {Nachschlagen|Rollback|Wiederherstellen} zu einem Radius."
HELP_PARAMS_5: "Beschränken Sie die {Nachschlagen|Rollback|Wiederherstellen} zu einer Aktion."
HELP_PARAMS_6: "Einschließen von Blöcken/Entitäten in die {Nachschlagen|Rollback|Wiederherstellen}."
HELP_PARAMS_7: "Sperren/Benutzer von der ausschließen {Nachschlagen|Rollback|Wiederherstellen}."
HELP_PARAMS_1: "Führe einen {Lookup|Rollback|Restore} durch."
HELP_PARAMS_2: "Spieler, für die der {Lookup|Rollback|Restore} gemacht werden soll."
HELP_PARAMS_3: "Zeitspanne, für die der {Lookup|Rollback|Restore} gemacht werden soll."
HELP_PARAMS_4: "Radius, in dem der {Lookup|Rollback|Restore} gemacht werden soll."
HELP_PARAMS_5: "Aktion, für die der {Lookup|Rollback|Restore} gemacht werden soll."
HELP_PARAMS_6: "Schließe Blöcke/Entitäten in den {Lookup|Rollback|Restore} ein."
HELP_PARAMS_7: "Schließe Blöcke/Benutzer von dem {Lookup|Rollback|Restore} aus."
HELP_PURGE_1: "Alte Daten entfernen."
HELP_PURGE_2: "Beispielsweise, \"{0}\" löscht alle Daten, die älter als einen Monat sind, und behält nur die Daten der letzten 30 Tage."
HELP_PURGE_COMMAND: "Alte Blockdaten löschen."
@@ -70,19 +74,19 @@ HELP_ROLLBACK_COMMAND: "Rollback-Blockdaten."
HELP_STATUS: "Sehen Sie sich den Plugin-Status an."
HELP_STATUS_COMMAND: "Zeigt den Plugin-Status an."
HELP_TELEPORT: "Teleportieren."
HELP_TIME_1: "Geben Sie die Uhrzeit an."
HELP_TIME_1: "Geben Sie einen Zeitraum an."
HELP_TIME_2: "Beispiele: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Geben Sie die Benutzer an."
HELP_USER_1: "Geben Sie den/die Benutzer an."
HELP_USER_2: "Beispiele: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" kann mit dieser Aktion nicht verwendet werden."
INSPECTOR_ERROR: "Inspektor schon {aktiviert|deaktiviert}."
INSPECTOR_ERROR: "Inspektor bereits {aktiviert|deaktiviert}."
INSPECTOR_TOGGLED: "Inspektor jetzt {aktiviert|deaktiviert}."
INTEGRATION_ERROR: "Unfähig zu {initialisieren|deaktivieren} {0} Protokollierung."
INTEGRATION_SUCCESS: "{0} erfolgreich anmelden {initialisiert|deaktiviert}."
INTEGRATION_VERSION: "Ungültig {0} Version gefunden."
INTEGRATION_ERROR: "Unfähig, {0} Protokollierung zu {initialisieren|deaktivieren}."
INTEGRATION_SUCCESS: "{0} Protokollierung erfolgreich {initialisiert|deaktiviert}."
INTEGRATION_VERSION: "Ungültige {0} Version gefunden."
INTERACTIONS_HEADER: "Spielerinteraktionen"
INVALID_ACTION: "Das ist keine gültige Aktion."
INVALID_BRANCH_1: "Ungültige Plugin-Version (Verzweigung wurde nicht gesetzt)."
INVALID_BRANCH_1: "Ungültige Plugin-Version (Branch wurde nicht gesetzt)."
INVALID_BRANCH_2: "Um fortzufahren, setzen Sie den Projektzweig auf \"Entwicklung\"."
INVALID_BRANCH_3: "Das Ausführen von Entwicklungscode kann zu Datenbeschädigungen führen."
INVALID_CONTAINER: "Bitte überprüfen Sie zuerst einen gültigen Behälter."
@@ -99,82 +103,87 @@ LINK_DOWNLOAD: "Herunterladen: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Blocknamen: {0}"
LINK_WIKI_ENTITY: "Entitätsnamen: {0}"
LOGGING_ITEMS: "{0} Elemente, die noch zu loggen sind. Warten Sie mal..."
LOGGING_ITEMS: "{0} Elemente, die noch zu loggen sind. Bitte warten..."
LOGGING_TIME_LIMIT: "Zeitlimit für die Protokollierung erreicht. Daten verwerfen und herunterfahren."
LOOKUP_BLOCK: "{0} {platziert|kaputt} {1}."
LOOKUP_BLOCK: "{0} {platziert|entfernt} {1}."
LOOKUP_CONTAINER: "{0} {hinzugefügt|entfernt} {1} {2}."
LOOKUP_HEADER: "{0} Ergebnisse"
LOOKUP_INTERACTION: "{0} {angeklickt|getötet} {1}."
LOOKUP_ITEM: "{0} {abgeholt|abgeworfen} {1} {2}."
LOOKUP_LOGIN: "{0} eingeloggt {rein|raus}."
LOOKUP_ITEM: "{0} {aufgehoben|weggeworfen} {1} {2}."
LOOKUP_LOGIN: "{0} {ein|aus}geloggt."
LOOKUP_PAGE: "Seite {0}"
LOOKUP_PROJECTILE: "{0} {geworfen|geschossen} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {Reihe|Reihen} gefunden."
LOOKUP_SEARCHING: "Suche nachschlagen. Warten Sie mal..."
LOOKUP_SEARCHING: "Suchen. Bitte warten..."
LOOKUP_STORAGE: "{0} {hinterlegt|abgehoben} {1} {2}."
LOOKUP_TIME: "{0} vor"
LOOKUP_USERNAME: "{0} Eingeloggt als {1}."
LOOKUP_VIEW_PAGE: "Um eine Seite anzuzeigen, geben Sie \"{0}\"."
MAXIMUM_RADIUS: "Das Maximum {Nachschlagen|Rollback|Wiederherstellen} Radius ist {0}."
LOOKUP_TIME: "vor {0}"
LOOKUP_USERNAME: "{0} eingeloggt als {1}."
MAXIMUM_RADIUS: "Der maximale {Nachschlag|Rollback|Restore} Radius ist {0}."
MISSING_ACTION_USER: "Um diese Aktion zu verwenden, geben Sie bitte einen Benutzer an."
MISSING_LOOKUP_TIME: "Bitte geben Sie die Zeit an, um {Nachschlagen|Rollback|Wiederherstellen}."
MISSING_LOOKUP_USER: "Bitte geben Sie einen Benutzer an oder {Block|Radius}."
MISSING_LOOKUP_TIME: "Bitte geben Sie den Zeitraum an, um {Nachzuschlagen|ein Rollback auszuführen|einen Restore auszuführen}."
MISSING_LOOKUP_USER: "Bitte geben Sie einen Benutzer oder {Block|Radius} an."
MISSING_PARAMETERS: "Bitte verwenden Sie \"{0}\"."
MISSING_ROLLBACK_RADIUS: "Sie haben nicht angegeben a {Zurücksetzen|Wiederherstellen} Radius."
MISSING_ROLLBACK_USER: "Sie haben nicht angegeben a {Zurücksetzen|Wiederherstellen} Benutzer."
MYSQL_UNAVAILABLE: "Verbindung zu nicht möglich MySQL Server."
NO_DATA: "Keine Daten gefunden bei {0}."
NO_DATA_LOCATION: "Nein {Daten|Transaktionen|Interaktionen|Nachrichten} gefunden."
MISSING_ROLLBACK_RADIUS: "Sie haben keinen {Rollback|Restore} Radius angegeben."
MISSING_ROLLBACK_USER: "Sie haben keinen {Rollback|Restore} Benutzer angegeben."
MYSQL_UNAVAILABLE: "Verbindung zu MySQL Server nicht möglich."
NETWORK_CONNECTION: "Verbindung von {0} {erfolgreich|fehlgeschlagen}. Nutze {1} {2}."
NETWORK_TEST: "Netzwerktestdaten wurden erfolgreich gesendet."
NO_DATA: "Keine Daten bei {0} gefunden."
NO_DATA_LOCATION: "Keine {Daten|Transaktionen|Interaktionen|Nachrichten} gefunden."
NO_PERMISSION: "Du hast keine Erlaubnis das zu tun."
NO_RESULTS: "Keine Ergebnisse gefunden."
NO_RESULTS_PAGE: "Nein {Ergebnisse|Daten} für diese Seite gefunden."
NO_ROLLBACK: "Nein {ausstehend|zurück} Rollback/Wiederherstellung gefunden."
PATCH_INTERRUPTED: "Upgrade unterbrochen. Werde es beim Neustart nochmal versuchen."
PATCH_OUTDATED_1: "Datenbanken, die älter als sind, können nicht aktualisiert werden {0}."
PATCH_OUTDATED_2: "Bitte aktualisieren Sie mit einer unterstützten Version von CoreProtect."
PATCH_PROCESSING: "Verarbeitung neuer Daten. Warten Sie mal..."
PATCH_SKIP_UPDATE: "Überspringen {Tabelle|Index} {Aktualisieren|Erstellen|Entfernen} auf {0}."
PATCH_STARTED: "Performing {0} Aktualisierung. Warten Sie mal..."
NO_RESULTS_PAGE: "Keine {Ergebnisse|Daten} für diese Seite gefunden."
NO_ROLLBACK: "Kein(en) {ausstehende(s|n)|vorherige(s|n)} Rollback/Restore gefunden."
PATCH_INTERRUPTED: "Upgrade unterbrochen. Wird bei einem Neustart erneut versucht."
PATCH_OUTDATED_1: "Datenbanken, die älter als {0} sind, können nicht aktualisiert werden."
PATCH_OUTDATED_2: "Bitte updaten Sie mit einer unterstützten Version von CoreProtect."
PATCH_PROCESSING: "Neue Daten werden verarbeitet. Bitte warten..."
PATCH_SKIP_UPDATE: "Überspringe {Tabellen|Index}{aktualisierung|erstellung|entfernung} bei {0}."
PATCH_STARTED: "Führe {0} Upgrade durch. Bitte warten..."
PATCH_SUCCESS: "Erfolgreich aktualisiert auf {0}."
PATCH_UPGRADING: "Datenbank-Upgrade im Gange. Warten Sie mal..."
PATCH_UPGRADING: "Datenbank-Upgrade im Gange. Bitte warten..."
PLEASE_SELECT: "Bitte auswählen: \"{0}\" oder \"{1}\"."
PREVIEW_CANCELLED: "Vorschau abgebrochen."
PREVIEW_CANCELLING: "Vorschau abbrechen..."
PREVIEW_CONTAINER: "Sie können keine Containertransaktionen in der Vorschau anzeigen."
PREVIEW_IN_GAME: "Sie können Rollbacks nur im Spiel in der Vorschau anzeigen."
PREVIEW_IN_GAME: "Sie können Rollbacks nur im Spiel als Vorschau anzeigen."
PREVIEW_TRANSACTION: "Sie können keine {Behälter|Inventar}-Transaktionen als Vorschau anzeigen."
PRIMARY_THREAD_ERROR: "Diese API-Methode kann nicht im Haupt-Thread verwendet werden."
PURGE_ABORTED: "Bereinigung fehlgeschlagen. Die Datenbank ist möglicherweise beschädigt."
PURGE_ERROR: "Kann nicht verarbeitet werden {0} Daten!"
PURGE_ERROR: "{0} Daten können nicht verarbeitet werden!"
PURGE_FAILED: "Bereinigung fehlgeschlagen. Bitte versuchen Sie es später erneut."
PURGE_IN_PROGRESS: "Bereinigung läuft. Bitte versuchen Sie es später erneut."
PURGE_MINIMUM_TIME: "Sie können nur Daten löschen, die älter sind als {0} {Tage|Stunden}."
PURGE_MINIMUM_TIME: "Sie können nur Daten löschen, die älter als {0} {Tage|Stunden} sind."
PURGE_NOTICE_1: "Bitte beachten Sie, dass dies einige Zeit dauern kann."
PURGE_NOTICE_2: "Starten Sie Ihren Server nicht neu, bis dies abgeschlossen ist."
PURGE_OPTIMIZING: "Optimierung der Datenbank. Warten Sie mal..."
PURGE_PROCESSING: "wird bearbeitet {0} Daten..."
PURGE_OPTIMIZING: "Optimierung der Datenbank. Bitte warten..."
PURGE_PROCESSING: "{0} Daten werden verarbeitet..."
PURGE_REPAIRING: "Versuch einer Reparatur. Dies könnte eine Weile dauern..."
PURGE_ROWS: "{0} {Reihe|Reihen} von Daten gelöscht."
PURGE_STARTED: "Datenbereinigung gestartet am \"{0}\"."
PURGE_SUCCESS: "Datenbereinigung erfolgreich."
RAM_STATS: "{0}GB / {1}GB RAM"
RELOAD_STARTED: "Konfiguration wird neu geladen - bitte warten."
RELOAD_SUCCESS: "Konfiguration erfolgreich neu geladen."
ROLLBACK_ABORTED: "Rollback oder Wiederherstellung abgebrochen."
ROLLBACK_CHUNKS_FOUND: "Gefunden {0} {Brocken|Brocken} ändern."
ROLLBACK_CHUNKS_MODIFIED: "Geändert {0}/{1} {Brocken|Brocken}."
ROLLBACK_COMPLETED: "{Rollback|Wiederherstellen|Vorschau} abgeschlossen für \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Ausgeschlossen {Benutzer|Benutzer}: \"{0}\"."
ROLLBACK_INCLUDE: "{Eingeschlossen|Ausgeschlossen} {Block|Entität|Ziel} {Typ|Typen}: \"{0}\"."
ROLLBACK_ABORTED: "Rollback oder Restore abgebrochen."
ROLLBACK_CHUNKS_FOUND: "{0} {bearbeiteter|bearbeitete} {Chunk|Chunks} gefunden."
ROLLBACK_CHUNKS_MODIFIED: "{0}/{1} {Chunk|Chunks} bearbeitet."
ROLLBACK_COMPLETED: "{Rollback|Restore|Vorschau} für \"{0}\" abgeschlossen."
ROLLBACK_EXCLUDED_USERS: "{Ausgeschlossener Benutzer|Ausgeschlossene Benutzer}: \"{0}\"."
ROLLBACK_INCLUDE: "{Block|Entität|Ziel} {Typ|Typen} {einbezogen|ausgeschlossen}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Ein Rollback/Restore ist bereits im Gange."
ROLLBACK_LENGTH: "Zeit genommen: {0} {Sekunde|Sekunden}."
ROLLBACK_LENGTH: "Zeit beansprucht: {0} {Sekunde|Sekunden}."
ROLLBACK_MODIFIED: "{Modifiziert|Modifiziert} {0}."
ROLLBACK_RADIUS: "Radius: {0} {block|blöcke}."
ROLLBACK_RADIUS: "Radius: {0} {Block|Blöcke}."
ROLLBACK_SELECTION: "Radius eingestellt auf \"{0}\"."
ROLLBACK_STARTED: "{Rollback|Wiederherstellen|Vorschau} begann am \"{0}\"."
ROLLBACK_STARTED: "{Rollback|Restore|Vorschau} gestartet für \"{0}\"."
ROLLBACK_TIME: "Zeitspanne: {0}."
ROLLBACK_WORLD_ACTION: "Beschränkt auf {Welt|Aktion} \"{0}\"."
SIGN_HEADER: "Nachrichten signieren"
STATUS_CONSUMER: "Verbraucher: {0} {Artikel|Artikel} in der Warteschleife."
STATUS_DATABASE: "Datenbank: Verwenden {0}."
SIGN_HEADER: "Schildnachrichten"
STATUS_CONSUMER: "Warteschleife: {0} {Datensatz|Datensätze}"
STATUS_DATABASE: "Datenbank: {0}."
STATUS_INTEGRATION: "{0}: Integration {aktiviert|deaktiviert}."
STATUS_LICENSE: "Lizenz: {0}"
STATUS_SYSTEM: "System: {0}"
STATUS_VERSION: "Version: {0}"
TELEPORTED: "Teleportiert nach {0}."
TELEPORTED_SAFETY: "Hab dich in Sicherheit teleportiert."
@@ -183,15 +192,17 @@ TIME_DAYS: "{0} {Tag|Tage}"
TIME_HOURS: "{0} {Stunde|Stunden}"
TIME_MINUTES: "{0} {Minute|Minuten}"
TIME_SECONDS: "{0} {Sekunde|Sekunden}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {Woche|Wochen}"
UPDATE_ERROR: "Beim Suchen nach Updates ist ein Fehler aufgetreten."
UPDATE_HEADER: "{0} Aktualisieren"
UPDATE_NOTICE: "Beachten: {0} ist nun verfügbar."
UPDATE_NOTICE: "Hinweis: {0} ist nun verfügbar."
UPGRADE_IN_PROGRESS: "Upgrade läuft. Bitte versuchen Sie es später erneut."
USER_NOT_FOUND: "Benutzer \"{0}\" nicht gefunden."
USER_OFFLINE: "Der Benutzer \"{0}\" ist nicht online."
USING_MYSQL: "Verwenden von MySQL zur Datenspeicherung."
USING_SQLITE: "Verwenden von SQLite zur Datenspeicherung."
VALID_DONATION_KEY: "Gültiger Spendenschlüssel."
VERSION_NOTICE: "Ausführung {0} ist nun verfügbar."
VERSION_NOTICE: "Version {0} ist nun verfügbar."
VERSION_REQUIRED: "{0} {1} oder höher erforderlich."
WORLD_NOT_FOUND: "Welt \"{0}\" nicht gefunden."
WORLD_NOT_FOUND: "Welt \"{0}\" nicht gefunden."
+47 -4
View File
@@ -9,19 +9,31 @@ API_TEST: "API test successful."
CACHE_ERROR: "WARNING: Error while validating {0} cache."
CACHE_RELOAD: "Forcing reload of {mapping|world} caches from database."
CHECK_CONFIG: "Please check config.yml"
COMMAND_CONSOLE: "Please run the command from the console."
COMMAND_NOT_FOUND: "Command \"{0}\" not found."
COMMAND_THROTTLED: "Please wait a moment and try again."
CONSUMER_ERROR: "Consumer queue processing already {paused|resumed}."
CONSUMER_TOGGLED: "Consumer queue processing has been {paused|resumed}."
CONTAINER_HEADER: "Container Transactions"
CPU_CORES: "CPU cores."
DATABASE_BUSY: "Database busy. Please try again later."
DATABASE_FALLBACK: "Unable to load {0}. Using {1}; config.yml updated."
DATABASE_INDEX_ERROR: "Unable to validate database indexes."
DATABASE_LOCKED_1: "Database locked. Waiting up to 15 seconds..."
DATABASE_LOCKED_2: "Database is already in use. Please try again."
DATABASE_LOCKED_3: "To disable database locking, set \"database-lock: false\"."
DATABASE_LOCKED_4: "Disabling database locking can result in data corruption."
DATABASE_PERSISTENCE_HALTED: "Database writes halted. Please restart the server."
DATABASE_QUEUE_LOSS: "Queued data will be lost when the server stops."
DATABASE_RECOVERY_FAILED: "Database recovery failed after {0} attempts."
DATABASE_RECOVERY_STARTED: "Database became unavailable. Attempting database recovery."
DATABASE_RECOVERY_SUCCESS: "Database recovery completed. Queued writes will resume."
DATABASE_TYPE_OVERRIDE: "Option \"database-type\" overrides \"use-mysql\"."
DATABASE_UNREACHABLE: "Database is unreachable. Discarding data and shutting down."
DEVELOPMENT_BRANCH: "Development branch detected, skipping patch scripts."
DIRT_BLOCK: "Placed a dirt block under you."
DIRT_BLOCK: "Placed a temporary safety block under you."
DISABLE_SUCCESS: "Success! Disabled {0}"
DONATION_KEY_REQUIRED: "A valid donation key is required for that command."
ENABLE_FAILED: "{0} was unable to start."
ENABLE_SUCCESS: "{0} has been successfully enabled!"
ENJOY_COREPROTECT: "Enjoy {0}? Join our Discord!"
@@ -31,10 +43,12 @@ FIRST_VERSION: "Initial DB: {0}"
GLOBAL_LOOKUP: "Don't specify a radius to do a global lookup."
GLOBAL_ROLLBACK: "Use \"{0}\" to do a global {rollback|restore}"
HELP_ACTION_1: "Restrict the lookup to a certain action."
HELP_ACTION_2: "Examples: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_ACTION_2: "Examples: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:spawn], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Display more info for that command."
HELP_EXCLUDE_1: "Exclude blocks/users."
HELP_EXCLUDE_2: "Examples: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_FILTER_1: "Filter chat, command, or sign text."
HELP_FILTER_2: "Examples: [a:command f:/co], [a:sign f:Shop]"
HELP_HEADER: "{0} Help"
HELP_INCLUDE_1: "Include specific blocks/entities."
HELP_INCLUDE_2: "Examples: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
@@ -61,6 +75,11 @@ HELP_PARAMS_6: "Include specific blocks/entities in the {lookup|rollback|restore
HELP_PARAMS_7: "Exclude blocks/users from the {lookup|rollback|restore}."
HELP_PURGE_1: "Delete data older than specified time."
HELP_PURGE_2: "For example, \"{0}\" will delete all data older than one month, and only keep the last 30 days of data."
HELP_PURGE_3: "Specify the amount of time to purge."
HELP_PURGE_4: "Specify a world or radius to limit the purge to."
HELP_PURGE_5: "Include specific blocks or entity kills in the purge."
HELP_PURGE_6: "Keep entity kills of these types."
HELP_PURGE_7: "Only purge entity kills."
HELP_PURGE_COMMAND: "Delete old block data."
HELP_RADIUS_1: "Specify a radius area."
HELP_RADIUS_2: "Examples: [r:10] (Only make changes within 10 blocks of you)"
@@ -89,6 +108,8 @@ INVALID_CONTAINER: "Please inspect a valid container first."
INVALID_DONATION_KEY: "Invalid donation key."
INVALID_INCLUDE: "\"{0}\" is an invalid block/entity name."
INVALID_INCLUDE_COMBO: "That is an invalid block/entity combination."
INVALID_ITEM_ID: "Please enter a valid item id."
INVALID_PARAMETER: "\"{0}\" is not a supported parameter."
INVALID_RADIUS: "Please enter a valid radius."
INVALID_SELECTION: "{0} selection not found."
INVALID_USERNAME: "\"{0}\" is an invalid username."
@@ -103,18 +124,24 @@ LOGGING_ITEMS: "{0} items left to log. Please wait..."
LOGGING_TIME_LIMIT: "Logging time limit reached. Discarding data and shutting down."
LOOKUP_BLOCK: "{0} {placed|broke} {1}."
LOOKUP_CONTAINER: "{0} {added|removed} {1} {2}."
LOOKUP_ENTITY_INTERACTION: "{0} {clicked|sheared|leashed|unleashed} {1}."
LOOKUP_ENTITY_INTERACTION_ORIGIN: "Origin location"
LOOKUP_ENTITY_ORIGIN: "Spawn location"
LOOKUP_ENTITY_SPAWN: "{0} spawned {1}."
LOOKUP_HEADER: "{0} Lookup Results"
LOOKUP_INTERACTION: "{0} {clicked|killed} {1}."
LOOKUP_ITEM: "{0} {picked up|dropped} {1} {2}."
LOOKUP_LOGIN: "{0} logged {in|out}."
LOOKUP_PAGE: "Page {0}"
LOOKUP_PROJECTILE: "{0} {threw|shot} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {row|rows} found."
LOOKUP_SEARCHING: "Lookup searching. Please wait..."
LOOKUP_STORAGE: "{0} {deposited|withdrew} {1} {2}."
LOOKUP_TIME: "{0} ago"
LOOKUP_USERNAME: "{0} logged in as {1}."
LOOKUP_VIEW_PAGE: "To view a page, type \"{0}\"."
MAXIMUM_PURGE_RADIUS: "The maximum purge radius is {0}."
MAXIMUM_RADIUS: "The maximum {lookup|rollback|restore} radius is {0}."
MESSAGE_FILTER_TOO_SHORT: "Minimum message filter length is {0} characters."
MISSING_ACTION_USER: "To use that action, please specify a user."
MISSING_LOOKUP_TIME: "Please specify the amount of time to {lookup|rollback|restore}."
MISSING_LOOKUP_USER: "Please specify a user or {block|radius} to lookup."
@@ -122,6 +149,8 @@ MISSING_PARAMETERS: "Please use \"{0}\"."
MISSING_ROLLBACK_RADIUS: "You did not specify a {rollback|restore} radius."
MISSING_ROLLBACK_USER: "You did not specify a {rollback|restore} user."
MYSQL_UNAVAILABLE: "Unable to connect to MySQL server."
NETWORK_CONNECTION: "Connection by {0} {successful|failed}. Using {1} {2}."
NETWORK_TEST: "Network test data has been successful sent."
NO_DATA: "No data found at {0}."
NO_DATA_LOCATION: "No {data|transactions|interactions|messages} found at this location."
NO_PERMISSION: "You do not have permission to do that."
@@ -135,12 +164,15 @@ PATCH_PROCESSING: "Processing new data. Please wait..."
PATCH_SKIP_UPDATE: "Skipping {table|index} {update|creation|removal} on {0}."
PATCH_STARTED: "Performing {0} upgrade. Please wait..."
PATCH_SUCCESS: "Successfully upgraded to {0}."
PATCH_TABLE_COMPLETED: "Successfully updated {0} table."
PATCH_TABLE_STARTED: "Updating {0} table. Please wait..."
PATCH_UPGRADING: "Database upgrade in progress. Please wait..."
PLEASE_SELECT: "Please select: \"{0}\" or \"{1}\"."
PREVIEW_CANCELLED: "Preview cancelled."
PREVIEW_CANCELLING: "Cancelling preview..."
PREVIEW_CONTAINER: "You can't preview container transactions."
PREVIEW_IN_GAME: "You can only preview rollbacks in-game."
PREVIEW_TRANSACTION: "You can't preview {container|inventory} transactions."
PRIMARY_THREAD_ERROR: "That API method can't be used on the primary thread."
PURGE_ABORTED: "Purge failed. Database may be corrupt."
PURGE_ERROR: "Unable to process {0} data!"
PURGE_FAILED: "Purge failed. Please try again later."
@@ -154,6 +186,8 @@ PURGE_REPAIRING: "Attempting to repair. This may take some time..."
PURGE_ROWS: "{0} {row|rows} of data deleted."
PURGE_STARTED: "Data purge started on \"{0}\"."
PURGE_SUCCESS: "Data purge successful."
RAM_STATS: "{0}GB / {1}GB RAM"
RELOAD_FAILED: "Database reload failed. Please try again."
RELOAD_STARTED: "Reloading configuration - please wait."
RELOAD_SUCCESS: "Configuration successfully reloaded."
ROLLBACK_ABORTED: "Rollback or restore aborted."
@@ -171,10 +205,13 @@ ROLLBACK_STARTED: "{Rollback|Restore|Preview} started on \"{0}\"."
ROLLBACK_TIME: "Time range: {0}."
ROLLBACK_WORLD_ACTION: "Restricted to {world|action} \"{0}\"."
SIGN_HEADER: "Sign Messages"
STATUS_AUTO_PURGE: "Cleanup: {0} {row|rows} auto purged since restart."
STATUS_CONSUMER: "Consumer: {0} {item|items} in queue."
STATUS_DATABASE: "Database: Using {0}."
STATUS_DATABASE_STATE: "({writes halted|unreachable})"
STATUS_INTEGRATION: "{0}: Integration {enabled|disabled}."
STATUS_LICENSE: "License: {0}"
STATUS_SYSTEM: "System: {0}"
STATUS_VERSION: "Version: {0}"
TELEPORTED: "Teleported to {0}."
TELEPORTED_SAFETY: "Teleported you to safety."
@@ -182,16 +219,22 @@ TELEPORT_PLAYERS: "Teleport command can only be used by players."
TIME_DAYS: "{0} {day|days}"
TIME_HOURS: "{0} {hour|hours}"
TIME_MINUTES: "{0} {minute|minutes}"
TIME_MONTHS: "{0} {month|months}"
TIME_SECONDS: "{0} {second|seconds}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {week|weeks}"
TIME_YEARS: "{0} {year|years}"
UPDATE_ERROR: "An error occurred while checking for updates."
UPDATE_HEADER: "{0} Update"
UPDATE_NOTICE: "Notice: {0} is now available."
UPGRADE_IN_PROGRESS: "Upgrade in progress. Please try again later."
USER_NOT_FOUND: "User \"{0}\" not found."
USER_OFFLINE: "The user \"{0}\" is not online."
USING_DATABASE: "Using {0} for data storage."
USING_MYSQL: "Using MySQL for data storage."
USING_SQLITE: "Using SQLite for data storage."
VALID_DONATION_KEY: "Valid donation key."
VERSION_INCOMPATIBLE: "{0} {1} is not supported."
VERSION_NOTICE: "Version {0} is now available."
VERSION_REQUIRED: "{0} {1} or higher is required."
WORLD_NOT_FOUND: "World \"{0}\" not found."
+174 -163
View File
@@ -1,197 +1,208 @@
# CoreProtect Language File (es)
ACTION_NOT_SUPPORTED: "Esa acción no es compatible."
ACTION_NOT_SUPPORTED: "Está acción no es compatible con el comando."
AMOUNT_BLOCK: "{0} {bloque|bloques}"
AMOUNT_CHUNK: "{0} {trozo|trozos}"
AMOUNT_CHUNK: "{0} {chunk|chunks}"
AMOUNT_ENTITY: "{0} {entidad|entidades}"
AMOUNT_ITEM: "{0} {artículo|artículos}"
API_TEST: "Prueba de API exitosa."
CACHE_ERROR: "ADVERTENCIA: Error al validar {0} cache."
CACHE_RELOAD: "Forzando la recarga de {mapeo|mundo} cachés de la base de datos."
CHECK_CONFIG: "Por favor revise config.yml"
COMMAND_NOT_FOUND: "Comando \"{0}\" extraviado."
COMMAND_THROTTLED: "Espere un momento y vuelva a intentarlo."
CONTAINER_HEADER: "Transacciones de contenedores"
DATABASE_BUSY: "Base de datos ocupado. Por favor, inténtelo de nuevo más tarde."
DATABASE_INDEX_ERROR: "No se pueden validar los índices de la base de datos."
DATABASE_LOCKED_1: "Base de datos bloqueada. Esperando hasta 15 segundos..."
DATABASE_LOCKED_2: "La base de datos ya está en uso. Inténtalo de nuevo."
DATABASE_LOCKED_3: "Para deshabilitar el bloqueo de la base de datos, establezca \"database-lock: false\"."
DATABASE_LOCKED_4: "La desactivación del bloqueo de la base de datos puede provocar daños en los datos."
DATABASE_UNREACHABLE: "La base de datos es inalcanzable. Descartando datos y cerrando."
DEVELOPMENT_BRANCH: "Se detectó una rama de desarrollo, omitiendo los scripts de parches."
DIRT_BLOCK: "Colocó un bloque de tierra debajo de usted."
DISABLE_SUCCESS: "¡Éxito! Discapacitado {0}"
ENABLE_FAILED: "{0} fue incapaz de comenzar."
ENABLE_SUCCESS: "{0} ahora está habilitado!"
ENJOY_COREPROTECT: "Disfrutar {0}? Únete a nuestro Discord!"
FINISHING_CONVERSION: "Terminando la conversión de datos. Espere por favor..."
FINISHING_LOGGING: "Terminando el registro de datos. Espere por favor..."
FIRST_VERSION: "DB inicial: {0}"
GLOBAL_LOOKUP: "No especifique un radio para realizar una búsqueda global."
GLOBAL_ROLLBACK: "Usar \"{0}\" para hacer un global {deshacer|restaurar}"
HELP_ACTION_1: "Restrinja la búsqueda a una acción."
AMOUNT_ITEM: "{0} {objeto|objetos}"
API_TEST: "Testeo de la API exitosa."
CACHE_ERROR: "ADVERTENCIA: Error mientras se validaba el caché {0}."
CACHE_RELOAD: "Forzando recarga de cachés desde la base de datos de {mapeado|mundo}."
CHECK_CONFIG: "Por favor revise su config.yml"
COMMAND_CONSOLE: "Intente ejecutar el comando desde la consola."
COMMAND_NOT_FOUND: "Comando \"{0}\" no encontrado."
COMMAND_THROTTLED: "Espere un momento e intente nuevamente."
CONSUMER_ERROR: "La espera en el procesamiento del consumidor ha sido {pausada|continuada}."
CONSUMER_TOGGLED: "Consumer queue processing has been {pausado|continuado}."
CONTAINER_HEADER: "Transacciones del contenedor"
CPU_CORES: "CPU cores."
DATABASE_BUSY: "Base de datos ocupada. Intente más tarde."
DATABASE_INDEX_ERROR: "Imposible validar los indices de la base de datos."
DATABASE_LOCKED_1: "Base de datos bloqueada. Espere en los siguientes 15 segundos..."
DATABASE_LOCKED_2: "Base de datos ya en uso. Intente nuevamente."
DATABASE_LOCKED_3: "Para desactivar el bloqueo de la base de datos, coloque \"database-lock: false\"."
DATABASE_LOCKED_4: "Desactivando el bloqueo en la base de datos resultaría en una posible corrupción de datos."
DATABASE_UNREACHABLE: "No se pudo alcanzar la base de datos. Descartando datos y apagando."
DEVELOPMENT_BRANCH: "Rama de desarrollo detectada, saltando scripts de parche."
DIRT_BLOCK: "Se ha colocado un bloque de tierra debajo de tí."
DISABLE_SUCCESS: "¡Se ha desactivado exitosamente {0}!"
ENABLE_FAILED: "Imposible de iniciar {0}."
ENABLE_SUCCESS: "¡{0} ha sido exitosamente activado!"
ENJOY_COREPROTECT: "¿Disfrutando {0}? ¡Ingrese a nuestro Discord!"
FINISHING_CONVERSION: "Terminando la conversión de datos. Espere..."
FINISHING_LOGGING: "Terminando el registro de datos. Espere..."
FIRST_VERSION: "Base de datos inicial: {0}"
GLOBAL_LOOKUP: "No especificaste el radio para una busqueda global."
GLOBAL_ROLLBACK: "Usa \"{0}\" para hacer un {respaldo|restauración} global"
HELP_ACTION_1: "Restringir la busqueda a una acción."
HELP_ACTION_2: "Ejemplos: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Muestra información adicional."
HELP_EXCLUDE_1: "Excluir bloques / usuarios."
HELP_COMMAND: "Mostrar información sobre un comando."
HELP_EXCLUDE_1: "Excluir bloques/usuarios."
HELP_EXCLUDE_2: "Ejemplos: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Ayudar"
HELP_INCLUDE_1: "Incluya bloques / entidades."
HELP_HEADER: "Ayuda {0}"
HELP_INCLUDE_1: "Incluir bloques/entidades exactos."
HELP_INCLUDE_2: "Ejemplos: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Con el inspector habilitado, puede:"
HELP_INSPECT_2: "Haga clic con el botón izquierdo para ver la ubicación."
HELP_INSPECT_3: "Haga clic derecho para ver las eliminaciones adyacentes."
HELP_INSPECT_4: "Coloque un bloque para ver la eliminación."
HELP_INSPECT_5: "Coloque un bloque en líquido para ver la ubicación."
HELP_INSPECT_6: "Haga clic derecho para ver las transacciones del cofre."
HELP_INSPECT_7: "Sugerencia: puede usar solo \"/co i\"."
HELP_INSPECT_COMMAND: "Alterna el inspector de bloques."
HELP_INSPECT_1: "Con el inspeccionador activo, puedes hacer lo siguiente:"
HELP_INSPECT_2: "Clic izquierdo para ver quien colocó ese bloque."
HELP_INSPECT_3: "Clic derecho para ver quien rompió el bloque de encima."
HELP_INSPECT_4: "Coloca un bloque para ver si se ha roto alguno en la zona."
HELP_INSPECT_5: "Coloca el bloque en un liquido (etc) para ver quien lo colocó."
HELP_INSPECT_6: "Clic derecho sobre un cofres y ver sus transacciones."
HELP_INSPECT_7: "Consejo: Puedes usar \"/co i\" para un acceso rápido."
HELP_INSPECT_COMMAND: "Alterna el revisor de bloques."
HELP_LIST: "Muestra una lista de todos los comandos."
HELP_LOOKUP_1: "Atajo de comando."
HELP_LOOKUP_2: "Ver páginas adicionales."
HELP_LOOKUP_COMMAND: "Búsqueda de datos de bloques."
HELP_NO_INFO: "Información para el mando \"{0}\" extraviado."
HELP_PARAMETER: "Por favor mira \"{0}\" para obtener información detallada sobre los parámetros."
HELP_PARAMS_1: "Realizar el {buscar|deshacer|restaurar}."
HELP_PARAMS_2: "Especifique los usuarios para {buscar|deshacer|restaurar}."
HELP_PARAMS_3: "Especifique el tiempo para {buscar|deshacer|restaurar}."
HELP_PARAMS_4: "Restringir el {buscar|deshacer|restaurar} a un radio."
HELP_PARAMS_5: "Restringir el {buscar|deshacer|restaurar} a una acción."
HELP_PARAMS_6: "Incluir bloques / entidades en el {buscar|deshacer|restaurar}."
HELP_PARAMS_7: "Excluir bloques / usuarios del {buscar|deshacer|restaurar}."
HELP_PURGE_1: "Eliminar datos antiguos."
HELP_PURGE_2: "Por ejemplo, \"{0}\" eliminará todos los datos anteriores a un mes y solo conservará los datos de los últimos 30 días."
HELP_PURGE_COMMAND: "Elimina los datos del bloque antiguo."
HELP_RADIUS_1: "Especifique un área de radio."
HELP_RADIUS_2: "Ejemplos: [r:10] (Solo haga cambios dentro de las 10 cuadras de usted)"
HELP_RELOAD_COMMAND: "Vuelve a cargar el archivo de configuración."
HELP_RESTORE_COMMAND: "Restaurar los datos del bloque."
HELP_ROLLBACK_COMMAND: "Revertir los datos del bloque."
HELP_STATUS: "Ver el estado del complemento."
HELP_STATUS_COMMAND: "Muestra el estado del complemento."
HELP_TELEPORT: "Teletransportarse."
HELP_TIME_1: "Especifique la hora."
HELP_LOOKUP_1: "Atajo de comandos."
HELP_LOOKUP_2: "Mostrar páginas adicionales."
HELP_LOOKUP_COMMAND: "Busqueda de bloques avanzada."
HELP_NO_INFO: "Información para el comando \"{0}\" no encontrada."
HELP_PARAMETER: "Revisa \"{0}\" para una información más detallada."
HELP_PARAMS_1: "Ingrese la {búsqueda|respaldo|restauración}."
HELP_PARAMS_2: "Especifique al usuario a {buscar|respaldar|restaurar}."
HELP_PARAMS_3: "Especifique la cantidad de tiempo a {buscar|respaldar|restaurar}."
HELP_PARAMS_4: "Especifique el radio de area a {buscar|respaldar|restaurar}."
HELP_PARAMS_5: "Restringir la {búsqueda|respaldo|restauración} a una acción dada."
HELP_PARAMS_6: "Incluir bloques/entidades exactas a {buscar|respaldar|restaurar}."
HELP_PARAMS_7: "Excluir bloques/usuarios a {buscar|respaldar|resturar}."
HELP_PURGE_1: "Eliminar los datos en un tiempo dado."
HELP_PURGE_2: "Por ejemplo, \"{0}\" eliminará los datos que sean más viejos, y solo mantendrá lo que está en los 30d."
HELP_PURGE_COMMAND: "Elimina los datos de bloques viejos."
HELP_RADIUS_1: "Especifica un radio del area."
HELP_RADIUS_2: "Ejemplos: [r:10] (Solo cambiará 10 bloques delante de ti)"
HELP_RELOAD_COMMAND: "Recarga los archivos de configuración."
HELP_RESTORE_COMMAND: "Restaurar los datos de bloques."
HELP_ROLLBACK_COMMAND: "Restaurar bloques."
HELP_STATUS: "Muestra el estado del plugin y su información."
HELP_STATUS_COMMAND: "Muestra el estado del plugin."
HELP_TELEPORT: "Teletransportar a una coordenada."
HELP_TIME_1: "Especifique una cantidad de tiempo."
HELP_TIME_2: "Ejemplos: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Especifique los usuarios."
HELP_USER_1: "Especifica al usuario para buscar."
HELP_USER_2: "Ejemplos: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" no se puede utilizar con esa acción."
INSPECTOR_ERROR: "Inspector ya {habilitado|deshabilitado}."
INSPECTOR_TOGGLED: "Inspector ahora {habilitado|deshabilitado}."
INTEGRATION_ERROR: "Incapaz de {inicializar|deshabilitar} {0} Inicio sesión."
INTEGRATION_SUCCESS: "{0} registrando exitosamente {inicializado|deshabilitado}."
INTEGRATION_VERSION: "Inválido {0} versión encontrada."
INCOMPATIBLE_ACTION: "\"{0}\" no puede ser usado para esta acción."
INSPECTOR_ERROR: "Inspeccionador ya {activado|desactivado}."
INSPECTOR_TOGGLED: "Inspeccionador {activado|desactivado}."
INTEGRATION_ERROR: "Imposible {inicializar|desactivar} los registros {0}."
INTEGRATION_SUCCESS: "Registros en {0} han sido correctamente {inicializados|desactivados}."
INTEGRATION_VERSION: "Versión inválida {0} encontrada."
INTERACTIONS_HEADER: "Interacciones del jugador"
INVALID_ACTION: "Esa no es una acción válida."
INVALID_BRANCH_1: "Versión de complemento no válida (la rama no se ha configurado)."
INVALID_BRANCH_2: "Para continuar, establezca la rama del proyecto en \"desarrollo\"."
INVALID_BRANCH_3: "La ejecución de código de desarrollo puede provocar daños en los datos."
INVALID_CONTAINER: "Primero inspeccione un contenedor válido."
INVALID_DONATION_KEY: "Clave de donación no válida."
INVALID_INCLUDE: "\"{0}\" es un valor no válido."
INVALID_INCLUDE_COMBO: "Esa es una combinación de tipos no válida."
INVALID_RADIUS: "Ingrese un radio válido."
INVALID_SELECTION: "{0} selección no encontrada."
INVALID_USERNAME: "\"{0}\" es un nombre de usuario no válido."
INVALID_ACTION: "No es una acción válida."
INVALID_BRANCH_1: "Versión del plugin inválida (la rama no ha sido definida)."
INVALID_BRANCH_2: "Para continuar, coloque la rama del proyecto a \"development\"."
INVALID_BRANCH_3: "Ejecutar códigos en desarrollo puede resultar en una corrupción de datos."
INVALID_CONTAINER: "Intente inspeccionar un contenedor válido primero."
INVALID_DONATION_KEY: "Clave de donación inválida."
INVALID_INCLUDE: "\"{0}\" es un bloque inválido/ o nombre de entidad."
INVALID_INCLUDE_COMBO: "Es una combinación inválida de bloque/entidad."
INVALID_RADIUS: "Coloque un radio válido."
INVALID_SELECTION: "{0} sección no encontrada."
INVALID_USERNAME: "\"{0}\" no es usuario existente."
INVALID_WORLD: "Especifique un mundo válido."
LATEST_VERSION: "Última versión: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Descargar: {0}"
LINK_DOWNLOAD: "Descarga: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Nombres de bloque: {0}"
LINK_WIKI_BLOCK: "Nombres de bloques: {0}"
LINK_WIKI_ENTITY: "Nombres de entidades: {0}"
LOGGING_ITEMS: "{0} elementos que quedan por registrar. Espere por favor..."
LOGGING_TIME_LIMIT: "Se alcanzó el límite de tiempo de registro. Descartando datos y cerrando."
LOOKUP_BLOCK: "{0} {colocado|roto} {1}."
LOOKUP_CONTAINER: "{0} {añadido|eliminado} {1} {2}."
LOGGING_ITEMS: "{0} objetos restantes por registrar. Espere un momento..."
LOGGING_TIME_LIMIT: "Limite de tiempo de registros. Descartamdo pérdida de información."
LOOKUP_BLOCK: "{0} {colocó|rompió} {1}."
LOOKUP_CONTAINER: "{0} {agregó|removió} {1} {2}."
LOOKUP_HEADER: "{0} Resultados"
LOOKUP_INTERACTION: "{0} {clickeado|muerto} {1}."
LOOKUP_ITEM: "{0} {recogido|caído} {1} {2}."
LOOKUP_LOGIN: "{0} registrado {adentro|afuera}."
LOOKUP_INTERACTION: "{0} {clickeó|mató} {1}."
LOOKUP_ITEM: "{0} {recogió|soltó} {1} {2}."
LOOKUP_LOGIN: "{0} ha {entrado|salido}."
LOOKUP_PAGE: "Página {0}"
LOOKUP_ROWS_FOUND: "{0} {fila|filas} encontró."
LOOKUP_SEARCHING: "Búsqueda de búsqueda. Espere por favor..."
LOOKUP_STORAGE: "{0} {depositado|retirado} {1} {2}."
LOOKUP_TIME: "{0} atrás"
LOOKUP_USERNAME: "{0} Conectado como {1}."
LOOKUP_VIEW_PAGE: "Para ver una página, escriba \"{0}\"."
MAXIMUM_RADIUS: "El maximo {buscar|deshacer|restaurar} el radio es {0}."
MISSING_ACTION_USER: "Para utilizar esa acción, especifique un usuario."
MISSING_LOOKUP_TIME: "Por favor, especifique el tiempo para {buscar|deshacer|restaurar}."
MISSING_LOOKUP_USER: "Especifique un usuario o {bloque|radio}."
MISSING_PARAMETERS: "Por favor use \"{0}\"."
MISSING_ROLLBACK_RADIUS: "No especificó un {deshacer|restaurar} radio."
MISSING_ROLLBACK_USER: "No especificó un {deshacer|restaurar} usuario."
MYSQL_UNAVAILABLE: "Incapaz de conectarse a MySQL servidor."
NO_DATA: "No se encontraron datos en {0}."
NO_DATA_LOCATION: "No {datos|transacciones|interacciones|mensajes} encontró."
NO_PERMISSION: "No tienes permiso para hacer eso."
NO_RESULTS: "No se han encontrado resultados."
NO_RESULTS_PAGE: "No {resultados|datos} encontrado para esa página."
NO_ROLLBACK: "No {pendiente|anterior} deshacer / restaurar encontrado."
PATCH_INTERRUPTED: "Actualización interrumpida. Intentará de nuevo al reiniciar."
PATCH_OUTDATED_1: "No se pueden actualizar las bases de datos anteriores a {0}."
PATCH_OUTDATED_2: "Actualice con una versión compatible de CoreProtect."
PATCH_PROCESSING: "Procesando nuevos datos. Espere por favor..."
PATCH_SKIP_UPDATE: "Salto a la comba {tabla|índice} {actualización|creación|eliminación} en {0}."
PATCH_STARTED: "Ejecutando {0} potenciar. Espere por favor..."
PATCH_SUCCESS: "Actualizado con éxito a {0}."
PATCH_UPGRADING: "Actualización de la base de datos en curso. Espere por favor..."
LOOKUP_PROJECTILE: "{0} {tiró|disparó} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {fila|filas} encontradas."
LOOKUP_SEARCHING: "Busqueda iniciada. Espere..."
LOOKUP_STORAGE: "{0} {depositó|retiró} {1} {2}."
LOOKUP_TIME: "Hace {0}"
LOOKUP_USERNAME: "{0} entró como {1}."
MAXIMUM_RADIUS: "El radio máximo de {búsqueda|retorno|restauración} es {0}."
MISSING_ACTION_USER: "Para usar esta acción, especifique a un usuario."
MISSING_LOOKUP_TIME: "Especifique la cantidad de tiempo para {buscar|retornar|restaurar}."
MISSING_LOOKUP_USER: "Especifique un usuario o {bloque|radio} para buscar."
MISSING_PARAMETERS: "Usa \"{0}\"."
MISSING_ROLLBACK_RADIUS: "No especificaste un radio para {retornar|resturar}."
MISSING_ROLLBACK_USER: "No especificaste a un usuario para {retornar|restaurar}."
MYSQL_UNAVAILABLE: "No se pudo conectar con el servidor de MySQL."
NETWORK_CONNECTION: "Conexión por {0} {exitosa|fallida}. Usando {1} {2}."
NETWORK_TEST: "Los datos de la prueba de red se han enviado con éxito."
NO_DATA: "Sin datos para {0}."
NO_DATA_LOCATION: "Sin {datos|transacciones|interacciones|mensajes} encontrados acá."
NO_PERMISSION: "No tienes permisos para hacer esto."
NO_RESULTS: "No se hallaron resultados."
NO_RESULTS_PAGE: "Sin {resultados|datos} encontados para esta página."
NO_ROLLBACK: "No hay rollbacks {pendientes|previos} encontrados."
PATCH_INTERRUPTED: "Mejora interrumpida. Se intentará nuevamente en el reinicio."
PATCH_OUTDATED_1: "No se puede mejorar base de datos más antiguas de {0}."
PATCH_OUTDATED_2: "Actualice con una versión soportada de CoreProtect."
PATCH_PROCESSING: "Procesando nueva información. Espere un momento..."
PATCH_SKIP_UPDATE: "Saltando {tabla|índice} {actualización|creación|remoción} en {0}."
PATCH_STARTED: "Intentando actualizar {0}. Espere..."
PATCH_SUCCESS: "Exitosamente actualizada a {0}."
PATCH_UPGRADING: "Mejora de base de datos en progreso. Espere un momento..."
PLEASE_SELECT: "Seleccione: \"{0}\" o \"{1}\"."
PREVIEW_CANCELLED: "Vista previa cancelada."
PREVIEW_CANCELLING: "Cancelando vista previa..."
PREVIEW_CONTAINER: "No puede obtener una vista previa de las transacciones de contenedor."
PREVIEW_IN_GAME: "Solo puedes obtener una vista previa de las reversiones en el juego."
PURGE_ABORTED: "La purga falló. La base de datos puede estar dañada."
PURGE_ERROR: "No se puede procesar {0} ¡datos!"
PURGE_FAILED: "La purga falló. Por favor, inténtelo de nuevo más tarde."
PURGE_IN_PROGRESS: "Purga en curso. Por favor, inténtelo de nuevo más tarde."
PURGE_MINIMUM_TIME: "Solo puede depurar datos anteriores a {0} {días|horas}."
PURGE_NOTICE_1: "Tenga en cuenta que esto puede llevar algún tiempo."
PURGE_NOTICE_2: "No reinicie su servidor hasta que haya terminado."
PURGE_OPTIMIZING: "Optimización de la base de datos. Espere por favor..."
PREVIEW_CANCELLED: "Muestra cancelada."
PREVIEW_CANCELLING: "Cancelando muestra..."
PREVIEW_IN_GAME: "Solo puedes mostrar respaldos en el juego."
PREVIEW_TRANSACTION: "No puedes mostrar transacciones de {contenedor|inventario}."
PRIMARY_THREAD_ERROR: "Ese método de API no se puede usar en el hilo principal."
PURGE_ABORTED: "Purga fallida. La base de datos debe estar corrupta."
PURGE_ERROR: "¡No se pudo procesar {0} datos!"
PURGE_FAILED: "Purga fallida. Intente nuevamente."
PURGE_IN_PROGRESS: "Purga en progreso. Intentelo nuevamente."
PURGE_MINIMUM_TIME: "Solo puedes limpiar datos viejos desde {0} {días|horas}."
PURGE_NOTICE_1: "Ten en cuenta que va a demorar un poco."
PURGE_NOTICE_2: "No reinicies el servidor hasta que se complete."
PURGE_OPTIMIZING: "Optimizando base de datos. Espere..."
PURGE_PROCESSING: "Procesando {0} datos..."
PURGE_REPAIRING: "Intentando reparar. Esto puede tomar algo de tiempo..."
PURGE_ROWS: "{0} {fila|filas} de datos eliminados."
PURGE_STARTED: "La depuración de datos comenzó el \"{0}\"."
PURGE_REPAIRING: "Intentando reparar. Esto tomará algo de tiempo..."
PURGE_ROWS: "{0} {fila|filas} de datos limpiados."
PURGE_STARTED: "Purga de datos iniciada en \"{0}\"."
PURGE_SUCCESS: "Purga de datos exitosa."
RELOAD_STARTED: "Recargando la configuración - espere."
RELOAD_SUCCESS: "Configuración recargada correctamente."
ROLLBACK_ABORTED: "Reversión o restauración abortada."
ROLLBACK_CHUNKS_FOUND: "Encontró {0} {trozo|trozos} Modificar."
ROLLBACK_CHUNKS_MODIFIED: "Modificado {0}/{1} {trozo|trozos}."
ROLLBACK_COMPLETED: "{Retroceder|Restaurar|Vista previa} completado para \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Excluido {usuario|usuarios}: \"{0}\"."
RAM_STATS: "{0}GB / {1}GB RAM"
RELOAD_STARTED: "Recargando configuración - espere."
RELOAD_SUCCESS: "Configuración exitosamente recargada."
ROLLBACK_ABORTED: "Retorno o restauración abortado."
ROLLBACK_CHUNKS_FOUND: "Encontrado {0} {chunk|chunks} para modificar."
ROLLBACK_CHUNKS_MODIFIED: "Se han modificado {0}/{1} {chunk|chunks}."
ROLLBACK_COMPLETED: "{Respaldo|Restauración|Muestra} completado por \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Excluir al {usuario|usuarios}: \"{0}\"."
ROLLBACK_INCLUDE: "{Incluido|Excluido} {bloque|entidad|objetivo} {tipo|tipos}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Ya se está realizando una reversión / restauración."
ROLLBACK_LENGTH: "Tiempo tomado: {0} {segundo|segundos}."
ROLLBACK_IN_PROGRESS: "Un retorno/resturación ya está en progreso."
ROLLBACK_LENGTH: "Tiempo gastado: {0} {segundo|segundos}."
ROLLBACK_MODIFIED: "{Modificado|Modificando} {0}."
ROLLBACK_RADIUS: "Radio: {0} {bloque|bloques}."
ROLLBACK_SELECTION: "Radio establecido en \"{0}\"."
ROLLBACK_STARTED: "{Retroceder|Restaurar|Vista previa} comenzó en \"{0}\"."
ROLLBACK_TIME: "Intervalo de tiempo: {0}."
ROLLBACK_WORLD_ACTION: "Prohibido para {mundo|acción} \"{0}\"."
SIGN_HEADER: "Firmar mensajes"
STATUS_CONSUMER: "Consumidor: {0} {artículo|artículos} en fila."
ROLLBACK_SELECTION: "Radio definido a \"{0}\"."
ROLLBACK_STARTED: "{Retorno|Restauración|Muestra} iniciado en \"{0}\"."
ROLLBACK_TIME: "Tiempo hecho: {0}."
ROLLBACK_WORLD_ACTION: "Restringido para {mundo|acción} \"{0}\"."
SIGN_HEADER: "Mensajes de cartel"
STATUS_CONSUMER: "Consumidor: {0} {objeto|objetos} en espera."
STATUS_DATABASE: "Base de datos: Usando {0}."
STATUS_INTEGRATION: "{0}: Integración {habilitado|deshabilitado}."
STATUS_INTEGRATION: "{0}: Integración {activada|desactivada}."
STATUS_LICENSE: "Licencia: {0}"
STATUS_SYSTEM: "Sistema: {0}"
STATUS_VERSION: "Versión: {0}"
TELEPORTED: "Teletransportado a {0}."
TELEPORTED_SAFETY: "Te teletransportó a un lugar seguro."
TELEPORT_PLAYERS: "El comando de teletransporte solo puede ser utilizado por jugadores."
TELEPORTED_SAFETY: "Te he teletransportado a un lugar seguro."
TELEPORT_PLAYERS: "El comando de teletransporte solo puede ser usado por jugadores."
TIME_DAYS: "{0} {día|días}"
TIME_HOURS: "{0} {hora|horas}"
TIME_MINUTES: "{0} {minuto|minutos}"
TIME_SECONDS: "{0} {segundo|segundos}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {semana|semanas}"
UPDATE_ERROR: "Se produjo un error al buscar actualizaciones."
UPDATE_HEADER: "{0} Actualizar"
UPDATE_NOTICE: "Darse cuenta: {0} Ya está disponible."
UPGRADE_IN_PROGRESS: "Actualización en curso. Por favor, inténtelo de nuevo más tarde."
USER_NOT_FOUND: "Usuario \"{0}\" extraviado."
USING_MYSQL: "Utilizando MySQL para el almacenamiento de datos."
USING_SQLITE: "Utilizando SQLite para el almacenamiento de datos."
UPDATE_ERROR: "Un error ocurrió mientras se revisaban las actualizaciones."
UPDATE_HEADER: "Actualización {0}"
UPDATE_NOTICE: "Noticia: {0} está disponible."
UPGRADE_IN_PROGRESS: "Actualización en progreso. Intente nuevamente."
USER_NOT_FOUND: "Usuario \"{0}\" no encontrado."
USER_OFFLINE: "El usuario \"{0}\" no está conectado."
USING_MYSQL: "Usando MySQL para el guardado de información."
USING_SQLITE: "Usando SQLite para el guardado de información."
VALID_DONATION_KEY: "Clave de donación válida."
VERSION_NOTICE: "Versión {0} Ya está disponible."
VERSION_REQUIRED: "{0} {1} se requiere o superior."
WORLD_NOT_FOUND: "Mundo \"{0}\" extraviado."
VERSION_NOTICE: "Versión {0} está disponible."
VERSION_REQUIRED: "{0} {1} o súperior es requerida."
WORLD_NOT_FOUND: "Mundo \"{0}\" inexistente."
+197 -192
View File
@@ -1,197 +1,202 @@
# CoreProtect Language File (fr)
ACTION_NOT_SUPPORTED: "Esa acción no es compatible."
AMOUNT_BLOCK: "{0} {bloque|bloques}"
AMOUNT_CHUNK: "{0} {trozo|trozos}"
AMOUNT_ENTITY: "{0} {entidad|entidades}"
AMOUNT_ITEM: "{0} {artículo|artículos}"
API_TEST: "Prueba de API exitosa."
CACHE_ERROR: "ADVERTENCIA: Error al validar {0} cache."
CACHE_RELOAD: "Forzando la recarga de {mapeo|mundo} cachés de la base de datos."
CHECK_CONFIG: "Por favor revise config.yml"
COMMAND_NOT_FOUND: "Comando \"{0}\" extraviado."
COMMAND_THROTTLED: "Espere un momento y vuelva a intentarlo."
CONTAINER_HEADER: "Transacciones de contenedores"
DATABASE_BUSY: "Base de datos ocupado. Por favor, inténtelo de nuevo más tarde."
DATABASE_INDEX_ERROR: "No se pueden validar los índices de la base de datos."
DATABASE_LOCKED_1: "Base de datos bloqueada. Esperando hasta 15 segundos..."
DATABASE_LOCKED_2: "La base de datos ya está en uso. Inténtalo de nuevo."
DATABASE_LOCKED_3: "Para deshabilitar el bloqueo de la base de datos, establezca \"database-lock: false\"."
DATABASE_LOCKED_4: "La desactivación del bloqueo de la base de datos puede provocar daños en los datos."
DATABASE_UNREACHABLE: "La base de datos es inalcanzable. Descartando datos y cerrando."
DEVELOPMENT_BRANCH: "Se detectó una rama de desarrollo, omitiendo los scripts de parches."
DIRT_BLOCK: "Colocó un bloque de tierra debajo de usted."
DISABLE_SUCCESS: "¡Éxito! Discapacitado {0}"
ENABLE_FAILED: "{0} fue incapaz de comenzar."
ENABLE_SUCCESS: "{0} ahora está habilitado!"
ENJOY_COREPROTECT: "Disfrutar {0}? Únete a nuestro Discord!"
FINISHING_CONVERSION: "Terminando la conversión de datos. Espere por favor..."
FINISHING_LOGGING: "Terminando el registro de datos. Espere por favor..."
FIRST_VERSION: "DB inicial: {0}"
GLOBAL_LOOKUP: "No especifique un radio para realizar una búsqueda global."
GLOBAL_ROLLBACK: "Usar \"{0}\" para hacer un global {deshacer|restaurar}"
HELP_ACTION_1: "Restrinja la búsqueda a una acción."
HELP_ACTION_2: "Ejemplos: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Muestra información adicional."
HELP_EXCLUDE_1: "Excluir bloques / usuarios."
HELP_EXCLUDE_2: "Ejemplos: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Ayudar"
HELP_INCLUDE_1: "Incluya bloques / entidades."
HELP_INCLUDE_2: "Ejemplos: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Con el inspector habilitado, puede:"
HELP_INSPECT_2: "Haga clic con el botón izquierdo para ver la ubicación."
HELP_INSPECT_3: "Haga clic derecho para ver las eliminaciones adyacentes."
HELP_INSPECT_4: "Coloque un bloque para ver la eliminación."
HELP_INSPECT_5: "Coloque un bloque en líquido para ver la ubicación."
HELP_INSPECT_6: "Haga clic derecho para ver las transacciones del cofre."
HELP_INSPECT_7: "Sugerencia: puede usar solo \"/co i\"."
HELP_INSPECT_COMMAND: "Alterna el inspector de bloques."
HELP_LIST: "Muestra una lista de todos los comandos."
HELP_LOOKUP_1: "Atajo de comando."
HELP_LOOKUP_2: "Ver páginas adicionales."
HELP_LOOKUP_COMMAND: "Búsqueda de datos de bloques."
HELP_NO_INFO: "Información para el mando \"{0}\" extraviado."
HELP_PARAMETER: "Por favor mira \"{0}\" para obtener información detallada sobre los parámetros."
HELP_PARAMS_1: "Realizar el {buscar|deshacer|restaurar}."
HELP_PARAMS_2: "Especifique los usuarios para {buscar|deshacer|restaurar}."
HELP_PARAMS_3: "Especifique el tiempo para {buscar|deshacer|restaurar}."
HELP_PARAMS_4: "Restringir el {buscar|deshacer|restaurar} a un radio."
HELP_PARAMS_5: "Restringir el {buscar|deshacer|restaurar} a una acción."
HELP_PARAMS_6: "Incluir bloques / entidades en el {buscar|deshacer|restaurar}."
HELP_PARAMS_7: "Excluir bloques / usuarios del {buscar|deshacer|restaurar}."
HELP_PURGE_1: "Eliminar datos antiguos."
HELP_PURGE_2: "Por ejemplo, \"{0}\" eliminará todos los datos anteriores a un mes y solo conservará los datos de los últimos 30 días."
HELP_PURGE_COMMAND: "Elimina los datos del bloque antiguo."
HELP_RADIUS_1: "Especifique un área de radio."
HELP_RADIUS_2: "Ejemplos: [r:10] (Solo haga cambios dentro de las 10 cuadras de usted)"
HELP_RELOAD_COMMAND: "Vuelve a cargar el archivo de configuración."
HELP_RESTORE_COMMAND: "Restaurar los datos del bloque."
HELP_ROLLBACK_COMMAND: "Revertir los datos del bloque."
HELP_STATUS: "Ver el estado del complemento."
HELP_STATUS_COMMAND: "Muestra el estado del complemento."
HELP_TELEPORT: "Teletransportarse."
HELP_TIME_1: "Especifique la hora."
HELP_TIME_2: "Ejemplos: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Especifique los usuarios."
HELP_USER_2: "Ejemplos: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" no se puede utilizar con esa acción."
INSPECTOR_ERROR: "Inspector ya {habilitado|deshabilitado}."
INSPECTOR_TOGGLED: "Inspector ahora {habilitado|deshabilitado}."
INTEGRATION_ERROR: "Incapaz de {inicializar|deshabilitar} {0} Inicio sesión."
INTEGRATION_SUCCESS: "{0} registrando exitosamente {inicializado|deshabilitado}."
INTEGRATION_VERSION: "Inválido {0} versión encontrada."
INTERACTIONS_HEADER: "Interacciones del jugador"
INVALID_ACTION: "Esa no es una acción válida."
INVALID_BRANCH_1: "Versión de complemento no válida (la rama no se ha configurado)."
INVALID_BRANCH_2: "Para continuar, establezca la rama del proyecto en \"desarrollo\"."
INVALID_BRANCH_3: "La ejecución de código de desarrollo puede provocar daños en los datos."
INVALID_CONTAINER: "Primero inspeccione un contenedor válido."
INVALID_DONATION_KEY: "Clave de donación no válida."
INVALID_INCLUDE: "\"{0}\" es un valor no válido."
INVALID_INCLUDE_COMBO: "Esa es una combinación de tipos no válida."
INVALID_RADIUS: "Ingrese un radio válido."
INVALID_SELECTION: "{0} selección no encontrada."
INVALID_USERNAME: "\"{0}\" es un nombre de usuario no válido."
INVALID_WORLD: "Especifique un mundo válido."
LATEST_VERSION: "Última versión: {0}"
ACTION_NOT_SUPPORTED: "Cette action n'est pas prise en charge."
AMOUNT_BLOCK: "{0} {bloquer|blocs}"
AMOUNT_CHUNK: "{0} {morceau|morceaux}"
AMOUNT_ENTITY: "{0} {entité|entités}"
AMOUNT_ITEM: "{0} {article|articles}"
API_TEST: "Test API réussi."
CACHE_ERROR: "ATTENTION: Erreur lors de la validation {0} cache."
CACHE_RELOAD: "Forcer le rechargement de {cartographie|monde} cache de la base de données."
CHECK_CONFIG: "Veuillez vérifier config.yml"
COMMAND_CONSOLE: "Veuillez exécuter la commande depuis la console."
COMMAND_NOT_FOUND: "Commande \"{0}\" pas trouvé."
COMMAND_THROTTLED: "Veuillez patienter un instant et réessayer."
CONSUMER_ERROR: "Traitement de la file d'attente des consommateurs déjà {mis en pause|repris}."
CONSUMER_TOGGLED: "Le traitement de la file d'attente des consommateurs a été {mis en pause|repris}."
CONTAINER_HEADER: "Transactions de conteneurs"
DATABASE_BUSY: "Base de données occupée. Veuillez réessayer plus tard."
DATABASE_INDEX_ERROR: "Impossible de valider les index de la base de données."
DATABASE_LOCKED_1: "Base de données verrouillée. Attendre jusqu'à 15 secondes..."
DATABASE_LOCKED_2: "La base de données est déjà utilisée. Veuillez réessayer."
DATABASE_LOCKED_3: "Pour désactiver le verrouillage de la base de données, définissez \"database-lock: false\"."
DATABASE_LOCKED_4: "La désactivation du verrouillage de la base de données peut entraîner une corruption des données."
DATABASE_UNREACHABLE: "La base de données est inaccessible. Suppression des données et arrêt."
DEVELOPMENT_BRANCH: "Branche de développement détectée, ignorant les scripts de correctif."
DIRT_BLOCK: "Placé un bloc de terre sous vous."
DISABLE_SUCCESS: "Succès! Désactivée {0}"
ENABLE_FAILED: "{0} n'a pas pu démarrer."
ENABLE_SUCCESS: "{0} est maintenant activé !"
ENJOY_COREPROTECT: "Prendre plaisir {0}? Rejoignez notre Discord!"
FINISHING_CONVERSION: "Fin de la conversion des données. S'il vous plaît, attendez..."
FINISHING_LOGGING: "Fin de l'enregistrement des données. S'il vous plaît, attendez..."
FIRST_VERSION: "BD initiale: {0}"
GLOBAL_LOOKUP: "Ne spécifiez pas de rayon pour effectuer une recherche globale."
GLOBAL_ROLLBACK: "Utiliser \"{0}\" pour faire une globalité {restauration|restauration}"
HELP_ACTION_1: "Restreindre la recherche à une action."
HELP_ACTION_2: "Exemples: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Afficher des informations supplémentaires."
HELP_EXCLUDE_1: "Exclure les blocs/utilisateurs."
HELP_EXCLUDE_2: "Exemples: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Aider"
HELP_INCLUDE_1: "Inclure les blocs/entités."
HELP_INCLUDE_2: "Exemples: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Avec l'inspecteur activé, vous pouvez:"
HELP_INSPECT_2: "Faites un clic gauche pour voir l'emplacement."
HELP_INSPECT_3: "Cliquez avec le bouton droit pour voir les suppressions adjacentes."
HELP_INSPECT_4: "Placez un bloc pour voir la suppression."
HELP_INSPECT_5: "Placer un bloc dans du liquide pour voir le placement."
HELP_INSPECT_6: "Faites un clic droit pour voir les transactions de coffre."
HELP_INSPECT_7: "Astuce: Vous pouvez utiliser uniquement \"/co i\"."
HELP_INSPECT_COMMAND: "Bascule l'inspecteur de bloc."
HELP_LIST: "Affiche une liste de toutes les commandes."
HELP_LOOKUP_1: "Raccourci de commande."
HELP_LOOKUP_2: "Afficher des pages supplémentaires."
HELP_LOOKUP_COMMAND: "Rechercher des données de bloc."
HELP_NO_INFO: "Informations pour la commande \"{0}\" pas trouvé."
HELP_PARAMETER: "S'il te plait regarde \"{0}\" pour des informations détaillées sur les paramètres."
HELP_PARAMS_1: "Effectuer la {recherche|restauration|restauration}."
HELP_PARAMS_2: "Spécifiez les utilisateurs à {recherche|restauration|restauration}."
HELP_PARAMS_3: "Précisez l'heure de {recherche|restauration|restauration}."
HELP_PARAMS_4: "Restreindre le {recherche|restauration|restauration} à un rayon."
HELP_PARAMS_5: "Restreindre le {recherche|restauration|restauration} à une action."
HELP_PARAMS_6: "Inclure des blocs/entités dans le {recherche|restauration|restauration}."
HELP_PARAMS_7: "Exclure les blocs/utilisateurs du {recherche|restauration|restauration}."
HELP_PURGE_1: "Supprimer les anciennes données."
HELP_PURGE_2: "Par example, \"{0}\" supprimera toutes les données de plus d'un mois et ne conservera que les 30 derniers jours de données."
HELP_PURGE_COMMAND: "Supprimez les anciennes données de bloc."
HELP_RADIUS_1: "Spécifiez une zone de rayon."
HELP_RADIUS_2: "Exemples: [r:10] (N'apportez des modifications qu'à moins de 10 blocs de vous)"
HELP_RELOAD_COMMAND: "Recharge le fichier de configuration."
HELP_RESTORE_COMMAND: "Restaurer les données de bloc."
HELP_ROLLBACK_COMMAND: "Données de bloc d'annulation."
HELP_STATUS: "Afficher l'état du plug-in."
HELP_STATUS_COMMAND: "Affiche l'état du plug-in."
HELP_TELEPORT: "Téléportation."
HELP_TIME_1: "Précisez l'heure."
HELP_TIME_2: "Exemples: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Spécifiez les utilisateurs."
HELP_USER_2: "Exemples: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" ne peut pas être utilisé avec cette action."
INSPECTOR_ERROR: "Inspecteur déjà {activé|désactivé}."
INSPECTOR_TOGGLED: "Inspecteur maintenant {activé|désactivé}."
INTEGRATION_ERROR: "Incapable de {initialiser|désactiver} {0} enregistrement."
INTEGRATION_SUCCESS: "{0} connexion avec succès {initialisé|désactivé}."
INTEGRATION_VERSION: "Invalide {0} version trouvée."
INTERACTIONS_HEADER: "Interactions avec les joueurs"
INVALID_ACTION: "Ce n'est pas une action valable."
INVALID_BRANCH_1: "Version du plugin non valide (la branche n'a pas été définie)."
INVALID_BRANCH_2: "Pour continuer, définissez la branche du projet sur \"développement\"."
INVALID_BRANCH_3: "L'exécution du code de développement peut entraîner une corruption des données."
INVALID_CONTAINER: "Veuillez d'abord inspecter un conteneur valide."
INVALID_DONATION_KEY: "Clé de don invalide."
INVALID_INCLUDE: "\"{0}\" est une valeur invalide."
INVALID_INCLUDE_COMBO: "C'est une combinaison de type invalide."
INVALID_RADIUS: "Veuillez saisir un rayon valide."
INVALID_SELECTION: "{0} sélection introuvable."
INVALID_USERNAME: "\"{0}\" est un nom d'utilisateur invalide."
INVALID_WORLD: "Veuillez spécifier un monde valide."
LATEST_VERSION: "Dernière version: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Descargar: {0}"
LINK_DOWNLOAD: "Télécharger: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Nombres de bloque: {0}"
LINK_WIKI_ENTITY: "Nombres de entidades: {0}"
LOGGING_ITEMS: "{0} elementos que quedan por registrar. Espere por favor..."
LOGGING_TIME_LIMIT: "Se alcanzó el límite de tiempo de registro. Descartando datos y cerrando."
LOOKUP_BLOCK: "{0} {colocado|roto} {1}."
LOOKUP_CONTAINER: "{0} {añadido|eliminado} {1} {2}."
LOOKUP_HEADER: "{0} Resultados"
LOOKUP_INTERACTION: "{0} {clickeado|muerto} {1}."
LOOKUP_ITEM: "{0} {recogido|caído} {1} {2}."
LOOKUP_LOGIN: "{0} registrado {adentro|afuera}."
LOOKUP_PAGE: "Página {0}"
LOOKUP_ROWS_FOUND: "{0} {fila|filas} encontró."
LOOKUP_SEARCHING: "Búsqueda de búsqueda. Espere por favor..."
LINK_WIKI_BLOCK: "Noms de bloc: {0}"
LINK_WIKI_ENTITY: "Noms des entités: {0}"
LOGGING_ITEMS: "{0} éléments laissés à enregistrer. S'il vous plaît, attendez..."
LOGGING_TIME_LIMIT: "Limite de temps d'enregistrement atteinte. Suppression des données et arrêt."
LOOKUP_BLOCK: "{0} {placé|cassé} {1}."
LOOKUP_CONTAINER: "{0} {ajouté|supprimé} {1} {2}."
LOOKUP_HEADER: "{0} Résultats"
LOOKUP_INTERACTION: "{0} {cliqué|tué} {1}."
LOOKUP_ITEM: "{0} {ramassé|lâché} {1} {2}."
LOOKUP_LOGIN: "{0} connecté {entrée|sortie}."
LOOKUP_PAGE: "Page {0}"
LOOKUP_PROJECTILE: "{0} {jeté|tiré} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {ligne|lignes} trouvé."
LOOKUP_SEARCHING: "Recherche de recherche. S'il vous plaît, attendez..."
LOOKUP_STORAGE: "{0} {déposé|retiré} {1} {2}."
LOOKUP_TIME: "{0} atrás"
LOOKUP_USERNAME: "{0} Conectado como {1}."
LOOKUP_VIEW_PAGE: "Para ver una página, escriba \"{0}\"."
MAXIMUM_RADIUS: "El maximo {buscar|deshacer|restaurar} el radio es {0}."
MISSING_ACTION_USER: "Para utilizar esa acción, especifique un usuario."
MISSING_LOOKUP_TIME: "Por favor, especifique el tiempo para {buscar|deshacer|restaurar}."
MISSING_LOOKUP_USER: "Especifique un usuario o {bloque|radio}."
MISSING_PARAMETERS: "Por favor use \"{0}\"."
MISSING_ROLLBACK_RADIUS: "No especificó un {deshacer|restaurar} radio."
MISSING_ROLLBACK_USER: "No especificó un {deshacer|restaurar} usuario."
MYSQL_UNAVAILABLE: "Incapaz de conectarse a MySQL servidor."
NO_DATA: "No se encontraron datos en {0}."
NO_DATA_LOCATION: "No {datos|transacciones|interacciones|mensajes} encontró."
NO_PERMISSION: "No tienes permiso para hacer eso."
NO_RESULTS: "No se han encontrado resultados."
NO_RESULTS_PAGE: "No {resultados|datos} encontrado para esa página."
NO_ROLLBACK: "No {pendiente|anterior} deshacer / restaurar encontrado."
PATCH_INTERRUPTED: "Actualización interrumpida. Intentará de nuevo al reiniciar."
PATCH_OUTDATED_1: "No se pueden actualizar las bases de datos anteriores a {0}."
PATCH_OUTDATED_2: "Actualice con una versión compatible de CoreProtect."
PATCH_PROCESSING: "Procesando nuevos datos. Espere por favor..."
PATCH_SKIP_UPDATE: "Salto a la comba {tabla|índice} {actualización|creación|eliminación} en {0}."
PATCH_STARTED: "Ejecutando {0} potenciar. Espere por favor..."
PATCH_SUCCESS: "Actualizado con éxito a {0}."
PATCH_UPGRADING: "Actualización de la base de datos en curso. Espere por favor..."
PLEASE_SELECT: "Seleccione: \"{0}\" o \"{1}\"."
PREVIEW_CANCELLED: "Vista previa cancelada."
PREVIEW_CANCELLING: "Cancelando vista previa..."
PREVIEW_CONTAINER: "No puede obtener una vista previa de las transacciones de contenedor."
PREVIEW_IN_GAME: "Solo puedes obtener una vista previa de las reversiones en el juego."
PURGE_ABORTED: "La purga falló. La base de datos puede estar dañada."
PURGE_ERROR: "No se puede procesar {0} ¡datos!"
PURGE_FAILED: "La purga falló. Por favor, inténtelo de nuevo más tarde."
PURGE_IN_PROGRESS: "Purga en curso. Por favor, inténtelo de nuevo más tarde."
PURGE_MINIMUM_TIME: "Solo puede depurar datos anteriores a {0} {días|horas}."
PURGE_NOTICE_1: "Tenga en cuenta que esto puede llevar algún tiempo."
PURGE_NOTICE_2: "No reinicie su servidor hasta que haya terminado."
PURGE_OPTIMIZING: "Optimización de la base de datos. Espere por favor..."
PURGE_PROCESSING: "Procesando {0} datos..."
PURGE_REPAIRING: "Intentando reparar. Esto puede tomar algo de tiempo..."
PURGE_ROWS: "{0} {fila|filas} de datos eliminados."
PURGE_STARTED: "La depuración de datos comenzó el \"{0}\"."
PURGE_SUCCESS: "Purga de datos exitosa."
RELOAD_STARTED: "Recargando la configuración - espere."
RELOAD_SUCCESS: "Configuración recargada correctamente."
ROLLBACK_ABORTED: "Reversión o restauración abortada."
ROLLBACK_CHUNKS_FOUND: "Encontró {0} {trozo|trozos} Modificar."
ROLLBACK_CHUNKS_MODIFIED: "Modificado {0}/{1} {trozo|trozos}."
ROLLBACK_COMPLETED: "{Retroceder|Restaurar|Vista previa} completado para \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Excluido {usuario|usuarios}: \"{0}\"."
ROLLBACK_INCLUDE: "{Incluido|Excluido} {bloque|entidad|objetivo} {tipo|tipos}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Ya se está realizando una reversión / restauración."
ROLLBACK_LENGTH: "Tiempo tomado: {0} {segundo|segundos}."
ROLLBACK_MODIFIED: "{Modificado|Modificando} {0}."
ROLLBACK_RADIUS: "Radio: {0} {bloque|bloques}."
ROLLBACK_SELECTION: "Radio establecido en \"{0}\"."
ROLLBACK_STARTED: "{Retroceder|Restaurar|Vista previa} comenzó en \"{0}\"."
ROLLBACK_TIME: "Intervalo de tiempo: {0}."
ROLLBACK_WORLD_ACTION: "Prohibido para {mundo|acción} \"{0}\"."
SIGN_HEADER: "Firmar mensajes"
STATUS_CONSUMER: "Consumidor: {0} {artículo|artículos} en fila."
STATUS_DATABASE: "Base de datos: Usando {0}."
STATUS_INTEGRATION: "{0}: Integración {habilitado|deshabilitado}."
STATUS_LICENSE: "Licencia: {0}"
STATUS_VERSION: "Versión: {0}"
TELEPORTED: "Teletransportado a {0}."
TELEPORTED_SAFETY: "Te teletransportó a un lugar seguro."
TELEPORT_PLAYERS: "El comando de teletransporte solo puede ser utilizado por jugadores."
TIME_DAYS: "{0} {día|días}"
TIME_HOURS: "{0} {hora|horas}"
TIME_MINUTES: "{0} {minuto|minutos}"
TIME_SECONDS: "{0} {segundo|segundos}"
TIME_WEEKS: "{0} {semana|semanas}"
UPDATE_ERROR: "Se produjo un error al buscar actualizaciones."
UPDATE_HEADER: "{0} Actualizar"
UPDATE_NOTICE: "Darse cuenta: {0} Ya está disponible."
UPGRADE_IN_PROGRESS: "Actualización en curso. Por favor, inténtelo de nuevo más tarde."
USER_NOT_FOUND: "Usuario \"{0}\" extraviado."
USING_MYSQL: "Utilizando MySQL para el almacenamiento de datos."
USING_SQLITE: "Utilizando SQLite para el almacenamiento de datos."
VALID_DONATION_KEY: "Clave de donación válida."
VERSION_NOTICE: "Versión {0} Ya está disponible."
VERSION_REQUIRED: "{0} {1} se requiere o superior."
WORLD_NOT_FOUND: "Mundo \"{0}\" extraviado."
LOOKUP_TIME: "{0} depuis"
LOOKUP_USERNAME: "{0} connecté en tant que {1}."
MAXIMUM_RADIUS: "Le maximum {recherche|restauration|restauration} le rayon est {0}."
MISSING_ACTION_USER: "Pour utiliser cette action, veuillez spécifier un utilisateur."
MISSING_LOOKUP_TIME: "Veuillez préciser l'heure à laquelle {recherche|restauration|restauration}."
MISSING_LOOKUP_USER: "Veuillez spécifier un utilisateur ou {bloc|rayon}."
MISSING_PARAMETERS: "Veuillez utiliser \"{0}\"."
MISSING_ROLLBACK_RADIUS: "Vous n'avez pas spécifié de {restauration|restauration} rayon."
MISSING_ROLLBACK_USER: "Vous n'avez pas spécifié de {restauration|restauration} utilisateur."
MYSQL_UNAVAILABLE: "Impossible de se connecter MySQL serveur."
NO_DATA: "Aucune donnée trouvée sur {0}."
NO_DATA_LOCATION: "Non {données|transactions|interactions|messages} trouvé."
NO_PERMISSION: "Vous n'avez pas la permission de le faire."
NO_RESULTS: "Aucun résultat trouvé."
NO_RESULTS_PAGE: "Non {résultats|données} trouvé pour cette page."
NO_ROLLBACK: "Non {en attente|précédent} annulation/restauration trouvée."
PATCH_INTERRUPTED: "Mise à niveau interrompue. Je réessayerai au redémarrage."
PATCH_OUTDATED_1: "Impossible de mettre à niveau les bases de données antérieures à {0}."
PATCH_OUTDATED_2: "Veuillez mettre à niveau avec une version prise en charge de CoreProtect."
PATCH_PROCESSING: "Traitement de nouvelles données. S'il vous plaît, attendez..."
PATCH_SKIP_UPDATE: "Saut {table|index} {mise à jour|création|suppression} au {0}."
PATCH_STARTED: "Performant {0} améliorer. S'il vous plaît, attendez..."
PATCH_SUCCESS: "Mise à niveau réussie vers {0}."
PATCH_UPGRADING: "Mise à niveau de la base de données en cours. S'il vous plaît, attendez..."
PLEASE_SELECT: "Veuillez sélectionner: \"{0}\" ou alors \"{1}\"."
PREVIEW_CANCELLED: "Aperçu annulé."
PREVIEW_CANCELLING: "Annulation de l'aperçu..."
PREVIEW_IN_GAME: "Vous ne pouvez prévisualiser les rollbacks que dans le jeu."
PREVIEW_TRANSACTION: "Vous ne pouvez pas prévisualiser {conteneur|inventaire} transactions."
PURGE_ABORTED: "Échec de la purge. La base de données est peut-être corrompue."
PURGE_ERROR: "Impossible de traiter {0} Les données!"
PURGE_FAILED: "Échec de la purge. Veuillez réessayer plus tard."
PURGE_IN_PROGRESS: "Purge en cours. Veuillez réessayer plus tard."
PURGE_MINIMUM_TIME: "Vous ne pouvez purger que les données antérieures à {0} {jours|heures}."
PURGE_NOTICE_1: "Veuillez noter que cela peut prendre un certain temps."
PURGE_NOTICE_2: "Ne redémarrez pas votre serveur avant d'avoir terminé."
PURGE_OPTIMIZING: "Optimisation de la base de données. S'il vous plaît, attendez..."
PURGE_PROCESSING: "Traitement {0} Les données..."
PURGE_REPAIRING: "Tenter de réparer. Cela peut prendre un certain temps..."
PURGE_ROWS: "{0} {ligne|lignes} de données supprimées."
PURGE_STARTED: "La purge des données a commencé le \"{0}\"."
PURGE_SUCCESS: "Purge des données réussie."
RELOAD_STARTED: "Rechargement de la configuration - veuillez patienter."
RELOAD_SUCCESS: "Configuration rechargée avec succès."
ROLLBACK_ABORTED: "Annulation de l'annulation ou de la restauration."
ROLLBACK_CHUNKS_FOUND: "Trouvé {0} {morceau|morceaux} modifier."
ROLLBACK_CHUNKS_MODIFIED: "Modifié {0}/{1} {morceau|morceaux}."
ROLLBACK_COMPLETED: "{Retour en arrière|Restaurer|Aperçu} terminé pour \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Exclu {utilisateur|utilisateurs}: \"{0}\"."
ROLLBACK_INCLUDE: "{Inclus|Exclus} {bloc|entité|cible} {type|types}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Une annulation/restauration est déjà en cours."
ROLLBACK_LENGTH: "Temps pris: {0} {seconde|secondes}."
ROLLBACK_MODIFIED: "{Modifié|Modification} {0}."
ROLLBACK_RADIUS: "Rayon: {0} {bloquer|blocs}."
ROLLBACK_SELECTION: "Rayon réglé sur \"{0}\"."
ROLLBACK_STARTED: "{Retour en arrière|Restaurer|Aperçu} commencé sur \"{0}\"."
ROLLBACK_TIME: "Intervalle de temps: {0}."
ROLLBACK_WORLD_ACTION: "Limité à {monde|action} \"{0}\"."
SIGN_HEADER: "Signer des messages"
STATUS_CONSUMER: "Consommateur: {0} {article|articles} dans la queue."
STATUS_DATABASE: "Base de données: Utilisation {0}."
STATUS_INTEGRATION: "{0}: Intégration {activé|désactivé}."
STATUS_LICENSE: "Licence: {0}"
STATUS_VERSION: "Version: {0}"
TELEPORTED: "Téléporté à {0}."
TELEPORTED_SAFETY: "Vous a téléporté en lieu sûr."
TELEPORT_PLAYERS: "La commande de téléportation ne peut être utilisée que par les joueurs."
TIME_DAYS: "{0} {jour|jours}"
TIME_HOURS: "{0} {heure|heures}"
TIME_MINUTES: "{0} {minute|minutes}"
TIME_SECONDS: "{0} {seconde|secondes}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {semaine|semaines}"
UPDATE_ERROR: "Une erreur s'est produite lors de la recherche de mises à jour."
UPDATE_HEADER: "{0} Mettre à jour"
UPDATE_NOTICE: "Remarquer: {0} est maintenant disponible."
UPGRADE_IN_PROGRESS: "Mise à niveau en cours. Veuillez réessayer plus tard."
USER_NOT_FOUND: "Utilisateur \"{0}\" pas trouvé."
USER_OFFLINE: "L'utilisateur \"{0}\" n'est pas en ligne."
USING_MYSQL: "Utilisant MySQL pour le stockage des données."
USING_SQLITE: "Utilisant SQLite pour le stockage des données."
VALID_DONATION_KEY: "Clé de don valide."
VERSION_NOTICE: "Version {0} est maintenant disponible."
VERSION_REQUIRED: "{0} {1} ou supérieur est requis."
WORLD_NOT_FOUND: "Monde \"{0}\" pas trouvé."
+212
View File
@@ -0,0 +1,212 @@
# CoreProtect Language File (ja)
ACTION_NOT_SUPPORTED: "そのアクションはサポートされていません"
AMOUNT_BLOCK: "{0}{ブロック|ブロック}"
AMOUNT_CHUNK: "{0}{チャンク|チャンク}"
AMOUNT_ENTITY: "{0}体の{エンティティ|エンティティ}"
AMOUNT_ITEM: "{0}個の{アイテム|アイテム}"
API_TEST: "APIテストに成功しました"
CACHE_ERROR: "警告: 検証中にエラーが発生しました {0}"
CACHE_RELOAD: "データベースから{マッピング|ワールド}キャッシュを強制リロードします"
CHECK_CONFIG: "config.ymlを確認してください"
COMMAND_CONSOLE: "コンソールからコマンドを実行してください"
COMMAND_NOT_FOUND: "{0} は不明なコマンドです"
COMMAND_THROTTLED: "しばらく待ってから、もう一度お試しください"
CONSUMER_ERROR: "コンシューマーキューの処理は既に{一時停止|再開}されています"
CONSUMER_TOGGLED: "コンシューマーキューの処理が{一時停止|再開}されました"
CONTAINER_HEADER: "コンテナのトランザクション"
CPU_CORES: "CPUコア"
DATABASE_BUSY: "データベースがビジー状態です。後でもう一度お試しください"
DATABASE_INDEX_ERROR: "データベースインデックスを検証できません"
DATABASE_LOCKED_1: "データベースがロックされています。最大15秒間お待ちください"
DATABASE_LOCKED_2: "そのデータベースは既に使用されています。もう一度やり直してください"
DATABASE_LOCKED_3: "データベースロックを無効にするには、\"database-lock: false\"を設定してください"
DATABASE_LOCKED_4: "データベースロックを無効にすると、データが破損する可能性があります"
DATABASE_UNREACHABLE: "データベースにアクセスできません。データを破棄してシャットダウンします"
DEVELOPMENT_BRANCH: "開発ブランチが検出され、パッチスクリプトをスキップしました"
DIRT_BLOCK: "あなたの下に仮ブロックを設置しました"
DISABLE_SUCCESS: "{0} を無効化しました"
DONATION_KEY_REQUIRED: "このコマンドには有効な寄付キーが必要です。"
ENABLE_FAILED: "{0} を開始できませんでした"
ENABLE_SUCCESS: "{0} が有効化されました"
ENJOY_COREPROTECT: "{0} を楽しんでますか?ぜひDiscordにもご参加ください!"
FINISHING_CONVERSION: "データ変換を終了します。しばらくお待ちください..."
FINISHING_LOGGING: "データのロギングを終了します。しばらくお待ちください..."
FIRST_VERSION: "初期データベース: {0}"
GLOBAL_LOOKUP: "グローバル検索を行う際は、半径を指定しないでください"
GLOBAL_ROLLBACK: "{0}を使用してグローバル{ロールバック|復元}を実行してください"
HELP_ACTION_1: "検索対象をアクションのみに制限します"
HELP_ACTION_2: "例: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "追加情報を表示します"
HELP_EXCLUDE_1: "ブロック/ユーザーを除外します"
HELP_EXCLUDE_2: "例: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} ヘルプ"
HELP_INCLUDE_1: "ブロック/エンティティを含めます"
HELP_INCLUDE_2: "例: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "インスペクターが有効な場合、次のことができます:"
HELP_INSPECT_2: "ブロックを左クリックして、誰がそのブロックを配置したかを確認します"
HELP_INSPECT_3: "ブロックを右クリックして、隣接するどのブロックが破壊されたかを確認します"
HELP_INSPECT_4: "ブロックを配置して、その場所でどのブロックが破壊されたかを確認します"
HELP_INSPECT_5: "液体中などにブロックを配置して、誰がそれを配置したかを確認します"
HELP_INSPECT_6: "ドアやチェストなどを右クリックして、誰が最後に使用したかを確認します"
HELP_INSPECT_7: "ヒント:\"/co i\"を使用すると、より手軽にインスペクターにアクセスできます"
HELP_INSPECT_COMMAND: "ブロックインスペクターのオン/オフを切り替えます"
HELP_LIST: "全てのコマンドのリストを表示します"
HELP_LOOKUP_1: "コマンドのショートカットです"
HELP_LOOKUP_2: "ブロックを調査した後に使用すると、ログを表示できます"
HELP_LOOKUP_COMMAND: "ブロックデータの詳細検索"
HELP_NO_INFO: "コマンド情報 \"{0}\" が見つかりません"
HELP_PARAMETER: "詳細なパラメータ情報については、{0}を参照してください"
HELP_PARAMS_1: "{検索|ロールバック|復元}を実行します"
HELP_PARAMS_2: "{検索|ロールバック|復元}するユーザーを指定します"
HELP_PARAMS_3: "{検索|ロールバック|復元}をする際に時間を指定します"
HELP_PARAMS_4: "{検索|ロールバック|復元}をする際に半径を指定します"
HELP_PARAMS_5: "{検索|ロールバック|復元}をする際にアクションを制限します"
HELP_PARAMS_6: "{検索|ロールバック|復元}をする際にブロック/エンティティを含めます"
HELP_PARAMS_7: "{検索|ロールバック|復元}をする際にブロック/ユーザーを除外します"
HELP_PURGE_1: "指定した時間より古いデータを削除します"
HELP_PURGE_2: "例えば、 \"{0}\" は、1か月より古い全てのデータを削除し、過去30日間のデータのみを保持します"
HELP_PURGE_COMMAND: "古いブロックデータを削除します"
HELP_RADIUS_1: "半径領域を指定します"
HELP_RADIUS_2: "例: [r:10] (注: 変更は10ブロック以内でのみ行ってください)"
HELP_RELOAD_COMMAND: "構成ファイルを再読み込みします"
HELP_RESTORE_COMMAND: "ブロックデータを復元します"
HELP_ROLLBACK_COMMAND: "ブロックデータをロールバックします"
HELP_STATUS: "プラグインのステータスとバージョン情報を表示します"
HELP_STATUS_COMMAND: "プラグインのステータスを表示します"
HELP_TELEPORT: "場所にテレポートします"
HELP_TIME_1: "検索する時間を指定します"
HELP_TIME_2: "例: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "検索するユーザーを指定します"
HELP_USER_2: "例: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "そのアクションで \"{0}\" は使用できません"
INSPECTOR_ERROR: "インスペクターは既に{有効|無効}になっています"
INSPECTOR_TOGGLED: "インスペクターが{有効|無効}になりました"
INTEGRATION_ERROR: "{0}のロギングを{初期化|無効化}できませんでした"
INTEGRATION_SUCCESS: "{0}のロギングが正常に{初期化|無効化}されました"
INTEGRATION_VERSION: "無効な{0}バージョンが見つかりました"
INTERACTIONS_HEADER: "プレイヤーの相互作用"
INVALID_ACTION: "それは有効なアクションではありません"
INVALID_BRANCH_1: "プラグインのバージョンが無効です(ブランチが設定されていません)"
INVALID_BRANCH_2: "続行するには、プロジェクトブランチを \"development\" に設定してください"
INVALID_BRANCH_3: "開発者用コードを実行すると、データが破損する可能性があります"
INVALID_CONTAINER: "最初に有効なコンテナを調べてください"
INVALID_DONATION_KEY: "寄付キーが無効です"
INVALID_INCLUDE: "\"{0}\"は無効なブロック/エンティティ名です"
INVALID_INCLUDE_COMBO: "これは無効な型の組み合わせです"
INVALID_RADIUS: "有効な半径を入力してください"
INVALID_SELECTION: "{0} 選択が見つかりません"
INVALID_USERNAME: "\"{0}\"は無効なユーザー名です"
INVALID_WORLD: "有効なワールドを指定してください"
LATEST_VERSION: "最新バージョン: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "ダウンロード: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "ブロック名: {0}"
LINK_WIKI_ENTITY: "エンティティ名: {0}"
LOGGING_ITEMS: "ログに記録すべきアイテムが{0}個残っています。しばらくお待ちください..."
LOGGING_TIME_LIMIT: "記録制限時間に達しました。データを破棄してシャットダウンします"
LOOKUP_BLOCK: "{0}が{1}を{設置した|破壊した}"
LOOKUP_CONTAINER: "{0}が{2}個の{1}を{追加した|取り出した}"
LOOKUP_HEADER: "{0} の検索結果"
LOOKUP_INTERACTION: "{0}が{1}を{クリックした|殺害した}"
LOOKUP_ITEM: "{0}が{2}個の{1}を{拾った|落とした}"
LOOKUP_LOGIN: "{0}の{ログイン|ログアウト}がログに記録されます"
LOOKUP_PAGE: "{0} ページ目"
LOOKUP_PROJECTILE: "{0}が{2}個の{1}を{投げた|撃った}"
LOOKUP_ROWS_FOUND: "{0}行見つかりました"
LOOKUP_SEARCHING: "検索中。しばらくお待ちください..."
LOOKUP_STORAGE: "{0}が{2}個の{1}を{預け入れた|引き出した}"
LOOKUP_TIME: "{0}前"
LOOKUP_USERNAME: "{0}が{1}としてログインしました"
MAXIMUM_RADIUS: "最大{0}ブロックの半径内で{検索|ロールバック|復元}を実行します"
MISSING_ACTION_USER: "そのアクションを使用するには、ユーザーを指定してください"
MISSING_LOOKUP_TIME: "{検索|ロールバック|復元}する時間を指定してください"
MISSING_LOOKUP_USER: "ユーザー、ブロック、または半径を指定してください"
MISSING_PARAMETERS: "\"{0}\" のパラメータが不足しています"
MISSING_ROLLBACK_RADIUS: "{ロールバック|復元}対象の半径を指定しませんでした"
MISSING_ROLLBACK_USER: "{ロールバック|復元}対象のユーザーを指定しませんでした"
MYSQL_UNAVAILABLE: "MySQLサーバーに接続できません"
NETWORK_CONNECTION: "{0}の接続に{成功|失敗}しました。{1}{2}を使用します"
NETWORK_TEST: "ネットワーク・テスト・データが正常に送信されました"
NO_DATA: "{0}でデータが見つかりません"
NO_DATA_LOCATION: "この場所には{データ|トランザクション|相互作用|メッセージ}が見つかりませんでした"
NO_PERMISSION: "あなたにはそれをする権限がありません"
NO_RESULTS: "結果が見つかりませんでした"
NO_RESULTS_PAGE: "このページには{結果|データ}は見つかりませんでした"
NO_ROLLBACK: "{保留中または以前の}ロールバック/復元は見つかりませんでした"
PATCH_INTERRUPTED: "アップグレードが中断されました。再起動時に再試行します"
PATCH_OUTDATED_1: "{0}より古いデータベースはアップグレードできません"
PATCH_OUTDATED_2: "CoreProtect がサポートされているバージョンにアップグレードしてください"
PATCH_PROCESSING: "新しいデータを処理しています。しばらくお待ちください..."
PATCH_SKIP_UPDATE: "{0}の{テーブル|インデックス}の{更新|作成|削除}をスキップします"
PATCH_STARTED: "{0} のアップグレードを実行します。しばらくお待ちください..."
PATCH_SUCCESS: "{0} は正常にアップグレードされました"
PATCH_UPGRADING: "データベースのアップグレードが進行中です。しばらくお待ちください..."
PLEASE_SELECT: "\"{0}\" または \"{1}\" を選択してください"
PREVIEW_CANCELLED: "プレビューがキャンセルされました"
PREVIEW_CANCELLING: "プレビューをキャンセルしています..."
PREVIEW_IN_GAME: "ゲーム内でのみロールバックをプレビューできます"
PREVIEW_TRANSACTION: "{コンテナ|インベントリ}のトランザクションをプレビューすることはできません"
PRIMARY_THREAD_ERROR: "そのAPIメソッドはメインスレッドでは使用できません"
PURGE_ABORTED: "パージに失敗しました。データベースが破損している可能性があります"
PURGE_ERROR: "{0} のデータを処理できません"
PURGE_FAILED: "パージに失敗しました。もう一度やり直してください"
PURGE_IN_PROGRESS: "パージは進行中です。もう一度やり直してください"
PURGE_MINIMUM_TIME: "パージできるのは、{0}{日|時間}より古いデータのみです"
PURGE_NOTICE_1: "これには時間がかかる場合があります..."
PURGE_NOTICE_2: "完了するまでサーバーを再起動しないでください"
PURGE_OPTIMIZING: "データベースの最適化をしています。しばらくお待ちください..."
PURGE_PROCESSING: "{0} のデータを処理中..."
PURGE_REPAIRING: "修復しようとしています。これには時間がかかる場合があります..."
PURGE_ROWS: "{0}行のデータが削除されました"
PURGE_STARTED: "データの削除は\"{0}\"で開始されました"
PURGE_SUCCESS: "データのパージに成功しました"
RAM_STATS: "{0}GB / {1}GB RAM"
RELOAD_STARTED: "構成の再読み込みをしています。しばらくお待ちください"
RELOAD_SUCCESS: "構成が正常に再読み込みされました"
ROLLBACK_ABORTED: "ロールバックまたは復元が中止されました"
ROLLBACK_CHUNKS_FOUND: "{0}個の{チャンク|チャンク}の変更が見つかりました"
ROLLBACK_CHUNKS_MODIFIED: "{0}/{1}個の{チャンク|チャンク}を{変更しました|変更中}"
ROLLBACK_COMPLETED: "{ロールバック|復元|プレビュー} は\"{0}\"で完了しました"
ROLLBACK_EXCLUDED_USERS: "除外するユーザー: \"{0}\" "
ROLLBACK_INCLUDE: "{含まれる|除外される}{ブロック|エンティティ|ターゲット}タイプ: \"{0}\""
ROLLBACK_IN_PROGRESS: "ロールバック/復元はすでに進行中です"
ROLLBACK_LENGTH: "所要時間: {0}{秒|秒間}"
ROLLBACK_MODIFIED: "{0}を{変更しました|変更中}"
ROLLBACK_RADIUS: "半径: {0}{ブロック|ブロック}"
ROLLBACK_SELECTION: "半径を\"{0}\"に設定しました"
ROLLBACK_STARTED: "{ロールバック|復元|プレビュー}が\"{0}\"で開始されました"
ROLLBACK_TIME: "時間範囲: {0}"
ROLLBACK_WORLD_ACTION: "{ワールド|アクション}を\"{0}\"に制限しました"
SIGN_HEADER: "看板のメッセージ"
STATUS_CONSUMER: "キューに{0}個の{アイテム|アイテム}があります"
STATUS_DATABASE: "データベース: {0}を使用中"
STATUS_INTEGRATION: "{0}: 統合 {有効|無効}"
STATUS_LICENSE: "ライセンス: {0}"
STATUS_SYSTEM: "システム: {0}"
STATUS_VERSION: "バージョン: {0}"
TELEPORTED: "{0} にテレポートしました"
TELEPORTED_SAFETY: "あなたを安全な場所にテレポートさせました"
TELEPORT_PLAYERS: "テレポートコマンドはプレイヤーのみが使用できます"
TIME_DAYS: "{0}{日|日間}"
TIME_HOURS: "{0}{時|時間}"
TIME_MINUTES: "{0}{分|分間}"
TIME_MONTHS: "{0}{ヶ月|ヶ月間}"
TIME_SECONDS: "{0}{秒|秒間}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0}{週|週間}"
TIME_YEARS: "{0}{年|年間}"
UPDATE_ERROR: "更新の確認中にエラーが発生しました"
UPDATE_HEADER: "{0} アップデート"
UPDATE_NOTICE: "お知らせ: {0} が利用可能です"
UPGRADE_IN_PROGRESS: "アップグレードが進行中です。しばらくしてからもう一度やり直してください"
USER_NOT_FOUND: "ユーザー \"{0}\" が見つかりません"
USER_OFFLINE: "ユーザー \"{0}\" はオンラインではありません"
USING_MYSQL: "MySQL データストレージを使用します"
USING_SQLITE: "SQLite データストレージを使用します"
VALID_DONATION_KEY: "有効な寄付キーです"
VERSION_INCOMPATIBLE: "{0} {1} はサポートされていません"
VERSION_NOTICE: "バージョン {0} が利用可能です"
VERSION_REQUIRED: "{0} {1} 以上が必要です"
WORLD_NOT_FOUND: "ワールド \"{0}\" は見つかりませんでした"
+209
View File
@@ -0,0 +1,209 @@
# CoreProtect Language File (ko)
ACTION_NOT_SUPPORTED: "이 명령어는 해당 작업을 지원하지 않습니다."
AMOUNT_BLOCK: "{0}개의 {블록|블록}"
AMOUNT_CHUNK: "{0}개의 {청크|청크}"
AMOUNT_ENTITY: "{0}개의 {엔티티|엔티티}"
AMOUNT_ITEM: "{0}개의 {아이템|아이템}"
API_TEST: "API 테스트가 성공했습니다."
CACHE_ERROR: "경고: {0} 캐시를 검증하는 중 오류가 발생했습니다."
CACHE_RELOAD: "데이터베이스에서 {매핑|월드} 캐시를 강제로 다시 로드합니다."
CHECK_CONFIG: "config.yml을 확인해주세요"
COMMAND_CONSOLE: "이 명령어는 콘솔에서만 실행할 수 있습니다."
COMMAND_NOT_FOUND: "\"{0}\" 명령어를 찾을 수 없습니다."
COMMAND_THROTTLED: "잠시 기다렸다가 다시 시도하세요."
CONSUMER_ERROR: "데이터베이스 처리가 이미 {일시정지|재개}되어 있습니다!"
CONSUMER_TOGGLED: "데이터베이스 처리가 {일시정지|재개}되었습니다."
CONTAINER_HEADER: "컨테이너 기록"
CPU_CORES: "CPU 코어"
DATABASE_BUSY: "데이터베이스가 사용 중입니다. 나중에 다시 시도하세요."
DATABASE_INDEX_ERROR: "데이터베이스 인덱스의 유효성을 검사할 수 없습니다."
DATABASE_LOCKED_1: "데이터베이스가 잠겼습니다. 최대 15초 대기 중..."
DATABASE_LOCKED_2: "데이터베이스가 이미 사용 중입니다. 다시 시도하세요."
DATABASE_LOCKED_3: "데이터베이스 잠금을 해제하려면 \"database-lock: false\"로 설정하세요."
DATABASE_LOCKED_4: "데이터베이스 잠금을 비활성화하면 데이터가 손상될 수도 있습니다."
DATABASE_UNREACHABLE: "데이터베이스에 연결할 수 없습니다. 데이터를 삭제하고 종료합니다."
DEVELOPMENT_BRANCH: "개발 브랜치가 감지되어 패치 스크립트를 건너뜁니다."
DIRT_BLOCK: "당신 아래에 임시 안전 블록을 설치했습니다."
DISABLE_SUCCESS: "성공! {0} 비활성화됨"
DONATION_KEY_REQUIRED: "해당 명령어를 사용하려면 유효한 후원 키가 필요합니다."
ENABLE_FAILED: "{0}을(를) 시작할 수 없습니다."
ENABLE_SUCCESS: "{0}이(가) 성공적으로 활성화되었습니다!"
ENJOY_COREPROTECT: "{0}을(를) 잘 사용하고 계신가요? 그렇다면 저희의 Discord 서버에 참여하세요!"
FINISHING_CONVERSION: "데이터 변환을 완료하는 중입니다. 잠시 기다려 주세요..."
FINISHING_LOGGING: "데이터 로깅을 완료하는 중입니다. 잠시 기다려 주세요..."
FIRST_VERSION: "초기 DB: {0}"
GLOBAL_LOOKUP: "전체 조회를 수행하려면 반경을 지정하지 마세요."
GLOBAL_ROLLBACK: "전역 {롤백|복원}을 수행하려면 \"{0}\"을(를) 사용하세요."
HELP_ACTION_1: "특정 작업으로 조회를 제한합니다."
HELP_ACTION_2: "예: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a :kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "해당 명령어에 대한 추가 정보를 표시합니다."
HELP_EXCLUDE_1: "블록/사용자를 제외합니다."
HELP_EXCLUDE_2: "예: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} 도움말"
HELP_INCLUDE_1: "특정 블록/엔티티를 포함합니다."
HELP_INCLUDE_2: "예: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "로그 조사가 활성화되면 다음을 수행할 수 있습니다:"
HELP_INSPECT_2: "블록을 마우스 왼쪽 버튼으로 클릭하면 누가 그 블록을 설치했는지 확인할 수 있습니다."
HELP_INSPECT_3: "블록을 마우스 오른쪽 버튼으로 클릭하면 인접한 블록이 부서졌는지 확인할 수 있습니다."
HELP_INSPECT_4: "블록을 배치하여 해당 위치에서 어떤 블록이 부서졌는지 확인할 수 있습니다."
HELP_INSPECT_5: "누가 설치했는지 확인하려면 액체(등)에 블록을 배치하세요."
HELP_INSPECT_6: "문, 상자 등을 마우스 오른쪽 버튼으로 클릭하면 누가 마지막으로 사용했는지 확인할 수 있습니다."
HELP_INSPECT_7: "팁: 더 빠른 사용을 위해 \"/co i\"명령어를 사용할 수 있습니다."
HELP_INSPECT_COMMAND: "블록 로그 조사를 켜거나 끕니다."
HELP_LIST: "모든 명령어 목록을 표시합니다."
HELP_LOOKUP_1: "단축 명령어."
HELP_LOOKUP_2: "로그를 보려면 블록을 검사한 후 사용하세요."
HELP_LOOKUP_COMMAND: "고급 블록 데이터 조회."
HELP_NO_INFO: "\"{0}\" 명령어에 대한 정보가 없습니다."
HELP_PARAMETER: "자세한 매개변수 정보는 \"{0}\"을 참조하세요."
HELP_PARAMS_1: "{조회|롤백|복원}을(를) 수행합니다."
HELP_PARAMS_2: "{조회|롤백|복원}할 사용자를 지정합니다."
HELP_PARAMS_3: "{조회|롤백|복원}할 시간을 지정합니다."
HELP_PARAMS_4: "{조회|롤백|복원}을(를) 제한할 반경을 지정합니다."
HELP_PARAMS_5: "{조회|롤백|복원}을(를) 특정 작업으로 제한합니다."
HELP_PARAMS_6: "{조회|롤백|복원}에 특정 블록/엔티티를 포함합니다."
HELP_PARAMS_7: "{조회|롤백|복원}에서 블록/사용자를 제외합니다."
HELP_PURGE_1: "지정된 시간보다 오래된 데이터를 삭제합니다."
HELP_PURGE_2: "예를 들어 \"{0}\"은(는) 한 달이 지난 모든 데이터를 삭제하고 지난 30일 동안의 데이터만 보관합니다."
HELP_PURGE_COMMAND: "오래된 블록 데이터를 삭제합니다."
HELP_RADIUS_1: "반경을 지정합니다."
HELP_RADIUS_2: "예: [r:10] (10블록 이내에서만 변경합니다.)"
HELP_RELOAD_COMMAND: "구성 파일을 다시 로드합니다."
HELP_RESTORE_COMMAND: "블록 데이터를 복원합니다."
HELP_ROLLBACK_COMMAND: "블록 데이터를 롤백합니다."
HELP_STATUS: "플러그인 상태 및 버전 정보를 확인합니다."
HELP_STATUS_COMMAND: "플러그인 상태를 표시합니다."
HELP_TELEPORT: "특정 위치로 순간이동합니다."
HELP_TIME_1: "조회 시간을 지정합니다."
HELP_TIME_2: "예: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "조회할 사용자를 지정합니다."
HELP_USER_2: "예: [u:notch], [u:notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\"은(는) 해당 작업과 함께 사용할 수 없습니다."
INSPECTOR_ERROR: "로그 조사가 이미 {활성화|비활성화}되어 있습니다!"
INSPECTOR_TOGGLED: "로그 조사가 {활성화|비활성화}되었습니다."
INTEGRATION_ERROR: "{0} 로깅을 {초기화|비활성화}할 수 없습니다."
INTEGRATION_SUCCESS: "{0} 로깅이 성공적으로 {초기화|비활성화}되었습니다."
INTEGRATION_VERSION: "잘못된 {0} 버전이 발견되었습니다."
INTERACTIONS_HEADER: "플레이어 상호작용"
INVALID_ACTION: "유효하지 않은 작업입니다."
INVALID_BRANCH_1: "잘못된 플러그인 버전(브랜치가 설정되지 않음)."
INVALID_BRANCH_2: "계속하려면 프로젝트 브랜치를 \"development\"로 설정하세요."
INVALID_BRANCH_3: "개발중인 코드를 실행하면 데이터 손상이 발생할 수도 있습니다."
INVALID_CONTAINER: "먼저 유효한 컨테이너를 검사하세요."
INVALID_DONATION_KEY: "잘못된 후원 키입니다."
INVALID_INCLUDE: "\"{0}\"은(는) 잘못된 블록/엔티티 이름입니다."
INVALID_INCLUDE_COMBO: "잘못된 블록/엔티티 조합입니다."
INVALID_RADIUS: "유효한 반경을 입력하세요."
INVALID_SELECTION: "{0} 선택된 항목을 찾을 수 없습니다."
INVALID_USERNAME: "\"{0}\"은(는) 잘못된 사용자 이름입니다."
INVALID_WORLD: "유효한 월드를 지정하세요."
LATEST_VERSION: "최신 버전: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "다운로드: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "블록 이름: {0}"
LINK_WIKI_ENTITY: "엔티티 이름: {0}"
LOGGING_ITEMS: "{0} 아이템이 남았습니다. 잠시만 기다려 주세요..."
LOGGING_TIME_LIMIT: "로깅 시간 제한에 도달했습니다. 데이터를 삭제하고 종료합니다."
LOOKUP_BLOCK: "{0}이(가) {1}을(를) {설치|파괴}함"
LOOKUP_CONTAINER: "{0}이(가) {2}에서 {1}을(를) {추가|제거}함"
LOOKUP_HEADER: "{0} 조회 결과"
LOOKUP_INTERACTION: "{0}이(가) {1}을(를) {클릭함|죽임}
LOOKUP_ITEM: "{0}이(가) {2}에서 {1}을(를) {획득|드롭}함"
LOOKUP_LOGIN: "{0}이(가) {로그인|로그아웃}함"
LOOKUP_PAGE: "페이지 {0}"
LOOKUP_PROJECTILE: "{0}이(가) {2}에서 {1}을(를) {던짐|발사함}"
LOOKUP_ROWS_FOUND: "{0} {열|열}이 발견되었습니다."
LOOKUP_SEARCHING: "조회 중입니다. 잠시만 기다려 주세요..."
LOOKUP_STORAGE: "{0}이(가) {2}에서 {1}을(를) {추가|제거}함"
LOOKUP_TIME: "{0} 전"
LOOKUP_USERNAME: "{0}이(가) {1}으로 로그인함"
MAXIMUM_RADIUS: "최대 {조회|롤백|복원} 반경은 {0}입니다."
MISSING_ACTION_USER: "해당 작업을 사용하려면 사용자를 지정하세요."
MISSING_LOOKUP_TIME: "{조회|롤백|복원}할 시간을 지정하세요."
MISSING_LOOKUP_USER: "조회할 사용자 또는 {블록|반경}을 지정하세요."
MISSING_PARAMETERS: "\"{0}\"을(를) 사용하세요."
MISSING_ROLLBACK_RADIUS: "{롤백|복원}할 반경을 지정하지 않았습니다."
MISSING_ROLLBACK_USER: "{롤백|복원}할 사용자를 지정하지 않았습니다."
MYSQL_UNAVAILABLE: "MySQL 서버에 연결할 수 없습니다."
NETWORK_CONNECTION: "{0} 연결 {성공|실패}. {1} {2} 사용 중"
NETWORK_TEST: "네트워크 테스트 데이터가 성공적으로 전송되었습니다."
NO_DATA: "{0}에서 데이터를 찾을 수 없습니다."
NO_DATA_LOCATION: "해당 위치에서 {데이터|거래|상호작용|메시지}(을)를 찾을 수 없습니다."
NO_PERMISSION: "해당 작업을 수행할 권한이 없습니다."
NO_RESULTS: "결과를 찾을 수 없습니다."
NO_RESULTS_PAGE: "해당 페이지에 대한 {결과|데이터}를 찾을 수 없습니다."
NO_ROLLBACK: "{보류 중인|이전} 롤백/복원을 찾을 수 없습니다."
PATCH_INTERRUPTED: "업그레이드가 중단되었습니다. 서버를 재시작하면 재시도합니다."
PATCH_OUTDATED_1: "{0}보다 오래된 데이터베이스를 업그레이드할 수 없습니다."
PATCH_OUTDATED_2: "지원되는 CoreProtect 버전으로 업그레이드하세요."
PATCH_PROCESSING: "새 데이터를 처리 중입니다. 잠시 기다려 주세요..."
PATCH_SKIP_UPDATE: "{0}에서 {테이블|인덱스} {업데이트|생성|제거}를 건너뜁니다."
PATCH_STARTED: "{0} 업그레이드를 수행 중입니다. 잠시 기다려 주세요..."
PATCH_SUCCESS: "{0}으로 성공적으로 업그레이드되었습니다."
PATCH_UPGRADING: "데이터베이스 업그레이드가 진행 중입니다. 잠시 기다려 주세요..."
PLEASE_SELECT: "\"{0}\" 또는 \"{1}\" 중 하나를 선택하세요."
PREVIEW_CANCELLED: "미리보기가 취소되었습니다."
PREVIEW_CANCELLING: "미리보기 취소 중..."
PREVIEW_IN_GAME: "게임 내에서만 롤백 미리보기가 가능합니다."
PREVIEW_TRANSACTION: "{컨테이너|인벤토리} 기록은 미리보기할 수 없습니다."
PRIMARY_THREAD_ERROR: "기본 스레드에서는 API 메소드를 사용할 수 없습니다."
PURGE_ABORTED: "삭제에 실패하였습니다. 데이터베이스가 손상되었을 수도 있습니다."
PURGE_ERROR: "{0} 데이터를 처리할 수 없습니다!"
PURGE_FAILED: "삭제에 실패하였습니다. 나중에 다시 시도하세요."
PURGE_IN_PROGRESS: "삭제가 진행 중입니다. 나중에 다시 시도하세요."
PURGE_MINIMUM_TIME: "{0} {일|시간}보다 오래된 데이터만 삭제할 수 있습니다."
PURGE_NOTICE_1: "이 작업은 시간이 걸릴 수 있습니다."
PURGE_NOTICE_2: "완료될 때까지 서버를 재시작하지 마세요."
PURGE_OPTIMIZING: "데이터베이스를 최적화 중입니다. 잠시 기다려 주세요..."
PURGE_PROCESSING: "{0} 데이터를 처리 중입니다..."
PURGE_REPAIRING: "복구 시도 중입니다. 시간이 걸릴 수 있습니다..."
PURGE_ROWS: "{0} {열|열}의 데이터가 삭제되었습니다."
PURGE_STARTED: "\"{0}\"에서 데이터 삭제 시작됨"
PURGE_SUCCESS: "데이터 삭제가 성공적으로 완료되었습니다."
RAM_STATS: "램: {0}GB / {1}GB"
RELOAD_STARTED: "구성 리로드 중 - 잠시 기다려 주세요."
RELOAD_SUCCESS: "구성이 성공적으로 리로드되었습니다."
ROLLBACK_ABORTED: "롤백 또는 복원이 중단되었습니다."
ROLLBACK_CHUNKS_FOUND: "수정할 {청크|청크} {0}개를 찾았습니다."
ROLLBACK_CHUNKS_MODIFIED: "{1}개 중 {0}개의 {청크|청크} 수정됨"
ROLLBACK_COMPLETED: "\"{0}\"에 대한 {롤백이|복원이|미리보기가} 완료되었습니다."
ROLLBACK_EXCLUDED_USERS: "제외된 {사용자|사용자}: \"{0}\""
ROLLBACK_INCLUDE: "{포함된|제외된} {블록|엔티티|대상} {타입|타입}: \"{0}\""
ROLLBACK_IN_PROGRESS: "롤백/복원이 이미 진행 중입니다."
ROLLBACK_LENGTH: "소요 시간: {0} {초|초}"
ROLLBACK_MODIFIED: "{0}이(가) {수정됨|수정 중}"
ROLLBACK_RADIUS: "반경: {0} {블록|블록}"
ROLLBACK_SELECTION: "반경이 \"{0}\"로 설정되었습니다."
ROLLBACK_STARTED: "\"{0}\"에서 {롤백이|복원이|미리보기가} 시작되었습니다."
ROLLBACK_TIME: "시간 범위: {0}"
ROLLBACK_WORLD_ACTION: "{월드|작업} \"{0}\"으로 제한됨"
SIGN_HEADER: "표지판 메시지"
STATUS_CONSUMER: "데이터베이스 처리: 대기 중인 {항목|항목} {0}개"
STATUS_DATABASE: "데이터베이스: {0} 사용 중"
STATUS_INTEGRATION: "{0}: 통합 {활성화됨|비활성화됨}"
STATUS_LICENSE: "라이선스: {0}"
STATUS_SYSTEM: "시스템: {0}"
STATUS_VERSION: "버전: {0}"
TELEPORTED: "{0}(으)로 텔레포트되었습니다."
TELEPORTED_SAFETY: "안전하게 텔레포트되었습니다."
TELEPORT_PLAYERS: "텔레포트 명령어는 플레이어만 사용할 수 있습니다."
TIME_DAYS: "{0} {일|일}"
TIME_HOURS: "{0} {시간|시간}"
TIME_MINUTES: "{0} {분|분}"
TIME_SECONDS: "{0} {초|초}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {주|주}"
UPDATE_ERROR: "업데이트 확인 중 오류가 발생했습니다."
UPDATE_HEADER: "{0} 업데이트"
UPDATE_NOTICE: "알림: {0}을(를) 사용할 수 있습니다."
UPGRADE_IN_PROGRESS: "업그레이드가 진행 중입니다. 나중에 다시 시도하세요."
USER_NOT_FOUND: "사용자 \"{0}\"을(를) 찾을 수 없습니다."
USER_OFFLINE: "사용자 \"{0}\"은(는) 온라인 상태가 아닙니다."
USING_MYSQL: "데이터 저장에 MySQL을 사용 중입니다."
USING_SQLITE: "데이터 저장에 SQLite를 사용 중입니다."
VALID_DONATION_KEY: "유효한 후원 키입니다."
VERSION_NOTICE: "버전 {0}(를) 사용할 수 있습니다."
VERSION_REQUIRED: "{0} {1} 이상이 필요합니다."
WORLD_NOT_FOUND: " \"{0}\"을(를) 찾을 수 없습니다."
+212
View File
@@ -0,0 +1,212 @@
# CoreProtect Language File (pl)
ACTION_NOT_SUPPORTED: "Ta akcja nie jest obsługiwana."
AMOUNT_BLOCK: "{0} {blok|bloki}"
AMOUNT_CHUNK: "{0} {chunk|chunków}"
AMOUNT_ENTITY: "{0} {byt|bytów}"
AMOUNT_ITEM: "{0} {przedmiot|przedmioty}"
API_TEST: "Test interfejsu API pomyślny."
CACHE_ERROR: "OSTRZEŻENIE: Błąd podczas walidacji {0} Pamięć podręczna."
CACHE_RELOAD: "Wymuszanie przeładowania {mapowanie|świat} pamięci podręczne z bazy danych."
CHECK_CONFIG: "Sprawdź plik config.yml"
COMMAND_CONSOLE: "Uruchom polecenie z konsoli."
COMMAND_NOT_FOUND: "Nie znaleziono komendy \"{0}\"."
COMMAND_THROTTLED: "Poczekaj chwilę i spróbuj ponownie."
CONSUMER_ERROR: "Przetwarzanie kolejki klientów jest już {wstrzymane|wznowione}."
CONSUMER_TOGGLED: "Przetwarzanie kolejki klientów zostało {wstrzymane|wznowione}."
CONTAINER_HEADER: "Transakcje kontenerowe"
CPU_CORES: "Rdzenie CPU."
DATABASE_BUSY: "Baza danych zajęta. Spróbuj ponownie później."
DATABASE_INDEX_ERROR: "Nie można zweryfikować indeksów bazy danych."
DATABASE_LOCKED_1: "Baza danych zablokowana. Czekam do 15 sekund..."
DATABASE_LOCKED_2: "Baza danych jest już używana. Proszę spróbuj ponownie."
DATABASE_LOCKED_3: "Aby wyłączyć blokowanie bazy danych, ustaw \"database-lock: false\"."
DATABASE_LOCKED_4: "Wyłączenie blokowania bazy danych może spowodować uszkodzenie danych."
DATABASE_UNREACHABLE: "Baza danych jest niedostępna. Odrzucanie danych i zamykanie."
DEVELOPMENT_BRANCH: "Wykryto gałąź deweloperską, pomijam skrypty poprawek."
DIRT_BLOCK: "Umieściłem pod Tobą tymczasowy blok bezpieczeństwa."
DONATION_KEY_REQUIRED: "Dla tej komendy wymagany jest prawidłowy klucz darowizny."
DISABLE_SUCCESS: "Sukces! Wyłączono {0}"
ENABLE_FAILED: "{0} nie mógł wystartować."
ENABLE_SUCCESS: "{0} jest teraz włączony!"
ENJOY_COREPROTECT: "Podoba Ci się {0}? Dołącz do naszego Discorda!"
FINISHING_CONVERSION: "Kończenie konwersji danych. Proszę czekać..."
FINISHING_LOGGING: "Kończenie rejestracji danych. Proszę czekać..."
FIRST_VERSION: "Początkowa baza danych: {0}"
GLOBAL_LOOKUP: "Nie określaj promienia, aby wykonać globalne wyszukiwanie."
GLOBAL_ROLLBACK: "Użyj \"{0}\", aby zrobić globalne {wycofywanie|przywracanie}"
HELP_ACTION_1: "Ogranicz wyszukiwanie do konkretnej akcji."
HELP_ACTION_2: "Przykłady: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Wyświetl dodatkowe informacje."
HELP_EXCLUDE_1: "Wyklucz bloki/użytkowników."
HELP_EXCLUDE_2: "Przykłady: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Pomoc"
HELP_INCLUDE_1: "Uwzględnij bloki/elementy."
HELP_INCLUDE_2: "Przykłady: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Po włączeniu inspektora możesz:"
HELP_INSPECT_2: "Kliknij lewym przyciskiem myszy, aby zobaczyć kto postawił ten blok."
HELP_INSPECT_3: "Kliknij prawym przyciskiem myszy, aby zobaczyć jaki sąsiedni blok został usunięty."
HELP_INSPECT_4: "Umieść blok, aby zobaczyć jaki blok został usunięty w tym miejscu."
HELP_INSPECT_5: "Umieść blok w płynie, aby zobaczyć kto go postawił."
HELP_INSPECT_6: "Kliknij prawym przyciskiem myszy na drzwi, skrzynię, itp., aby zobaczyć, kto ostatnio z nich korzystał."
HELP_INSPECT_7: "Wskazówka: możesz użyć po prostu \"/co i\"."
HELP_INSPECT_COMMAND: "Przełącza inspektora bloków."
HELP_LIST: "Wyświetla listę wszystkich poleceń."
HELP_LOOKUP_1: "Skrót poleceń."
HELP_LOOKUP_2: "Zobacz dodatkowe strony."
HELP_LOOKUP_COMMAND: "Dane bloku wyszukiwania."
HELP_NO_INFO: "Nie znaleziono informacji dla polecenia \"{0}\"."
HELP_PARAMETER: "Proszę zobaczyć \"{0}\", aby uzyskać szczegółowe informacje o parametrach."
HELP_PARAMS_1: "Wykonaj {wyszukiwanie|wycofywanie|przywracanie}."
HELP_PARAMS_2: "Określ użytkowników do {wyszukiwania|wycofywania|przywracania}."
HELP_PARAMS_3: "Określ czas do {wyszukiwania|wycofywania|przywracania}."
HELP_PARAMS_4: "Ogranicz promień {wyszukiwania|wycofywania|przywracania}."
HELP_PARAMS_5: "Ogranicz akcję {wyszukiwania|wycofywania|przywracania}."
HELP_PARAMS_6: "Uwzględnij bloki/byty w {wyszukiwaniu|wycofywaniu|przywracaniu}."
HELP_PARAMS_7: "Wyklucz bloki/użytkowników z {wyszukiwania|wycofywania|przywracania}."
HELP_PURGE_1: "Usuń dane starsze od określonego czasu."
HELP_PURGE_2: "Na przykład, \"{0}” usunie wszystkie dane starsze niż jeden miesiąc i zachowa tylko dane z ostatnich 30 dni."
HELP_PURGE_COMMAND: "Usuń stare dane bloku."
HELP_RADIUS_1: "Określ promień obszaru."
HELP_RADIUS_2: "Przykłady: [r:10] (Dokonuj zmian tylko w promieniu 10 bloków od Ciebie)"
HELP_RELOAD_COMMAND: "Ponownie ładuje plik konfiguracyjny."
HELP_RESTORE_COMMAND: "Przywróć dane bloku."
HELP_ROLLBACK_COMMAND: "Wycofaj dane bloku."
HELP_STATUS: "Wyświetl stan i wersję pluginu."
HELP_STATUS_COMMAND: "Wyświetla stan pluginu."
HELP_TELEPORT: "Teleport."
HELP_TIME_1: "Określ czas."
HELP_TIME_2: "Przykłady: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Określ użytkowników."
HELP_USER_2: "Przykłady: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" nie może być użyte z tą akcją."
INSPECTOR_ERROR: "Inspektor jest już {włączony|wyłączony}."
INSPECTOR_TOGGLED: "{Włączono|Wyłączono} inspektor."
INTEGRATION_ERROR: "Nie można {zainicjalizować|wyłączyć} logowania {0}."
INTEGRATION_SUCCESS: "Logowanie {0} pomyślnie {zainicjowane|wyłączone}."
INTEGRATION_VERSION: "Znaleziono nieprawidłową wersję {0}."
INTERACTIONS_HEADER: "Interakcje graczy"
INVALID_ACTION: "To nie jest prawidłowa akcja."
INVALID_BRANCH_1: "Nieprawidłowa wersja pluginu (nie ustawiono gałęzi)."
INVALID_BRANCH_2: "Aby kontynuować, ustaw gałąź projektu na \"development\"."
INVALID_BRANCH_3: "Uruchomienie kodu programistycznego może spowodować uszkodzenie danych."
INVALID_CONTAINER: "Proszę najpierw sprawdzić prawidłowy pojemnik."
INVALID_DONATION_KEY: "Nieprawidłowy klucz darowizny."
INVALID_INCLUDE: "\"{0}\" to nieprawidłowa nazwa bloku/bytu."
INVALID_INCLUDE_COMBO: "To jest nieprawidłowa kombinacja typów."
INVALID_RADIUS: "Podaj poprawny promień."
INVALID_SELECTION: "{0} nie znaleziono wyboru."
INVALID_USERNAME: "\"{0}\" to nieprawidłowa nazwa użytkownika."
INVALID_WORLD: "Podaj poprawny świat."
LATEST_VERSION: "Najnowsza wersja: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Pobierz: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Nazwy bloków: {0}"
LINK_WIKI_ENTITY: "Nazwy bytów: {0}"
LOGGING_ITEMS: "Pozostało {0} elementów do logowania. Proszę czekać..."
LOGGING_TIME_LIMIT: "Osiągnięto limit czasu logowania. Odrzucanie danych i zamykanie."
LOOKUP_BLOCK: "{0} {postawił|zniszczył} {1}."
LOOKUP_CONTAINER: "{0} {dodał|usunął} {1} {2}."
LOOKUP_HEADER: "{0} Wyników Wyszukiwania"
LOOKUP_INTERACTION: "{0} {kliknął|zabił} {1}."
LOOKUP_ITEM: "{0} {podniósł|upuścił} {1} {2}."
LOOKUP_LOGIN: "{0} się {zalogował|wylogował}."
LOOKUP_PAGE: "Strona {0}"
LOOKUP_PROJECTILE: "{0} {rzucił|strzelił} {1} {2}."
LOOKUP_ROWS_FOUND: "Znaleziono {0} {wiersz|wierszy}."
LOOKUP_SEARCHING: "Wyszukiwanie wyszukiwania. Proszę czekać..."
LOOKUP_STORAGE: "{0} {zdeponował|wyciągnął} {1} {2}."
LOOKUP_TIME: "{0} temu"
LOOKUP_USERNAME: "{0} zalogowany jako {1}."
MAXIMUM_RADIUS: "Maksymalny promień {wyszukiwania|wycofywania|przywracania} to {0}."
MISSING_ACTION_USER: "Aby skorzystać z tej akcji, określ użytkownika."
MISSING_LOOKUP_TIME: "Proszę określić czas {wyszukiwania|wycofywania|przywracania}."
MISSING_LOOKUP_USER: "Podaj użytkownika lub {blok|promień}."
MISSING_PARAMETERS: "Proszę użyć \"{0}\"."
MISSING_ROLLBACK_RADIUS: "Nie podałeś promienia {wycofywania|przywracania}."
MISSING_ROLLBACK_USER: "Nie podałeś użytkownika {wycofywania|przywracania}."
MYSQL_UNAVAILABLE: "Nie można się połączyć z serwerem MySQL."
NETWORK_CONNECTION: "Połączenie przez {0} {pomyślne|nieudane}. Używam {1} {2}."
NETWORK_TEST: "Pomyślnie wysłano dane testu sieci."
NO_DATA: "Nie znaleziono danych w {0}."
NO_DATA_LOCATION: "Nie znaleziono {danych|transakcji|interakcji|wiadomości} w tym miejscu."
NO_PERMISSION: "Nie masz na to pozwolenia."
NO_RESULTS: "Nie znaleziono wyników."
NO_RESULTS_PAGE: "Nie znaleziono {wyników|danych} dla tej strony."
NO_ROLLBACK: "Nie znaleziono {oczekujących|poprzednich} wycofywań/przywracań."
PATCH_INTERRUPTED: "Aktualizacja przerwana. Spróbuję ponownie przy restarcie."
PATCH_OUTDATED_1: "Nie można zaktualizować baz danych starszych niż {0}."
PATCH_OUTDATED_2: "Uaktualnij do obsługiwanej wersji CoreProtect."
PATCH_PROCESSING: "Przetwarzanie nowych danych. Proszę czekać..."
PATCH_SKIP_UPDATE: "Pomijam {tabeli|indeksu} {aktualizację|tworzenie|usuwanie} na {0}."
PATCH_STARTED: "Przeprowadzam aktualizację do {0}. Proszę czekać..."
PATCH_SUCCESS: "Pomyślnie uaktualniono do {0}."
PATCH_UPGRADING: "Trwa aktualizacja bazy danych. Proszę czekać..."
PLEASE_SELECT: "Proszę wybrać: \"{0}\" lub \"{1}\"."
PREVIEW_CANCELLED: "Podgląd anulowany."
PREVIEW_CANCELLING: "Anulowanie podglądu..."
PREVIEW_IN_GAME: "Możesz tylko podglądać wycofania w grze."
PREVIEW_TRANSACTION: "Nie możesz wyświetlić podglądu transakcji {kontenera|ekwipunku}."
PRIMARY_THREAD_ERROR: "Ta metoda API nie może być używana w głównym wątku."
PURGE_ABORTED: "Czyszczenie nie powiodło się. Baza danych może być uszkodzona."
PURGE_ERROR: "Nie można przetworzyć danych {0}!"
PURGE_FAILED: "Czyszczenie nie powiodło się. Spróbuj ponownie później."
PURGE_IN_PROGRESS: "Trwa czyszczenie. Spróbuj ponownie później."
PURGE_MINIMUM_TIME: "Możesz usunąć tylko dane starsze niż {0} {dni|godzin}."
PURGE_NOTICE_1: "Uwaga, może to zająć trochę czasu."
PURGE_NOTICE_2: "Nie restartuj serwera przed zakończeniem."
PURGE_OPTIMIZING: "Optymalizacja bazy danych. Proszę czekać..."
PURGE_PROCESSING: "Przetwarzanie danych {0}..."
PURGE_REPAIRING: "Próba naprawy. To może zająć troche czasu..."
PURGE_ROWS: "Usunięto {0} {wiersz|wierszy} danych."
PURGE_STARTED: "Usuwanie danych rozpoczęte na \"{0}\"."
PURGE_SUCCESS: "Czyszczenie danych zakończono pomyślnie."
RAM_STATS: "{0}GB / {1}GB RAM"
RELOAD_STARTED: "Ponowne ładowanie konfiguracji - proszę czekać."
RELOAD_SUCCESS: "Pomyślnie wczytano konfigurację."
ROLLBACK_ABORTED: "Wycofanie lub przywracanie zostało przerwane."
ROLLBACK_CHUNKS_FOUND: "Znaleziono {0} {chunk|chunków} do zmodyfikowania."
ROLLBACK_CHUNKS_MODIFIED: "Zmodyfikowano {0}/{1} {chunk|chunków}."
ROLLBACK_COMPLETED: "{Wycofywanie|Przywrócenie|Podgląd} ukończone dla \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "{Wykluczony użytkownik|Wykluczeni użytkownicy}: \"{0}\"."
ROLLBACK_INCLUDE: "{Zawarte|Wykluczone} {bloku|bytu|celu} {typ|typy}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Już trwa wycofywanie/przywracanie."
ROLLBACK_LENGTH: "Zajęty czas: {0} {sekunda|sekund}."
ROLLBACK_MODIFIED: "{Zmodyfikowane|Modyfikowanie} {0}."
ROLLBACK_RADIUS: "Promień: {0} {blok|bloków}."
ROLLBACK_SELECTION: "Promień ustawiony na \"{0}\"."
ROLLBACK_STARTED: "{Wycofywanie|Przywrócenie|Podgląd} zaczęło się na \"{0}\"."
ROLLBACK_TIME: "Zakres czasu: {0}."
ROLLBACK_WORLD_ACTION: "Ograniczony do {świata|akcji} \"{0}\"."
SIGN_HEADER: "Wiadomości Tabliczki"
STATUS_CONSUMER: "Konsument: {0} {przedmiot|przedmiotów} w kolejce."
STATUS_DATABASE: "Baza danych: Używa {0}."
STATUS_INTEGRATION: "{0}: Integracja {włączona|wyłączona}."
STATUS_LICENSE: "Licencja: {0}"
STATUS_SYSTEM: "System: {0}"
STATUS_VERSION: "Wersja: {0}"
TELEPORTED: "Teleportowany do {0}."
TELEPORTED_SAFETY: "Teleportowano Cię w bezpieczne miejsce."
TELEPORT_PLAYERS: "Polecenie Teleport może być używane tylko przez graczy."
TIME_DAYS: "{0} {dzień|dni}"
TIME_HOURS: "{0} {godzina|godzin(y)}"
TIME_MINUTES: "{0} {minuta|minut(y)}"
TIME_MONTHS: "{0} {miesiąc|miesięcy}"
TIME_SECONDS: "{0} {sekunda|sekund(y)}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {tydzień|tygodni(e)}"
TIME_YEARS: "{0} {rok|lat(a)}"
UPDATE_ERROR: "Wystąpił błąd podczas sprawdzania aktualizacji."
UPDATE_HEADER: "Aktualizacja {0}"
UPDATE_NOTICE: "Ogłoszenie: {0} jest teraz dostępne."
UPGRADE_IN_PROGRESS: "Trwa aktualizacja. Spróbuj ponownie później."
USER_NOT_FOUND: "Nie znaleziono użytkownika \"{0}\"."
USER_OFFLINE: "Użytkownik \"{0}\" nie jest online."
USING_MYSQL: "MySQL używany do przechowywania danych."
USING_SQLITE: "SQLite używany do przechowywania danych."
VALID_DONATION_KEY: "Ważny klucz darowizny."
VERSION_INCOMPATIBLE: "{0} {1} nie jest wspierany."
VERSION_NOTICE: "Wersja {0} jest teraz dostępna."
VERSION_REQUIRED: "{0} {1} lub wyższy jest wymagany."
WORLD_NOT_FOUND: "Nie znaleziono świata \"{0}\"."
+153 -130
View File
@@ -1,197 +1,220 @@
# CoreProtect Language File (ru)
ACTION_NOT_SUPPORTED: "Данное действие не поддерживается этой командой."
ACTION_NOT_SUPPORTED: "Это действие не поддерживается данной командой."
AMOUNT_BLOCK: "{0} {блок|блоков}"
AMOUNT_CHUNK: "{0} {чанк|чанков}"
AMOUNT_ENTITY: "{0} {существо|существ}"
AMOUNT_ITEM: "{0} {предмет|предметов}"
API_TEST: "Тест API прошёл успешно!"
CACHE_ERROR: "ВНИМАНИЕ: Ошибка при проверке кеша {0}."
CACHE_RELOAD: "Принудительная перезагрузка кеша {отображения|мира} из базы данных."
API_TEST: "Тестирование API завершено успешно!"
CACHE_ERROR: "ВНИМАНИЕ: Ошибка при проверке кэша {0}."
CACHE_RELOAD: "Принудительная перезагрузка кэша {сопоставлений|мира} из базы данных."
CHECK_CONFIG: "Пожалуйста, проверьте config.yml"
COMMAND_NOT_FOUND: "Команда \"{0}\" не найдена."
COMMAND_THROTTLED: "Подождите немного и попробуйте еще раз."
CONTAINER_HEADER: "Контейнерные транзакции"
COMMAND_CONSOLE: "Пожалуйста, запустите эту команду из консоли."
COMMAND_NOT_FOUND: "Команда «{0}» не найдена."
COMMAND_THROTTLED: "Пожалуйста, подождите немного и попробуйте ещё раз."
CONSUMER_ERROR: "Обработка очереди потребителя уже {приостановлена|возобновлена}."
CONSUMER_TOGGLED: "Обработка очереди потребителя была {приостановлена|возобновлена}."
CONTAINER_HEADER: "Транзакции контейнеров"
CPU_CORES: "Ядер процессора."
DATABASE_BUSY: "База данных занята. Пожалуйста, повторите попытку позже."
DATABASE_INDEX_ERROR: "Невозможно проверить индексы базы данных."
DATABASE_LOCKED_1: "База данных заблокирована. Подождите около 15 секунд..."
DATABASE_LOCKED_2: "База данных уже используется. Пожалуйста, попробуйте еще раз."
DATABASE_LOCKED_3: "Чтобы отключить блокировку базы данных, установите \"database-lock: false\"."
DATABASE_INDEX_ERROR: "Не удалось проверить индексы базы данных."
DATABASE_LOCKED_1: "База данных заблокирована. Ожидание до 15 секунд..."
DATABASE_LOCKED_2: "База данных уже используется. Пожалуйста, попробуйте ещё раз."
DATABASE_LOCKED_3: "Чтобы отключить блокировку базы данных, установите «database-lock: false»."
DATABASE_LOCKED_4: "Отключение блокировки базы данных может привести к повреждению данных."
DATABASE_UNREACHABLE: "База данных недоступна. Удаление данных и завершение работы."
DEVELOPMENT_BRANCH: "Обнаружена ветвь разработки, скрипты исправлений пропущены."
DIRT_BLOCK: "Под тебя установлен блок грязи."
DISABLE_SUCCESS: "Успех! Отключено {0}"
ENABLE_FAILED: "{0} не удалось запустить."
ENABLE_SUCCESS: "{0} был успешно запущен!"
DATABASE_UNREACHABLE: "База данных недоступна. Сброс данных и завершение работы."
DEVELOPMENT_BRANCH: "Обнаружена ветка разработки, выполнение скриптов обновления пропущено."
DIRT_BLOCK: "Под вами размещён временный безопасный блок."
DISABLE_SUCCESS: "Успешно! {0} отключён."
DONATION_KEY_REQUIRED: "Для этой команды требуется действующий ключ пожертвования."
ENABLE_FAILED: "Не удалось запустить {0}."
ENABLE_SUCCESS: "{0} успешно запущен!"
ENJOY_COREPROTECT: "Нравится {0}? Присоединяйтесь к нашему Discord!"
FINISHING_CONVERSION: "Завершение преобразования данных. Пожалуйста, подождите..."
FINISHING_LOGGING: "Завершение регистрации данных. Пожалуйста, подождите..."
FINISHING_LOGGING: "Завершение записи данных. Пожалуйста, подождите..."
FIRST_VERSION: "Исходная БД: {0}"
GLOBAL_LOOKUP: "Не указывайте радиус для глобального поиска."
GLOBAL_ROLLBACK: "Используйте \"{0}\", чтобы сделать {откат|восстановление} глобально"
HELP_ACTION_1: "Ограничьте поиск определенным действием."
GLOBAL_ROLLBACK: "Используйте «{0}» для глобального {отката|восстановления}."
HELP_ACTION_1: "Ограничить поиск определённым действием."
HELP_ACTION_2: "Например: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Показать дополнительную информацию."
HELP_EXCLUDE_1: "Исключите блоки/пользователей."
HELP_COMMAND: "Показать подробную информацию о команде."
HELP_EXCLUDE_1: "Исключить блоки или пользователей."
HELP_EXCLUDE_2: "Например: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Помощь"
HELP_INCLUDE_1: "Включите определенные блоки/сущности."
HELP_HEADER: "{0} Справка"
HELP_INCLUDE_1: "Включить определённые блоки или сущности."
HELP_INCLUDE_2: "Например: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "С включенным режимом инспектора вы можете сделать следующее:"
HELP_INSPECT_2: "Щелкните левой кнопкой мыши по блоку, чтобы увидеть, кто разместил этот блок.."
HELP_INSPECT_3: "Щелкните блок правой кнопкой мыши, чтобы увидеть, какой соседний блок был сломан.."
HELP_INSPECT_4: "Поместите блок, чтобы увидеть, какой блок был сломан в этом месте."
HELP_INSPECT_5: "Поместите блок в жидкость (и т.п.), чтобы увидеть, кто его поместил."
HELP_INSPECT_6: "Щелкните правой кнопкой мыши дверь, сундук и т. Д., Чтобы узнать, кто последний использовал их."
HELP_INSPECT_7: "Подсказка: Вы можете использовать \"/co i\" для быстрого переключения режима инспектора."
HELP_INSPECT_COMMAND: "Включает или выключает режим инспектора блоков."
HELP_LIST: "Отображает список всех команд."
HELP_LOOKUP_1: "Команда быстрого доступа."
HELP_LOOKUP_2: "Используйте после проверки блока для просмотра журналов."
HELP_LOOKUP_COMMAND: "Расширенный поиск данных блока."
HELP_NO_INFO: "Информация о команде \"{0}\" не найдена."
HELP_PARAMETER: "Пожалуйста, просмотрите \"{0}\" для получения подробной информации о параметрах."
HELP_PARAMS_1: "Выполните {поиск|откат|восстановление}."
HELP_PARAMS_2: "Укажите пользователей для {поиска|отката|восстановления}."
HELP_PARAMS_3: "Укажите время для {поиска|отката|восстановления}."
HELP_PARAMS_4: "Укажите область радиуса, чтобы ограничить {поиск|откат|восстановление}."
HELP_PARAMS_5: "Ограничьте {поиск|откат|восстановление} определенным действием."
HELP_PARAMS_6: "Включите определенные блоки/объекты в {поиск|откат|восстановление}."
HELP_PARAMS_7: "Исключите блоки/пользователей из {поиска|отката|восстановления}."
HELP_INSPECT_1: "С включённым инспектором вы можете делать следующее:"
HELP_INSPECT_2: "Нажмите ЛКМ по блоку, чтобы узнать, кто его установил."
HELP_INSPECT_3: "Нажмите ПКМ по блоку, чтобы узнать, какой соседний блок был сломан."
HELP_INSPECT_4: "Установите блок, чтобы узнать, какой блок был сломан в этом месте."
HELP_INSPECT_5: "Установите блок в жидкость (и т.п.), чтобы узнать, кто её разлил."
HELP_INSPECT_6: "Нажмите ПКМ по двери, сундуку и т.д., чтобы узнать, кто последний их использовал."
HELP_INSPECT_7: "Подсказка: используйте «/co i» для быстрого доступа."
HELP_INSPECT_COMMAND: "Включает или выключает инспектор блоков."
HELP_LIST: "Отобразить список всех команд."
HELP_LOOKUP_1: "Быстрый доступ к команде."
HELP_LOOKUP_2: "Используйте после проверки блока для просмотра логов."
HELP_LOOKUP_COMMAND: "Расширенный поиск данных блоков."
HELP_NO_INFO: "Информация о команде «{0}» не найдена."
HELP_PARAMETER: "Пожалуйста, обратитесь к «{0}» для получения подробной информации о параметрах."
HELP_PARAMS_1: "Выполнить {поиск|откат|восстановление}."
HELP_PARAMS_2: "Указать пользователей для {поиска|отката|восстановления}."
HELP_PARAMS_3: "Указать промежуток времени для {поиска|отката|восстановления}."
HELP_PARAMS_4: "Указать радиус области, чтобы ограничить {поиск|откат|восстановление}."
HELP_PARAMS_5: "Ограничить {поиск|откат|восстановление} определённым действием."
HELP_PARAMS_6: "Включить определённые блоки/сущности в {поиск|откат|восстановление}."
HELP_PARAMS_7: "Исключить блоки/пользователей из {поиска|отката|восстановления}."
HELP_PURGE_1: "Удалить данные старше указанного времени."
HELP_PURGE_2: "Например, \"{0}\" удалит все данные старше одного месяца и сохранит данные только за последние 30 дней."
HELP_PURGE_COMMAND: "Удалит старые данные блока."
HELP_RADIUS_1: "Укажите радиус области."
HELP_RADIUS_2: "Например: [r:10] (Внесёт изменения только в пределах 10 блоков от вас)"
HELP_RELOAD_COMMAND: "Перезагружает файл конфигурации."
HELP_RESTORE_COMMAND: "Восстановит данные блока."
HELP_ROLLBACK_COMMAND: "Откатит данные блока."
HELP_PURGE_2: "Например, «{0}» удалит все данные старше одного месяца, оставив данные только за последние 30 дней."
HELP_PURGE_3: "Указать время для очистки."
HELP_PURGE_4: "Указать мир, чтобы ограничить очистку."
HELP_PURGE_5: "Включить определённые блоки в очистку."
HELP_PURGE_COMMAND: "Удаление старых данных блоков."
HELP_RADIUS_1: "Указать радиус области."
HELP_RADIUS_2: "Например: [r:10] (изменения только в радиусе 10 блоков от вас)"
HELP_RELOAD_COMMAND: "Перезагружает конфигурационный файл."
HELP_RESTORE_COMMAND: "Восстановление данных блоков."
HELP_ROLLBACK_COMMAND: "Откат данных блоков."
HELP_STATUS: "Просмотр статуса плагина и информации о версии."
HELP_STATUS_COMMAND: "Отображает статус плагина."
HELP_TELEPORT: "Телепорт в локацию."
HELP_TIME_1: "Укажите время поиска."
HELP_TELEPORT: "Телепортироваться к локации."
HELP_TIME_1: "Указать время для поиска."
HELP_TIME_2: "Например: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Укажите пользовател(я/ей) для поиска."
HELP_USER_1: "Указать пользователей для поиска."
HELP_USER_2: "Например: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" нельзя использовать с этим действием."
INSPECTOR_ERROR: "Режим инспектора уже {включён|выключен}."
INSPECTOR_TOGGLED: "Режим инспектора теперь {включён|выключен}."
INTEGRATION_ERROR: "Невозможно {инициализировать|отключить} регистрацию {0}."
INTEGRATION_SUCCESS: "{0} регистрация успешно {инициализирована|отключена}."
INTEGRATION_VERSION: "Обнаружена недопустимая версия {0}."
INTERACTIONS_HEADER: "Взаимодействие с игроками"
INCOMPATIBLE_ACTION: "Параметр «{0}» нельзя использовать с этим действием."
INSPECTOR_ERROR: "Инспектор уже {включён|выключен}."
INSPECTOR_TOGGLED: "Инспектор теперь {включён|выключен}."
INTEGRATION_ERROR: "Не удалось {инициализировать|отключить} логирование {0}."
INTEGRATION_SUCCESS: "Логирование {0} успешно {инициализировано|отключено}."
INTEGRATION_VERSION: "Обнаружена несовместимая версия {0}."
INTERACTIONS_HEADER: "Взаимодействие игроков"
INVALID_ACTION: "Это недопустимое действие."
INVALID_BRANCH_1: "Неверная версия плагина (ветка не задана)."
INVALID_BRANCH_2: "Чтобы продолжить, установите ветку проекта на \"development\"."
INVALID_BRANCH_3: "Запуск кода который находится в разработке может привести к повреждению данных."
INVALID_CONTAINER: "Пожалуйста, сначала проверьте действующий контейнер."
INVALID_BRANCH_1: "Некорректная версия плагина (ветка не задана)."
INVALID_BRANCH_2: "Чтобы продолжить, установите ветку проекта на «development»."
INVALID_BRANCH_3: "Запуск кода из ветки разработки может привести к повреждению данных."
INVALID_CONTAINER: "Пожалуйста, сначала осмотрите корректный контейнер."
INVALID_DONATION_KEY: "Недействительный ключ пожертвования."
INVALID_INCLUDE: "\"{0}\" неверное имя блока/сущности."
INVALID_INCLUDE_COMBO: "Это недопустимая комбинация блока/сущности."
INVALID_RADIUS: "Пожалуйста, введите допустимый радиус."
INVALID_SELECTION: "Выбор {0} не найден."
INVALID_USERNAME: "\"{0}\" неверное имя пользователя."
INVALID_WORLD: "Укажите действительный мир."
INVALID_INCLUDE: "«{0}» - некорректное имя блока/сущности."
INVALID_INCLUDE_COMBO: "Некорректная комбинация блока/сущности."
INVALID_ITEM_ID: "Пожалуйста, введите корректный ID предмета."
INVALID_PARAMETER: "Параметр «{0}» не поддерживается."
INVALID_RADIUS: "Пожалуйста, введите корректный радиус."
INVALID_SELECTION: "Выделение {0} не найдено."
INVALID_USERNAME: "«{0}» - некорректное имя пользователя."
INVALID_WORLD: "Пожалуйста, укажите существующий мир."
LATEST_VERSION: "Последняя версия: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Загрузить: {0}"
LINK_DOWNLOAD: "Скачать: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Названия блоков: {0}"
LINK_WIKI_ENTITY: "Названия сущностей: {0}"
LOGGING_ITEMS: "{0} предметы регистрируются. Пожалуйста, подождите..."
LOGGING_TIME_LIMIT: "Достигнуто ограничение по времени регистрации. Удаление данных и завершение работы."
LOGGING_ITEMS: "Осталось записать предметов: {0}. Пожалуйста, подождите..."
LOGGING_TIME_LIMIT: "Превышен лимит времени записи. Сброс данных и завершение работы."
LOOKUP_BLOCK: "{0} {установил|сломал} {1}."
LOOKUP_CONTAINER: "{0} {добавил|забрал} {1} {2}."
LOOKUP_HEADER: "{0} Результаты поиска"
LOOKUP_INTERACTION: "{0} {нажал|убил} {1}."
LOOKUP_ITEM: "{0} {подобрал|выкинул} {1} {2}."
LOOKUP_INTERACTION: "{0} {взаимодействовал с|убил} {1}."
LOOKUP_ITEM: "{0} {подобрал|выбросил} {1} {2}."
LOOKUP_LOGIN: "{0} {вошёл|вышел}."
LOOKUP_PAGE: "Страница {0}"
LOOKUP_ROWS_FOUND: "{0} {строка|строк} найдено."
LOOKUP_SEARCHING: "Ведётся поиск. Пожалуйста подождите..."
LOOKUP_STORAGE: "{0} {положил|забрал} {1} {2}."
LOOKUP_PROJECTILE: "{0} {бросил|выпустил} {1} {2}."
LOOKUP_ROWS_FOUND: "Найдено строк: {0}."
LOOKUP_SEARCHING: "Выполняется поиск. Пожалуйста, подождите..."
LOOKUP_STORAGE: "{0} {поместил|забрал} {1} {2}."
LOOKUP_TIME: "{0} назад"
LOOKUP_USERNAME: "{0} зарегистрирован как {1}."
LOOKUP_VIEW_PAGE: "Чтобы просмотреть страницу, введите \"{0}\"."
LOOKUP_USERNAME: "{0} вошёл как {1}."
MAXIMUM_RADIUS: "Максимальный радиус для {поиска|отката|восстановления} - {0}."
MISSING_ACTION_USER: "Чтобы использовать это действие, укажите пользователя."
MISSING_LOOKUP_TIME: "Пожалуйста, укажите количество времени, чтобы использовать {поиск|откат|восстановление}."
MISSING_LOOKUP_USER: "Укажите пользователя или {блок|радиус} для поиска."
MISSING_PARAMETERS: "Пожалуйста используйте \"{0}\"."
MISSING_ROLLBACK_RADIUS: "Вы не указали радиус {отката|восстановления}."
MISSING_LOOKUP_TIME: "Пожалуйста, укажите промежуток времени для {поиска|отката|восстановления}."
MISSING_LOOKUP_USER: "Пожалуйста, укажите пользователя или {блок|радиус} для поиска."
MISSING_PARAMETERS: "Пожалуйста, используйте «{0}»."
MISSING_ROLLBACK_RADIUS: "Вы не указали радиус для {отката|восстановления}."
MISSING_ROLLBACK_USER: "Вы не указали пользователя для {отката|восстановления}."
MYSQL_UNAVAILABLE: "Невозможно подключиться к серверу MySQL."
MYSQL_UNAVAILABLE: "Не удалось подключиться к серверу MySQL."
NETWORK_CONNECTION: "Подключение от {0} {успешно|не удалось}. Используется {1} {2}."
NETWORK_TEST: "Тестовые данные сети успешно отправлены."
NO_DATA: "Данные не найдены в {0}."
NO_DATA_LOCATION: "В этом месте {данные|транзакции|взаимодействия|сообщения} не обнаружены.."
NO_PERMISSION: "У вас нет прав чтобы сделать это."
NO_DATA_LOCATION: "В этом месте {данные|транзакции|взаимодействия|сообщения} не найдены."
NO_PERMISSION: "У вас недостаточно прав для выполнения этого действия."
NO_RESULTS: "Результаты не найдены."
NO_RESULTS_PAGE: "Для этой страницы {результатов|данных} не найдено."
NO_ROLLBACK: "{Ожидаемый|Предыдущий} откат/восстановление не найдены."
PATCH_INTERRUPTED: "Обновление прервано. Попробую снова при перезапуске."
NO_ROLLBACK: "Не найдено {ожидающих|предыдущих} откатов/восстановлений."
PATCH_INTERRUPTED: "Обновление прервано. Попытка будет повторена при перезапуске."
PATCH_OUTDATED_1: "Невозможно обновить базы данных старше {0}."
PATCH_OUTDATED_2: "Пожалуйста, обновите поддерживаемую версию CoreProtect."
PATCH_OUTDATED_2: "Пожалуйста, обновите CoreProtect до поддерживаемой версии."
PATCH_PROCESSING: "Обработка новых данных. Пожалуйста, подождите..."
PATCH_SKIP_UPDATE: "{Пропущена таблица|Пропущен индекс} {обновление|создание|удаление} в {0}."
PATCH_SKIP_UPDATE: "{Таблица|Индекс}: пропущено {обновление|создание|удаление} в {0}."
PATCH_STARTED: "Выполняется обновление {0}. Пожалуйста, подождите..."
PATCH_SUCCESS: "Успешно обновлен до {0}."
PATCH_SUCCESS: "Успешно обновлено до {0}."
PATCH_TABLE_COMPLETED: "Таблица {0} успешно обновлена."
PATCH_TABLE_STARTED: "Обновление таблицы {0}. Пожалуйста, подождите..."
PATCH_UPGRADING: "Выполняется обновление базы данных. Пожалуйста, подождите..."
PLEASE_SELECT: "Пожалуйта выберете: \"{0}\" или \"{1}\"."
PREVIEW_CANCELLED: "Предварительный просмотр отменен."
PREVIEW_CANCELLING: "Отмена предварительного просмотра..."
PREVIEW_CONTAINER: "Вы не можете предварительно просмотреть контейнерные транзакции."
PREVIEW_IN_GAME: "Предварительный просмотр откатов доступен только в игре."
PLEASE_SELECT: "Пожалуйста, выберите: «{0}» или «{1}»."
PREVIEW_CANCELLED: "Предпросмотр отменён."
PREVIEW_CANCELLING: "Отмена предпросмотра..."
PREVIEW_IN_GAME: "Предпросмотр откатов доступен только в игре."
PREVIEW_TRANSACTION: "{Контейнер|Инвентарь}: предпросмотр транзакции недоступен."
PRIMARY_THREAD_ERROR: "Этот метод API нельзя использовать в основном потоке."
PURGE_ABORTED: "Очистка не удалась. База данных может быть повреждена."
PURGE_ERROR: "Невозможно обработать данные {0}!"
PURGE_ERROR: "Не удалось обработать данные {0}!"
PURGE_FAILED: "Очистка не удалась. Пожалуйста, повторите попытку позже."
PURGE_IN_PROGRESS: "Выполняется чистка. Пожалуйста, повторите попытку позже."
PURGE_MINIMUM_TIME: "Удалить можно только данные старше {0} {дней|часов}."
PURGE_NOTICE_1: "Учтите, что это может занять некоторое время."
PURGE_NOTICE_2: "Не перезагружайте сервер до завершения."
PURGE_IN_PROGRESS: "Выполняется очистка. Пожалуйста, повторите попытку позже."
PURGE_MINIMUM_TIME: "Вы можете очистить только данные старше {0} {дней|часов}."
PURGE_NOTICE_1: "Пожалуйста, учтите, что это может занять некоторое время."
PURGE_NOTICE_2: "Не перезапускайте сервер до завершения процесса."
PURGE_OPTIMIZING: "Оптимизация базы данных. Пожалуйста, подождите..."
PURGE_PROCESSING: "Обработка данных {0}..."
PURGE_REPAIRING: "Попытка починки. Это может занять некоторое время..."
PURGE_ROWS: "{0} {строка|строки} данных очищены."
PURGE_STARTED: "Очистка данных началась \"{0}\"."
PURGE_SUCCESS: "Очистка данных прошла успешно."
RELOAD_STARTED: "Перезагрузка конфигурации - подождите."
PURGE_REPAIRING: "Попытка исправления. Это может занять некоторое время..."
PURGE_ROWS: "{0} {строка|строк} данных удалено."
PURGE_STARTED: "Начата очистка данных для «{0}»."
PURGE_SUCCESS: "Очистка данных успешно завершена."
RAM_STATS: "{0} ГБ / {1} ГБ ОЗУ"
RELOAD_STARTED: "Перезагрузка конфигурации - пожалуйста, подождите."
RELOAD_SUCCESS: "Конфигурация успешно перезагружена."
ROLLBACK_ABORTED: "Откат или восстановление прерваны."
ROLLBACK_CHUNKS_FOUND: "Найдено {0} {чанк|чанков} для изменения."
ROLLBACK_CHUNKS_MODIFIED: "Изменено {0}/{1} {чанк|чанков}."
ROLLBACK_COMPLETED: "{Откат|Восстановление|Предварительный просмотр} завершен для \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "{Исключен пользователь|Исключены пользователи}: \"{0}\"."
ROLLBACK_INCLUDE: "{Включён|Исключён} {блок|сущность|цель} {типа|типов}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Откат/восстановление уже выполняется."
ROLLBACK_LENGTH: "Заняло времени: {0} {секунду|секунд}."
ROLLBACK_MODIFIED: "{Изменено|Изменены} {0}."
ROLLBACK_COMPLETED: "{Откат|Восстановление|Предпросмотр} завершён для «{0}»."
ROLLBACK_EXCLUDED_USERS: "{Исключённый пользователь|Исключённые пользователи}: «{0}»."
ROLLBACK_INCLUDE: "{Включено|Исключено} ({блок|сущность|цель}) {типа|типов}: «{0}»."
ROLLBACK_IN_PROGRESS: "Откат или восстановление уже выполняется."
ROLLBACK_LENGTH: "Затрачено времени: {0} {секунда|секунд}."
ROLLBACK_MODIFIED: "{Изменено|Изменяется} {0}."
ROLLBACK_RADIUS: "Радиус: {0} {блок|блоков}."
ROLLBACK_SELECTION: "Радиус установлен \"{0}\"."
ROLLBACK_STARTED: "{Откат|Восстановление|Предварительный просмотр} начат \"{0}\"."
ROLLBACK_SELECTION: "Радиус установлен на «{0}»."
ROLLBACK_STARTED: "{Откат|Восстановление|Предпросмотр} запущен для «{0}»."
ROLLBACK_TIME: "Временной интервал: {0}."
ROLLBACK_WORLD_ACTION: "Только {мир|действие} \"{0}\"."
SIGN_HEADER: "Сообщения на табличке"
STATUS_CONSUMER: "Потребитель: {0} {предмет|предметы} в очереди."
STATUS_DATABASE: "База данных: Использует {0}."
STATUS_INTEGRATION: "{0}: Интеграция {включена|отключена}."
ROLLBACK_WORLD_ACTION: "Ограничено {миром|действием} «{0}»."
SIGN_HEADER: "Сообщения на табличках"
STATUS_AUTO_PURGE: "Очистка: автоудалено {0} {строка|строк} с момента перезапуска."
STATUS_CONSUMER: "Очередь потребителя: {0} {предмет|предметов}."
STATUS_DATABASE: "База данных: используется {0}."
STATUS_INTEGRATION: "{0}: интеграция {включена|отключена}."
STATUS_LICENSE: "Лицензия: {0}"
STATUS_SYSTEM: "Система: {0}"
STATUS_VERSION: "Версия: {0}"
TELEPORTED: "Телепортирование в {0}."
TELEPORTED_SAFETY: "Телепортирование вас в безопасное место."
TELEPORT_PLAYERS: "Команду телепортации могут использовать только игроки.."
TIME_DAYS: "{0} {дня|дней}"
TELEPORTED: "Телепортация в {0}."
TELEPORTED_SAFETY: "Телепортируем вас в безопасное место."
TELEPORT_PLAYERS: "Команду телепортации могут использовать только игроки."
TIME_DAYS: "{0} {день|дней}"
TIME_HOURS: "{0} {час|часов}"
TIME_MINUTES: "{0} {минута|минут}"
TIME_MONTHS: "{0} {месяц|месяцев}"
TIME_SECONDS: "{0} {секунда|секунд}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {неделя|недель}"
TIME_YEARS: "{0} {год|лет}"
UPDATE_ERROR: "Произошла ошибка при проверке обновлений."
UPDATE_HEADER: "{0} Обновление"
UPDATE_NOTICE: "Примечание: теперь доступен {0}."
UPDATE_NOTICE: "Примечание: теперь доступна версия {0}."
UPGRADE_IN_PROGRESS: "Выполняется обновление. Пожалуйста, повторите попытку позже."
USER_NOT_FOUND: "Пользователь \"{0}\" не найден."
USER_NOT_FOUND: "Пользователь «{0}» не найден."
USER_OFFLINE: "Пользователь «{0}» не в сети."
USING_MYSQL: "Для хранения данных используется MySQL."
USING_SQLITE: "Для хранения данных используется SQLite."
VALID_DONATION_KEY: "Действующий ключ пожертвования."
VERSION_INCOMPATIBLE: "{0} {1} не поддерживается."
VERSION_NOTICE: "Версия {0} уже доступна!"
VERSION_REQUIRED: "{0} {1} или выше требуется."
WORLD_NOT_FOUND: "Мир \"{0}\" не найден."
VERSION_REQUIRED: "Требуется {0} {1} или выше."
WORLD_NOT_FOUND: "Мир «{0}» не найден."
+202
View File
@@ -0,0 +1,202 @@
# CoreProtect Language File (tr)
ACTION_NOT_SUPPORTED: "Bu işlem desteklenmiyor."
AMOUNT_BLOCK: "{0} {blok|bloklar}"
AMOUNT_CHUNK: "{0} {yığın|parçalar}"
AMOUNT_ENTITY: "{0} {varlık|kuruluşlar}"
AMOUNT_ITEM: "{0} {öğe|öğeler}"
API_TEST: "API testi başarılı."
CACHE_ERROR: "UYARI: Doğrulama sırasında hata {0} önbellek."
CACHE_RELOAD: "Yeniden yüklemeye zorlama {haritalama|dünya} veritabanından önbellekler."
CHECK_CONFIG: "Lütfen config.yml'yi kontrol edin"
COMMAND_CONSOLE: "Lütfen komutu konsoldan çalıştırın."
COMMAND_NOT_FOUND: "Emretmek \"{0}\" bulunamadı."
COMMAND_THROTTLED: "Lütfen biraz bekleyin ve tekrar deneyin."
CONSUMER_ERROR: "Tüketici kuyruğu işleme zaten {duraklatıldı|devam ettirildi}."
CONSUMER_TOGGLED: "Tüketici kuyruğu işleme {duraklatıldı|devam ettirildi}."
CONTAINER_HEADER: "Konteyner İşlemleri"
DATABASE_BUSY: "Veritabanı meşgul. Lütfen daha sonra tekrar deneyiniz."
DATABASE_INDEX_ERROR: "Veritabanı dizinleri doğrulanamıyor."
DATABASE_LOCKED_1: "Veritabanı kilitlendi. 15 saniyeye kadar bekleniyor..."
DATABASE_LOCKED_2: "Veritabanı zaten kullanılıyor. Lütfen tekrar deneyin."
DATABASE_LOCKED_3: "Veritabanı kilitlemeyi devre dışı bırakmak için \"veritabanı kilidi: yanlış\" olarak ayarlayın."
DATABASE_LOCKED_4: "Veritabanı kilitlemeyi devre dışı bırakmak, verilerin bozulmasına neden olabilir."
DATABASE_UNREACHABLE: "Veritabanına erişilemiyor. Verileri atma ve kapatma."
DEVELOPMENT_BRANCH: "Geliştirme dalı algılandı, yama komut dosyaları atlandı."
DIRT_BLOCK: "Altına bir toprak bloğu yerleştirdi."
DISABLE_SUCCESS: "Başarı! Engelli {0}"
ENABLE_FAILED: "{0} başlayamadı."
ENABLE_SUCCESS: "{0} şimdi etkinleştirildi!"
ENJOY_COREPROTECT: "Eğlence {0}? Bize katılın Discord!"
FINISHING_CONVERSION: "Veri dönüştürme işlemi tamamlanıyor. Lütfen bekle..."
FINISHING_LOGGING: "Veri kaydının tamamlanması. Lütfen bekle..."
FIRST_VERSION: "İlk Veri Tabanı: {0}"
GLOBAL_LOOKUP: "Genel arama yapmak için bir yarıçap belirlemeyin."
GLOBAL_ROLLBACK: "Kullanmak \"{0}\" küresel yapmak {geri alma|geri yükleme}"
HELP_ACTION_1: "Aramayı bir eylemle sınırlayın."
HELP_ACTION_2: "Örnekler: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Ek bilgileri görüntüleyin."
HELP_EXCLUDE_1: "Blokları/kullanıcıları hariç tutun."
HELP_EXCLUDE_2: "Örnekler: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Yardım"
HELP_INCLUDE_1: "Blokları/varlıkları dahil et."
HELP_INCLUDE_2: "Örnekler: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Müfettiş etkinken şunları yapabilirsiniz:"
HELP_INSPECT_2: "Yerleşimi görmek için sol tıklayın."
HELP_INSPECT_3: "Bitişik kaldırmaları görmek için sağ tıklayın."
HELP_INSPECT_4: "Kaldırma işlemini görmek için bir blok yerleştirin."
HELP_INSPECT_5: "Yerleşimi görmek için sıvıya bir blok yerleştirin."
HELP_INSPECT_6: "Sandık işlemlerini görmek için sağ tıklayın."
HELP_INSPECT_7: "İpucu: Yalnızca \"/co i\" kullanabilirsiniz."
HELP_INSPECT_COMMAND: "Blok denetçisini değiştirir."
HELP_LIST: "Tüm komutların bir listesini görüntüler."
HELP_LOOKUP_1: "Komut kısayolu."
HELP_LOOKUP_2: "Ek sayfaları görüntüleyin."
HELP_LOOKUP_COMMAND: "Blok verilerini arayın."
HELP_NO_INFO: "Komut için bilgi \"{0}\" bulunamadı."
HELP_PARAMETER: "Bakınız \"{0}\" detaylı parametre bilgisi için."
HELP_PARAMS_1: "Gerçekleştir {arama|geri alma|geri yükleme}."
HELP_PARAMS_2: "Kullanıcıları belirtin {arama|geri alma|geri yükleme}."
HELP_PARAMS_3: "Zamanı belirtin {arama|geri alma|geri yükleme}."
HELP_PARAMS_4: "kısıtlamak {arama|geri alma|geri yükleme} bir yarıçapa."
HELP_PARAMS_5: "kısıtlamak {arama|geri alma|geri yükleme} bir eyleme."
HELP_PARAMS_6: "Blokları/varlıkları dahil et {arama|geri alma|geri yükleme}."
HELP_PARAMS_7: "Blokları/kullanıcıları {arama|geri alma|geri yükleme}."
HELP_PURGE_1: "Eski verileri kaldırın."
HELP_PURGE_2: "Örneğin, \"{0}\" bir aydan eski tüm verileri silecek ve yalnızca son 30 güne ait verileri tutacaktır."
HELP_PURGE_COMMAND: "Eski blok verilerini silin."
HELP_RADIUS_1: "Bir yarıçap alanı belirtin."
HELP_RADIUS_2: "Örnekler: [r:10] (Sadece 10 blok içinde değişiklik yapın)"
HELP_RELOAD_COMMAND: "Yapılandırma dosyasını yeniden yükler."
HELP_RESTORE_COMMAND: "Blok verilerini geri yükleyin."
HELP_ROLLBACK_COMMAND: "Geri alma blok verileri."
HELP_STATUS: "Eklenti durumunu görüntüleyin."
HELP_STATUS_COMMAND: "Eklenti durumunu görüntüler."
HELP_TELEPORT: "Işınlanma."
HELP_TIME_1: "Zamanı belirtin."
HELP_TIME_2: "Örnekler: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Kullanıcıları belirtin."
HELP_USER_2: "Örnekler: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" bu eylemle kullanılamaz."
INSPECTOR_ERROR: "Müfettiş zaten {etkin|devre dışı}."
INSPECTOR_TOGGLED: "Müfettiş şimdi {etkin|devre dışı}."
INTEGRATION_ERROR: "Olanaksız {başlat|devre dışı bırak} {0} Kerestecilik."
INTEGRATION_SUCCESS: "{0} başarıyla giriş yapmak {başlatıldı|devre dışı}."
INTEGRATION_VERSION: "Geçersiz {0} sürüm bulundu."
INTERACTIONS_HEADER: "Oyuncu Etkileşimleri"
INVALID_ACTION: "Bu geçerli bir eylem değil."
INVALID_BRANCH_1: "Geçersiz eklenti sürümü (dal ayarlanmadı)."
INVALID_BRANCH_2: "Devam etmek için proje dalını \"geliştirme\" olarak ayarlayın."
INVALID_BRANCH_3: "Geliştirme kodunu çalıştırmak, verilerin bozulmasına neden olabilir."
INVALID_CONTAINER: "Lütfen önce geçerli bir kapsayıcıyı inceleyin."
INVALID_DONATION_KEY: "Geçersiz bağış anahtarı."
INVALID_INCLUDE: "\"{0}\" geçersiz bir değerdir."
INVALID_INCLUDE_COMBO: "Bu geçersiz bir tür birleşimidir."
INVALID_RADIUS: "Lütfen geçerli bir yarıçap girin."
INVALID_SELECTION: "{0} seçim bulunamadı."
INVALID_USERNAME: "\"{0}\" geçersiz bir kullanıcı adıdır."
INVALID_WORLD: "Lütfen geçerli bir dünya belirtin."
LATEST_VERSION: "En son sürüm: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "İndirmek: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Blok İsimleri: {0}"
LINK_WIKI_ENTITY: "Varlık Adları: {0}"
LOGGING_ITEMS: "{0} günlüğe bırakılan öğeler. Lütfen bekle..."
LOGGING_TIME_LIMIT: "Günlüğe kaydetme zaman sınırına ulaşıldı. Verileri atma ve kapatma."
LOOKUP_BLOCK: "{0} {yerleştirilmiş|kırıldı} {1}."
LOOKUP_CONTAINER: "{0} {eklendi|kaldırıldı} {1} {2}."
LOOKUP_HEADER: "{0} Sonuçlar"
LOOKUP_INTERACTION: "{0} {tıklandı|öldürüldü} {1}."
LOOKUP_ITEM: "{0} {aldı|bıraktı} {1} {2}."
LOOKUP_LOGIN: "{0} günlüğe kaydedildi {içeri|dışarı}."
LOOKUP_PAGE: "Sayfa {0}"
LOOKUP_PROJECTILE: "{0} {fırlattı|vuruş} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {satır|satır} kurmak."
LOOKUP_SEARCHING: "Arama arama. Lütfen bekle..."
LOOKUP_STORAGE: "{0} {yatırıldı|geri çekildi} {1} {2}."
LOOKUP_TIME: "{0} evvel"
LOOKUP_USERNAME: "{0} olarak giriş yaptı {1}."
MAXIMUM_RADIUS: "Maksimum {arama|geri alma|geri yükleme} yarıçap {0}."
MISSING_ACTION_USER: "Bu eylemi kullanmak için lütfen bir kullanıcı belirtin."
MISSING_LOOKUP_TIME: "Lütfen zamanı belirtin {arama|geri alma|geri yükleme}."
MISSING_LOOKUP_USER: "Lütfen bir kullanıcı belirtin veya {blok|yarıçap}."
MISSING_PARAMETERS: "Lütfen kullan \"{0}\"."
MISSING_ROLLBACK_RADIUS: "bir belirtmemişsin {geri alma|geri yükleme} yarıçap."
MISSING_ROLLBACK_USER: "bir belirtmemişsin {geri alma|geri yükleme} kullanıcı."
MYSQL_UNAVAILABLE: "Bağlantı kurulamıyor MySQL sunucu."
NO_DATA: "şurada veri bulunamadı {0}."
NO_DATA_LOCATION: "Numara {veri|işlemler|etkileşimler|mesajlar} kurmak."
NO_PERMISSION: "Bunu yapmaya iznin yok."
NO_RESULTS: "Sonuç bulunamadı."
NO_RESULTS_PAGE: "Numara {sonuçlar|veri} o sayfa için bulundu."
NO_ROLLBACK: "Numara {beklemede|önceki} geri alma/geri yükleme bulundu."
PATCH_INTERRUPTED: "Yükseltme kesintiye uğradı. Yeniden başlatıldığında tekrar deneyecek."
PATCH_OUTDATED_1: "Daha eski veritabanları yükseltilemiyor {0}."
PATCH_OUTDATED_2: "Lütfen desteklenen bir sürümle yükseltin CoreProtect."
PATCH_PROCESSING: "Yeni veriler işleniyor. Lütfen bekle..."
PATCH_SKIP_UPDATE: "Atlama {tablo|dizin} {güncelleme|oluşturma|kaldırma} üzerinde {0}."
PATCH_STARTED: "performans {0} Yükselt. Lütfen bekle..."
PATCH_SUCCESS: "Başarıyla yükseltildi {0}."
PATCH_UPGRADING: "Veritabanı yükseltmesi devam ediyor. Lütfen bekle..."
PLEASE_SELECT: "Lütfen seçin: \"{0}\" veya \"{1}\"."
PREVIEW_CANCELLED: "Önizleme iptal edildi."
PREVIEW_CANCELLING: "Önizleme iptal ediliyor..."
PREVIEW_IN_GAME: "Geri almaları yalnızca oyun içinde önizleyebilirsiniz."
PREVIEW_TRANSACTION: "önizleme yapamazsınız {konteyner|envanter} işlemler."
PURGE_ABORTED: "Temizleme başarısız oldu. Veritabanı bozuk olabilir."
PURGE_ERROR: "işlenemiyor {0} veri!"
PURGE_FAILED: "Temizleme başarısız oldu. Lütfen daha sonra tekrar deneyiniz."
PURGE_IN_PROGRESS: "Temizleme işlemi devam ediyor. Lütfen daha sonra tekrar deneyiniz."
PURGE_MINIMUM_TIME: "Yalnızca şu tarihten daha eski verileri temizleyebilirsiniz: {0} {gün|saat}."
PURGE_NOTICE_1: "Lütfen bunun biraz zaman alabileceğini unutmayın."
PURGE_NOTICE_2: "Tamamlanana kadar sunucunuzu yeniden başlatmayın."
PURGE_OPTIMIZING: "Veritabanını optimize etme. Lütfen bekle..."
PURGE_PROCESSING: "İşleme {0} veri..."
PURGE_REPAIRING: "Tamir etmeye çalışmak. Bu biraz zaman alabilir..."
PURGE_ROWS: "{0} {satır|satır} silinen veriler."
PURGE_STARTED: "Veri temizleme başladı \"{0}\"."
PURGE_SUCCESS: "Veri temizleme başarılı."
RELOAD_STARTED: "Yapılandırma yeniden yükleniyor - lütfen bekleyin."
RELOAD_SUCCESS: "Yapılandırma başarıyla yeniden yüklendi."
ROLLBACK_ABORTED: "Geri alma veya geri yükleme iptal edildi."
ROLLBACK_CHUNKS_FOUND: "Kurmak {0} {yığın|parçalar} düzenlemek."
ROLLBACK_CHUNKS_MODIFIED: "Değiştirilmiş {0}/{1} {yığın|parçalar}."
ROLLBACK_COMPLETED: "{Geri Alma|Geri Yükleme|Önizleme} için tamamlandı \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Hariç tutulan {kullanıcı|kullanıcılar}: \"{0}\"."
ROLLBACK_INCLUDE: "{Dahil|Hariç} {blok|varlık|hedef} {tip|tür}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Halihazırda bir geri alma/geri yükleme işlemi devam ediyor."
ROLLBACK_LENGTH: "Geçen süre: {0} {saniye|saniye}."
ROLLBACK_MODIFIED: "{Değiştirildi|Değiştiriliyor} {0}."
ROLLBACK_RADIUS: "yarıçap: {0} {blok|bloklar}."
ROLLBACK_SELECTION: "Yarıçap \" olarak ayarlandı{0}\"."
ROLLBACK_STARTED: "{Geri Alma|Geri Yükleme|Önizleme} başladı \"{0}\"."
ROLLBACK_TIME: "Zaman aralığı: {0}."
ROLLBACK_WORLD_ACTION: "Sınırlı {dünya|eylem} \"{0}\"."
SIGN_HEADER: "Mesajları İmzala"
STATUS_CONSUMER: "Tüketici: {0} {öğe|öğeler} sırada."
STATUS_DATABASE: "Veri tabanı: kullanma {0}."
STATUS_INTEGRATION: "{0}: Entegrasyon {etkin|devre dışı}."
STATUS_LICENSE: "Lisans: {0}"
STATUS_VERSION: "Sürüm: {0}"
TELEPORTED: "Işınlandı {0}."
TELEPORTED_SAFETY: "Sizi emniyete ışınladım."
TELEPORT_PLAYERS: "Teleport komutu sadece oyuncular tarafından kullanılabilir."
TIME_DAYS: "{0} {gün|gün}"
TIME_HOURS: "{0} {saat|saat}"
TIME_MINUTES: "{0} {dakika|dakika}"
TIME_SECONDS: "{0} {saniye|saniye}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {hafta|hafta}"
UPDATE_ERROR: "Güncellemeler kontrol edilirken bir hata oluştu."
UPDATE_HEADER: "{0} Güncelleme"
UPDATE_NOTICE: "Fark etme: {0} şimdi kullanılabilir."
UPGRADE_IN_PROGRESS: "Yükseltme devam ediyor. Lütfen daha sonra tekrar deneyiniz."
USER_NOT_FOUND: "kullanıcı \"{0}\" bulunamadı."
USER_OFFLINE: "Kullanıcı \"{0}\" çevrimiçi değil."
USING_MYSQL: "kullanma MySQL veri depolama için."
USING_SQLITE: "kullanma SQLite veri depolama için."
VALID_DONATION_KEY: "Geçerli bağış anahtarı."
VERSION_NOTICE: "sürüm {0} şimdi kullanılabilir."
VERSION_REQUIRED: "{0} {1} veya daha yüksek gereklidir."
WORLD_NOT_FOUND: "Dünya \"{0}\" bulunamadı."
+207
View File
@@ -0,0 +1,207 @@
# CoreProtect v24.0 Language Cache (tt)
ACTION_NOT_SUPPORTED: "Бу гамәл хупланмый."
AMOUNT_BLOCK: "{0} {блок|блоклар}"
AMOUNT_CHUNK: "{0} {кисәк|кисәкчәләр}"
AMOUNT_ENTITY: "{0} {зат|затлар}"
AMOUNT_ITEM: "{0} {әйбер|әйберләр}"
API_TEST: "API тесты уңышлы үтте."
CACHE_ERROR: "КИСӘТҮ: Валидацияләү вакытында хата килеп чыкты {0} кэш."
CACHE_RELOAD: "Мәҗбүри яңадан йөкләү {картага төшерү|дөнья} мәгълүмат базасыннан алынган кэшлар."
CHECK_CONFIG: "Зинһар, config.yml файлын тикшерегез"
COMMAND_CONSOLE: "Зинһар, команданы консольдән башкарыгыз."
COMMAND_NOT_FOUND: "Команда \"{0}\" табылмады.\""
COMMAND_THROTTLED: "Зинһар, бераз көтегез һәм кабатлап карагыз."
CONSUMER_ERROR: "Кулланучылар чиратлары эшкәртелә инде {туктатылган|дәвам иттерелгән}."
CONSUMER_TOGGLED: "Кулланучылар чиратларын эшкәртү {туктатылган|дәвам иттерелгән}."
CONTAINER_HEADER: "Контейнер транзакцияләре"
CPU_CORES: "Үзәк процессор үзәкләре."
DATABASE_BUSY: "Мәгълүмат базасы мәшгуль. Зинһар, соңрак кабатлап карагыз."
DATABASE_INDEX_ERROR: "Мәгълүмат базасы индексларын тикшереп булмый."
DATABASE_LOCKED_1: "Мәгълүмат базасы бикләнгән. 15 секундка кадәр көтәм..."
DATABASE_LOCKED_2: "Мәгълүмат базасы инде кулланыла. Зинһар, кабатлап карагыз."
DATABASE_LOCKED_3: "Мәгълүмат базасын блоклауны сүндерү өчен, \"database-lock: false\" куегыз."
DATABASE_LOCKED_4: "Мәгълүмат базасын блоклауны сүндерү мәгълүматларның бозылуына китерергә мөмкин."
DATABASE_UNREACHABLE: "Мәгълүматлар базасына кереп булмый. Мәгълүматларны юкка чыгаралар һәм сүндерәләр."
DEVELOPMENT_BRANCH: "Үсеш тармагы ачыкланды, патч скриптларын үткәреп җибәрә."
DIRT_BLOCK: "Сезнең астыгызга вакытлыча куркынычсызлык блогы куйдылар."
DISABLE_SUCCESS: "Уңышлы! Инвалид {0}"
ENABLE_FAILED: "{0} башлый алмады."
ENABLE_SUCCESS: "{0} хәзер активлаштырылган!"
ENJOY_COREPROTECT: "Рәхәтләнегез {0}Безнең белән кушылыгыз Discord!"
FINISHING_CONVERSION: "Мәгълүматларны үзгәртү тәмамлана. Зинһар, көтегез..."
FINISHING_LOGGING: "Мәгълүматларны теркәү тәмамлана. Зинһар, көтегез..."
FIRST_VERSION: "Башлангыч мәгълүмат базасы: {0}"
GLOBAL_LOOKUP: "Глобаль эзләү өчен радиус күрсәтмәгез."
GLOBAL_ROLLBACK: "Куллану \"{0}\" глобаль эш башкарырга {кире кайтару|торгызу}"
HELP_ACTION_1: "Эзләүне гамәл белән чикләгез."
HELP_ACTION_2: "Мисаллар: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Өстәмә мәгълүматны күрсәтегез."
HELP_EXCLUDE_1: "Блокларны/кулланучыларны чыгарыгыз."
HELP_EXCLUDE_2: "Мисаллар: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Ярдәм"
HELP_INCLUDE_1: "Блокларны/объектларны кертегез."
HELP_INCLUDE_2: "Мисаллар: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Инспекторны кабызганда, сез:"
HELP_INSPECT_2: "Урынны карау өчен сул төймә белән басыгыз."
HELP_INSPECT_3: "Күршедәге бетерүләрне карау өчен уң төймә белән басыгыз."
HELP_INSPECT_4: "Алып ташлауны күрү өчен блок урнаштырыгыз."
HELP_INSPECT_5: "Урынын күрү өчен, сыеклыкка блок салыгыз."
HELP_INSPECT_6: "Күкрәк транзакцияләрен карау өчен уң төймә белән басыгыз."
HELP_INSPECT_7: "Киңәш: Сез бары тик \"/co i\" куллана аласыз."
HELP_INSPECT_COMMAND: "Блок инспекторын кабыза/яба."
HELP_LIST: "Барлык командалар исемлеген күрсәтә."
HELP_LOOKUP_1: "Команда кыска юлы"
HELP_LOOKUP_2: "Өстәмә битләрне карау."
HELP_LOOKUP_COMMAND: "Блок мәгълүматларын эзләү."
HELP_NO_INFO: "Команда өчен мәгълүмат \"{0}\" табылмады.\""
HELP_PARAMETER: "Зинһар, карагыз \"{0}\" параметрлар турында тулырак мәгълүмат алу өчен."
HELP_PARAMS_1: "Башкарыгыз {эзләү|кайтару|торгызу}."
HELP_PARAMS_2: "Кулланучыларны күрсәтегез {эзләү|кайтару|торгызу}."
HELP_PARAMS_3: "Вакытны күрсәтегез {эзләү|кайтару|торгызу}."
HELP_PARAMS_4: "Чикләү {эзләү|кайтару|торгызу} радиуска кадәр."
HELP_PARAMS_5: "Чикләү {эзләү|кайтару|торгызу} гамәленә."
HELP_PARAMS_6: "Блокларны/объектларны кертегез {эзләү|кайтару|торгызу}."
HELP_PARAMS_7: "Блокларны/кулланучыларны чыгарыгыз {эзләү|кайтару|торгызу}."
HELP_PURGE_1: "Иске мәгълүматларны бетерегез."
HELP_PURGE_2: "Мәсәлән, \"{0}\" бер айдан иске барлык мәгълүматларны бетерәчәк һәм соңгы 30 көнлек мәгълүматларны гына саклаячак.\""
HELP_PURGE_COMMAND: "Иске блок мәгълүматларын бетерегез."
HELP_RADIUS_1: "Радиус мәйданын күрсәтегез."
HELP_RADIUS_2: "Мисаллар: [r:10] (Үзгәрешләрне сездән 10 квартал эчендә генә кертегез)"
HELP_RELOAD_COMMAND: "Конфигурация файлын яңадан йөкли."
HELP_RESTORE_COMMAND: "Блок мәгълүматларын торгызу."
HELP_ROLLBACK_COMMAND: "Кире кайтару блогы мәгълүматлары."
HELP_STATUS: "Плагинның статусын карагыз."
HELP_STATUS_COMMAND: "Плагин статусын күрсәтә."
HELP_TELEPORT: "Телепорт."
HELP_TIME_1: "Вакытны күрсәтегез."
HELP_TIME_2: "Мисаллар: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Кулланучыларны күрсәтегез."
HELP_USER_2: "Мисаллар: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" бу гамәл белән кулланыла алмый."
INSPECTOR_ERROR: "Инспектор инде {активлаштырылган|сүндерелгән}."
INSPECTOR_TOGGLED: "Инспектор хәзер {активлаштырылган|сүндерелгән}."
INTEGRATION_ERROR: "Мөмкин түгел {башлангыч хәлгә китерү|сүндерү} {0} агач эшкәртү"
INTEGRATION_SUCCESS: "{0} уңышлы теркәлү {башлангыч хәлгә китерелгән|сүндерелгән}."
INTEGRATION_VERSION: "Яраксыз {0} версиясе табылды."
INTERACTIONS_HEADER: "Уенчыларның үзара бәйләнеше"
INVALID_ACTION: "Бу дөрес гамәл түгел."
INVALID_BRANCH_1: "Плагин версиясе дөрес түгел (тармак куелмаган)."
INVALID_BRANCH_2: "Дәвам итү өчен, проект тармагын \"эшләү\"гә куегыз."
INVALID_BRANCH_3: "Эшләү кодын эшләтеп җибәрү мәгълүматларның бозылуына китерергә мөмкин."
INVALID_CONTAINER: "Зинһар, башта яраклы контейнерны тикшерегез."
INVALID_DONATION_KEY: "Иганә ачкычы дөрес түгел."
INVALID_INCLUDE: "\"{0}\" - яраксыз кыйммәт."
INVALID_INCLUDE_COMBO: "Бу яраксыз тип комбинациясе."
INVALID_RADIUS: "Зинһар, дөрес радиус кертегез."
INVALID_SELECTION: "{0} сайлау табылмады."
INVALID_USERNAME: "\"{0}\" - яраксыз кулланучы исеме."
INVALID_WORLD: "Зинһар, дөрес дөнья күрсәтегез."
LATEST_VERSION: "Соңгы версия: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Йөкләү: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Блок исемнәре: {0}"
LINK_WIKI_ENTITY: "Объект исемнәре: {0}"
LOGGING_ITEMS: "{0} Элемтәгә кертергә калган әйберләр. Зинһар, көтегез..."
LOGGING_TIME_LIMIT: "Журналлаштыру вакыты чикләнде. Мәгълүматлар юкка чыгарыла һәм сүндерелә."
LOOKUP_BLOCK: "{0} {урнаштырылган|ватык} {1}."
LOOKUP_CONTAINER: "{0} {өстәлде|алып ташланды} {1} {2}."
LOOKUP_HEADER: "{0} Нәтиҗәләр"
LOOKUP_INTERACTION: "{0} {басылды|үтерелде} {1}."
LOOKUP_ITEM: "{0} {алды|ташлады} {1} {2}."
LOOKUP_LOGIN: "{0} теркәлгән {керү|чыгу}."
LOOKUP_PAGE: "Бит {0}"
LOOKUP_PROJECTILE: "{0} {ыргытты|атты} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {рәт|рәтләр} табылган."
LOOKUP_SEARCHING: "Эзләү эзләнә. Зинһар, көтегез..."
LOOKUP_STORAGE: "{0} {салынган|алынган} {1} {2}."
LOOKUP_TIME: "{0} элек"
LOOKUP_USERNAME: "{0} буларак керде {1}."
MAXIMUM_RADIUS: "Максималь {эзләү|кайтару|торгызу} радиус {0}."
MISSING_ACTION_USER: "Бу гамәлне куллану өчен, зинһар, кулланучыны күрсәтегез."
MISSING_LOOKUP_TIME: "Зинһар, вакытны күрсәтегез {эзләү|кайтару|торгызу}."
MISSING_LOOKUP_USER: "Зинһар, кулланучыны күрсәтегез яки {блок|радиус}."
MISSING_PARAMETERS: "Зинһар, \" кулланыгыз{0}\"."
MISSING_ROLLBACK_RADIUS: "Сез күрсәтмәдегез {кире кайтару|торгызу} радиус."
MISSING_ROLLBACK_USER: "Сез күрсәтмәдегез {кире кайтару|торгызу} кулланучы."
MYSQL_UNAVAILABLE: "Тоташып булмый MySQL сервер."
NETWORK_CONNECTION: "Тоташу {0} {уңышлы|уңышсыз}Куллану {1} {2}."
NETWORK_TEST: "Челтәр сынау мәгълүматлары уңышлы җибәрелде."
NO_DATA: "Бернинди мәгълүмат табылмады {0}."
NO_DATA_LOCATION: "Юк {мәгълүматлар|транзакцияләр|үзара бәйләнешләр|хәбәрләр} табылган."
NO_PERMISSION: "Сезнең моны эшләргә рөхсәтегез юк."
NO_RESULTS: "Нәтиҗәләр табылмады."
NO_RESULTS_PAGE: "Юк {нәтиҗәләр|мәгълүматлар} шул бит өчен табылды."
NO_ROLLBACK: "Юк {көтелгән|алдагы} кире кайтару/торгызу табылды."
PATCH_INTERRUPTED: "Яңарту өзелде. Кабат эшләтеп җибәргәндә тагын бер кат тырышып карыйм."
PATCH_OUTDATED_1: "Искерәк мәгълүмат базаларын яңартып булмый {0}."
PATCH_OUTDATED_2: "Зинһар, ярдәм ителә торган версия белән яңартыгыз CoreProtect."
PATCH_PROCESSING: "Яңа мәгълүматлар эшкәртелә. Зинһар, көтегез..."
PATCH_SKIP_UPDATE: "Сикереп бару {таблица|индекс} {яңарту|булдыру|бетерү} өстендә {0}."
PATCH_STARTED: "Башкару {0} яңарту. Зинһар, көтегез..."
PATCH_SUCCESS: "Уңышлы рәвештә яңартылды {0}."
PATCH_UPGRADING: "Мәгълүмат базасы яңартыла. Зинһар, көтегез..."
PLEASE_SELECT: "Зинһар, сайлагыз: \"{0}\" яки \"{1}\"."
PREVIEW_CANCELLED: "Алдан карау юкка чыгарылды."
PREVIEW_CANCELLING: "Алдан карауны юкка чыгару..."
PREVIEW_IN_GAME: "Сез кире кайтаруларны уен эчендә генә карый аласыз."
PREVIEW_TRANSACTION: "Алдан карап булмый {контейнер|инвентаризация} транзакцияләр."
PRIMARY_THREAD_ERROR: "Бу API ысулын төп җептә кулланып булмый."
PURGE_ABORTED: "Чистарту уңышсыз тәмамланды. Мәгълүмат базасы бозылган булырга мөмкин."
PURGE_ERROR: "Эшкәртеп булмый {0} мәгълүматлар!"
PURGE_FAILED: "Чистарту уңышсыз тәмамланды. Зинһар, соңрак кабатлап карагыз."
PURGE_IN_PROGRESS: "Чистарту бара. Зинһар, соңрак кабатлап карагыз."
PURGE_MINIMUM_TIME: "Сез бары тик искерәк мәгълүматларны гына бетерә аласыз {0} {көннәр|сәгатьләр}."
PURGE_NOTICE_1: "Зинһар, игътибар итегез, бу бераз вакыт алырга мөмкин."
PURGE_NOTICE_2: "Серверны тәмамланганчы яңадан эшләтеп җибәрмәгез."
PURGE_OPTIMIZING: "Мәгълүмат базасы оптимальләштерелә. Зинһар, көтегез..."
PURGE_PROCESSING: "Эшкәртү {0} мәгълүматлар..."
PURGE_REPAIRING: "Төзәтергә тырышам. Бу бераз вакыт алырга мөмкин..."
PURGE_ROWS: "{0} {рәт|рәтләр} бетерелгән мәгълүматлар саны."
PURGE_STARTED: "Мәгълүматларны чистарту башланды \"{0}\"."
PURGE_SUCCESS: "Мәгълүматларны чистарту уңышлы тәмамланды."
RAM_STATS: "{0}ГБ / {1}ГБ оператив хәтер"
RELOAD_STARTED: "Конфигурация яңадан йөкләнә - зинһар, көтегез."
RELOAD_SUCCESS: "Конфигурация уңышлы яңадан йөкләнде."
ROLLBACK_ABORTED: "Кире кайтару яки торгызу туктатылды."
ROLLBACK_CHUNKS_FOUND: "Табылды {0} {кисәк|кисәк} үзгәртү өчен."
ROLLBACK_CHUNKS_MODIFIED: "Үзгәртелгән {0}/{1} {кисәк|кисәкчәләр}."
ROLLBACK_COMPLETED: "{Кире кайтару|Төзетү|Алдан карау} өчен тәмамланды \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Чыгарылган {кулланучы|кулланучылар}: \"{0}\"."
ROLLBACK_INCLUDE: "{Кертелгән|Чыгарылган} {блок|объект|максат} {төр|төрләр}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Кире кайтару/торгызу процессы инде бара."
ROLLBACK_LENGTH: "Кирәкле вакыт: {0} {секунд|секунд}."
ROLLBACK_MODIFIED: "{Үзгәртелгән|Үзгәртелә} {0}."
ROLLBACK_RADIUS: "Радиус: {0} {блок|блоклар}."
ROLLBACK_SELECTION: "Радиус \"га куелды{0}\"."
ROLLBACK_STARTED: "{Кире кайтару|Төзетү|Алдан карау} башланды \"{0}\"."
ROLLBACK_TIME: "Вакыт диапазоны: {0}."
ROLLBACK_WORLD_ACTION: "Чикләнгән {дөнья|гамәл} \"{0}\"."
SIGN_HEADER: "Хәбәрләргә кул кую"
STATUS_CONSUMER: "Кулланучы: {0} {әйбер|әйберләр} чиратта"
STATUS_DATABASE: "Мәгълүмат базасы: Куллану {0}."
STATUS_INTEGRATION: "{0}: Интеграция {активлаштырылган|сүндерелгән}."
STATUS_LICENSE: "Лицензия: {0}"
STATUS_SYSTEM: "Система: {0}"
STATUS_VERSION: "Версия: {0}"
TELEPORTED: "Телепортацияләнгән {0}."
TELEPORTED_SAFETY: "Сезне куркынычсыз урынга телепортладылар."
TELEPORT_PLAYERS: "Телепорт командасын бары тик уенчылар гына куллана ала."
TIME_DAYS: "{0} {көн|көннәр}"
TIME_HOURS: "{0} {сәгать|сәгатьләр}"
TIME_MINUTES: "{0} {минут|минут}"
TIME_SECONDS: "{0} {секунд|секунд}"
TIME_WEEKS: "{0} {атна|атналар}"
UPDATE_ERROR: "Яңартуларны тикшергәндә хата килеп чыкты."
UPDATE_HEADER: "{0} Яңарту"
UPDATE_NOTICE: "Игътибар: {0} хәзер мөмкин."
UPGRADE_IN_PROGRESS: "Яңарту бара. Зинһар, соңрак кабатлап карагыз."
USER_NOT_FOUND: "Кулланучы \"{0}\" табылмады.\""
USER_OFFLINE: "Кулланучы \"{0}\" онлайн түгел.\""
USING_MYSQL: "Куллану MySQL мәгълүматларны саклау өчен."
USING_SQLITE: "Куллану SQLite мәгълүматларны саклау өчен."
VALID_DONATION_KEY: "Гамәлдәге иганә ачкычы."
VERSION_NOTICE: "Версия {0} хәзер мөмкин."
VERSION_REQUIRED: "{0} {1} яки аннан да югарырак таләп ителә."
WORLD_NOT_FOUND: "Дөнья \"{0}\" табылмады.\""
+202
View File
@@ -0,0 +1,202 @@
# CoreProtect Language File (uk)
ACTION_NOT_SUPPORTED: "Ця дія не підтримується."
AMOUNT_BLOCK: "{0} {блок|блоки}"
AMOUNT_CHUNK: "{0} {шматок|шматки}"
AMOUNT_ENTITY: "{0} {сутність|сутності}"
AMOUNT_ITEM: "{0} {пункт|предмети}"
API_TEST: "Тест API успішний."
CACHE_ERROR: "УВАГА: Помилка під час перевірки {0} кеш."
CACHE_RELOAD: "Примусове перезавантаження {картографування|світу} кеші з бази даних."
CHECK_CONFIG: "Перевірте config.yml"
COMMAND_CONSOLE: "Будь ласка, запустіть команду з консолі."
COMMAND_NOT_FOUND: "Команда \"{0}\" не знайдено."
COMMAND_THROTTLED: "Зачекайте хвилину і повторіть спробу."
CONSUMER_ERROR: "Вже обробка черги споживачів {призупинено|відновлено}."
CONSUMER_TOGGLED: "Обробка черги споживачів була {призупинено|відновлено}."
CONTAINER_HEADER: "Контейнерні операції"
DATABASE_BUSY: "База даних зайнята. Будь-ласка спробуйте пізніше."
DATABASE_INDEX_ERROR: "Неможливо перевірити індекси бази даних."
DATABASE_LOCKED_1: "База даних заблокована. Очікування до 15 секунд..."
DATABASE_LOCKED_2: "База даних вже використовується. Будь ласка спробуйте ще раз."
DATABASE_LOCKED_3: "Щоб вимкнути блокування бази даних, встановіть \"database-lock: false\"."
DATABASE_LOCKED_4: "Вимкнення блокування бази даних може призвести до пошкодження даних."
DATABASE_UNREACHABLE: "База даних недоступна. Видалення даних та завершення роботи."
DEVELOPMENT_BRANCH: "Виявлено гілку розробки, пропущено сценарії виправлення."
DIRT_BLOCK: "Поклали під себе брудний блок."
DISABLE_SUCCESS: "Успіху! Інвалід {0}"
ENABLE_FAILED: "{0} не вдалося почати."
ENABLE_SUCCESS: "{0} тепер увімкнено!"
ENJOY_COREPROTECT: "Насолоджуйтесь {0}? Приєднуйтесь до наших Discord!"
FINISHING_CONVERSION: "Завершення перетворення даних. Будь ласка, зачекайте..."
FINISHING_LOGGING: "Завершення реєстрації даних. Будь ласка, зачекайте..."
FIRST_VERSION: "Початкова БД: {0}"
GLOBAL_LOOKUP: "Не вказуйте радіус для глобального пошуку."
GLOBAL_ROLLBACK: "Використовувати \"{0}\" зробити глобальний {відкат|відновлення}"
HELP_ACTION_1: "Обмежити пошук до дії."
HELP_ACTION_2: "Приклади: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Відображення додаткової інформації."
HELP_EXCLUDE_1: "Виключити блоки/користувачів."
HELP_EXCLUDE_2: "Приклади: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Довідка"
HELP_INCLUDE_1: "Включити блоки/сутності."
HELP_INCLUDE_2: "Приклади: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Якщо інспектор увімкнено, ви можете:"
HELP_INSPECT_2: "Клацніть лівою кнопкою миші, щоб побачити місце розташування."
HELP_INSPECT_3: "Клацніть правою кнопкою миші, щоб побачити суміжні видалення."
HELP_INSPECT_4: "Поставте блок, щоб побачити видалення."
HELP_INSPECT_5: "Помістіть блок у рідину, щоб побачити розташування."
HELP_INSPECT_6: "Клацніть правою кнопкою миші, щоб побачити операції з суднами."
HELP_INSPECT_7: "Порада: Ви можете використовувати лише \"/co i\"."
HELP_INSPECT_COMMAND: "Перемикає інспектор блоків."
HELP_LIST: "Відображає список усіх команд."
HELP_LOOKUP_1: "Ярлик команди."
HELP_LOOKUP_2: "Переглянути додаткові сторінки."
HELP_LOOKUP_COMMAND: "Перегляд даних блоку."
HELP_NO_INFO: "Інформація для команди \"{0}\" не знайдено."
HELP_PARAMETER: "Будь ласка, подивіться \"{0}\" для отримання детальної інформації про параметри."
HELP_PARAMS_1: "Виконайте {пошук|відкат|відновлення}."
HELP_PARAMS_2: "Вкажіть користувачів для {пошук|відкат|відновлення}."
HELP_PARAMS_3: "Вкажіть час до {пошук|відкат|відновлення}."
HELP_PARAMS_4: "Обмежте {пошук|відкат|відновлення} до радіусу."
HELP_PARAMS_5: "Обмежте {пошук|відкат|відновлення} до дії."
HELP_PARAMS_6: "Включити блоки/сутності до {пошук|відкат|відновлення}."
HELP_PARAMS_7: "Виключити блокування/користувачів із {пошук|відкат|відновлення}."
HELP_PURGE_1: "Видаліть старі дані."
HELP_PURGE_2: "Наприклад, \"{0}\" видалить усі дані старше одного місяця та збереже дані лише за останні 30 днів."
HELP_PURGE_COMMAND: "Видалити дані старого блоку."
HELP_RADIUS_1: "Вкажіть зону радіуса."
HELP_RADIUS_2: "Приклади: [r:10] (Вносити зміни лише в межах 10 блоків від вас)"
HELP_RELOAD_COMMAND: "Перезавантажує файл конфігурації."
HELP_RESTORE_COMMAND: "Відновлення даних блоку."
HELP_ROLLBACK_COMMAND: "Дані блоку відкату."
HELP_STATUS: "Переглянути стан плагіна."
HELP_STATUS_COMMAND: "Відображає стан плагіна."
HELP_TELEPORT: "Телепорт."
HELP_TIME_1: "Вкажіть час."
HELP_TIME_2: "Приклади: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Вкажіть користувачів."
HELP_USER_2: "Приклади: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" не можна використовувати з цією дією."
INSPECTOR_ERROR: "Вже інспектор {включено|вимкнено}."
INSPECTOR_TOGGLED: "Зараз інспектор {включено|вимкнено}."
INTEGRATION_ERROR: "Вдалося {ініціалізувати|вимкнути} {0} лісозаготівлі."
INTEGRATION_SUCCESS: "{0} реєстрація успішно {ініціалізовано|відключено}."
INTEGRATION_VERSION: "Недійсний {0} версія знайдена."
INTERACTIONS_HEADER: "Взаємодії гравців"
INVALID_ACTION: "Це не дійсна дія."
INVALID_BRANCH_1: "Недійсна версія плагіна (гілка не встановлена)."
INVALID_BRANCH_2: "Щоб продовжити, встановіть гілку проекту на \"розвиток\"."
INVALID_BRANCH_3: "Запуск коду розробки може призвести до пошкодження даних."
INVALID_CONTAINER: "Спочатку перевірте дійсний контейнер."
INVALID_DONATION_KEY: "Недійсний ключ пожертви."
INVALID_INCLUDE: "\"{0}\" є недійсним значенням."
INVALID_INCLUDE_COMBO: "Це недійсна комбінація типів."
INVALID_RADIUS: "Введіть дійсний радіус."
INVALID_SELECTION: "{0} виділення не знайдено."
INVALID_USERNAME: "\"{0}\" є недійсним ім'ям користувача."
INVALID_WORLD: "Укажіть дійсний світ."
LATEST_VERSION: "Остання версія: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Завантажити: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Імена блоків: {0}"
LINK_WIKI_ENTITY: "Назви сутностей: {0}"
LOGGING_ITEMS: "{0} елементи, залишені для реєстрації. Будь ласка, зачекайте..."
LOGGING_TIME_LIMIT: "Досягнуто обмеження часу реєстрації. Видалення даних та завершення роботи."
LOOKUP_BLOCK: "{0} {розміщено|зламано} {1}."
LOOKUP_CONTAINER: "{0} {додано|видалено} {1} {2}."
LOOKUP_HEADER: "{0} Результати"
LOOKUP_INTERACTION: "{0} {натиснув|убитий} {1}."
LOOKUP_ITEM: "{0} {взяв|скинув} {1} {2}."
LOOKUP_LOGIN: "{0} зареєстрований {в|з ~ ~}."
LOOKUP_PAGE: "Сторінка {0}"
LOOKUP_PROJECTILE: "{0} {кинув|постріл} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {рядок|рядки} знайдено."
LOOKUP_SEARCHING: "Пошук під час пошуку. Будь ласка, зачекайте..."
LOOKUP_STORAGE: "{0} {здано на зберігання|вилучено} {1} {2}."
LOOKUP_TIME: "{0} тому"
LOOKUP_USERNAME: "{0} Ви увійшли як {1}."
MAXIMUM_RADIUS: "Максимум {пошук|відкат|відновлення} радіус дорівнює {0}."
MISSING_ACTION_USER: "Щоб використати цю дію, вкажіть користувача."
MISSING_LOOKUP_TIME: "Будь ласка, вкажіть час до {пошук|відкат|відновлення}."
MISSING_LOOKUP_USER: "Будь ласка, вкажіть користувача або {блок|радіус}."
MISSING_PARAMETERS: "Будь ласка, використовуйте \"{0}\"."
MISSING_ROLLBACK_RADIUS: "Ви не вказали a {відкат|відновлення} радіус."
MISSING_ROLLBACK_USER: "Ви не вказали a {відкат|відновлення} користувача."
MYSQL_UNAVAILABLE: "Не вдається підключитися до MySQL сервер."
NO_DATA: "Дані не знайдені за адресою {0}."
NO_DATA_LOCATION: "Ні {дані|транзакції|взаємодії|повідомлення} знайдено."
NO_PERMISSION: "У вас немає дозволу на це."
NO_RESULTS: "Нічого не знайдено."
NO_RESULTS_PAGE: "Ні {результати|дані} знайдено для цієї сторінки."
NO_ROLLBACK: "Ні {в очікуванні|попередній} відкат/відновлення знайдено."
PATCH_INTERRUPTED: "Оновлення перервано. Спробую ще раз при перезавантаженні."
PATCH_OUTDATED_1: "Не вдається оновити бази даних старші {0}."
PATCH_OUTDATED_2: "Оновіть підтримувану версію CoreProtect."
PATCH_PROCESSING: "Обробка нових даних. Будь ласка, зачекайте..."
PATCH_SKIP_UPDATE: "Пропуск {таблиця|індекс} {оновлення|створення|видалення} на {0}."
PATCH_STARTED: "Виконання {0} оновлення. Будь ласка, зачекайте..."
PATCH_SUCCESS: "Успішно оновлено до {0}."
PATCH_UPGRADING: "Триває оновлення бази даних. Будь ласка, зачекайте..."
PLEASE_SELECT: "Виберіть будь ласка: \"{0}\" або \"{1}\"."
PREVIEW_CANCELLED: "Попередній перегляд скасовано."
PREVIEW_CANCELLING: "Скасування попереднього перегляду..."
PREVIEW_IN_GAME: "Попередній перегляд можна лише в грі."
PREVIEW_TRANSACTION: "Ви не можете попередньо переглянути {тару|інвентар} трансакцій."
PURGE_ABORTED: "Помилка очищення. Можливо, база даних пошкоджена."
PURGE_ERROR: "Не вдається обробити {0} дані!"
PURGE_FAILED: "Помилка очищення. Будь-ласка спробуйте пізніше."
PURGE_IN_PROGRESS: "Триває чистка. Будь-ласка спробуйте пізніше."
PURGE_MINIMUM_TIME: "Очистити дані можна лише старше {0} {днів|годин}."
PURGE_NOTICE_1: "Зверніть увагу, що це може зайняти деякий час."
PURGE_NOTICE_2: "Не перезавантажуйте сервер до завершення."
PURGE_OPTIMIZING: "Оптимізація бази даних. Будь ласка, зачекайте..."
PURGE_PROCESSING: "Обробка {0} дані..."
PURGE_REPAIRING: "Спроба ремонту. Це може зайняти деякий час..."
PURGE_ROWS: "{0} {рядок|рядки} даних видалено."
PURGE_STARTED: "Очищення даних розпочато \"{0}\"."
PURGE_SUCCESS: "Очищення даних успішно."
RELOAD_STARTED: "Перезавантаження конфігурації - зачекайте."
RELOAD_SUCCESS: "Конфігурацію успішно перезавантажено."
ROLLBACK_ABORTED: "Відкат або відновлення припинено."
ROLLBACK_CHUNKS_FOUND: "Знайдено {0} {шматок|шматки} змінити."
ROLLBACK_CHUNKS_MODIFIED: "Змінено {0}/{1} {шматок|шматки}."
ROLLBACK_COMPLETED: "{Відкат|Відновлення|Попередній перегляд} завершено за \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Виключено {користувач|користувачі}: \"{0}\"."
ROLLBACK_INCLUDE: "{Включено|Виключено} {блок|сутність|ціль} {тип|типи}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Відкат/відновлення вже триває."
ROLLBACK_LENGTH: "Витрачений час: {0} {секунда|секунди}."
ROLLBACK_MODIFIED: "{Змінено|Змінюється} {0}."
ROLLBACK_RADIUS: "Радіус: {0} {блок|блоки}."
ROLLBACK_SELECTION: "Радіус встановлено на \"{0}\"."
ROLLBACK_STARTED: "{Відкат|Відновлення|Попередній перегляд} почав \"{0}\"."
ROLLBACK_TIME: "Часовий діапазон: {0}."
ROLLBACK_WORLD_ACTION: "Обмежено до {світ|дія} \"{0}\"."
SIGN_HEADER: "Підписуйте повідомлення"
STATUS_CONSUMER: "Споживач: {0} {пункт|предмети} в черзі."
STATUS_DATABASE: "База даних: Використання {0}."
STATUS_INTEGRATION: "{0}: Інтеграція {включено|вимкнено}."
STATUS_LICENSE: "Ліцензія: {0}"
STATUS_VERSION: "Версія: {0}"
TELEPORTED: "Телепортувався до {0}."
TELEPORTED_SAFETY: "Телепортував вас у безпечне місце."
TELEPORT_PLAYERS: "Команду телепорту можуть використовувати лише гравці."
TIME_DAYS: "{0} {день|дні}"
TIME_HOURS: "{0} {година|години}"
TIME_MINUTES: "{0} {хвилина|хвилини}"
TIME_SECONDS: "{0} {секунда|секунди}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {тиждень|тиждень}"
UPDATE_ERROR: "Під час перевірки оновлень сталася помилка."
UPDATE_HEADER: "{0} Оновлення"
UPDATE_NOTICE: "Примітка: {0} тепер доступний."
UPGRADE_IN_PROGRESS: "Оновлення триває. Будь-ласка спробуйте пізніше."
USER_NOT_FOUND: "Користувач \"{0}\" не знайдено."
USER_OFFLINE: "Користувач \"{0}\" не в мережі."
USING_MYSQL: "Використання MySQL для зберігання даних."
USING_SQLITE: "Використання SQLite для зберігання даних."
VALID_DONATION_KEY: "Дійсний ключ пожертви."
VERSION_NOTICE: "Версія {0} тепер доступний."
VERSION_REQUIRED: "{0} {1} або вище."
WORLD_NOT_FOUND: "Світ \"{0}\" не знайдено."
+202
View File
@@ -0,0 +1,202 @@
# CoreProtect Language File (vi)
ACTION_NOT_SUPPORTED: "Hành động đó không được hỗ trợ."
AMOUNT_BLOCK: "{0} {khối|khối}"
AMOUNT_CHUNK: "{0} {chunk|chunks}"
AMOUNT_ENTITY: "{0} {thực thể|thực thể}"
AMOUNT_ITEM: "{0} {item|item}"
API_TEST: "Kiểm tra API thành công."
CACHE_ERROR: "CẢNH BÁO: Lỗi khi xác thực {0} bộ nhớ đệm."
CACHE_RELOAD: "Buộc tải lại {lập bản đồ|thế giới} bộ nhớ đệm từ cơ sở dữ liệu."
CHECK_CONFIG: "Vui lòng kiểm tra config.yml"
COMMAND_CONSOLE: "Vui lòng chạy lệnh từ bảng điều khiển."
COMMAND_NOT_FOUND: "Yêu cầu \"{0}\" không tìm thấy."
COMMAND_THROTTLED: "Vui lòng đợi trong giây lát và thử lại."
CONSUMER_ERROR: "Đã xử lý hàng đợi của người tiêu dùng {bị tạm dừng|tiếp tục}."
CONSUMER_TOGGLED: "Xử lý hàng đợi của người tiêu dùng đã được {bị tạm dừng|tiếp tục}."
CONTAINER_HEADER: "Giao dịch vùng chứa"
DATABASE_BUSY: "Cơ sở dữ liệu bận rộn. Vui lòng thử lại sau."
DATABASE_INDEX_ERROR: "Không thể xác thực các chỉ mục cơ sở dữ liệu."
DATABASE_LOCKED_1: "Cơ sở dữ liệu bị khóa. Chờ tối đa 15 giây..."
DATABASE_LOCKED_2: "Cơ sở dữ liệu đã được sử dụng. Vui lòng thử lại."
DATABASE_LOCKED_3: "Để tắt khóa cơ sở dữ liệu, hãy đặt \"khóa cơ sở dữ liệu: false\"."
DATABASE_LOCKED_4: "Vô hiệu hóa khóa cơ sở dữ liệu có thể dẫn đến hỏng dữ liệu."
DATABASE_UNREACHABLE: "Cơ sở dữ liệu không thể truy cập được. Đang hủy bỏ dữ liệu và tắt."
DEVELOPMENT_BRANCH: "Đã phát hiện nhánh phát triển, bỏ qua các tập lệnh vá lỗi."
DIRT_BLOCK: "Đặt một khối bẩn dưới bạn."
DISABLE_SUCCESS: "Sự thành công! Tàn tật {0}"
ENABLE_FAILED: "{0} đã không thể bắt đầu."
ENABLE_SUCCESS: "{0} hiện đã được kích hoạt!"
ENJOY_COREPROTECT: "Vui thích {0}? Gia nhập với chúng tôi Discord!"
FINISHING_CONVERSION: "Kết thúc chuyển đổi dữ liệu. Vui lòng chờ..."
FINISHING_LOGGING: "Hoàn thành việc ghi dữ liệu. Vui lòng chờ..."
FIRST_VERSION: "DB ban đầu: {0}"
GLOBAL_LOOKUP: "Không chỉ định bán kính để thực hiện tra cứu toàn cục."
GLOBAL_ROLLBACK: "Sử dụng \"{0}\" để làm một toàn cầu {quay lại|khôi phục}"
HELP_ACTION_1: "Hạn chế tra cứu đối với một hành động."
HELP_ACTION_2: "Ví dụ: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "Hiển thị thông tin bổ sung."
HELP_EXCLUDE_1: "Loại trừ khối / người dùng."
HELP_EXCLUDE_2: "Ví dụ: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} Trợ giúp"
HELP_INCLUDE_1: "Bao gồm các khối / thực thể."
HELP_INCLUDE_2: "Ví dụ: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "Với trình kiểm tra được bật, bạn có thể:"
HELP_INSPECT_2: "Nhấp chuột trái để xem vị trí."
HELP_INSPECT_3: "Nhấp chuột phải để xem các lần xóa liền kề."
HELP_INSPECT_4: "Đặt một khối để xem việc loại bỏ."
HELP_INSPECT_5: "Đặt một khối vào chất lỏng để xem vị trí."
HELP_INSPECT_6: "Nhấp chuột phải để xem các giao dịch rương."
HELP_INSPECT_7: "Mẹo: Bạn chỉ có thể sử dụng \"/co i\"."
HELP_INSPECT_COMMAND: "Chuyển đổi trình kiểm tra khối."
HELP_LIST: "Hiển thị danh sách tất cả các lệnh."
HELP_LOOKUP_1: "Phím tắt lệnh."
HELP_LOOKUP_2: "Xem các trang bổ sung."
HELP_LOOKUP_COMMAND: "Dữ liệu khối tra cứu."
HELP_NO_INFO: "Thông tin cho lệnh \"{0}\" không tìm thấy."
HELP_PARAMETER: "Hãy xem \"{0}\" để biết thông tin chi tiết về thông số."
HELP_PARAMS_1: "Thực hiện {tra cứu|khôi phục|khôi phục}."
HELP_PARAMS_2: "Chỉ định người dùng {tra cứu|khôi phục|khôi phục}."
HELP_PARAMS_3: "Chỉ định thời gian để {tra cứu|khôi phục|khôi phục}."
HELP_PARAMS_4: "Hạn chế {tra cứu|khôi phục|khôi phục} đến một bán kính."
HELP_PARAMS_5: "Hạn chế {tra cứu|khôi phục|khôi phục} cho một hành động."
HELP_PARAMS_6: "Bao gồm các khối / thực thể trong {tra cứu|khôi phục|khôi phục}."
HELP_PARAMS_7: "Loại trừ các khối / người dùng khỏi {tra cứu|khôi phục|khôi phục}."
HELP_PURGE_1: "Xóa dữ liệu cũ."
HELP_PURGE_2: "Ví dụ, \"{0}\" sẽ xóa tất cả dữ liệu cũ hơn một tháng và chỉ giữ lại dữ liệu trong 30 ngày qua."
HELP_PURGE_COMMAND: "Xóa dữ liệu khối cũ."
HELP_RADIUS_1: "Chỉ định một khu vực bán kính."
HELP_RADIUS_2: "Ví dụ: [r:10] (Chỉ thực hiện các thay đổi trong vòng 10 khối nhà bạn)"
HELP_RELOAD_COMMAND: "Tải lại tệp cấu hình."
HELP_RESTORE_COMMAND: "Khôi phục dữ liệu khối."
HELP_ROLLBACK_COMMAND: "Dữ liệu khối khôi phục."
HELP_STATUS: "Xem trạng thái plugin."
HELP_STATUS_COMMAND: "Hiển thị trạng thái plugin."
HELP_TELEPORT: "Dịch chuyển."
HELP_TIME_1: "Chỉ định thời gian."
HELP_TIME_2: "Ví dụ: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "Chỉ định người dùng."
HELP_USER_2: "Ví dụ: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" không thể được sử dụng với hành động đó."
INSPECTOR_ERROR: "Thanh tra đã {đã bật|đã tắt}."
INSPECTOR_TOGGLED: "Thanh tra ngay {đã bật|đã tắt}."
INTEGRATION_ERROR: "Không có khả năng {khởi tạo|vô hiệu hóa} {0} khai thác gỗ."
INTEGRATION_SUCCESS: "{0} ghi nhật ký thành công {khởi tạo|bị vô hiệu hóa}."
INTEGRATION_VERSION: "Không hợp lệ {0} phiên bản được tìm thấy."
INTERACTIONS_HEADER: "Tương tác của người chơi"
INVALID_ACTION: "Đó không phải là một hành động hợp lệ."
INVALID_BRANCH_1: "Phiên bản plugin không hợp lệ (chưa đặt nhánh)."
INVALID_BRANCH_2: "Để tiếp tục, hãy đặt nhánh dự án thành \"phát triển\"."
INVALID_BRANCH_3: "Chạy mã phát triển có thể dẫn đến hỏng dữ liệu."
INVALID_CONTAINER: "Vui lòng kiểm tra một thùng chứa hợp lệ trước."
INVALID_DONATION_KEY: "Khóa quyên góp không hợp lệ."
INVALID_INCLUDE: "\"{0}\" là một giá trị không hợp lệ."
INVALID_INCLUDE_COMBO: "Đó là sự kết hợp kiểu không hợp lệ."
INVALID_RADIUS: "Vui lòng nhập bán kính hợp lệ."
INVALID_SELECTION: "{0} lựa chọn không được tìm thấy."
INVALID_USERNAME: "\"{0}\" là tên người dùng không hợp lệ."
INVALID_WORLD: "Vui lòng chỉ định một thế giới hợp lệ."
LATEST_VERSION: "Phiên bản mới nhất: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "Tải xuống: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "Tên khối: {0}"
LINK_WIKI_ENTITY: "Tên thực thể: {0}"
LOGGING_ITEMS: "{0} mục còn lại để ghi. Vui lòng chờ..."
LOGGING_TIME_LIMIT: "Đã đạt đến giới hạn thời gian ghi nhật ký. Đang hủy bỏ dữ liệu và tắt."
LOOKUP_BLOCK: "{0} {đặt|đã phá vỡ} {1}."
LOOKUP_CONTAINER: "{0} {đã thêm|đã loại bỏ} {1} {2}."
LOOKUP_HEADER: "{0} Kết quả"
LOOKUP_INTERACTION: "{0} {đã nhấp vào|giết} {1}."
LOOKUP_ITEM: "{0} {nhặt lên|đánh rơi} {1} {2}."
LOOKUP_LOGIN: "{0} ghi nhật ký {vào|ra}."
LOOKUP_PAGE: "Trang {0}"
LOOKUP_PROJECTILE: "{0} {ném|bắn} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {hàng|hàng} tìm."
LOOKUP_SEARCHING: "Tra cứu tìm kiếm. Vui lòng chờ..."
LOOKUP_STORAGE: "{0} {ký gửi|rút tiền} {1} {2}."
LOOKUP_TIME: "{0} trước kia"
LOOKUP_USERNAME: "{0} đăng nhập với tư cách {1}."
MAXIMUM_RADIUS: "Giá trị lớn nhất {tra cứu|khôi phục|khôi phục} bán kính là {0}."
MISSING_ACTION_USER: "Để sử dụng hành động đó, vui lòng chỉ định người dùng."
MISSING_LOOKUP_TIME: "Vui lòng xác định thời gian để {tra cứu|khôi phục|khôi phục}."
MISSING_LOOKUP_USER: "Vui lòng chỉ định người dùng hoặc {khối|bán kính}."
MISSING_PARAMETERS: "Vui lòng sử dụng \"{0}\"."
MISSING_ROLLBACK_RADIUS: "Bạn đã không chỉ định một {khôi phục lại|khôi phục} bán kính."
MISSING_ROLLBACK_USER: "Bạn đã không chỉ định một {khôi phục lại|khôi phục} người sử dụng."
MYSQL_UNAVAILABLE: "Không thể kết nối MySQL người phục vụ."
NO_DATA: "Không tìm thấy dữ liệu tại {0}."
NO_DATA_LOCATION: "Không {dữ liệu|giao dịch|tương tác|tin nhắn} tìm."
NO_PERMISSION: "Bạn không có quyền để làm điều đó."
NO_RESULTS: "Không tìm thấy kết quả nào."
NO_RESULTS_PAGE: "Không {kết quả|dữ liệu} được tìm thấy cho trang đó."
NO_ROLLBACK: "Không {đang chờ xử lý|trước đó} khôi phục / khôi phục được tìm thấy."
PATCH_INTERRUPTED: "Nâng cấp bị gián đoạn. Sẽ thử lại khi khởi động lại."
PATCH_OUTDATED_1: "Không thể nâng cấp cơ sở dữ liệu cũ hơn {0}."
PATCH_OUTDATED_2: "Vui lòng nâng cấp bằng phiên bản được hỗ trợ của CoreProtect."
PATCH_PROCESSING: "Xử lý dữ liệu mới. Vui lòng chờ..."
PATCH_SKIP_UPDATE: "Bỏ qua {bảng|chỉ mục} {cập nhật|tạo|loại bỏ} trên {0}."
PATCH_STARTED: "Biểu diễn {0} nâng cấp. Vui lòng chờ..."
PATCH_SUCCESS: "Đã nâng cấp thành công lên {0}."
PATCH_UPGRADING: "Đang nâng cấp cơ sở dữ liệu. Vui lòng chờ..."
PLEASE_SELECT: "Xin hãy lựa chọn: \"{0}\" hoặc \"{1}\"."
PREVIEW_CANCELLED: "Đã hủy bản xem trước."
PREVIEW_CANCELLING: "Đang hủy bản xem trước..."
PREVIEW_IN_GAME: "Bạn chỉ có thể xem trước các lần quay lại trong trò chơi."
PREVIEW_TRANSACTION: "Bạn không thể xem trước {container|hàng tồn kho} các giao dịch."
PURGE_ABORTED: "Thanh trừng không thành công. Cơ sở dữ liệu có thể bị hỏng."
PURGE_ERROR: "Không thể xử lý {0} dữ liệu!"
PURGE_FAILED: "Thanh trừng không thành công. Vui lòng thử lại sau."
PURGE_IN_PROGRESS: "Đang tiến hành thanh lọc. Vui lòng thử lại sau."
PURGE_MINIMUM_TIME: "Bạn chỉ có thể xóa dữ liệu cũ hơn {0} {ngày|giờ}."
PURGE_NOTICE_1: "Xin lưu ý rằng điều này có thể mất một thời gian."
PURGE_NOTICE_2: "Không khởi động lại máy chủ của bạn cho đến khi hoàn thành."
PURGE_OPTIMIZING: "Tối ưu hóa cơ sở dữ liệu. Vui lòng chờ..."
PURGE_PROCESSING: "Xử lý {0} dữ liệu..."
PURGE_REPAIRING: "Đang cố gắng sửa chữa. Điều này có thể mất một thời gian..."
PURGE_ROWS: "{0} {hàng|hàng} dữ liệu bị xóa."
PURGE_STARTED: "Quá trình thanh lọc dữ liệu bắt đầu vào \"{0}\"."
PURGE_SUCCESS: "Xóa dữ liệu thành công."
RELOAD_STARTED: "Đang tải lại cấu hình - vui lòng đợi."
RELOAD_SUCCESS: "Đã tải lại cấu hình thành công."
ROLLBACK_ABORTED: "Đã hủy bỏ khôi phục hoặc khôi phục."
ROLLBACK_CHUNKS_FOUND: "Tìm {0} {khối|khối} để sửa đổi."
ROLLBACK_CHUNKS_MODIFIED: "Đã sửa đổi {0}/{1} {chunk|chunks}."
ROLLBACK_COMPLETED: "{Quay lại|Khôi phục|Xem trước} hoàn thành cho \"{0}\"."
ROLLBACK_EXCLUDED_USERS: "Bị loại trừ {người dùng|người dùng}: \"{0}\"."
ROLLBACK_INCLUDE: "{Đã bao gồm|Đã loại trừ} {khối|thực thể|mục tiêu} {loại|loại}: \"{0}\"."
ROLLBACK_IN_PROGRESS: "Quá trình khôi phục / khôi phục đang được tiến hành."
ROLLBACK_LENGTH: "Mất thời gian: {0} {giây|giây}."
ROLLBACK_MODIFIED: "{Đã sửa đổi|Đang sửa đổi} {0}."
ROLLBACK_RADIUS: "Bán kính: {0} {khối|khối}."
ROLLBACK_SELECTION: "Bán kính được đặt thành \"{0}\"."
ROLLBACK_STARTED: "{Quay lại|Khôi phục|Xem trước} Bắt đầu vào \"{0}\"."
ROLLBACK_TIME: "Phạm vi thời gian: {0}."
ROLLBACK_WORLD_ACTION: "Bị hạn chế đối với {thế giới|hành động} \"{0}\"."
SIGN_HEADER: "Ký tin nhắn"
STATUS_CONSUMER: "Khách hàng: {0} {mặt hàng|mặt hàng} đợi."
STATUS_DATABASE: "Cơ sở dữ liệu: Sử dụng {0}."
STATUS_INTEGRATION: "{0}: Hội nhập {đã bật|đã tắt}."
STATUS_LICENSE: "Giấy phép: {0}"
STATUS_VERSION: "Phiên bản: {0}"
TELEPORTED: "Dịch chuyển đến {0}."
TELEPORTED_SAFETY: "Dịch chuyển bạn đến nơi an toàn."
TELEPORT_PLAYERS: "Lệnh dịch chuyển chỉ có thể được sử dụng bởi người chơi."
TIME_DAYS: "{0} {ngày|ngày}"
TIME_HOURS: "{0} {giờ|giờ}"
TIME_MINUTES: "{0} {phút|phút}"
TIME_SECONDS: "{0} {giây|giây}"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {tuần|tuần}"
UPDATE_ERROR: "Đã xảy ra lỗi khi kiểm tra các bản cập nhật."
UPDATE_HEADER: "{0} Cập nhật"
UPDATE_NOTICE: "Lưu ý: {0} hiện đã có sẵn."
UPGRADE_IN_PROGRESS: "Đang nâng cấp. Vui lòng thử lại sau."
USER_NOT_FOUND: "Người sử dụng \"{0}\" không tìm thấy."
USER_OFFLINE: "Người dùng \"{0}\" không trực tuyến."
USING_MYSQL: "Sử dụng MySQL để lưu trữ dữ liệu."
USING_SQLITE: "Sử dụng SQLite để lưu trữ dữ liệu."
VALID_DONATION_KEY: "Chìa khóa quyên góp hợp lệ."
VERSION_NOTICE: "Phiên bản {0} hiện đã có sẵn."
VERSION_REQUIRED: "{0} {1} hoặc cao hơn là bắt buộc."
WORLD_NOT_FOUND: "Thế giới \"{0}\" không tìm thấy."
+158 -143
View File
@@ -1,198 +1,213 @@
# CoreProtect Language File (zh-CN)
# Origin: Intelli, Translator: DreamVoid
# Origin: Intelli, Translator: DreamVoid, StarWishsama, YuanYuanOwO, Halogly
ACTION_NOT_SUPPORTED: "不支持该操作。"
AMOUNT_BLOCK: "{0}个{方块|方块块}"
ACTION_NOT_SUPPORTED: "此命令不支持该操作。"
AMOUNT_BLOCK: "{0}个{方块|方块}"
AMOUNT_CHUNK: "{0}个{区块|区块}"
AMOUNT_ENTITY: "{0}个{实体|实体}"
AMOUNT_ITEM: "{0}个{物品|物品}"
API_TEST: "API 测试成功。"
CACHE_ERROR: "警告: 验证缓存 {0} 时出错。"
CACHE_RELOAD: "强制从数据库缓存重新加载{映射|世界} 。"
CHECK_CONFIG: "请检查 config.yml"
COMMAND_NOT_FOUND: "找不到命令 “{0}”"
COMMAND_THROTTLED: "请稍等片刻,然后重试。"
CONTAINER_HEADER: "容器物品变更"
DATABASE_BUSY: "数据库繁忙,请稍后重试。"
CACHE_ERROR: "警告:验证缓存 {0} 时出错。"
CACHE_RELOAD: "正在强制从数据库中重新加载{映射|世界}缓存。"
CHECK_CONFIG: "请检查 config.yml 文件"
COMMAND_CONSOLE: "请在控制台中运行此命令。"
COMMAND_NOT_FOUND: "命令 \"{0}\" 未找到。"
COMMAND_THROTTLED: "请稍后再试。"
CONSUMER_ERROR: "数据处理队列已经{暂停|恢复}。"
CONSUMER_TOGGLED: "数据处理队列已{暂停|恢复}。"
CONTAINER_HEADER: "容器物品变更记录"
CPU_CORES: "CPU 核心。"
DATABASE_BUSY: "数据库繁忙,请稍后再试。"
DATABASE_INDEX_ERROR: "无法验证数据库索引。"
DATABASE_LOCKED_1: "数据库已被锁定,请等待至少15秒后重试..."
DATABASE_LOCKED_2: "数据库已在使用,请再试一次。"
DATABASE_LOCKED_3: "要禁用数据库锁定,请在配置文件中设置“database-lock: false”。"
DATABASE_LOCKED_1: "数据库已锁定,请等待约15秒后再试..."
DATABASE_LOCKED_2: "数据库正在使用中,请稍后再试。"
DATABASE_LOCKED_3: "要禁用数据库锁定,请在配置文件中设置 \"database-lock: false\"。"
DATABASE_LOCKED_4: "禁用数据库锁定可能会导致数据损坏。"
DATABASE_UNREACHABLE: "数据库无法访问。正在丢弃数据并关闭。"
DEVELOPMENT_BRANCH: "检测到开发分支,已跳过补丁脚本。"
DIRT_BLOCK: "已在你下方放置了一个泥土。"
DISABLE_SUCCESS: "成功!已禁用 {0}"
ENABLE_FAILED: "{0} 无法启动。"
ENABLE_SUCCESS: "{0} 已成功启用!"
ENJOY_COREPROTECT: "对 {0} 感到满意吗?请加入我们 Discord!"
FINISHING_CONVERSION: "正在完成数据转换,请稍等..."
FINISHING_LOGGING: "正在完成数据记录,请稍等..."
FIRST_VERSION: "初始化数据库: {0}"
DATABASE_UNREACHABLE: "数据库连接失败,正在丢弃数据并关闭。"
DEVELOPMENT_BRANCH: "检测到正在使用开发分支的版本,跳过使用补丁脚本。"
DIRT_BLOCK: "已在你的下方放置了一个泥土。"
DISABLE_SUCCESS: "已成功禁用 {0}!"
DONATION_KEY_REQUIRED: "需要有效的捐赠密钥才能执行此命令。"
ENABLE_FAILED: "无法启用 {0}。"
ENABLE_SUCCESS: "已成功启用 {0}!"
ENJOY_COREPROTECT: "对 {0} 感到满意吗?请加入我们的 Discord 频道!"
FINISHING_CONVERSION: "正在完成数据转换,请稍候..."
FINISHING_LOGGING: "正在完成数据记录,请稍候..."
FIRST_VERSION: "初始化数据库:{0}"
GLOBAL_LOOKUP: "请勿在执行全局查询时指定半径。"
GLOBAL_ROLLBACK: "使用 ”{0}“做一个全局{回滚|恢复}"
HELP_ACTION_1: "将查询限制为一个动作。"
HELP_ACTION_2: "例子: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
GLOBAL_ROLLBACK: "使用 \"{0}\" 进行全局{回滚|恢复}操作"
HELP_ACTION_1: "将查询限制为一个操作。"
HELP_ACTION_2: "例子:[a:block],[a:+block],[a:-block],[a:click],[a:container],[a:inventory],[a:item],[a:kill],[a:chat],[a:command],[a:sign],[a:session],[a:username]"
HELP_COMMAND: "显示附加信息。"
HELP_EXCLUDE_1: "排除方块/玩家。"
HELP_EXCLUDE_2: "例子: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_EXCLUDE_2: "例子:[e:stone],[e:Notch],[e:stone,Notch]"
HELP_HEADER: "{0} 帮助"
HELP_INCLUDE_1: "包括方块/实体。"
HELP_INCLUDE_2: "例子: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "启用检查器后,你可以:"
HELP_INSPECT_2: "左键单击以查看谁放置了那个方块。"
HELP_INSPECT_3: "右键单击以查看哪个相邻方块被破坏。"
HELP_INCLUDE_2: "例子:[i:stone],[i:zombie],[i:stone,wood,bedrock]"
HELP_INSPECT_1: "启用检查器后,你可以:"
HELP_INSPECT_2: "左键单击以查看方块放置记录。"
HELP_INSPECT_3: "右键单击以查看相邻方块破坏记录。"
HELP_INSPECT_4: "放置一个方块以查看放置位置的破坏记录。"
HELP_INSPECT_5: "在液体中放置一个方块以查看谁放置的液体。"
HELP_INSPECT_6: "右键单击门、箱子等以查看交互情况。"
HELP_INSPECT_7: "提示: 您可以只使用“/co i”进行快速查询。"
HELP_INSPECT_COMMAND: "切换方块检查开启或关闭。"
HELP_LIST: "显示所有命令的列表。"
HELP_INSPECT_5: "在液体中放置一个方块以查看液体变更记录。"
HELP_INSPECT_6: "右键单击门、箱子等以查看交互记录。"
HELP_INSPECT_7: "提示:你可以使用 \"/co i\" 进行快速查询。"
HELP_INSPECT_COMMAND: "开启或关闭方块检查器。"
HELP_LIST: "列出所有命令。"
HELP_LOOKUP_1: "命令快捷方式。"
HELP_LOOKUP_2: "在查询记录后使用以查看日志"
HELP_LOOKUP_2: "检查方块后使用以查看日志。"
HELP_LOOKUP_COMMAND: "高级方块数据查询。"
HELP_NO_INFO: "命令的信息“{0}“ 未找到。"
HELP_PARAMETER: "请参阅 ”{0}\" 了解详细的参数信息。"
HELP_PARAMS_1: "执行{查找|回滚|恢复}."
HELP_PARAMS_2: "指定{查找|回滚|恢复}的玩家."
HELP_PARAMS_3: "指定{查找|回滚|恢复}的时间."
HELP_PARAMS_4: "限制{查找|回滚|恢复}到一个半径。"
HELP_PARAMS_5: "限制{查找|回滚|恢复}到一个动作。"
HELP_PARAMS_6: "包括方块/实体{查找|回滚|恢复}."
HELP_PARAMS_7: "排除方块/玩家{查找|回滚|恢复}."
HELP_PURGE_1: "删除旧数据。"
HELP_PURGE_2: "例如,”{0}” 将删除超过一个月的所有数据,只保留最近30天的数据。"
HELP_NO_INFO: "未找到命令 \"{0}\" 的信息。"
HELP_PARAMETER: "请参阅 \"{0}\" 了解详细的参数信息。"
HELP_PARAMS_1: "执行{查找|回滚|恢复}操作。"
HELP_PARAMS_2: "指定要{查找|回滚|恢复}的玩家。"
HELP_PARAMS_3: "指定要{查找|回滚|恢复}的时间。"
HELP_PARAMS_4: "指定一个半径区域以限制{查找|回滚|恢复}的范围。"
HELP_PARAMS_5: "将{查找|回滚|恢复}限制为一个操作。"
HELP_PARAMS_6: "{查找|回滚|恢复}时包括指定的方块/实体。"
HELP_PARAMS_7: "{查找|回滚|恢复}时排除指定的方块/玩家。"
HELP_PURGE_1: "删除早于指定时间的数据。"
HELP_PURGE_2: "例如,\"{0}\" 将删除超过一个月以上的所有数据,只保留最近30天的数据。"
HELP_PURGE_COMMAND: "删除旧的方块数据。"
HELP_RADIUS_1: "指定半径区域。"
HELP_RADIUS_2: "例子: [r:10] (只影响以你为中心的10个区块)"
HELP_RADIUS_2: "例子:[r:10](只会影响你周围十格方块的范围)"
HELP_RELOAD_COMMAND: "重新加载配置文件。"
HELP_RESTORE_COMMAND: "恢复方块数据。"
HELP_ROLLBACK_COMMAND: "回滚方块数据。"
HELP_STATUS: "查看插件状态。"
HELP_STATUS: "查看插件状态与版本信息。"
HELP_STATUS_COMMAND: "显示插件状态。"
HELP_TELEPORT: "传送到一个地点。"
HELP_TIME_1: "指定时间。"
HELP_TIME_2: "例子: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "指定玩家。"
HELP_USER_2: "例子: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "”{0}\" 不能与那个动作一起使用。"
INSPECTOR_ERROR: "检查已经{启用|禁用}."
INSPECTOR_TOGGLED: "检查已{启用|禁用}."
HELP_TIME_1: "指定要查找的时间。"
HELP_TIME_2: "例子:[t:2w,5d,7h,2m,10s],[t:5d2h],[t:2.50h]"
HELP_USER_1: "指定要查找的玩家。"
HELP_USER_2: "例子:[u:Notch],[u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" 不能与那个操作一起使用。"
INSPECTOR_ERROR: "检查器已经{启用|禁用}。"
INSPECTOR_TOGGLED: "检查器已{启用|禁用}。"
INTEGRATION_ERROR: "无法{初始化|禁用} {0} 的日志记录。"
INTEGRATION_SUCCESS: "{0} 成功 {初始化|禁用}."
INTEGRATION_VERSION: "找到无效的 {0} 版本。"
INTEGRATION_SUCCESS: "已成功{初始化|禁用} {0} 的日志记录。"
INTEGRATION_VERSION: "{0} 版本无效。"
INTERACTIONS_HEADER: "玩家交互"
INVALID_ACTION: "这不是有效的动作。"
INVALID_BRANCH_1: "插件版本无效(未设置分支)。"
INVALID_BRANCH_2: "要继续,请将项目分支设置为“开发”。"
INVALID_BRANCH_3: "运行开发代码可能会导致数据损坏。"
INVALID_CONTAINER: "请检查有效的容器。"
INVALID_ACTION: "这不是一个有效的操作。"
INVALID_BRANCH_1: "插件版本无效(尚未设置分支)。"
INVALID_BRANCH_2: "请将项目分支设置为 \"development\" 以继续。"
INVALID_BRANCH_3: "运行开发版本的插件可能会导致数据损坏。"
INVALID_CONTAINER: "请先检查一个有效的容器。"
INVALID_DONATION_KEY: "无效的捐赠密钥。"
INVALID_INCLUDE: "”{0}\" 是无效的方块/实体名称。"
INVALID_INCLUDE: "\"{0}\" 是无效的方块/实体名称。"
INVALID_INCLUDE_COMBO: "那是无效的方块/实体组合。"
INVALID_RADIUS: "请输入有效的半径。"
INVALID_SELECTION: "{0} 的选择未找到。"
INVALID_USERNAME: "玩家名”{0}”无效。"
INVALID_SELECTION: "未找到 {0} 的选择。"
INVALID_USERNAME: "玩家名 \"{0}\" 无效。"
INVALID_WORLD: "请指定一个有效的世界。"
LATEST_VERSION: "最新版本: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "下载:{0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "方块名称: {0}"
LINK_WIKI_ENTITY: "实体名称: {0}"
LOGGING_ITEMS: "{0} 物品等待记录,请稍等..."
LOGGING_TIME_LIMIT: "已达到记录时间限制。丢弃数据并关闭。"
LOOKUP_BLOCK: "{0} {放置|破坏} {1}."
LOOKUP_CONTAINER: "{0} {放入|取出} {1} {2}."
LATEST_VERSION: "最新版本:{0}"
LINK_DISCORD: "Discord:{0}"
LINK_DOWNLOAD: "下载:{0}"
LINK_PATREON: "Patreon:{0}"
LINK_WIKI_BLOCK: "方块名称:{0}"
LINK_WIKI_ENTITY: "实体名称:{0}"
LOGGING_ITEMS: "{0}个物品等待记录,请稍候..."
LOGGING_TIME_LIMIT: "已达到日志时间限制,正在丢弃剩余的数据并关闭。"
LOOKUP_BLOCK: "{0} {放置|破坏}了 {1}。"
LOOKUP_CONTAINER: "{0} {放入|取出}了 {1} {2}。"
LOOKUP_HEADER: "{0} 查询结果"
LOOKUP_INTERACTION: "{0} {点击|击杀} {1}."
LOOKUP_ITEM: "{0} {拾起|丢弃} {1} {2}."
LOOKUP_LOGIN: "{0} {登入|离开}."
LOOKUP_PAGE: "页 {0}"
LOOKUP_ROWS_FOUND: "已找到{0}{行|行}。"
LOOKUP_SEARCHING: "正在搜索,请稍等..."
LOOKUP_STORAGE: "{0} {放入|取出} {1} {2}."
LOOKUP_TIME: "{0} 前"
LOOKUP_USERNAME: "{0} 已登入为 {1}."
LOOKUP_VIEW_PAGE: "要查看一个页面,请输入“{0}”。"
MAXIMUM_RADIUS: "最大{查找|回滚|恢复}半径是 {0}."
LOOKUP_INTERACTION: "{0} {点击|击杀}了 {1}。"
LOOKUP_ITEM: "{0} {拾起|丢弃}了 {1} {2}。"
LOOKUP_LOGIN: "{0} 已{登录|离开}。"
LOOKUP_PAGE: "第 {0} 页"
LOOKUP_PROJECTILE: "{0} {扔出|发射}了 {1} {2}。"
LOOKUP_ROWS_FOUND: "{0} {行|行}已找到。"
LOOKUP_SEARCHING: "正在搜索,请稍候..."
LOOKUP_STORAGE: "{0} {放入|取出}了 {1} {2}。"
LOOKUP_TIME: "{0}前"
LOOKUP_USERNAME: "{0} 已登录为 {1}。"
MAXIMUM_RADIUS: "最大{查找|回滚|恢复}半径是 {0}。"
MISSING_ACTION_USER: "要使用该标识符,请指定玩家名。"
MISSING_LOOKUP_TIME: "请指定{查找|回滚|恢复}时间."
MISSING_LOOKUP_USER: "请指定查找玩家或查找{方块|半径}."
MISSING_PARAMETERS: "请使用 ”{0}”。"
MISSING_ROLLBACK_RADIUS: "你没有指定{回滚|恢复}半径。"
MISSING_ROLLBACK_USER: "你没有指定{回滚|恢复}的玩家操作。"
MISSING_LOOKUP_TIME: "请指定要{查找|回滚|恢复}的时间。"
MISSING_LOOKUP_USER: "请指定要查找的玩家或{方块|半径}。"
MISSING_PARAMETERS: "请使用 \"{0}\"。"
MISSING_ROLLBACK_RADIUS: "你没有指定{回滚|恢复}的半径。"
MISSING_ROLLBACK_USER: "你没有指定{回滚|恢复}的玩家。"
MYSQL_UNAVAILABLE: "无法连接到 MySQL 服务器。"
NO_DATA: "在{0}中没有找到数据"
NO_DATA_LOCATION: "没有{数据|存取|交互|消息}在此位置被找到。"
NETWORK_CONNECTION: "{0} 连接{成功|失败},正在使用 {1} {2}。"
NETWORK_TEST: "网络测试数据已成功发送。"
NO_DATA: "没有在 {0} 中找到数据。"
NO_DATA_LOCATION: "此位置找不到任何{数据|存取|交互|消息}记录。"
NO_PERMISSION: "你没有权限执行此操作。"
NO_RESULTS: "未找到结果。"
NO_RESULTS_PAGE: "没有在指定页面找到{结果|数据}。"
NO_ROLLBACK: "{等待的|上一个}回滚/恢复没有被找到。"
PATCH_INTERRUPTED: "升级中断,将在服务器重新启动时重试。"
PATCH_OUTDATED_1: "无法升级早于 {0} 的数据库."
PATCH_OUTDATED_2: "请升级到受支持的 CoreProtect 版本."
PATCH_PROCESSING: "正在处理新数据,请稍等..."
PATCH_SKIP_UPDATE: "在 {0} 跳过{表|索引}{更新|创建|删除}."
PATCH_STARTED: "准备{0}的升级,请稍等..."
PATCH_SUCCESS: "成功升级到 {0}."
PATCH_UPGRADING: "正在进行数据库升级,请稍等..."
PLEASE_SELECT: "请选择: ”{0}“或”{1}”。"
NO_ROLLBACK: "未找到{待处理的|上一个}回滚/恢复。"
PATCH_INTERRUPTED: "升级已中断,将在服务器重新启动时重试。"
PATCH_OUTDATED_1: "无法升级早于 {0} 的数据库。"
PATCH_OUTDATED_2: "请升级到受支持的 CoreProtect 版本。"
PATCH_PROCESSING: "正在处理新数据,请稍候..."
PATCH_SKIP_UPDATE: "正在跳过 {0} 的{表格|索引}{更新|创建|删除}操作。"
PATCH_STARTED: "正在进行 {0} 升级,请稍候..."
PATCH_SUCCESS: "成功升级到 {0}。"
PATCH_UPGRADING: "正在进行数据库升级,请稍候..."
PLEASE_SELECT: "请选择:\"{0}\" 或 \"{1}\"。"
PREVIEW_CANCELLED: "预览已取消。"
PREVIEW_CANCELLING: "正在取消预览..."
PREVIEW_CONTAINER: "您无法预览容器存取。"
PREVIEW_IN_GAME: "您只能在游戏中预览回滚操作。"
PREVIEW_IN_GAME: "你只能在游戏中预览回滚操作。"
PREVIEW_TRANSACTION: "你不能预览{容器|背包}物品交换。"
PRIMARY_THREAD_ERROR: "该 API 方法不能在主线程上使用。"
PURGE_ABORTED: "清除失败,数据库可能已损坏。"
PURGE_ERROR: "无法处理 {0} 数据!"
PURGE_FAILED: "数据清除失败,请稍后再试。"
PURGE_FAILED: "清除失败,请稍后再试。"
PURGE_IN_PROGRESS: "正在清除数据,请稍后再试。"
PURGE_MINIMUM_TIME: "您只能清除早于{0}{天|小时}的数据."
PURGE_MINIMUM_TIME: "你只能清除{0}{天|小时}之前的数据。"
PURGE_NOTICE_1: "请注意,这可能需要一些时间。"
PURGE_NOTICE_2: "在完成之前不要重新启动服务器。"
PURGE_OPTIMIZING: "正在优化数据库,请稍等..."
PURGE_PROCESSING: "处理{0}数据..."
PURGE_REPAIRING: "正在尝试修复,这可能需要一定的时间..."
PURGE_ROWS: "{0}{行|行}的数据已被删除。"
PURGE_STARTED: "数据清除开始于“{0}”。"
PURGE_SUCCESS: "数据清除成功。"
RELOAD_STARTED: "正在重新加载配置 - 请稍候。"
PURGE_NOTICE_2: "完成前请勿重新启动服务器。"
PURGE_OPTIMIZING: "正在优化数据库,请稍候..."
PURGE_PROCESSING: "正在处理 {0} 的数据..."
PURGE_REPAIRING: "正在尝试修复。这可能需要一些时间..."
PURGE_ROWS: "{0}{行|行}数据已删除。"
PURGE_STARTED: "数据清除开始于 \"{0}\"。"
PURGE_SUCCESS: "数据已清除。"
RAM_STATS: "{0}GB / {1}GB 内存"
RELOAD_STARTED: "正在重新加载配置文件 - 请稍候。"
RELOAD_SUCCESS: "配置文件已重新加载。"
ROLLBACK_ABORTED: "回滚或恢复已中止。"
ROLLBACK_CHUNKS_FOUND: "找到{0}{区块|区块}需要更改。"
ROLLBACK_CHUNKS_MODIFIED: "已修改{0}/{1}{区块|区块}."
ROLLBACK_COMPLETED: "{回滚|恢复|预览}在{0}内完成。"
ROLLBACK_EXCLUDED_USERS: "排除{玩家|玩家}: “{0}”。"
ROLLBACK_INCLUDE: "{包括|不包括}{方块|实体|目标}{类型|类型}: “{0}”。"
ROLLBACK_CHUNKS_FOUND: "找到{0}个待修改的{区块|区块}。"
ROLLBACK_CHUNKS_MODIFIED: "{0}/{1}个{区块|区块}已修改。"
ROLLBACK_COMPLETED: "{0} 的{回滚|恢复|预览}操作已完成。"
ROLLBACK_EXCLUDED_USERS: "已排除{玩家|玩家}:\"{0}\"。"
ROLLBACK_INCLUDE: "{包括|不包括}的{方块|实体|目标}{类型|类型}:\"{0}\"。"
ROLLBACK_IN_PROGRESS: "一个回滚/恢复操作已在进行。"
ROLLBACK_LENGTH: "花费时间: {0}{秒|秒}."
ROLLBACK_MODIFIED: "{已修改|正在修改}{0}."
ROLLBACK_RADIUS: "半径: {0}{方块|方块}."
ROLLBACK_SELECTION: "半径设置为“{0}”。"
ROLLBACK_STARTED: "{回滚|恢复|预览}开始于“{0}”。"
ROLLBACK_TIME: "时间范围: {0}."
ROLLBACK_WORLD_ACTION: "限制{世界|行动}为 ”{0}”。"
SIGN_HEADER: "签名消息"
STATUS_CONSUMER: "消费者: {0}个{项目|项目} 在队列中。" # TO DO: What is "CONSUMER" in game?
STATUS_DATABASE: "数据库: 正在使用 {0}."
STATUS_INTEGRATION: "{0}: 合并{启用|禁用}."
STATUS_LICENSE: "协议: {0}"
STATUS_VERSION: "版本: {0}"
TELEPORTED: "传送到 {0}."
TELEPORTED_SAFETY: "将你传送到安全地点。"
TELEPORT_PLAYERS: "传送命令只能作为玩家使用。"
ROLLBACK_LENGTH: "花费时间:{0}{秒|秒}。"
ROLLBACK_MODIFIED: "{已修改|正在修改} {0}。"
ROLLBACK_RADIUS: "半径:{0}个{方块|方块}。"
ROLLBACK_SELECTION: "半径设置为 {0}\"。"
ROLLBACK_STARTED: "{回滚|恢复|预览}开始于 \"{0}\"。"
ROLLBACK_TIME: "时间范围:{0}。"
ROLLBACK_WORLD_ACTION: "限制为{世界|操作} \"{0}\"。"
SIGN_HEADER: "告示牌消息"
STATUS_CONSUMER: "数据处理器:{0}个{数据|数据}待处理。"
STATUS_DATABASE: "数据库:正在使用 {0}。"
STATUS_INTEGRATION: "{0}:{启用|禁用}合并。"
STATUS_LICENSE: "协议:{0}"
STATUS_SYSTEM: "系统:{0}"
STATUS_VERSION: "版本:{0}"
TELEPORTED: "传送到 {0}。"
TELEPORTED_SAFETY: "将你传送到安全的位置。"
TELEPORT_PLAYERS: "只有玩家才能使用传送命令。"
TIME_DAYS: "{0}{天|天}"
TIME_HOURS: "{0}{小时|小时}"
TIME_MINUTES: "{0}{分钟|分钟}"
TIME_MONTHS: "{0} {月|月}"
TIME_SECONDS: "{0}{秒|秒}"
TIME_UNITS: "{分钟|小时|天}"
TIME_WEEKS: "{0}{周|周}"
TIME_YEARS: "{0} {年|年}"
UPDATE_ERROR: "检查更新时出错。"
UPDATE_HEADER: "{0} 更新"
UPDATE_NOTICE: "注意: {0} 现在可用。"
UPDATE_NOTICE: "注意:{0} 现在可用。"
UPGRADE_IN_PROGRESS: "正在进行升级,请稍后再试。"
USER_NOT_FOUND: "玩家“{0}”未找到。"
USER_NOT_FOUND: "玩家 \"{0}\" 未找到。"
USER_OFFLINE: "玩家 \"{0}\" 不在线。"
USING_MYSQL: "使用 MySQL 用于数据存储。"
USING_SQLITE: "使用 SQLite 用于数据存储。"
VALID_DONATION_KEY: "有效的捐赠密钥。"
VERSION_NOTICE: "版本{0}现在可用。"
VERSION_REQUIRED: "{0} {1} 或更高的要求。"
WORLD_NOT_FOUND: "世界“{0}”未找到。"
VERSION_INCOMPATIBLE: "{0} {1} 不受支持。"
VERSION_NOTICE: "版本 {0} 现在可用。"
VERSION_REQUIRED: "{0} 需要使用 {1} 或更高版本。"
WORLD_NOT_FOUND: "世界 \"{0}\" 未找到。"
+169 -157
View File
@@ -1,180 +1,190 @@
# CoreProtect Language File (zh-TW)
# Translators: whitebear13579, flandretw, ianiiaannn, hanshans135
ACTION_NOT_SUPPORTED: "此指令不支援該操作。"
AMOUNT_BLOCK: "{0} {方塊|方塊}"
AMOUNT_CHUNK: "{0} {區塊|區塊}"
AMOUNT_ENTITY: "{0} {實體|實體}"
AMOUNT_ITEM: "{0} {物品|物品}"
API_TEST: "API 測試成功!"
CACHE_ERROR: "警告!: 驗證快取 {0} 時出現錯誤。"
CACHE_RELOAD: "強制從資料庫中載入 {鏡像|世界} 快取。"
CHECK_CONFIG: "請檢查 config.yml"
COMMAND_NOT_FOUND: "未知的指令。"
COMMAND_THROTTLED: "你已受到速率限制,請稍後重試。"
CONTAINER_HEADER: "儲存容器已轉換。"
DATABASE_BUSY: "資料庫請求過多,請稍後在重試。"
DATABASE_INDEX_ERROR: "無法驗證的資料庫索引條件。"
DATABASE_LOCKED_1: "資料庫已被鎖定,請等待15秒後在重試。"
DATABASE_LOCKED_2: "資料庫正在使用中,請稍後在試一次。"
DATABASE_LOCKED_3: "若需要將資料庫鎖定的功能關閉,請設定 \"database-lock: false\"."
DATABASE_LOCKED_4: "關閉資料庫鎖定功能可能導致資料造成不可逆的損壞!"
DATABASE_UNREACHABLE: "資料庫無法存取,請刪除資料並關閉。"
DEVELOPMENT_BRANCH: "你使用的版本為開發的分支版本,已跳過補丁腳本。"
DIRT_BLOCK: "已在你的身下放置一個泥土塊。"
DISABLE_SUCCESS: "執行成功!已關閉 {0}"
ENABLE_FAILED: "{0} 無法成功啟動。"
ENABLE_SUCCESS: "{0} 已經成功開啟。"
ENJOY_COREPROTECT: "喜歡 {0}嗎? 加入我們的開發Discord!"
FINISHING_CONVERSION: "完成資料轉換中,請稍後..."
FINISHING_LOGGING: "完成數據紀錄中,請稍後..."
FIRST_VERSION: "初始化資料庫: {0}"
GLOBAL_LOOKUP: "請不要指定一個範圍來進行整個世界的搜尋。"
GLOBAL_ROLLBACK: "使用 \"{0}\" 來進行全面的 {回朔|恢復}"
HELP_ACTION_1: "將某個尋找的東西限制。"
ACTION_NOT_SUPPORTED: "此指令不支援該行為。"
AMOUNT_BLOCK: "{0} 格{方塊|方塊}"
AMOUNT_CHUNK: "{0} 個{區塊|區塊}"
AMOUNT_ENTITY: "{0} 個{實體|實體}"
AMOUNT_ITEM: "{0} 個{物品|物品}"
API_TEST: "API 測試成功。"
CACHE_ERROR: "警告: 驗證快取 {0} 時出現錯誤。"
CACHE_RELOAD: "正在強制從資料庫中重新載入{鏡像|世界}快取。"
CHECK_CONFIG: "請檢查 config.yml 檔案。"
COMMAND_CONSOLE: "請從控制台執行此指令。"
COMMAND_NOT_FOUND: "未知的指令「{0}」。"
COMMAND_THROTTLED: "請稍後再試。"
CONSUMER_ERROR: "消費者隊列處理已經{暫停|繼續}."
CONSUMER_TOGGLED: "消費者隊列處理已{暫停|繼續}."
CONTAINER_HEADER: "儲存容器變更紀錄。"
CPU_CORES: "CPU核心."
DATABASE_BUSY: "資料庫繁忙,請稍後再試。"
DATABASE_INDEX_ERROR: "無法驗證資料庫索引。"
DATABASE_LOCKED_1: "資料庫已被鎖定,請稍後 15 秒後再試……"
DATABASE_LOCKED_2: "資料庫正在使用,請稍後再試一次。"
DATABASE_LOCKED_3: "若要停用資料庫鎖定功能,請設定「database-lock: false」。"
DATABASE_LOCKED_4: "停用資料庫鎖定功能可能導致資料毀損。"
DATABASE_UNREACHABLE: "資料庫無法存取,正在拋棄資料並停用。"
DEVELOPMENT_BRANCH: "檢測到正在使用開發分支的版本,已跳過資料庫版本更新。"
DIRT_BLOCK: "已在你的下方放置了泥土。"
DISABLE_SUCCESS: "執行成功!已停用 {0}!"
ENABLE_FAILED: "無法啟用 {0}。"
ENABLE_SUCCESS: "已經成功啟用 {0}!"
ENJOY_COREPROTECT: "喜歡 {0} 嗎?加入我們的 Discord!"
FINISHING_CONVERSION: "正在完成資料轉換,請稍候……"
FINISHING_LOGGING: "正在完成紀錄檔處理,請稍候……"
FIRST_VERSION: "資料庫初始化版本: {0}"
GLOBAL_LOOKUP: "請勿指定範圍來進行全面的搜尋。"
GLOBAL_ROLLBACK: "使用「{0}」來進行全面的{回滾|恢復}。"
HELP_ACTION_1: "僅過濾某個行為。"
HELP_ACTION_2: "範例: [a:block], [a:+block], [a:-block] [a:click], [a:container], [a:inventory], [a:item], [a:kill], [a:chat], [a:command], [a:sign], [a:session], [a:username]"
HELP_COMMAND: "顯示該指令的更多訊息。"
HELP_EXCLUDE_1: "排除 方塊 / 玩家"
HELP_COMMAND: "顯示該指令的更多資訊。"
HELP_EXCLUDE_1: "排除方塊/玩家"
HELP_EXCLUDE_2: "範例: [e:stone], [e:Notch], [e:stone,Notch]"
HELP_HEADER: "{0} 幫助"
HELP_INCLUDE_1: "包括指定的 方塊 / 實體"
HELP_HEADER: "{0} 說明"
HELP_INCLUDE_1: "僅過濾指定的方塊/實體"
HELP_INCLUDE_2: "範例: [i:stone], [i:zombie], [i:stone,wood,bedrock]"
HELP_INSPECT_1: "當檢查器啟用後,你可以執行以下操作:"
HELP_INSPECT_2: "左鍵點擊你欲查詢的方塊,可以查看誰曾經破壞過該方塊。"
HELP_INSPECT_3: "右鍵點擊你欲查詢的方塊,可以查看相鄰的方塊曾被破壞過的訊息。"
HELP_INSPECT_4: "在欲查詢的位置放置一個方塊,可以查看該位置曾被破壞過哪個方塊。"
HELP_INSPECT_5: "在液體(水、岩漿...)中放置一個方塊,可以查看是誰放置此液體的。"
HELP_INSPECT_6: "右鍵點擊門、箱子等物品,可以查看玩家的使用紀錄。"
HELP_INSPECT_7: "提示:你可以使用 \"/co i\" 來加快尋找的速度。"
HELP_INSPECT_COMMAND: "開啟或關閉檢查器。"
HELP_INSPECT_2: "對你欲查詢的方塊點擊左鍵,可以查看方塊放置紀錄。"
HELP_INSPECT_3: "對你欲查詢的方塊點擊右鍵,可以查看相鄰方塊破壞紀錄。"
HELP_INSPECT_4: "對你欲查詢的位置放置方塊,可以查看放置位置破壞紀錄。"
HELP_INSPECT_5: "對你欲查詢的液體中放置方塊,可以查看液體變更紀錄。"
HELP_INSPECT_6: "對你欲查詢的門、儲物箱等方塊點擊右鍵,可以查看使用紀錄。"
HELP_INSPECT_7: "小提醒: 你可以使用「/co i」來快速啟用或停用檢查器。"
HELP_INSPECT_COMMAND: "啟用或停用檢查器。"
HELP_LIST: "顯示所有指令列表。"
HELP_LOOKUP_1: "指令快捷鍵"
HELP_LOOKUP_2: "在使用檢查器檢查完方塊後以便查看日誌。"
HELP_LOOKUP_COMMAND: "進階查詢方塊的資料。"
HELP_NO_INFO: "未找到相關的命令訊息: \"{0}\""
HELP_PARAMETER: "若需查看相關詳細參數訊息,請查閱\"{0}\"。"
HELP_PARAMS_1: "執行 {查詢|回朔|恢復}。"
HELP_PARAMS_2: "指定一個需要 {查詢|回朔|恢復}的玩家。"
HELP_PARAMS_3: "指定一個需要 {查詢|回朔|恢復}的時間軸。"
HELP_PARAMS_4: "指定一個範圍來限制 {查詢|回朔|恢復} 。"
HELP_PARAMS_5: "將 {查詢|回朔|恢復} 限制在某個動作內。"
HELP_PARAMS_6: "在 {查詢|回朔|恢復} 中指定一個特定的 實體 / 方塊。"
HELP_PARAMS_7: "從 {查詢|回朔|恢復} 中排除一項特定的 實體 / 方塊。"
HELP_PURGE_1: "刪除了早於指定時間軸的資料。"
HELP_PURGE_2: "舉例來說,\"{0}\" 會刪除已廢棄的方塊資料,只保留近30天的資料。"
HELP_PURGE_COMMAND: "刪除舊的方塊資料。"
HELP_RADIUS_1: "指定一個範圍。"
HELP_RADIUS_2: "範例: [r:10] (僅會更改你周圍的10格方塊)"
HELP_RELOAD_COMMAND: "重新載入配置文件。"
HELP_LOOKUP_1: "指令捷徑"
HELP_LOOKUP_2: "在使用檢查器檢查後以便查看日誌。"
HELP_LOOKUP_COMMAND: "進階查詢方塊資料。"
HELP_NO_INFO: "未找到「{0}」的相關指令資訊。"
HELP_PARAMETER: "若需查看相關詳細參數資訊,請查閱「{0}」。"
HELP_PARAMS_1: "執行{查詢|回滾|恢復}。"
HELP_PARAMS_2: "指定需要{查詢|回滾|恢復}的玩家。"
HELP_PARAMS_3: "指定需要{查詢|回滾|恢復}的時間範圍。"
HELP_PARAMS_4: "指定限制{查詢|回滾|恢復}的範圍。"
HELP_PARAMS_5: "將{查詢|回滾|恢復}限制在某個行為。"
HELP_PARAMS_6: "在{查詢|回滾|恢復}中指定特定的實體/方塊。"
HELP_PARAMS_7: "從{查詢|回滾|恢復}中排除特定的方塊/玩家。"
HELP_PURGE_1: "清除指定時間範圍內的資料。"
HELP_PURGE_2: "舉例來說,「{0}」將會清除超過一個月的所有資料,只保留最近 30 天的資料。"
HELP_PURGE_COMMAND: "清除舊資料。"
HELP_RADIUS_1: "指定半徑範圍。"
HELP_RADIUS_2: "範例: [r:10](僅會更改你周圍的 10 格方塊)"
HELP_RELOAD_COMMAND: "重新載入設定檔。"
HELP_RESTORE_COMMAND: "恢復方塊資料。"
HELP_ROLLBACK_COMMAND: "回朔方塊資料。"
HELP_ROLLBACK_COMMAND: "回滾方塊資料。"
HELP_STATUS: "查看插件狀態與版本資訊。"
HELP_STATUS_COMMAND: "顯示插件狀態。"
HELP_TELEPORT: "傳送到一個位置。"
HELP_TIME_1: "指定查找的時間軸。"
HELP_TELEPORT: "傳送到座標。"
HELP_TIME_1: "指定搜尋的時間範圍。"
HELP_TIME_2: "範例: [t:2w,5d,7h,2m,10s], [t:5d2h], [t:2.50h]"
HELP_USER_1: "指定要尋找的玩家。"
HELP_USER_1: "指定要搜尋的玩家。"
HELP_USER_2: "範例: [u:Notch], [u:Notch,#enderman]"
INCOMPATIBLE_ACTION: "\"{0}\" 不能用於此操作。"
INSPECTOR_ERROR: "檢查器已經 {開啟|關閉}。"
INSPECTOR_TOGGLED: "檢查器現在已 {開啟|關閉}。"
INTEGRATION_ERROR: "無法 {初始化|關閉} {0} 日誌紀錄。"
INTEGRATION_SUCCESS: "{0} 成功紀錄 {初始化|關閉}."
INTEGRATION_VERSION: "發現無效的版本: {0}"
INCOMPATIBLE_ACTION: "「{0}」不能用於此行為。"
INSPECTOR_ERROR: "檢查器已經{啟用|停用}了。"
INSPECTOR_TOGGLED: "已{啟用|停用}檢查器。"
INTEGRATION_ERROR: "無法{初始化|停用}{0}紀錄檔。"
INTEGRATION_SUCCESS: "{0} 紀錄檔已成功{初始化|停用}。"
INTEGRATION_VERSION: "{0} 的版本無效。"
INTERACTIONS_HEADER: "玩家互動"
INVALID_ACTION: "這不是一個有效的行為。"
INVALID_BRANCH_1: "CoreProtect插件版本無效(未設置分支)。"
INVALID_BRANCH_2: "若要繼續動作,請將你的項目分之設定為 \"development\"。"
INVALID_BRANCH_3: "執行開發中的代碼可能會導致資料損毀。"
INVALID_CONTAINER: "請先檢查有效的容器。"
INVALID_DONATION_KEY: "無效的贈品密鑰。"
INVALID_INCLUDE: "\"{0}\" 是無效的 方塊 / 實體 名稱。"
INVALID_INCLUDE_COMBO: "這是一個無效的塊/實體組合。"
INVALID_RADIUS: "請輸入一個有效的範圍。"
INVALID_SELECTION: "你選擇的內容 {0} 未找到。"
INVALID_USERNAME: "\"{0}\" 是一個無效的玩名稱。"
INVALID_WORLD: "請指定一個有效的世界。"
INVALID_ACTION: "此並非有效的行為。"
INVALID_BRANCH_1: "插件版本無效(未設定分支)。"
INVALID_BRANCH_2: "若要繼續,請將分支設定為「development」。"
INVALID_BRANCH_3: "執行開發中的插件版本可能會導致資料損毀。"
INVALID_CONTAINER: "此並非有效的容器。"
INVALID_DONATION_KEY: "無效的贊助金鑰。"
INVALID_INCLUDE: "「{0}」為無效的方塊/實體名稱。"
INVALID_INCLUDE_COMBO: "此為無效的方塊/實體組合。"
INVALID_RADIUS: "此並非有效的範圍。"
INVALID_SELECTION: "「{0}」為無效的選擇範圍。"
INVALID_USERNAME: "「{0}」為無效的使用者名稱。"
INVALID_WORLD: "此並非有效的世界。"
LATEST_VERSION: "最新版本: {0}"
LINK_DISCORD: "Discord: {0}"
LINK_DOWNLOAD: "下載: {0}"
LINK_PATREON: "Patreon: {0}"
LINK_WIKI_BLOCK: "方塊名稱 {0}"
LINK_WIKI_ENTITY: "實體名稱 {0}"
LOGGING_ITEMS: "還剩 {0} 項要進行紀錄,請稍後..."
LOGGING_TIME_LIMIT: "已達到紀錄的時間上限。刪除資料並關閉中..."
LINK_WIKI_BLOCK: "方塊名稱: {0}"
LINK_WIKI_ENTITY: "實體名稱: {0}"
LOGGING_ITEMS: "還剩 {0} 項要進行紀錄,請稍候……"
LOGGING_TIME_LIMIT: "已達到紀錄的時間上限,正在捨棄資料並停用……"
LOOKUP_BLOCK: "{0} {放置|破壞} {1}。"
LOOKUP_CONTAINER: "{0} {增加|刪除} {1} {2}."
LOOKUP_HEADER: "{0} 尋找結果。"
LOOKUP_INTERACTION: "{0} {點擊|殺死} {1}."
LOOKUP_ITEM: "{0} {拾起|放下} {1} {2}."
LOOKUP_LOGIN: "{0} 你已登 {入|出}。"
LOOKUP_PAGE: "頁數 {0}"
LOOKUP_ROWS_FOUND: "{0} {row|rows} 找到。"
LOOKUP_SEARCHING: "尋找結果中,請稍後..."
LOOKUP_STORAGE: "{0} {存|取} {1} {2}."
LOOKUP_CONTAINER: "{0} {存入|取出} {1} {2}。"
LOOKUP_HEADER: "{0} 條搜尋結果。"
LOOKUP_INTERACTION: "{0} {點擊|殺死} {1}。"
LOOKUP_ITEM: "{0} {撿起|拋棄} {1} {2}。"
LOOKUP_LOGIN: "{0} 已登{入|出}。"
LOOKUP_PAGE: "第 {0} 頁"
LOOKUP_PROJECTILE: "{0} {扔|射} {1} {2}."
LOOKUP_ROWS_FOUND: "已找到 {0} {條|條}。"
LOOKUP_SEARCHING: "正在搜尋,請稍候……"
LOOKUP_STORAGE: "{0} {存入|取出} {1} {2}。"
LOOKUP_TIME: "{0} 前"
LOOKUP_USERNAME: "{0} 以 {1}的身分登入。"
LOOKUP_VIEW_PAGE: "若要查看指定頁面,請輸入 \"{0}\"。"
MAXIMUM_RADIUS: "最大 {查詢|回朔|恢復} 的範圍為 {0}。"
MISSING_ACTION_USER: "若要使用此操作,請指定玩家。"
MISSING_LOOKUP_TIME: "請指定 {查詢|回朔|恢復}的時間軸。"
MISSING_LOOKUP_USER: "請指定要尋找的玩家或是 {方塊|範圍} 。"
MISSING_PARAMETERS: "請使用 \"{0}\"."
MISSING_ROLLBACK_RADIUS: "你沒有指定 {回朔|恢復} 的範圍。"
MISSING_ROLLBACK_USER: "你沒有指定 {回朔|恢復} 的玩家。"
LOOKUP_USERNAME: "{0} 已改變使用者名稱為 {1}。"
MAXIMUM_RADIUS: "最大{查詢|回滾|恢復}的範圍為 {0}。"
MISSING_ACTION_USER: "若要使用此行為,請指定玩家。"
MISSING_LOOKUP_TIME: "請指定{查詢|回滾|恢復}的時間範圍。"
MISSING_LOOKUP_USER: "請指定搜尋的玩家或是{方塊|範圍}。"
MISSING_PARAMETERS: "請使用「{0}」。"
MISSING_ROLLBACK_RADIUS: "你沒有指定{回滾|恢復}的範圍。"
MISSING_ROLLBACK_USER: "你沒有指定{回滾|恢復}的玩家。"
MYSQL_UNAVAILABLE: "無法連線到 MySQL 伺服器。"
NO_DATA: "在 {0} 找不到資料。"
NO_DATA_LOCATION: "在此位置找不到 {資料|交易|互動狀態|訊息} 。"
NO_PERMISSION: "你沒有權限這麼做。"
NETWORK_CONNECTION: "連接到 {0} {成功|失敗}. 使用 {1} {2}."
NETWORK_TEST: "網絡測試數據已成功發送。"
NO_DATA: "未找到 {0} §f的資料。"
NO_DATA_LOCATION: "未找到此位置的{資料|交易|互動|訊息}。"
NO_PERMISSION: "你沒有使用該操作的權限。"
NO_RESULTS: "未找到相關結果。"
NO_RESULTS_PAGE: "未找到該頁面相關的 {結果|資料}。"
NO_ROLLBACK: "未找到 {待處理|舊的} 回朔 / 恢復。"
PATCH_INTERRUPTED: "更新時遭到無預警的中斷! 系統會在下次重啟時重新嘗試。"
NO_RESULTS_PAGE: "未找到該頁面相關的{結果|資料}。"
NO_ROLLBACK: "未找到{待處理|上一個}回滾/恢復。"
PATCH_INTERRUPTED: "更新已中斷!將會在下次啟動時再次嘗試。"
PATCH_OUTDATED_1: "無法更新早於 {0} 的資料庫。"
PATCH_OUTDATED_2: "請使用受到支援的CoreProtect版本。"
PATCH_PROCESSING: "處理新資訊中,請稍後..."
PATCH_SKIP_UPDATE: "已在 {0} 上跳過 {列表|索引} {更新|新建|刪除}。"
PATCH_STARTED: "執行 {0} 更新中,請稍後..."
PATCH_SUCCESS: "已成功升級到 {0}。"
PATCH_UPGRADING: "正在進行資料庫升級中,請稍後..."
PLEASE_SELECT: "請選擇 \"{0}\" 或是 \"{1}\"。"
PATCH_OUTDATED_2: "請使用受到支援的 CoreProtect 版本。"
PATCH_PROCESSING: "正在處理新資訊,請稍候……"
PATCH_SKIP_UPDATE: "已在 {0} 上跳過{資料表|索引}的{更新|建立|清除}。"
PATCH_STARTED: "正在執行 {0} 更新,請稍候……"
PATCH_SUCCESS: "資料庫已成功更新到 {0}。"
PATCH_UPGRADING: "正在進行資料庫更新,請稍候……"
PLEASE_SELECT: "請選擇:「{0}」或是「{1}」。"
PREVIEW_CANCELLED: "預覽已取消。"
PREVIEW_CANCELLING: "正在取消預覽。"
PREVIEW_CONTAINER: "你無法預覽容器內的物品資料。"
PREVIEW_IN_GAME: "你只能在遊戲中預覽回朔。"
PREVIEW_CANCELLING: "正在取消預覽……"
PREVIEW_IN_GAME: "你只能在遊戲中預覽回滾。"
PREVIEW_TRANSACTION: "您無法預覽{集裝箱|庫存}交易。"
PRIMARY_THREAD_ERROR: "該API方法不能在主線程上使用。"
PURGE_ABORTED: "清除失敗,你的資料可能已損毀。"
PURGE_ERROR: "無法處理 {0} 條資料。"
PURGE_FAILED: "清除失敗,請稍後在重試。"
PURGE_IN_PROGRESS: "正在執行清除作業中,請稍後在重試。"
PURGE_ERROR: "無法處理 {0} 筆資料!"
PURGE_FAILED: "清除失敗,請稍後再試。"
PURGE_IN_PROGRESS: "正在執行清除作業,請稍後再試。"
PURGE_MINIMUM_TIME: "你只能清除早於 {0} {天|小時}的資料。"
PURGE_NOTICE_1: "請注意,此操作可能需要一些時間!"
PURGE_NOTICE_2: "在系統完成此進程前請不要重新啟動伺服器。"
PURGE_OPTIMIZING: "最佳化資料庫中,請稍後..."
PURGE_PROCESSING: " 正在處理 {0} 條資料。"
PURGE_REPAIRING: "系統嘗試修復此問題中,這可能會花上一段時間。"
PURGE_ROWS: "{0} {行數|行數} 資料已刪除。"
PURGE_STARTED: "資料刪除進程開始於 \"{0}\"。"
PURGE_SUCCESS: "資料清除成功!"
RELOAD_STARTED: "正在重新載入配置文件中,請稍後..."
RELOAD_SUCCESS: "配置文件已重新載入成功!"
ROLLBACK_ABORTED: "回朔 / 恢復 已中止。"
ROLLBACK_CHUNKS_FOUND: "找到 {0} {區塊|區塊} 進行修改。"
ROLLBACK_CHUNKS_MODIFIED: "修改了 {0}/{1} {區塊|區塊}。"
ROLLBACK_COMPLETED: "\"{0}\" 的 {回朔|恢復|預覽} 已完成。"
ROLLBACK_EXCLUDED_USERS: "排除的 {玩家|玩家}: \"{0}\"。"
ROLLBACK_INCLUDE: "{包含|不包含} {方塊|實體|目標} {類型|類型}: \"{0}\"。"
ROLLBACK_IN_PROGRESS: "回朔 / 恢復 已在進行中。"
ROLLBACK_LENGTH: "所用時間: {0} {秒|秒}。"
ROLLBACK_MODIFIED: "{更動|更動} {0}。"
ROLLBACK_RADIUS: "範圍: {0} {方塊|方塊}。"
ROLLBACK_SELECTION: "範圍設為 \"{0}\"。"
ROLLBACK_STARTED: "{回朔|恢復|預覽} 開始於 \"{0}\"。"
ROLLBACK_TIME: "時間軸 {0}。"
ROLLBACK_WORLD_ACTION: "限制於 \"{0}\"."
PURGE_NOTICE_1: "請注意,此操作可能需要一些時間。"
PURGE_NOTICE_2: "在完成此程序前請不要重新啟動伺服器。"
PURGE_OPTIMIZING: "正在最佳化資料庫,請稍候……"
PURGE_PROCESSING: "正在清除 {0} 筆資料……"
PURGE_REPAIRING: "正在嘗試修復問題,這可能需要一些時間。"
PURGE_ROWS: "已清除 {0} {筆|筆}資料。"
PURGE_STARTED: "資料清除開始於「{0}」。"
PURGE_SUCCESS: "成功清除資料。"
RAM_STATS: "{0}GB / {1}GB 記憶體"
RELOAD_STARTED: "正在重新載入設定檔——請稍候……"
RELOAD_SUCCESS: "成功重新載入設定檔。"
ROLLBACK_ABORTED: "已中斷回滾/恢復。"
ROLLBACK_CHUNKS_FOUND: "找到 {0} 個{區塊|區塊}需要修改。"
ROLLBACK_CHUNKS_MODIFIED: "已修改 {0}/{1}個{區塊|區塊}。"
ROLLBACK_COMPLETED: "已完成「{0}」的{回滾|恢復|預覽}。"
ROLLBACK_EXCLUDED_USERS: "已排除{玩家|玩家}「{0}」。"
ROLLBACK_INCLUDE: "{包括|不包括}{方塊|實體|目標}{類型|類型}「{0}」。"
ROLLBACK_IN_PROGRESS: "已在進行回滾/恢復。"
ROLLBACK_LENGTH: "花費時間: {0} {秒|秒}。"
ROLLBACK_MODIFIED: "{已修改|正在修改} {0}。"
ROLLBACK_RADIUS: "範圍: {0} 格{方塊|方塊}。"
ROLLBACK_SELECTION: "選擇範圍為「{0}」。"
ROLLBACK_STARTED: "{回滾|恢復|預覽}開始於「{0}」。"
ROLLBACK_TIME: "時間範圍: {0}。"
ROLLBACK_WORLD_ACTION: "限制{世界|行為}「{0}」。"
SIGN_HEADER: "簽名訊息"
STATUS_CONSUMER: "玩家: {0} {物品|物品} 已在隊列中。"
STATUS_DATABASE: "資料庫: 使用 {0}."
STATUS_INTEGRATION: "{0}: 整合 {開啟|關閉}。"
STATUS_CONSUMER: "消費者隊列: {0} 筆{資料|資料}在隊列中。"
STATUS_DATABASE: "資料庫: 使用 {0}。"
STATUS_INTEGRATION: "{0}: 整合{啟用|停用}。"
STATUS_LICENSE: "憑證: {0}"
STATUS_SYSTEM: "系統: {0}"
STATUS_VERSION: "版本: {0}"
TELEPORTED: "傳送到 {0}。"
TELEPORTED_SAFETY: "已將你傳送到安全的區域。"
@@ -183,15 +193,17 @@ TIME_DAYS: "{0} {天|天}"
TIME_HOURS: "{0} {小時|小時}"
TIME_MINUTES: "{0} {分鐘|分鐘}"
TIME_SECONDS: "{0} {秒|秒}"
TIME_WEEKS: "{0} {周|周}"
UPDATE_ERROR: "檢查更新時出現錯誤!"
TIME_UNITS: "{/m|/h|/d}"
TIME_WEEKS: "{0} {週|週}"
UPDATE_ERROR: "檢查更新時發生錯誤。"
UPDATE_HEADER: "{0} 更新"
UPDATE_NOTICE: "通知: {0} 現已推出。"
UPGRADE_IN_PROGRESS: "正在進行更新中,請稍後再試。"
USER_NOT_FOUND: "玩家: \"{0}\"不存在(或為未上線)。"
UPGRADE_IN_PROGRESS: "正在進行更新,請稍後再試。"
USER_NOT_FOUND: "未找到玩家「{0}」。"
USER_OFFLINE: "用戶「{0}」不在線上。"
USING_MYSQL: "使用 MySQL 進行資料儲存。"
USING_SQLITE: "使用 SQLite 進行資料儲存。"
VALID_DONATION_KEY: "有效的贈品金鑰"
VALID_DONATION_KEY: "有效的贊助金鑰"
VERSION_NOTICE: "版本 {0} 現已推出。"
VERSION_REQUIRED: "需要有 {0} {1} 或更高的權限"
WORLD_NOT_FOUND: "世界: \"{0}\" 不存在。"
VERSION_REQUIRED: "{0} 需要使用 {1} 或更高版本。"
WORLD_NOT_FOUND: "世界「{0}」不存在。"
+1 -1
View File
@@ -4,4 +4,4 @@ theme: readthedocs
repo_url: https://github.com/PlayPro/CoreProtect
docs_dir: 'docs'
extra_css:
- css/extra.css
- css/extra.css
-4
View File
@@ -1,4 +0,0 @@
# netlify.toml
[build]
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF docs/"
Executable → Regular
+265 -33
View File
@@ -2,10 +2,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.coreprotect</groupId>
<artifactId>CoreProtect</artifactId>
<version>20.0</version>
<version>24.1</version>
<properties>
<project.branch></project.branch>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>true</skipTests>
<maven.compiler.release>11</maven.compiler.release>
<clickhouse.version>0.10.0</clickhouse.version>
<duckdb.version>1.4.5.0</duckdb.version>
</properties>
<build>
<resources>
@@ -23,16 +27,40 @@
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.15.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<release>${maven.compiler.release}</release>
</configuration>
<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<skip>${skipTests}</skip>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
@@ -46,79 +74,283 @@
<excludes>
<exclude>**/*.java</exclude>
<exclude>**/*.c</exclude>
<exclude>META-INF/maven/**</exclude>
<exclude>module-info.class</exclude>
<exclude>META-INF/versions/**/module-info.class</exclude>
<exclude>META-INF/maven/**/pom.properties</exclude>
<exclude>META-INF/services/**</exclude>
</excludes>
</filter>
</filters>
<artifactSet>
<excludes>
<exclude>com.google.code.gson:*</exclude>
<exclude>org.intellij:*</exclude>
<exclude>org.jetbrains:*</exclude>
<exclude>org.slf4j:*</exclude>
<exclude>org.apache.logging.log4j:*</exclude>
<exclude>net.java.dev.jna:*</exclude>
</excludes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>net.coreprotect</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer</pattern>
<shadedPattern>net.coreprotect</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
<configuration>
<systemPropertyVariables>
<plugin.version>${project.version}</plugin.version>
</systemPropertyVariables>
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>validate</phase>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireNoRepositories>
<allowedRepositories>
<allowedRepository>central</allowedRepository>
<allowedRepository>enginehub-repo</allowedRepository>
<allowedRepository>spigot-repo</allowedRepository>
<allowedRepository>paper-repo</allowedRepository>
<allowedRepository>codemc-repo</allowedRepository>
<allowedRepository>jitpack.io</allowedRepository>
</allowedRepositories>
</requireNoRepositories>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>maven-install</id>
<activation>
<property>
<name>env.BUILD_ID</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>install-validation</id>
<phase>install</phase>
<configuration>
<target>
<delete>
<fileset dir="${project.build.directory}">
<include name="${project.build.finalName}-shaded.jar"/>
</fileset>
</delete>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<!--
<repository>
<id>public</id>
<url>http://repo.mvdw-software.be/content/groups/public/</url>
<id>central</id>
<url>https://repo.maven.apache.org/maven2/</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<repository>
<id>enginehub-repo</id>
<url>https://maven.enginehub.org/repo/</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
-->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>paper-repo</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
<repository>
<id>sk89q-repo</id>
<url>http://maven.sk89q.com/repo/</url>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!--
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
paper-api: io.papermc.paper
paper-api (<= 1.16): com.destroystokyo.paper / 1.16.1-R0.1-SNAPSHOT
spigot-api: org.spigotmc
bukkit: org.bukkit
-->
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>26.3.build.38-alpha</version> <!-- https://repo.papermc.io/repository/maven-public/io/papermc/paper/paper-api/ -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit-lite</artifactId>
<version>1.8</version>
<artifactId>bstats-bukkit</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldedit</groupId>
<artifactId>worldedit-bukkit</artifactId>
<version>7.0.0-SNAPSHOT</version>
<groupId>com.fastasyncworldedit</groupId>
<artifactId>FastAsyncWorldEdit-Core</artifactId>
<version>2.15.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fastasyncworldedit</groupId>
<artifactId>FastAsyncWorldEdit-Bukkit</artifactId>
<version>2.15.0</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<groupId>*</groupId>
<artifactId>FastAsyncWorldEdit-Core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>7.0.2</version>
</dependency>
<dependency>
<groupId>org.duckdb</groupId>
<artifactId>duckdb_jdbc</artifactId>
<version>${duckdb.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>${clickhouse.version}</version>
<classifier>all</classifier>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.25.4</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.25.5</version>
</dependency>
<dependency>
<groupId>com.github.DeadSilenceIV</groupId>
<artifactId>AdvancedChestsAPI</artifactId>
<version>3.2-BETA</version>
<scope>provided</scope>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.14.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.14.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.14.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.seeseemelk</groupId>
<artifactId>MockBukkit-v1.21</artifactId>
<version>3.133.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.53.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.17</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.18.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.18.8</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
</project>
+1 -1
View File
@@ -1 +1 @@
mkdocs==1.2.1
mkdocs==1.3.1
+16 -150
View File
@@ -2,30 +2,21 @@ package net.coreprotect;
import java.io.File;
import org.bstats.bukkit.MetricsLite;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import net.coreprotect.command.CommandHandler;
import net.coreprotect.command.TabHandler;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.consumer.Consumer;
import net.coreprotect.consumer.process.Process;
import net.coreprotect.language.Language;
import net.coreprotect.language.Phrase;
import net.coreprotect.listener.ListenerHandler;
import net.coreprotect.thread.CacheHandler;
import net.coreprotect.thread.NetworkHandler;
import net.coreprotect.services.PluginInitializationService;
import net.coreprotect.services.ShutdownService;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.Util;
/**
* Main class for the CoreProtect plugin
*/
public final class CoreProtect extends JavaPlugin {
private static CoreProtect instance;
/**
* Get the instance of CoreProtect
*
@@ -35,7 +26,7 @@ public final class CoreProtect extends JavaPlugin {
return instance;
}
private CoreProtectAPI api = new CoreProtectAPI();
private final CoreProtectAPI api = new CoreProtectAPI();
/**
* Get the CoreProtect API
@@ -48,73 +39,15 @@ public final class CoreProtect extends JavaPlugin {
@Override
public void onEnable() {
// Set plugin instance and data folder path
instance = this;
ConfigHandler.path = this.getDataFolder().getPath() + File.separator;
Language.loadPhrases();
boolean start = performVersionChecks();
if (start) {
try {
Consumer.initialize(); // Prepare consumer (keep this here)
new ListenerHandler(this);
getCommand("coreprotect").setExecutor(CommandHandler.getInstance());
getCommand("coreprotect").setTabCompleter(new TabHandler());
getCommand("core").setExecutor(CommandHandler.getInstance());
getCommand("core").setTabCompleter(new TabHandler());
getCommand("co").setExecutor(CommandHandler.getInstance());
getCommand("co").setTabCompleter(new TabHandler());
// Initialize plugin using the initialization service
boolean initialized = PluginInitializationService.initializePlugin(this);
boolean exists = (new File(ConfigHandler.path)).exists();
if (!exists) {
new File(ConfigHandler.path).mkdir();
}
start = ConfigHandler.performInitialization(true); // Perform any necessary initialization
}
catch (Exception e) {
e.printStackTrace();
start = false;
}
}
if (start) {
PluginDescriptionFile pluginDescription = this.getDescription();
Util.sendConsoleComponentStartup(Bukkit.getServer().getConsoleSender(), Phrase.build(Phrase.ENABLE_SUCCESS, ConfigHandler.EDITION_NAME));
if (Config.getGlobal().MYSQL) {
Chat.console(Phrase.build(Phrase.USING_MYSQL));
}
else {
Chat.console(Phrase.build(Phrase.USING_SQLITE));
}
Chat.console("--------------------");
Chat.console(Phrase.build(Phrase.ENJOY_COREPROTECT, pluginDescription.getName()));
Chat.console(Phrase.build(Phrase.LINK_DISCORD, "www.coreprotect.net/discord/"));
Chat.console("--------------------");
getServer().getScheduler().scheduleSyncDelayedTask(this, () -> {
try {
Thread networkHandler = new Thread(new NetworkHandler(true, true));
networkHandler.start();
}
catch (Exception e) {
e.printStackTrace();
}
}, 0);
Thread cacheCleanUpThread = new Thread(new CacheHandler());
cacheCleanUpThread.start();
Consumer.startConsumer();
// Enabling bStats
try {
new MetricsLite(this, 2876);
}
catch (Exception e) {
// Failed to connect to bStats server or something else went wrong.
}
}
else {
// Disable plugin if initialization failed
if (!initialized) {
Chat.console(Phrase.build(Phrase.ENABLE_FAILED, ConfigHandler.EDITION_NAME));
getServer().getPluginManager().disablePlugin(this);
}
@@ -122,78 +55,11 @@ public final class CoreProtect extends JavaPlugin {
@Override
public void onDisable() {
safeShutdown(this);
ShutdownService.safeShutdown(this);
}
private static boolean performVersionChecks() {
try {
String[] bukkitVersion = Bukkit.getServer().getBukkitVersion().split("-|\\.");
if (Util.newVersion(bukkitVersion[0] + "." + bukkitVersion[1], ConfigHandler.SPIGOT_VERSION)) {
Chat.console(Phrase.build(Phrase.VERSION_REQUIRED, "Spigot", ConfigHandler.SPIGOT_VERSION));
return false;
}
String[] javaVersion = (System.getProperty("java.version").replaceAll("[^0-9.]", "") + ".0").split("\\.");
if (Util.newVersion(javaVersion[0] + "." + javaVersion[1], ConfigHandler.JAVA_VERSION)) {
Chat.console(Phrase.build(Phrase.VERSION_REQUIRED, "Java", ConfigHandler.JAVA_VERSION));
return false;
}
if (ConfigHandler.EDITION_BRANCH.length() == 0) {
Chat.sendConsoleMessage(Color.RED + "[CoreProtect] " + Phrase.build(Phrase.INVALID_BRANCH_1));
Chat.sendConsoleMessage(Color.GREY + "[CoreProtect] " + Phrase.build(Phrase.INVALID_BRANCH_2));
Chat.sendConsoleMessage(Color.GREY + "[CoreProtect] " + Phrase.build(Phrase.INVALID_BRANCH_3));
return false;
}
ConfigHandler.SERVER_VERSION = Integer.parseInt(bukkitVersion[1]);
}
catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
public boolean isAdvancedChestsEnabled() {
Plugin advancedChests = getServer().getPluginManager().getPlugin("AdvancedChests");
return advancedChests != null && advancedChests.isEnabled();
}
private static void safeShutdown(CoreProtect plugin) {
try {
ConfigHandler.serverRunning = false;
long shutdownTime = System.currentTimeMillis();
long alertTime = shutdownTime + (10 * 1000);
if (ConfigHandler.converterRunning) {
Chat.console(Phrase.build(Phrase.FINISHING_CONVERSION));
}
else {
Chat.console(Phrase.build(Phrase.FINISHING_LOGGING));
}
while ((Consumer.isRunning() || ConfigHandler.converterRunning) && !ConfigHandler.purgeRunning) {
long time = System.currentTimeMillis();
if (time >= alertTime) {
if (!ConfigHandler.converterRunning) {
int consumerId = (Consumer.currentConsumer == 1) ? 1 : 0;
int consumerCount = Consumer.getConsumerSize(consumerId) + Process.getCurrentConsumerSize();
Chat.console(Phrase.build(Phrase.LOGGING_ITEMS, String.format("%,d", consumerCount)));
}
alertTime = alertTime + (30 * 1000);
}
else if (!ConfigHandler.databaseReachable && (time - shutdownTime) >= (5 * 60 * 1000)) {
Chat.console(Phrase.build(Phrase.DATABASE_UNREACHABLE));
break;
}
else if ((time - shutdownTime) >= (15 * 60 * 1000)) {
Chat.console(Phrase.build(Phrase.LOGGING_TIME_LIMIT));
break;
}
Thread.sleep(100);
}
Chat.console(Phrase.build(Phrase.DISABLE_SUCCESS, "CoreProtect v" + plugin.getDescription().getVersion()));
}
catch (Exception e) {
e.printStackTrace();
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,309 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Location;
import org.bukkit.block.Block;
import net.coreprotect.api.result.BlockResult;
import net.coreprotect.api.result.ContainerResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.DuckDBLookupQuery;
import net.coreprotect.database.LocationQuery;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.utility.BlockUtils;
import net.coreprotect.utility.DatabaseUtils;
import net.coreprotect.utility.StringUtils;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for block-related lookups in the CoreProtect database.
*/
public class BlockAPI {
/**
* Private constructor to prevent instantiation.
* This is a utility class with static methods only.
*/
private BlockAPI() {
throw new IllegalStateException("API class");
}
/**
* Performs a lookup of block-related actions at the specified block.
*
* @param block
* The block to look up
* @param offset
* Time constraint in seconds (0 means no time constraint)
* @return List of results in a String array format
*/
public static List<String[]> performLookup(Block block, int offset) {
List<String[]> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (block == null) {
return result;
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
int x = block.getX();
int y = block.getY();
int z = block.getZ();
int time = (int) (System.currentTimeMillis() / 1000L);
int worldId = WorldUtils.getWorldId(block.getWorld().getName());
int checkTime = 0;
if (offset > 0) {
checkTime = time - offset;
}
try (Statement statement = connection.createStatement()) {
String table = DuckDBLookupQuery.spatialTable(connection, "block", worldId, x, x, z, z, "spatial_rows");
String index = ConfigHandler.databaseType.isDuckDB() ? "" : WorldUtils.getWidIndex("block");
String query = "SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",action,type,data,blockdata,rolled_back FROM " + table + " " + index + "WHERE " + LocationQuery.predicate("wid", " = " + worldId) + " AND " + LocationQuery.predicate("x", " = " + x) + " AND " + LocationQuery.predicate("z", " = " + z) + " AND y = " + y + " AND time > " + checkTime + " ORDER BY " + ConfigHandler.getDescendingEventOrder();
try (ResultSet results = statement.executeQuery(query)) {
while (results.next()) {
String resultTime = results.getString("time");
int resultUserId = results.getInt("user");
String resultAction = results.getString("action");
int resultType = results.getInt("type");
String resultData = results.getString("data");
byte[] resultBlockData = DatabaseUtils.getBytes(results, "blockdata");
String resultRolledBack = results.getString("rolled_back");
String resultUser = UserStatement.getName(connection, resultUserId);
String blockData = BlockUtils.byteDataToString(resultBlockData, resultType);
String[] lookupData = new String[] { resultTime, resultUser, String.valueOf(x), String.valueOf(y), String.valueOf(z), String.valueOf(resultType), resultData, resultAction, resultRolledBack, String.valueOf(worldId), blockData };
result.add(StringUtils.toStringArray(lookupData));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
/**
* Performs a typed lookup of block-related actions at the specified block.
*
* @param block
* The block to look up
* @param options
* Lookup options. World, location, and radius are ignored because the block supplies the exact location.
* @return List of results in a BlockResult format
*/
public static List<BlockResult> performLookup(Block block, LookupOptions options) {
if (!Config.getGlobal().API_ENABLED || block == null || block.getWorld() == null) {
return new ArrayList<>();
}
if (options == null) {
options = LookupOptions.builder().build();
}
return performLookup(LookupOptions.builder().location(block.getLocation())
.user(options.getUser()).users(options.getUsers()).excludeUsers(options.getExcludeUsers())
.time(options.getTime()).limit(options.getLimitOffset(), options.getLimitCount())
.includeMaterials(options.getIncludeMaterials()).excludeMaterials(options.getExcludeMaterials())
.blockActions(options.getBlockActions()).build(), false);
}
/**
* Performs a typed lookup of block-related actions using shared lookup options.
*
* @param options
* Lookup options
* @return List of results in a BlockResult format
*/
public static List<BlockResult> performLookup(LookupOptions options) {
return performLookup(options, true);
}
private static List<BlockResult> performLookup(LookupOptions options, boolean blocksOnly) {
List<BlockResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
StringBuilder query = new StringBuilder("SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",action,type,data,blockdata,rolled_back,wid,x,y,z FROM ");
query.append(filter.table(connection, "block", "")).append(' ');
if (filter.hasLocation() && !ConfigHandler.databaseType.isDuckDB()) {
query.append(WorldUtils.getWidIndex("block"));
}
filter.appendWhere(query);
filter.appendBlockMaterialWhere(query);
int[] actions = blocksOnly && options.getBlockActions().isEmpty()
? new int[] { BlockAction.BREAK.id(), BlockAction.PLACE.id(), BlockAction.INTERACTION.id() }
: options.getBlockActions().stream().mapToInt(BlockAction::id).toArray();
LookupFilter.appendActionWhere(query, "", actions);
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder());
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
filter.bind(statement);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseBlockResult(connection, results, WorldUtils.getWorldName(results.getInt("wid"))));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
/**
* Performs a lookup of container transactions at the specified location.
*
* @param location
* The location to look up
* @param offset
* Time constraint in seconds (0 means no time constraint)
* @return List of results in a ContainerResult format
*/
public static List<ContainerResult> performContainerLookup(Location location, int offset) {
if (location == null) {
return new ArrayList<>();
}
return performContainerLookup(LookupOptions.builder().time(offset).location(location).build());
}
/**
* Performs a lookup of container transactions using shared lookup options.
*
* @param options
* Lookup options. Tracked entity-container rows match their original or persisted current/final location and return the persisted
* current/final location.
* @return List of results in a ContainerResult format
*/
public static List<ContainerResult> performContainerLookup(LookupOptions options) {
List<ContainerResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
boolean snapshot = filter.beginDuckDBSnapshot(connection);
try {
StringBuilder containerWhere = new StringBuilder();
filter.appendWhere(containerWhere, "container_rows");
filter.appendMaterialWhere(containerWhere, "container_rows");
int[] actions = options.getContainerActions().stream().mapToInt(ContainerAction::id).toArray();
LookupFilter.appendActionWhere(containerWhere, "container_rows", actions);
StringBuilder entityWhere = new StringBuilder();
filter.appendEntityContainerWhere(entityWhere, "entity_rows", "spawn_rows");
filter.appendMaterialWhere(entityWhere, "entity_rows");
LookupFilter.appendActionWhere(entityWhere, "entity_rows", actions);
StringBuilder query = new StringBuilder("SELECT * FROM (");
query.append("SELECT 0 AS source,container_rows.rowid AS id,container_rows.time,container_rows.").append(ConfigHandler.databaseType.getUserColumn()).append(",container_rows.wid,container_rows.x,container_rows.y,container_rows.z,container_rows.action,container_rows.type,container_rows.data,container_rows.amount,container_rows.metadata,container_rows.rolled_back FROM ")
.append(filter.table(connection, "container", "container_rows")).append(' ').append(containerWhere);
query.append(" UNION ALL ");
query.append("SELECT 1 AS source,entity_rows.rowid AS id,entity_rows.time,entity_rows.").append(ConfigHandler.databaseType.getUserColumn()).append(",spawn_rows.current_wid AS wid,spawn_rows.x,spawn_rows.y,spawn_rows.z,entity_rows.action,entity_rows.type,entity_rows.data,entity_rows.amount,entity_rows.metadata,entity_rows.rolled_back FROM ")
.append(filter.entityContainerTable(connection, "entity_rows")).append(" JOIN ").append(ConfigHandler.prefix).append("entity_spawn spawn_rows ON spawn_rows.rowid=entity_rows.entity_spawn_rowid ").append(entityWhere);
query.append(") AS container_lookup ORDER BY time DESC,source DESC,id DESC");
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
int parameterIndex = filter.bind(statement);
filter.bindEntityContainer(statement, parameterIndex);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseContainerResult(connection, results));
}
}
}
}
finally {
filter.endDuckDBSnapshot(connection, snapshot);
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
private static ContainerResult parseContainerResult(Connection connection, ResultSet results) throws Exception {
int resultUserId = results.getInt("user");
String resultUser = UserStatement.getName(connection, resultUserId);
return new ContainerResult(
results.getLong("time"), resultUser, WorldUtils.getWorldName(results.getInt("wid")), (int) Math.floor(results.getDouble("x")), (int) Math.floor(results.getDouble("y")), (int) Math.floor(results.getDouble("z")),
results.getInt("type"), results.getInt("data"), results.getInt("amount"), DatabaseUtils.getBytes(results, "metadata"),
results.getInt("action"), results.getInt("rolled_back")
);
}
private static BlockResult parseBlockResult(Connection connection, ResultSet results, String worldName) throws Exception {
int resultUserId = results.getInt("user");
String resultUser = UserStatement.getName(connection, resultUserId);
int resultType = results.getInt("type");
String blockData = BlockUtils.byteDataToString(DatabaseUtils.getBytes(results, "blockdata"), resultType);
return new BlockResult(
results.getLong("time"), resultUser, worldName, results.getInt("x"), results.getInt("y"), results.getInt("z"),
resultType, results.getInt("data"), blockData, results.getInt("action"), results.getInt("rolled_back")
);
}
}
@@ -0,0 +1,22 @@
package net.coreprotect.api;
import net.coreprotect.model.action.LookupActions;
/**
* Block actions used by typed block lookup filters.
*/
public enum BlockAction {
BREAK(LookupActions.BLOCK_BREAK),
PLACE(LookupActions.BLOCK_PLACE),
INTERACTION(LookupActions.INTERACTION);
private final int id;
BlockAction(int id) {
this.id = id;
}
public int id() {
return id;
}
}
@@ -0,0 +1,21 @@
package net.coreprotect.api;
import net.coreprotect.model.item.ItemTransactionActions;
/**
* Container transaction actions used by typed container lookup filters.
*/
public enum ContainerAction {
REMOVE(ItemTransactionActions.REMOVE),
ADD(ItemTransactionActions.ADD);
private final int id;
ContainerAction(int id) {
this.id = id;
}
public int id() {
return id;
}
}
@@ -0,0 +1,46 @@
package net.coreprotect.api;
import net.coreprotect.model.action.LookupActions;
/**
* CoreProtect action identifiers used by lookup filters.
*/
public enum CoreProtectAction {
BLOCK_BREAK(LookupActions.BLOCK_BREAK),
BLOCK_PLACE(LookupActions.BLOCK_PLACE),
INTERACTION(LookupActions.INTERACTION),
ENTITY_KILL(LookupActions.ENTITY_KILL),
CONTAINER(LookupActions.CONTAINER),
CHAT(LookupActions.CHAT),
COMMAND(LookupActions.COMMAND),
SESSION(LookupActions.SESSION),
USERNAME(LookupActions.USERNAME),
SIGN(LookupActions.SIGN),
ITEM(LookupActions.ITEM),
UNKNOWN(-1),
ENTITY_SPAWN(LookupActions.ENTITY_SPAWN);
private final int id;
CoreProtectAction(int id) {
this.id = id;
}
public int id() {
return id;
}
public String actionString() {
return LookupActions.getActionString(id);
}
public static CoreProtectAction fromId(int id) {
for (CoreProtectAction action : values()) {
if (action.id == id) {
return action;
}
}
return UNKNOWN;
}
}
@@ -0,0 +1,110 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringJoiner;
import org.bukkit.entity.EntityType;
import net.coreprotect.api.result.EntityResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.utility.ErrorReporter;
import net.coreprotect.utility.WorldUtils;
/**
* Provides API methods for looking up entity spawn and kill events.
*/
public class EntityAPI {
private EntityAPI() {
throw new IllegalStateException("API class");
}
/**
* Performs a typed entity lookup, matching original or persisted current/final spawn locations.
*
* @param options
* Lookup options
* @return List of entity results at their original event locations
*/
public static List<EntityResult> performLookup(LookupOptions options) {
List<EntityResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
boolean snapshot = filter.beginDuckDBSnapshot(connection);
try {
StringBuilder query = new StringBuilder("SELECT entity_rows.time,entity_rows." + ConfigHandler.databaseType.getUserColumn() + ",entity_rows.wid,entity_rows.x,entity_rows.y,entity_rows.z,entity_rows.type,entity_rows.action,entity_rows.rolled_back FROM ");
query.append(filter.entityTable(connection, "entity_rows"));
query.append(' ');
filter.appendEntityWhere(connection, query, "entity_rows");
int[] actions = options.getEntityActions().isEmpty()
? new int[] { LookupActions.ENTITY_KILL, LookupActions.ENTITY_SPAWN }
: options.getEntityActions().stream().mapToInt(EntityAction::id).toArray();
LookupFilter.appendActionWhere(query, "entity_rows", actions);
if (!options.getIncludeEntities().isEmpty()) {
query.append(" AND ").append(entityPredicate(options.getIncludeEntities()));
}
if (!options.getExcludeEntities().isEmpty()) {
query.append(" AND NOT ").append(entityPredicate(options.getExcludeEntities()));
}
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder().replace("time", "entity_rows.time").replace("rowid", "entity_rows.rowid"));
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
filter.bindTrackedEntity(statement, 1);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(new EntityResult(
results.getLong("time"), UserStatement.getName(connection, results.getInt("user")), WorldUtils.getWorldName(results.getInt("wid")),
results.getInt("x"), results.getInt("y"), results.getInt("z"), results.getInt("type"), results.getInt("action"), results.getInt("rolled_back")
));
}
}
}
}
finally {
filter.endDuckDBSnapshot(connection, snapshot);
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
private static String entityPredicate(List<EntityType> entities) {
StringJoiner names = new StringJoiner(",");
for (EntityType entity : entities) {
String name = entity.name().toLowerCase(Locale.ROOT);
names.add("'" + name + "'");
names.add("'minecraft:" + name + "'");
}
String predicate = "entity_rows.type IN (SELECT id FROM " + ConfigHandler.prefix + "entity_map WHERE LOWER(entity) IN (" + names + "))";
if (entities.contains(EntityType.PLAYER)) {
predicate += " OR (entity_rows.action=" + LookupActions.ENTITY_KILL + " AND entity_rows.type=0)";
}
return "(" + predicate + ")";
}
}
@@ -0,0 +1,21 @@
package net.coreprotect.api;
import net.coreprotect.model.action.LookupActions;
/**
* Entity actions used by typed entity lookup filters.
*/
public enum EntityAction {
KILL(LookupActions.ENTITY_KILL),
SPAWN(LookupActions.ENTITY_SPAWN);
private final int id;
EntityAction(int id) {
this.id = id;
}
public int id() {
return id;
}
}
@@ -0,0 +1,155 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import net.coreprotect.api.result.InventoryResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.model.item.InventorySources;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.DatabaseUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for normalized player inventory transaction lookups. Tracked entity-container rows match their original or persisted
* current/final location and expose the persisted current/final location as container-source results.
*/
public class InventoryAPI {
private InventoryAPI() {
throw new IllegalStateException("API class");
}
public static List<InventoryResult> performLookup(LookupOptions options) {
List<InventoryResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
boolean snapshot = filter.beginDuckDBSnapshot(connection);
try {
StringBuilder whereBuilder = new StringBuilder();
filter.appendWhere(whereBuilder);
StringBuilder blockWhereBuilder = new StringBuilder(whereBuilder);
filter.appendMaterialWhere(blockWhereBuilder, "", true);
filter.appendMaterialWhere(whereBuilder);
String where = whereBuilder.toString();
StringBuilder entityWhereBuilder = new StringBuilder();
filter.appendEntityContainerWhere(entityWhereBuilder, "entity_rows", "spawn_rows");
filter.appendMaterialWhere(entityWhereBuilder, "entity_rows");
String query = buildQuery(
blockWhereBuilder.toString(),
where,
entityWhereBuilder.toString(),
options.getInventoryActions(),
options.hasLimit(),
filter.table(connection, "block", ""),
filter.table(connection, "container", ""),
filter.entityContainerTable(connection, "entity_rows"),
filter.table(connection, "item", "")
);
try (PreparedStatement statement = connection.prepareStatement(query)) {
int parameterIndex = filter.bind(statement);
parameterIndex = filter.bind(statement, parameterIndex);
parameterIndex = filter.bindEntityContainer(statement, parameterIndex);
parameterIndex = filter.bind(statement, parameterIndex);
if (options.hasLimit()) {
statement.setInt(parameterIndex++, options.getLimitCount());
statement.setInt(parameterIndex, options.getLimitOffset());
}
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseInventoryResult(connection, results));
}
}
}
}
finally {
filter.endDuckDBSnapshot(connection, snapshot);
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
private static String buildQuery(String blockWhere, String where, String entityWhere, List<InventoryAction> actions, boolean hasLimit, String blockTable, String containerTable, String entityContainerTable, String itemTable) {
StringBuilder query = new StringBuilder("SELECT * FROM (");
query.append("SELECT 0 AS source,rowid AS id,time,").append(ConfigHandler.databaseType.getUserColumn()).append(",wid,x,y,z,type,data,1 AS amount,meta AS metadata,action,rolled_back FROM ")
.append(blockTable).append(' ').append(blockWhere).append(" AND action = 1");
query.append(" UNION ALL ");
query.append("SELECT 1 AS source,rowid AS id,time,").append(ConfigHandler.databaseType.getUserColumn()).append(",wid,x,y,z,type,data,amount,metadata,action,rolled_back FROM ")
.append(containerTable).append(' ').append(where);
query.append(" UNION ALL ");
query.append("SELECT ").append(InventorySources.ENTITY_CONTAINER).append(" AS source,entity_rows.rowid AS id,entity_rows.time,entity_rows.").append(ConfigHandler.databaseType.getUserColumn()).append(",spawn_rows.current_wid AS wid,spawn_rows.x,spawn_rows.y,spawn_rows.z,entity_rows.type,entity_rows.data,entity_rows.amount,entity_rows.metadata,entity_rows.action,entity_rows.rolled_back FROM ")
.append(entityContainerTable).append(" JOIN ").append(ConfigHandler.prefix).append("entity_spawn spawn_rows ON spawn_rows.rowid=entity_rows.entity_spawn_rowid ").append(entityWhere);
query.append(" UNION ALL ");
query.append("SELECT 2 AS source,rowid AS id,time,").append(ConfigHandler.databaseType.getUserColumn()).append(",wid,x,y,z,type,0 AS data,amount,data AS metadata,action,rolled_back FROM ")
.append(itemTable).append(' ').append(where);
query.append(") AS inventory_lookup");
appendActionWhere(query, actions);
query.append(" ORDER BY time DESC, source DESC, id DESC");
if (hasLimit) {
query.append(" LIMIT ? OFFSET ?");
}
return query.toString();
}
private static void appendActionWhere(StringBuilder query, List<InventoryAction> actions) {
if (!actions.isEmpty()) {
StringJoiner predicates = new StringJoiner(" OR ");
for (InventoryAction action : actions) {
String sources = String.valueOf(action.sourceId());
if (action.sourceId() == InventorySources.CONTAINER) {
sources += "," + InventorySources.ENTITY_CONTAINER;
}
predicates.add("(source IN (" + sources + ") AND action = " + action.id() + ")");
}
query.append(" WHERE (").append(predicates).append(")");
}
}
private static InventoryResult parseInventoryResult(Connection connection, ResultSet results) throws Exception {
int userId = results.getInt("user");
String username = UserStatement.getName(connection, userId);
int source = results.getInt("source");
if (source == InventorySources.ENTITY_CONTAINER) {
source = InventorySources.CONTAINER;
}
return new InventoryResult(
results.getLong("time"), username, WorldUtils.getWorldName(results.getInt("wid")),
(int) Math.floor(results.getDouble("x")), (int) Math.floor(results.getDouble("y")), (int) Math.floor(results.getDouble("z")),
results.getInt("type"), results.getInt("data"), results.getInt("amount"), DatabaseUtils.getBytes(results, "metadata"),
results.getInt("action"), results.getInt("rolled_back"), source
);
}
}
@@ -0,0 +1,42 @@
package net.coreprotect.api;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.model.item.InventorySources;
import net.coreprotect.model.item.ItemTransactionActions;
/**
* Source-specific transaction actions used by typed inventory lookup filters.
* Container actions include tracked entity-container transactions.
*/
public enum InventoryAction {
BLOCK_PLACE(InventorySources.BLOCK, LookupActions.BLOCK_PLACE),
CONTAINER_REMOVE(InventorySources.CONTAINER, ItemTransactionActions.REMOVE),
CONTAINER_ADD(InventorySources.CONTAINER, ItemTransactionActions.ADD),
ITEM_DROP(InventorySources.ITEM, ItemTransactionActions.DROP),
ITEM_PICKUP(InventorySources.ITEM, ItemTransactionActions.PICKUP),
ITEM_REMOVE_ENDER(InventorySources.ITEM, ItemTransactionActions.REMOVE_ENDER),
ITEM_ADD_ENDER(InventorySources.ITEM, ItemTransactionActions.ADD_ENDER),
ITEM_THROW(InventorySources.ITEM, ItemTransactionActions.THROW),
ITEM_SHOOT(InventorySources.ITEM, ItemTransactionActions.SHOOT),
ITEM_BREAK(InventorySources.ITEM, ItemTransactionActions.BREAK),
ITEM_DESTROY(InventorySources.ITEM, ItemTransactionActions.DESTROY),
ITEM_CREATE(InventorySources.ITEM, ItemTransactionActions.CREATE),
ITEM_SELL(InventorySources.ITEM, ItemTransactionActions.SELL),
ITEM_BUY(InventorySources.ITEM, ItemTransactionActions.BUY);
private final int sourceId;
private final int id;
InventoryAction(int sourceId, int id) {
this.sourceId = sourceId;
this.id = id;
}
public int sourceId() {
return sourceId;
}
public int id() {
return id;
}
}
@@ -0,0 +1,98 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import net.coreprotect.api.result.ItemResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.logger.ItemLogger;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.DatabaseUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for world item transaction lookups.
*/
public class ItemAPI {
private ItemAPI() {
throw new IllegalStateException("API class");
}
public static List<ItemResult> performLookup(LookupOptions options) {
List<ItemResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
StringBuilder query = new StringBuilder("SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",wid,x,y,z,type,data,amount,action,rolled_back FROM ");
query.append(filter.table(connection, "item", "")).append(' ');
if (filter.hasLocation() && !ConfigHandler.databaseType.isDuckDB()) {
query.append(WorldUtils.getWidIndex("item"));
}
filter.appendWhere(query);
filter.appendMaterialWhere(query);
if (options.getItemActions().isEmpty()) {
query.append(" AND action NOT IN (")
.append(ItemLogger.ITEM_BREAK).append(",")
.append(ItemLogger.ITEM_DESTROY).append(",")
.append(ItemLogger.ITEM_CREATE).append(",")
.append(ItemLogger.ITEM_SELL).append(",")
.append(ItemLogger.ITEM_BUY).append(")");
}
else {
LookupFilter.appendActionWhere(query, "", options.getItemActions().stream().mapToInt(ItemAction::id).toArray());
}
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder());
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
filter.bind(statement);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseItemResult(connection, results));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
private static ItemResult parseItemResult(Connection connection, ResultSet results) throws Exception {
int userId = results.getInt("user");
String username = UserStatement.getName(connection, userId);
return new ItemResult(
results.getLong("time"), username, WorldUtils.getWorldName(results.getInt("wid")),
results.getInt("x"), results.getInt("y"), results.getInt("z"),
results.getInt("type"), results.getInt("amount"), DatabaseUtils.getBytes(results, "data"),
results.getInt("action"), results.getInt("rolled_back")
);
}
}
@@ -0,0 +1,30 @@
package net.coreprotect.api;
import net.coreprotect.model.item.ItemTransactionActions;
/**
* Item transaction actions used by typed item lookup filters.
*/
public enum ItemAction {
DROP(ItemTransactionActions.DROP),
PICKUP(ItemTransactionActions.PICKUP),
REMOVE_ENDER(ItemTransactionActions.REMOVE_ENDER),
ADD_ENDER(ItemTransactionActions.ADD_ENDER),
THROW(ItemTransactionActions.THROW),
SHOOT(ItemTransactionActions.SHOOT),
BREAK(ItemTransactionActions.BREAK),
DESTROY(ItemTransactionActions.DESTROY),
CREATE(ItemTransactionActions.CREATE),
SELL(ItemTransactionActions.SELL),
BUY(ItemTransactionActions.BUY);
private final int id;
ItemAction(int id) {
this.id = id;
}
public int id() {
return id;
}
}
@@ -0,0 +1,515 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.DuckDBLookupQuery;
import net.coreprotect.database.DuckDBSpatialIndex;
import net.coreprotect.database.LocationQuery;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.utility.ItemUtils;
import net.coreprotect.utility.MaterialUtils;
import net.coreprotect.utility.StringUtils;
import net.coreprotect.utility.WorldUtils;
final class LookupFilter {
private final Integer userId;
private final int checkTime;
private final Location location;
private final World world;
private final int radius;
private final int limitOffset;
private final int limitCount;
private final List<Material> includeMaterials;
private final List<Material> excludeMaterials;
private final Map<Integer, Material> materialTypes;
private final String includeUserIds;
private final String excludeUserIds;
private LookupFilter(Integer userId, int checkTime, Location location, World world, int radius, int limitOffset, int limitCount, List<Material> includeMaterials, List<Material> excludeMaterials, Map<Integer, Material> materialTypes, String includeUserIds, String excludeUserIds) {
this.userId = userId;
this.checkTime = checkTime;
this.location = location;
this.world = location == null ? world : location.getWorld();
this.radius = radius;
this.limitOffset = limitOffset;
this.limitCount = limitCount;
this.includeMaterials = includeMaterials;
this.excludeMaterials = excludeMaterials;
this.materialTypes = materialTypes;
this.includeUserIds = includeUserIds;
this.excludeUserIds = excludeUserIds;
}
static LookupFilter fromOptions(Connection connection, LookupOptions options) throws Exception {
if (options == null) {
options = LookupOptions.builder().build();
}
Integer userId = MessageAPI.getUserId(connection, options.getUser());
int checkTime = 0;
if (options.getTime() > 0) {
checkTime = (int) (System.currentTimeMillis() / 1000L) - options.getTime();
}
Map<Integer, Material> materialTypes = new HashMap<>();
if (!options.getIncludeMaterials().isEmpty() || !options.getExcludeMaterials().isEmpty()) {
try (PreparedStatement statement = connection.prepareStatement("SELECT id, material FROM " + ConfigHandler.prefix + "material_map");
ResultSet results = statement.executeQuery()) {
while (results.next()) {
materialTypes.put(results.getInt("id"), MaterialUtils.getTypeFromStoredName(results.getString("material")));
}
}
}
return new LookupFilter(userId, checkTime, options.getLocation(), options.getWorld(), options.getRadius(), options.getLimitOffset(), options.getLimitCount(),
options.getIncludeMaterials(), options.getExcludeMaterials(), materialTypes,
userIds(connection, options.getUsers()), userIds(connection, options.getExcludeUsers()));
}
boolean hasInvalidUser() {
return userId != null && userId == -1;
}
boolean hasInvalidLocation() {
return location != null && location.getWorld() == null;
}
boolean hasLocation() {
return location != null;
}
boolean beginDuckDBSnapshot(Connection connection) throws Exception {
if (!ConfigHandler.databaseType.isDuckDB() || location == null || !connection.getAutoCommit()) {
return false;
}
connection.setAutoCommit(false);
return true;
}
void endDuckDBSnapshot(Connection connection, boolean started) throws Exception {
if (!started) {
return;
}
try {
connection.rollback();
}
finally {
connection.setAutoCommit(true);
}
}
void appendWhere(StringBuilder query) {
appendWhere(query, "");
}
void appendWhere(StringBuilder query, String alias) {
String qualifier = alias.isEmpty() ? "" : alias + ".";
query.append("WHERE ").append(qualifier).append("time > ?");
if (userId != null) {
query.append(" AND ").append(qualifier).append(ConfigHandler.databaseType.getUserColumn()).append(" = ?");
}
appendUserWhere(query, alias, includeUserIds, excludeUserIds);
if (world != null) {
query.append(" AND ").append(LocationQuery.predicate(qualifier + "wid", " = ?"));
}
if (location != null) {
if (radius > 0) {
query.append(" AND ").append(LocationQuery.predicate(qualifier + "x", " >= ?"))
.append(" AND ").append(LocationQuery.predicate(qualifier + "x", " <= ?"))
.append(" AND ").append(LocationQuery.predicate(qualifier + "z", " >= ?"))
.append(" AND ").append(LocationQuery.predicate(qualifier + "z", " <= ?"));
}
else {
query.append(" AND ").append(LocationQuery.predicate(qualifier + "x", " = ?"))
.append(" AND ").append(qualifier).append("y = ? AND ").append(LocationQuery.predicate(qualifier + "z", " = ?"));
}
}
}
void appendEntityContainerWhere(StringBuilder query, String transactionAlias, String entityAlias) {
appendTrackedEntityWhere(query, transactionAlias, entityAlias, null);
}
void appendEntityWhere(Connection connection, StringBuilder query, String alias) {
appendTrackedEntityWhere(query, alias, "spawn_rows", table(connection, "block", "original_rows"));
}
private void appendTrackedEntityWhere(StringBuilder query, String transactionAlias, String entityAlias, String originalTable) {
String transaction = transactionAlias + ".";
String entity = entityAlias + ".";
query.append("WHERE ").append(transaction).append("time > ?");
if (userId != null) {
query.append(" AND ").append(transaction).append(ConfigHandler.databaseType.getUserColumn()).append(" = ?");
}
appendUserWhere(query, transactionAlias, includeUserIds, excludeUserIds);
if (world == null) {
return;
}
String original = originalTable == null ? transaction : "original_rows.";
String tracked = ") OR (";
String ending = "))";
if (originalTable != null) {
String trackingId = ConfigHandler.databaseType.isClickHouse() ? "accurateCastOrNull(linked_rows.data, 'UInt64')" : "linked_rows.data";
String trackedRows = "SELECT " + entity + "block_rowid FROM " + ConfigHandler.prefix + "entity_spawn " + entityAlias
+ " INNER JOIN " + ConfigHandler.prefix + "block linked_rows ON linked_rows.rowid=" + entity + "block_rowid AND " + trackingId + "=" + entity + "rowid"
+ " AND linked_rows.action=" + LookupActions.ENTITY_SPAWN + " WHERE (";
if (location == null) {
query.append(" AND (").append(LocationQuery.predicate(transaction + "wid", " = ?"))
.append(" OR ").append(transaction).append("rowid IN (").append(trackedRows).append(entity).append("current_wid = ?)))");
return;
}
// Separate location candidates keep both spatial indexes usable, including on MySQL.
query.append(" AND ").append(transaction).append("rowid IN (SELECT rowid FROM (SELECT original_rows.rowid FROM ").append(originalTable).append(" WHERE (");
tracked = ") UNION ALL " + trackedRows;
ending = ")) entity_locations)";
}
else {
query.append(" AND ((");
}
query.append(LocationQuery.predicate(original + "wid", " = ?"));
if (location == null) {
query.append(tracked).append(entity).append("current_wid = ?").append(ending);
return;
}
if (radius > 0) {
query.append(" AND ").append(LocationQuery.predicate(original + "x", " >= ?"))
.append(" AND ").append(LocationQuery.predicate(original + "x", " <= ?"))
.append(" AND ").append(LocationQuery.predicate(original + "z", " >= ?"))
.append(" AND ").append(LocationQuery.predicate(original + "z", " <= ?"));
}
else {
query.append(" AND ").append(LocationQuery.predicate(original + "x", " = ?"))
.append(" AND ").append(original).append("y = ? AND ").append(LocationQuery.predicate(original + "z", " = ?"));
}
query.append(tracked).append(entity).append("current_wid = ?");
if (radius > 0) {
query.append(" AND ").append(entity).append("x >= ? AND ").append(entity).append("x < ? AND ").append(entity).append("z >= ? AND ").append(entity).append("z < ?");
}
else {
query.append(" AND ").append(entity).append("x >= ? AND ").append(entity).append("x < ? AND ").append(entity).append("y >= ? AND ").append(entity).append("y < ? AND ").append(entity).append("z >= ? AND ").append(entity).append("z < ?");
}
query.append(ending);
}
static void appendActionWhere(StringBuilder query, String alias, int[] actions) {
if (actions.length > 0) {
StringJoiner ids = new StringJoiner(",");
for (int action : actions) {
ids.add(String.valueOf(action));
}
query.append(" AND ").append(alias.isEmpty() ? "" : alias + ".").append("action IN (").append(ids).append(")");
}
}
void appendMaterialWhere(StringBuilder query) {
appendMaterialWhere(query, "");
}
void appendMaterialWhere(StringBuilder query, String alias) {
appendMaterialWhere(query, alias, false);
}
void appendMaterialWhere(StringBuilder query, String alias, boolean inventoryBlock) {
String qualifier = alias.isEmpty() ? "" : alias + ".";
if (!includeMaterials.isEmpty()) {
query.append(" AND ").append(qualifier).append("type IN (").append(materialIds(includeMaterials, inventoryBlock)).append(")");
}
if (!excludeMaterials.isEmpty()) {
query.append(" AND ").append(qualifier).append("type NOT IN (").append(materialIds(excludeMaterials, inventoryBlock)).append(")");
}
}
void appendBlockMaterialWhere(StringBuilder query) {
String entityActions = LookupActions.ENTITY_KILL + "," + LookupActions.ENTITY_SPAWN;
if (!includeMaterials.isEmpty()) {
query.append(" AND action NOT IN (").append(entityActions).append(") AND ").append(blockMaterialPredicate(includeMaterials));
}
if (!excludeMaterials.isEmpty()) {
query.append(" AND (action IN (").append(entityActions).append(") OR NOT ").append(blockMaterialPredicate(excludeMaterials)).append(")");
}
}
private String blockMaterialPredicate(List<Material> materials) {
List<Material> blockMaterials = new ArrayList<>(materials);
blockMaterials.removeAll(List.of(Material.STONE));
return "(type IN (" + materialIds(blockMaterials, false) + ") OR " + legacyStonePredicate(materials) + ")";
}
private String legacyStonePredicate(List<Material> materials) {
StringJoiner stoneData = new StringJoiner(",");
for (int data = 1; data <= 6; data++) {
Material material = Material.getMaterial(StringUtils.nameFilter("stone", data).toUpperCase(Locale.ROOT));
if (materials.contains(material)) {
stoneData.add(String.valueOf(data));
}
}
StringJoiner predicates = new StringJoiner(" OR ");
if (materials.contains(Material.STONE)) {
predicates.add("COALESCE(data,0) NOT BETWEEN 1 AND 6");
}
if (stoneData.length() > 0) {
predicates.add("COALESCE(data,0) IN (" + stoneData + ")");
}
if (predicates.length() == 0) {
return "1 = 0";
}
return "(type IN (" + materialIds(List.of(Material.STONE), false) + ") AND (" + predicates + "))";
}
String table(Connection connection, String table, String alias) {
if (location == null) {
return ConfigHandler.prefix + table + alias(alias);
}
int x = location.getBlockX();
int z = location.getBlockZ();
int minimumX = radius > 0 ? MessageAPI.clampToInt((long) x - radius) : x;
int maximumX = radius > 0 ? MessageAPI.clampToInt((long) x + radius) : x;
int minimumZ = radius > 0 ? MessageAPI.clampToInt((long) z - radius) : z;
int maximumZ = radius > 0 ? MessageAPI.clampToInt((long) z + radius) : z;
int worldId = WorldUtils.getWorldId(location.getWorld().getName());
return DuckDBLookupQuery.spatialTable(connection, table, worldId, minimumX, maximumX, minimumZ, maximumZ, alias);
}
String entityContainerTable(Connection connection, String alias) throws Exception {
return trackedEntityTable(connection, "entity_container", alias);
}
String entityTable(Connection connection, String alias) throws Exception {
return trackedEntityTable(connection, "block", alias);
}
private String trackedEntityTable(Connection connection, String table, String alias) throws Exception {
if (location == null || !ConfigHandler.databaseType.isDuckDB()) {
return ConfigHandler.prefix + table + alias(alias);
}
int x = location.getBlockX();
int z = location.getBlockZ();
int minimumX = radius > 0 ? MessageAPI.clampToInt((long) x - radius) : x;
int maximumX = radius > 0 ? MessageAPI.clampToInt((long) x + radius) : x;
int minimumZ = radius > 0 ? MessageAPI.clampToInt((long) z - radius) : z;
int maximumZ = radius > 0 ? MessageAPI.clampToInt((long) z + radius) : z;
int worldId = WorldUtils.getWorldId(location.getWorld().getName());
boolean block = table.equals("block");
List<Long> rowIds = loadCurrentEntityRowIds(connection, block ? "block_rowid" : "rowid");
return DuckDBSpatialIndex.tableExpression(
connection,
ConfigHandler.prefix,
table,
worldId,
minimumX,
maximumX,
minimumZ,
maximumZ,
block ? Collections.emptySet() : rowIds.stream().map(Long::intValue).collect(Collectors.toList()),
block ? rowIds : Collections.emptySet(),
alias
);
}
private List<Long> loadCurrentEntityRowIds(Connection connection, String column) throws Exception {
List<Long> rowIds = new ArrayList<>();
StringBuilder query = new StringBuilder("SELECT ").append(column).append(" FROM ").append(ConfigHandler.prefix).append("entity_spawn WHERE current_wid=?");
if (radius > 0) {
query.append(" AND x>=? AND x<? AND z>=? AND z<?");
}
else {
query.append(" AND x>=? AND x<? AND y>=? AND y<? AND z>=? AND z<?");
}
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
int parameterIndex = 1;
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
statement.setInt(parameterIndex++, WorldUtils.getWorldId(location.getWorld().getName()));
if (radius > 0) {
int minimumX = MessageAPI.clampToInt((long) x - radius);
int maximumX = MessageAPI.clampToInt((long) x + radius);
int minimumZ = MessageAPI.clampToInt((long) z - radius);
int maximumZ = MessageAPI.clampToInt((long) z + radius);
statement.setInt(parameterIndex++, minimumX);
statement.setLong(parameterIndex++, (long) maximumX + 1L);
statement.setInt(parameterIndex++, minimumZ);
statement.setLong(parameterIndex, (long) maximumZ + 1L);
}
else {
statement.setInt(parameterIndex++, x);
statement.setLong(parameterIndex++, (long) x + 1L);
statement.setInt(parameterIndex++, y);
statement.setLong(parameterIndex++, (long) y + 1L);
statement.setInt(parameterIndex++, z);
statement.setLong(parameterIndex, (long) z + 1L);
}
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next() && rowIds.size() <= 4_096) {
long rowId = resultSet.getLong(1);
if (!resultSet.wasNull()) {
rowIds.add(rowId);
}
}
}
}
return rowIds;
}
void appendLimit(StringBuilder query) {
if (limitOffset >= 0 && limitCount >= 0) {
query.append(" LIMIT ").append(limitCount).append(" OFFSET ").append(limitOffset);
}
}
int bind(PreparedStatement statement) throws Exception {
return bind(statement, 1);
}
int bind(PreparedStatement statement, int parameterIndex) throws Exception {
statement.setInt(parameterIndex++, checkTime);
if (userId != null) {
statement.setInt(parameterIndex++, userId);
}
if (world != null) {
statement.setInt(parameterIndex++, WorldUtils.getWorldId(world.getName()));
}
if (location != null) {
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
if (radius > 0) {
statement.setInt(parameterIndex++, MessageAPI.clampToInt((long) x - radius));
statement.setInt(parameterIndex++, MessageAPI.clampToInt((long) x + radius));
statement.setInt(parameterIndex++, MessageAPI.clampToInt((long) z - radius));
statement.setInt(parameterIndex++, MessageAPI.clampToInt((long) z + radius));
}
else {
statement.setInt(parameterIndex++, x);
statement.setInt(parameterIndex++, y);
statement.setInt(parameterIndex++, z);
}
}
return parameterIndex;
}
int bindEntityContainer(PreparedStatement statement, int parameterIndex) throws Exception {
return bindTrackedEntity(statement, parameterIndex);
}
int bindTrackedEntity(PreparedStatement statement, int parameterIndex) throws Exception {
statement.setInt(parameterIndex++, checkTime);
if (userId != null) {
statement.setInt(parameterIndex++, userId);
}
if (world == null) {
return parameterIndex;
}
if (location == null) {
int worldId = WorldUtils.getWorldId(world.getName());
statement.setInt(parameterIndex++, worldId);
statement.setInt(parameterIndex++, worldId);
return parameterIndex;
}
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
int worldId = WorldUtils.getWorldId(location.getWorld().getName());
statement.setInt(parameterIndex++, worldId);
if (radius > 0) {
int minimumX = MessageAPI.clampToInt((long) x - radius);
int maximumX = MessageAPI.clampToInt((long) x + radius);
int minimumZ = MessageAPI.clampToInt((long) z - radius);
int maximumZ = MessageAPI.clampToInt((long) z + radius);
statement.setInt(parameterIndex++, minimumX);
statement.setInt(parameterIndex++, maximumX);
statement.setInt(parameterIndex++, minimumZ);
statement.setInt(parameterIndex++, maximumZ);
statement.setInt(parameterIndex++, worldId);
statement.setInt(parameterIndex++, minimumX);
statement.setLong(parameterIndex++, (long) maximumX + 1L);
statement.setInt(parameterIndex++, minimumZ);
statement.setLong(parameterIndex++, (long) maximumZ + 1L);
}
else {
statement.setInt(parameterIndex++, x);
statement.setInt(parameterIndex++, y);
statement.setInt(parameterIndex++, z);
statement.setInt(parameterIndex++, worldId);
statement.setInt(parameterIndex++, x);
statement.setLong(parameterIndex++, (long) x + 1L);
statement.setInt(parameterIndex++, y);
statement.setLong(parameterIndex++, (long) y + 1L);
statement.setInt(parameterIndex++, z);
statement.setLong(parameterIndex++, (long) z + 1L);
}
return parameterIndex;
}
static String userIds(Connection connection, List<String> users) throws Exception {
StringJoiner result = new StringJoiner(",");
for (String user : users) {
Integer id = MessageAPI.getUserId(connection, user);
if (id == null) {
// An empty name or #global matches every user.
return null;
}
result.add(String.valueOf(id));
}
return result.toString();
}
static void appendUserWhere(StringBuilder query, String alias, String includeUserIds, String excludeUserIds) {
String column = (alias.isEmpty() ? "" : alias + ".") + ConfigHandler.databaseType.getUserColumn();
if (includeUserIds != null && !includeUserIds.isEmpty()) {
query.append(" AND ").append(column).append(" IN (").append(includeUserIds).append(")");
}
if (excludeUserIds == null) {
query.append(" AND 1 = 0");
}
else if (!excludeUserIds.isEmpty()) {
query.append(" AND ").append(column).append(" NOT IN (").append(excludeUserIds).append(")");
}
}
private String materialIds(List<Material> materials, boolean inventoryBlock) {
StringJoiner result = new StringJoiner(",");
for (Map.Entry<Integer, Material> entry : materialTypes.entrySet()) {
Material material = inventoryBlock ? ItemUtils.itemFilter(entry.getValue(), true) : entry.getValue();
if (material != null && materials.contains(material)) {
result.add(String.valueOf(entry.getKey()));
}
}
return result.length() == 0 ? "-1" : result.toString();
}
private static String alias(String alias) {
return alias == null || alias.isEmpty() ? "" : " AS " + alias;
}
}
@@ -0,0 +1,309 @@
package net.coreprotect.api;
import java.util.List;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
/**
* Shared options for typed lookup API methods.
*/
public final class LookupOptions {
private final String user;
private final int time;
private final int radius;
private final Location location;
private final World world;
private final int limitOffset;
private final int limitCount;
private final List<Material> includeMaterials;
private final List<Material> excludeMaterials;
private final List<String> users;
private final List<String> excludeUsers;
private final List<String> textStartsWithAny;
private final List<String> textStartsWithNone;
private final List<ContainerAction> containerActions;
private final List<ItemAction> itemActions;
private final List<InventoryAction> inventoryActions;
private final List<BlockAction> blockActions;
private final List<SessionAction> sessionActions;
private final List<EntityAction> entityActions;
private final List<EntityType> includeEntities;
private final List<EntityType> excludeEntities;
private LookupOptions(Builder builder) {
this.user = builder.user;
this.time = builder.time;
this.radius = builder.radius;
this.location = builder.location;
this.world = builder.world;
this.limitOffset = builder.limitOffset;
this.limitCount = builder.limitCount;
this.includeMaterials = builder.includeMaterials;
this.excludeMaterials = builder.excludeMaterials;
this.users = builder.users;
this.excludeUsers = builder.excludeUsers;
this.textStartsWithAny = builder.textStartsWithAny;
this.textStartsWithNone = builder.textStartsWithNone;
this.containerActions = builder.containerActions;
this.itemActions = builder.itemActions;
this.inventoryActions = builder.inventoryActions;
this.blockActions = builder.blockActions;
this.sessionActions = builder.sessionActions;
this.entityActions = builder.entityActions;
this.includeEntities = builder.includeEntities;
this.excludeEntities = builder.excludeEntities;
}
public static Builder builder() {
return new Builder();
}
public String getUser() {
return user;
}
public int getTime() {
return time;
}
public int getRadius() {
return radius;
}
public Location getLocation() {
return location;
}
public World getWorld() {
return world;
}
public int getLimitOffset() {
return limitOffset;
}
public int getLimitCount() {
return limitCount;
}
public boolean hasLimit() {
return limitOffset >= 0 && limitCount >= 0;
}
public List<Material> getIncludeMaterials() {
return includeMaterials;
}
public List<Material> getExcludeMaterials() {
return excludeMaterials;
}
public List<String> getUsers() {
return users;
}
public List<String> getExcludeUsers() {
return excludeUsers;
}
public List<String> getTextStartsWithAny() {
return textStartsWithAny;
}
public List<String> getTextStartsWithNone() {
return textStartsWithNone;
}
public List<ContainerAction> getContainerActions() {
return containerActions;
}
public List<ItemAction> getItemActions() {
return itemActions;
}
public List<InventoryAction> getInventoryActions() {
return inventoryActions;
}
public List<BlockAction> getBlockActions() {
return blockActions;
}
public List<SessionAction> getSessionActions() {
return sessionActions;
}
public List<EntityAction> getEntityActions() {
return entityActions;
}
public List<EntityType> getIncludeEntities() {
return includeEntities;
}
public List<EntityType> getExcludeEntities() {
return excludeEntities;
}
public static final class Builder {
private String user;
private int time;
private int radius = -1;
private Location location;
private World world;
private int limitOffset = -1;
private int limitCount = -1;
private List<Material> includeMaterials = List.of();
private List<Material> excludeMaterials = List.of();
private List<String> users = List.of();
private List<String> excludeUsers = List.of();
private List<String> textStartsWithAny = List.of();
private List<String> textStartsWithNone = List.of();
private List<ContainerAction> containerActions = List.of();
private List<ItemAction> itemActions = List.of();
private List<InventoryAction> inventoryActions = List.of();
private List<BlockAction> blockActions = List.of();
private List<SessionAction> sessionActions = List.of();
private List<EntityAction> entityActions = List.of();
private List<EntityType> includeEntities = List.of();
private List<EntityType> excludeEntities = List.of();
private Builder() {
}
public Builder user(String user) {
this.user = user;
return this;
}
public Builder time(int time) {
this.time = time;
return this;
}
public Builder location(Location location) {
this.location = location;
this.world = null;
this.radius = 0;
return this;
}
public Builder radius(Location location, int radius) {
this.location = location;
this.world = null;
this.radius = radius;
return this;
}
public Builder world(World world) {
this.world = world;
this.location = null;
this.radius = -1;
return this;
}
public Builder limit(int offset, int count) {
this.limitOffset = offset;
this.limitCount = count;
return this;
}
public Builder includeMaterials(List<Material> materials) {
this.includeMaterials = List.copyOf(materials);
return this;
}
public Builder excludeMaterials(List<Material> materials) {
this.excludeMaterials = List.copyOf(materials);
return this;
}
public Builder users(List<String> users) {
this.users = List.copyOf(users);
return this;
}
public Builder excludeUsers(List<String> users) {
this.excludeUsers = List.copyOf(users);
return this;
}
/**
* Includes chat, command and sign text matching any literal prefix.
* Empty means no inclusion restriction. Other lookup types ignore this option.
* @throws IllegalArgumentException if a prefix has fewer than three Unicode code points
*/
public Builder textStartsWithAny(List<String> prefixes) {
this.textStartsWithAny = textPrefixes(prefixes);
return this;
}
/**
* Excludes chat, command and sign text matching any literal prefix.
* Exclusions take precedence over inclusions. A leading '-' is literal.
* @throws IllegalArgumentException if a prefix has fewer than three Unicode code points
*/
public Builder textStartsWithNone(List<String> prefixes) {
this.textStartsWithNone = textPrefixes(prefixes);
return this;
}
private static List<String> textPrefixes(List<String> prefixes) {
List<String> copy = List.copyOf(prefixes);
for (String prefix : copy) {
if (prefix.codePointCount(0, prefix.length()) < 3) {
throw new IllegalArgumentException("Text filter prefixes must contain at least three code points");
}
}
return copy;
}
public Builder containerActions(List<ContainerAction> actions) {
this.containerActions = List.copyOf(actions);
return this;
}
public Builder itemActions(List<ItemAction> actions) {
this.itemActions = List.copyOf(actions);
return this;
}
public Builder inventoryActions(List<InventoryAction> actions) {
this.inventoryActions = List.copyOf(actions);
return this;
}
public Builder blockActions(List<BlockAction> actions) {
this.blockActions = List.copyOf(actions);
return this;
}
public Builder sessionActions(List<SessionAction> actions) {
this.sessionActions = List.copyOf(actions);
return this;
}
public Builder entityActions(List<EntityAction> actions) {
this.entityActions = List.copyOf(actions);
return this;
}
public Builder includeEntities(List<EntityType> entities) {
this.includeEntities = List.copyOf(entities);
return this;
}
public Builder excludeEntities(List<EntityType> entities) {
this.excludeEntities = List.copyOf(entities);
return this;
}
public LookupOptions build() {
return new LookupOptions(this);
}
}
}
@@ -0,0 +1,121 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import net.coreprotect.api.result.MessageResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.LookupRaw;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.utility.ErrorReporter;
import net.coreprotect.utility.WorldUtils;
/**
* Provides API methods for player chat and command lookups.
*/
public class MessageAPI {
private MessageAPI() {
throw new IllegalStateException("API class");
}
public static List<MessageResult> performChatLookup(String user, int offset) {
return performChatLookup(LookupOptions.builder().user(user).time(offset).build());
}
public static List<MessageResult> performChatLookup(LookupOptions options) {
return performLookup("chat", CoreProtectAction.CHAT, options);
}
public static List<MessageResult> performCommandLookup(String user, int offset) {
return performCommandLookup(LookupOptions.builder().user(user).time(offset).build());
}
public static List<MessageResult> performCommandLookup(LookupOptions options) {
return performLookup("command", CoreProtectAction.COMMAND, options);
}
private static List<MessageResult> performLookup(String table, CoreProtectAction action, LookupOptions options) {
List<MessageResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
StringBuilder query = new StringBuilder("SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",wid,x,y,z,message FROM ");
query.append(filter.table(connection, table, "")).append(' ');
if (filter.hasLocation() && !ConfigHandler.databaseType.isDuckDB()) {
query.append(WorldUtils.getWidIndex(table));
}
filter.appendWhere(query);
List<String> messageBindings = new ArrayList<>();
query = new StringBuilder(LookupRaw.appendMessageFilters(query.toString(),
options == null ? List.of() : options.getTextStartsWithAny(),
options == null ? List.of() : options.getTextStartsWithNone(), table, messageBindings));
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder());
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
int parameterIndex = filter.bind(statement);
for (String binding : messageBindings) {
statement.setString(parameterIndex++, binding);
}
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseMessageResult(connection, results, action.id()));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
static Integer getUserId(Connection connection, String user) throws Exception {
if (user == null || user.isEmpty() || user.equalsIgnoreCase("#global")) {
return null;
}
return UserStatement.findId(connection, user);
}
private static MessageResult parseMessageResult(Connection connection, ResultSet results, int actionId) throws Exception {
int userId = results.getInt("user");
String username = UserStatement.getName(connection, userId);
return new MessageResult(
results.getLong("time"), username, WorldUtils.getWorldName(results.getInt("wid")),
results.getInt("x"), results.getInt("y"), results.getInt("z"),
results.getString("message"), actionId
);
}
static int clampToInt(long value) {
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
else if (value < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return (int) value;
}
}
@@ -0,0 +1,131 @@
package net.coreprotect.api;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import net.coreprotect.config.Config;
import net.coreprotect.consumer.Consumer;
import net.coreprotect.consumer.Queue;
import net.coreprotect.consumer.process.Process;
import net.coreprotect.utility.MaterialUtils;
import net.coreprotect.utility.StringUtils;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for looking up block-related actions in the processing queue.
* This class allows for retrieving actions that have not yet been saved to the database.
*/
public class QueueLookup extends Queue {
/**
* Private constructor to prevent instantiation.
* This is a utility class with static methods only.
*/
private QueueLookup() {
throw new IllegalStateException("API class");
}
/**
* Performs a lookup of block-related actions in the processing queue for the specified block.
* This allows retrieving actions that have not yet been committed to the database.
*
* @param block
* The block to look up in the processing queue
* @return List of results in a String array format, empty list if API is disabled or no results found
*/
public static List<String[]> performLookup(Block block) {
List<String[]> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (block == null) {
return result;
}
try {
ArrayList<Object[]> consumerData;
Map<Integer, String[]> users;
Map<Integer, Object> consumerObject;
synchronized (Queue.class) {
synchronized (Consumer.consumer_id) {
int currentConsumer = Consumer.currentConsumer;
consumerData = new ArrayList<>(Consumer.consumer.get(currentConsumer));
users = new HashMap<>(Consumer.consumerUsers.get(currentConsumer));
consumerObject = new HashMap<>(Consumer.consumerObjects.get(currentConsumer));
}
}
if (consumerData.isEmpty()) {
return result;
}
// Current block location for comparison with actions in the queue
Location blockLocation = block.getLocation();
// Check for block actions in the processing queue
for (Object[] data : consumerData) {
int id = (int) data[0];
int action = (int) data[1];
// Only process block break and place actions
if (action != Process.BLOCK_BREAK && action != Process.BLOCK_PLACE) {
continue;
}
String[] userData = users.get(id);
Object objectData = consumerObject.get(id);
// Verify the action pertains to the requested block
if (isActionForBlock(userData, objectData, blockLocation)) {
Material blockType = (Material) data[2];
int legacyData = (int) data[3];
String blockData = (String) data[7];
String user = userData[0];
BlockState blockState = (BlockState) objectData;
Location location = blockState.getLocation();
int worldId = WorldUtils.getWorldId(location.getWorld().getName());
int resultType = MaterialUtils.getBlockId(blockType);
int time = (int) (System.currentTimeMillis() / 1000L);
String[] lookupData = new String[] { String.valueOf(time), user, String.valueOf(location.getBlockX()), String.valueOf(location.getBlockY()), String.valueOf(location.getBlockZ()), String.valueOf(resultType), String.valueOf(legacyData), String.valueOf(action), "0", String.valueOf(worldId), blockData };
result.add(StringUtils.toStringArray(lookupData));
}
}
// Reverse the result list to match database lookup order (most recent first)
Collections.reverse(result);
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
/**
* Determines if an action in the queue pertains to the specified block location.
*
* @param userData
* User data associated with the action
* @param objectData
* Object data associated with the action
* @param blockLocation
* Location of the block being looked up
* @return true if the action pertains to the specified block, false otherwise
*/
private static boolean isActionForBlock(String[] userData, Object objectData, Location blockLocation) {
return userData != null && objectData != null && (objectData instanceof BlockState) && ((BlockState) objectData).getLocation().equals(blockLocation);
}
}
@@ -0,0 +1,21 @@
package net.coreprotect.api;
import net.coreprotect.model.action.SessionActions;
/**
* Session actions used by typed session lookup filters.
*/
public enum SessionAction {
LOGOUT(SessionActions.LOGOUT),
LOGIN(SessionActions.LOGIN);
private final int id;
SessionAction(int id) {
this.id = id;
}
public int id() {
return id;
}
}
@@ -0,0 +1,210 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import net.coreprotect.api.result.SessionResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for looking up player session data in the CoreProtect database.
* Session data includes login/logout events and their associated timestamps and locations.
*/
public class SessionLookup {
/**
* The session type ID used for identifying session-related events.
*/
public static final int ID = 0;
/**
* Private constructor to prevent instantiation.
* This is a utility class with static methods only.
*/
private SessionLookup() {
throw new IllegalStateException("API class");
}
/**
* Performs a lookup of session-related actions for the specified user.
* This returns login and logout events within the specified time constraint.
*
* @param user
* The username to look up session data for
* @param offset
* Time constraint in seconds (0 means no time constraint)
* @return List of results in a String array format, empty list if API is disabled or no results found
*/
public static List<String[]> performLookup(String user, int offset) {
List<String[]> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (user == null) {
return result;
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
// Prepare lookup parameters
String type = String.valueOf(ID);
int time = (int) (System.currentTimeMillis() / 1000L);
int checkTime = calculateCheckTime(time, offset);
Integer userId = MessageAPI.getUserId(connection, user);
if (userId == null || userId < 0) {
return result;
}
// Query session data from database
try (Statement statement = connection.createStatement()) {
String query = buildSessionQuery(userId, checkTime);
try (ResultSet results = statement.executeQuery(query)) {
while (results.next()) {
String[] sessionData = extractSessionData(connection, results, type);
result.add(sessionData);
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
/**
* Performs a typed lookup of session-related actions.
*
* @param options
* Lookup options
* @return List of typed session results, empty list if API is disabled or no results found
*/
public static List<SessionResult> performLookup(LookupOptions options) {
List<SessionResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
StringBuilder query = new StringBuilder("SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",wid,x,y,z,action FROM ");
query.append(filter.table(connection, "session", "")).append(' ');
if (filter.hasLocation() && !ConfigHandler.databaseType.isDuckDB()) {
query.append(WorldUtils.getWidIndex("session"));
}
filter.appendWhere(query);
LookupFilter.appendActionWhere(query, "", options.getSessionActions().stream().mapToInt(SessionAction::id).toArray());
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder());
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
filter.bind(statement);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseSessionResult(connection, results));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
/**
* Calculates the time threshold for the session lookup.
*
* @param currentTime
* The current time in seconds
* @param offset
* The time offset in seconds
* @return The calculated time threshold
*/
private static int calculateCheckTime(int currentTime, int offset) {
return (offset > 0) ? currentTime - offset : 0;
}
/**
* Builds the SQL query for retrieving session data.
*
* @param userId
* The user ID to query for
* @param checkTime
* The time threshold for the query
* @return The SQL query string
*/
private static String buildSessionQuery(int userId, int checkTime) {
return "SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",wid,x,y,z,action FROM " + ConfigHandler.prefix + "session WHERE " + ConfigHandler.databaseType.getUserColumn() + " = " + userId + " AND time > " + checkTime + " ORDER BY " + ConfigHandler.getDescendingEventOrder();
}
/**
* Extracts session data from a result set row.
*
* @param connection
* The database connection
* @param results
* The result set to extract data from
* @param type
* The session type ID
* @return An array of session data values
* @throws Exception
* if an error occurs while extracting data
*/
private static String[] extractSessionData(Connection connection, ResultSet results, String type) throws Exception {
String resultTime = results.getString("time");
int resultUserId = results.getInt("user");
String resultWorldId = results.getString("wid");
String resultX = results.getString("x");
String resultY = results.getString("y");
String resultZ = results.getString("z");
String resultAction = results.getString("action");
String resultUser = UserStatement.getName(connection, resultUserId);
// Create and return the session data array
return new String[] { resultTime, resultUser, resultX, resultY, resultZ, resultWorldId, type, resultAction };
}
private static SessionResult parseSessionResult(Connection connection, ResultSet results) throws Exception {
int userId = results.getInt("user");
String username = UserStatement.getName(connection, userId);
return new SessionResult(
results.getLong("time"), username, WorldUtils.getWorldName(results.getInt("wid")),
results.getInt("x"), results.getInt("y"), results.getInt("z"), results.getInt("action")
);
}
}
@@ -0,0 +1,115 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Location;
import net.coreprotect.api.result.SignResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.LookupRaw;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.model.action.SignActions;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for sign text lookups.
*/
public class SignAPI {
private SignAPI() {
throw new IllegalStateException("API class");
}
public static List<SignResult> performLookup(Location location, int offset) {
if (location == null) {
return new ArrayList<>();
}
return performLookup(LookupOptions.builder().time(offset).location(location).build());
}
public static List<SignResult> performLookup(LookupOptions options) {
List<SignResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
LookupFilter filter = LookupFilter.fromOptions(connection, options);
if (filter.hasInvalidUser() || filter.hasInvalidLocation()) {
return result;
}
StringBuilder query = new StringBuilder("SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",wid,x,y,z,action,color,color_secondary,data,waxed,face,line_1,line_2,line_3,line_4,line_5,line_6,line_7,line_8 FROM ");
query.append(filter.table(connection, "sign", "")).append(' ');
if (filter.hasLocation() && !ConfigHandler.databaseType.isDuckDB()) {
query.append(WorldUtils.getWidIndex("sign"));
}
filter.appendWhere(query);
List<String> messageBindings = new ArrayList<>();
query = new StringBuilder(LookupRaw.appendMessageFilters(query.toString(),
options == null ? List.of() : options.getTextStartsWithAny(),
options == null ? List.of() : options.getTextStartsWithNone(), "sign", messageBindings));
query.append(" AND action = '").append(SignActions.PLACE).append("' AND (LENGTH(line_1) > 0 OR LENGTH(line_2) > 0 OR LENGTH(line_3) > 0 OR LENGTH(line_4) > 0 OR LENGTH(line_5) > 0 OR LENGTH(line_6) > 0 OR LENGTH(line_7) > 0 OR LENGTH(line_8) > 0)");
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder());
filter.appendLimit(query);
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
int parameterIndex = filter.bind(statement);
for (String binding : messageBindings) {
statement.setString(parameterIndex++, binding);
}
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseSignResult(connection, results));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
private static SignResult parseSignResult(Connection connection, ResultSet results) throws Exception {
int userId = results.getInt("user");
String username = UserStatement.getName(connection, userId);
String[] lines = new String[] {
valueOrEmpty(results.getString("line_1")),
valueOrEmpty(results.getString("line_2")),
valueOrEmpty(results.getString("line_3")),
valueOrEmpty(results.getString("line_4")),
valueOrEmpty(results.getString("line_5")),
valueOrEmpty(results.getString("line_6")),
valueOrEmpty(results.getString("line_7")),
valueOrEmpty(results.getString("line_8"))
};
return new SignResult(
results.getLong("time"), username, WorldUtils.getWorldName(results.getInt("wid")),
results.getInt("x"), results.getInt("y"), results.getInt("z"),
results.getInt("action"), results.getInt("color"), results.getInt("color_secondary"),
results.getInt("data"), results.getInt("waxed") == 1, results.getInt("face") == 0, lines
);
}
private static String valueOrEmpty(String value) {
return value == null ? "" : value;
}
}
@@ -0,0 +1,185 @@
package net.coreprotect.api;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.coreprotect.api.result.UsernameResult;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.utility.DatabaseUtils;
import net.coreprotect.utility.ErrorReporter;
/**
* Provides API methods for username history lookups.
*/
public class UsernameAPI {
private UsernameAPI() {
throw new IllegalStateException("API class");
}
public static List<UsernameResult> performLookup(String user, int offset) {
return performLookup(LookupOptions.builder().user(user).time(offset).build());
}
public static List<UsernameResult> performLookup(LookupOptions options) {
List<UsernameResult> result = new ArrayList<>();
if (!Config.getGlobal().API_ENABLED) {
return result;
}
if (options == null) {
options = LookupOptions.builder().build();
}
try (Connection connection = Database.getConnection(false, 1000)) {
if (connection == null) {
return result;
}
Set<String> uuids = getUuids(connection, options.getUser());
if (uuids == null) {
return result;
}
Set<String> includedUuids = getUuids(connection, options.getUsers());
if (includedUuids != null && includedUuids.isEmpty() && !options.getUsers().isEmpty()) {
return result;
}
Set<String> excludedUuids = getUuids(connection, options.getExcludeUsers());
if (excludedUuids == null) {
return result;
}
// A NULL in NOT IN would also exclude unrelated users.
excludedUuids.remove(null);
int checkTime = 0;
if (options.getTime() > 0) {
checkTime = (int) (System.currentTimeMillis() / 1000L) - options.getTime();
}
StringBuilder query = new StringBuilder("SELECT time,uuid," + ConfigHandler.databaseType.getUserColumn() + " FROM ");
query.append(ConfigHandler.prefix).append("username_log WHERE time > ?");
appendUuidFilter(query, uuids, false);
appendUuidFilter(query, includedUuids, false);
appendUuidFilter(query, excludedUuids, true);
query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder());
if (options.hasLimit()) {
query.append(" LIMIT ").append(options.getLimitCount()).append(" OFFSET ").append(options.getLimitOffset());
}
try (PreparedStatement statement = connection.prepareStatement(query.toString())) {
int parameterIndex = 1;
statement.setInt(parameterIndex++, checkTime);
for (String uuid : uuids) {
statement.setString(parameterIndex++, uuid);
}
if (includedUuids != null) {
for (String uuid : includedUuids) {
statement.setString(parameterIndex++, uuid);
}
}
for (String uuid : excludedUuids) {
statement.setString(parameterIndex++, uuid);
}
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(parseUsernameResult(connection, results));
}
}
}
}
catch (Exception e) {
ErrorReporter.report(e);
}
return result;
}
private static void appendUuidFilter(StringBuilder query, Set<String> uuids, boolean excluded) {
if (uuids == null || uuids.isEmpty()) {
return;
}
query.append(excluded ? " AND uuid NOT IN (" : " AND uuid IN (");
for (int index = 0; index < uuids.size(); index++) {
if (index > 0) {
query.append(",");
}
query.append("?");
}
query.append(")");
}
private static Set<String> getUuids(Connection connection, String user) throws Exception {
Set<String> result = new LinkedHashSet<>();
if (user == null || user.isEmpty() || user.equalsIgnoreCase("#global")) {
return result;
}
String userMatch = DatabaseUtils.caseInsensitiveEquals(ConfigHandler.databaseType.getUserColumn());
try (PreparedStatement statement = connection.prepareStatement("SELECT uuid FROM " + ConfigHandler.prefix + "user WHERE " + userMatch)) {
statement.setString(1, user);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(results.getString("uuid"));
}
}
}
try (PreparedStatement statement = connection.prepareStatement("SELECT uuid FROM " + ConfigHandler.prefix + "username_log WHERE " + userMatch)) {
statement.setString(1, user);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
result.add(results.getString("uuid"));
}
}
}
if (looksLikeUuid(user)) {
result.add(user);
}
return result.isEmpty() ? null : result;
}
private static Set<String> getUuids(Connection connection, List<String> users) throws Exception {
Set<String> result = new LinkedHashSet<>();
for (String user : users) {
Set<String> matches = getUuids(connection, user);
if (matches != null) {
if (matches.isEmpty()) {
// An empty name or #global matches every user.
return null;
}
result.addAll(matches);
}
}
return result;
}
private static boolean looksLikeUuid(String value) {
return value.length() == 36 && value.charAt(8) == '-' && value.charAt(13) == '-' && value.charAt(18) == '-' && value.charAt(23) == '-';
}
private static UsernameResult parseUsernameResult(Connection connection, ResultSet results) throws Exception {
String uuid = results.getString("uuid");
String username = results.getString("user");
String player = UserStatement.getNameByUuid(connection, uuid);
if (player == null) {
player = username;
}
return new UsernameResult(results.getLong("time"), uuid, username, player);
}
}
@@ -0,0 +1,135 @@
package net.coreprotect.api.result;
import java.util.Locale;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.utility.BlockUtils;
import net.coreprotect.utility.BlockTypeUtils;
import net.coreprotect.utility.EntityUtils;
import net.coreprotect.utility.MaterialUtils;
import net.coreprotect.utility.StringUtils;
/**
* Represents a logged block action.
*/
public class BlockResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int type;
private final int data;
private final String blockData;
private final int actionId;
private final int rolledBack;
public BlockResult(long time, String username, String world, int x, int y, int z, int type, int data, String blockData, int actionId, int rolledBack) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.data = data;
this.blockData = blockData;
this.actionId = actionId;
this.rolledBack = rolledBack;
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return LookupActions.getActionString(actionId);
}
@Deprecated
public int getData() {
return data;
}
public String getPlayer() {
return username;
}
public long getTimestamp() {
return time * 1000L;
}
public Material getType() {
if (isEntityAction()) {
return null;
}
Material material = MaterialUtils.getType(type);
if (material == null) {
return null;
}
String typeName = material.name().toLowerCase(Locale.ROOT);
return MaterialUtils.getType(StringUtils.nameFilter(typeName, data));
}
public EntityType getEntityType() {
if (!isEntityAction()) {
return null;
}
if (actionId == LookupActions.ENTITY_KILL && type == 0) {
return EntityType.PLAYER;
}
return EntityUtils.getEntityType(type);
}
public BlockData getBlockData() {
if (isEntityAction()) {
return null;
}
if (blockData == null || blockData.isEmpty()) {
Material material = getType();
if (material != null) {
return material.createBlockData();
}
return BlockUtils.createBlockData(type);
}
BlockData result = BlockTypeUtils.createBlockDataFromString(blockData);
return result != null ? result : Bukkit.getServer().createBlockData(blockData);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isRolledBack() {
return rolledBack == 1 || rolledBack == 3;
}
private boolean isEntityAction() {
return actionId == LookupActions.ENTITY_KILL || actionId == LookupActions.ENTITY_SPAWN;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,173 @@
package net.coreprotect.api.result;
import org.bukkit.Material;
import net.coreprotect.model.item.ItemTransactionActions;
import net.coreprotect.utility.MaterialUtils;
/**
* Represents the result of a container action lookup with typed fields.
*/
public class ContainerResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int type;
private final int data;
private final int amount;
private final byte[] metadata;
private final int action;
private final int rolledBack;
/**
* Creates a new ContainerResult with the specified parameters.
*
* @param time The timestamp in seconds
* @param username The username of the player who performed the action
* @param world The world name where the action occurred
* @param x The X coordinate
* @param y The Y coordinate
* @param z The Z coordinate
* @param type The material type ID
* @param data The data value
* @param amount The amount of items
* @param metadata The item metadata
* @param action The action ID
* @param rolledBack Whether the action has been rolled back
*/
public ContainerResult(long time, String username, String world, int x, int y, int z,
int type, int data, int amount, byte[] metadata, int action, int rolledBack) {
this.time = time;
this.username = username != null ? username : "";
this.world = world != null ? world : "";
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.data = data;
this.amount = amount;
this.metadata = metadata != null ? metadata.clone() : null;
this.action = action;
this.rolledBack = rolledBack;
}
/**
* Gets the action ID.
*
* @return The action ID
*/
public int getActionId() {
return action;
}
/**
* Gets the action as a human-readable string.
*
* @return "remove", "add", or "unknown"
*/
public String getActionString() {
return ItemTransactionActions.getActionString(action);
}
/**
* Gets the data value.
*
* @return The data value
*/
public int getData() {
return data;
}
/**
* Gets the amount of items.
*
* @return The amount
*/
public int getAmount() {
return amount;
}
/**
* Gets the username of the player who performed the action.
*
* @return The player username
*/
public String getPlayer() {
return username;
}
/**
* Gets the timestamp in milliseconds.
*
* @return The timestamp in milliseconds
*/
public long getTimestamp() {
return time * 1000L;
}
/**
* Gets the material type of the item.
*
* @return The Material, or null if unknown
*/
public Material getType() {
return MaterialUtils.getType(type);
}
/**
* Gets the X coordinate.
*
* @return The X coordinate
*/
public int getX() {
return x;
}
/**
* Gets the Y coordinate.
*
* @return The Y coordinate
*/
public int getY() {
return y;
}
/**
* Gets the Z coordinate.
*
* @return The Z coordinate
*/
public int getZ() {
return z;
}
/**
* Gets the item metadata.
*
* @return A copy of the metadata array, or null if no metadata
*/
public byte[] getMetadata() {
return metadata != null ? metadata.clone() : null;
}
/**
* Checks if this action has been rolled back.
*
* @return true if rolled back, false otherwise
*/
public boolean isRolledBack() {
return rolledBack == 1 || rolledBack == 3;
}
/**
* Gets the world name where the action occurred.
*
* @return The world name
*/
public String worldName() {
return world;
}
}
@@ -0,0 +1,22 @@
package net.coreprotect.api.result;
/**
* Common fields shared by typed CoreProtect lookup results.
*/
public interface CoreProtectResult {
int getActionId();
String getActionString();
String getPlayer();
long getTimestamp();
int getX();
int getY();
int getZ();
String worldName();
}
@@ -0,0 +1,76 @@
package net.coreprotect.api.result;
import org.bukkit.entity.EntityType;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.utility.EntityUtils;
/**
* Represents a logged entity spawn or kill at its original event location.
*/
public class EntityResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int type;
private final int actionId;
private final int rolledBack;
public EntityResult(long time, String username, String world, int x, int y, int z, int type, int actionId, int rolledBack) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.actionId = actionId;
this.rolledBack = rolledBack;
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return LookupActions.getActionString(actionId);
}
public String getPlayer() {
return username;
}
public long getTimestamp() {
return time * 1000L;
}
public EntityType getEntityType() {
if (actionId == LookupActions.ENTITY_KILL && type == 0) {
return EntityType.PLAYER;
}
return EntityUtils.getEntityType(type);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isRolledBack() {
return rolledBack == 1 || rolledBack == 3;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,120 @@
package net.coreprotect.api.result;
import org.bukkit.Material;
import net.coreprotect.model.item.InventorySources;
import net.coreprotect.model.item.ItemTransactionActions;
import net.coreprotect.utility.ItemUtils;
import net.coreprotect.utility.MaterialUtils;
/**
* Represents a normalized player inventory transaction.
*/
public class InventoryResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int type;
private final int data;
private final int amount;
private final byte[] metadata;
private final int actionId;
private final int transactionActionId;
private final int rolledBack;
private final int sourceId;
public InventoryResult(long time, String username, String world, int x, int y, int z, int type, int data, int amount, byte[] metadata, int transactionActionId, int rolledBack, int sourceId) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.data = data;
this.amount = amount;
this.metadata = metadata != null ? metadata.clone() : null;
this.transactionActionId = transactionActionId;
this.rolledBack = rolledBack;
this.sourceId = sourceId;
this.actionId = ItemTransactionActions.getInventoryActionId(transactionActionId);
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return ItemTransactionActions.getActionString(actionId);
}
public int getAmount() {
return amount;
}
@Deprecated
public int getData() {
return data;
}
public byte[] getMetadata() {
return metadata != null ? metadata.clone() : null;
}
public String getPlayer() {
return username;
}
public int getSourceId() {
return sourceId;
}
public String getSource() {
return InventorySources.getSourceString(sourceId);
}
public long getTimestamp() {
return time * 1000L;
}
public int getTransactionActionId() {
return transactionActionId;
}
public String getTransactionActionString() {
return ItemTransactionActions.getActionString(transactionActionId);
}
public Material getType() {
Material material = MaterialUtils.getType(type);
if (sourceId == InventorySources.BLOCK) {
return ItemUtils.itemFilter(material, true);
}
return material;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isRolledBack() {
return rolledBack == 2 || rolledBack == 3;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,85 @@
package net.coreprotect.api.result;
import org.bukkit.Material;
import net.coreprotect.model.item.ItemTransactionActions;
import net.coreprotect.utility.MaterialUtils;
/**
* Represents a logged world item transaction.
*/
public class ItemResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int type;
private final int amount;
private final byte[] metadata;
private final int actionId;
private final int rolledBack;
public ItemResult(long time, String username, String world, int x, int y, int z, int type, int amount, byte[] metadata, int actionId, int rolledBack) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.amount = amount;
this.metadata = metadata != null ? metadata.clone() : null;
this.actionId = actionId;
this.rolledBack = rolledBack;
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return ItemTransactionActions.getActionString(actionId);
}
public int getAmount() {
return amount;
}
public byte[] getMetadata() {
return metadata != null ? metadata.clone() : null;
}
public String getPlayer() {
return username;
}
public long getTimestamp() {
return time * 1000L;
}
public Material getType() {
return MaterialUtils.getType(type);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isRolledBack() {
return rolledBack == 1 || rolledBack == 3;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,64 @@
package net.coreprotect.api.result;
import net.coreprotect.model.action.LookupActions;
/**
* Represents a logged player chat message or command.
*/
public class MessageResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final String message;
private final int actionId;
public MessageResult(long time, String username, String world, int x, int y, int z, String message, int actionId) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.message = message;
this.actionId = actionId;
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return LookupActions.getActionString(actionId);
}
public String getMessage() {
return message;
}
public String getPlayer() {
return username;
}
public long getTimestamp() {
return time * 1000L;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,146 @@
package net.coreprotect.api.result;
import java.util.Locale;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType;
import net.coreprotect.api.SessionLookup;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.model.action.SessionActions;
import net.coreprotect.model.item.InventorySources;
import net.coreprotect.utility.BlockUtils;
import net.coreprotect.utility.BlockTypeUtils;
import net.coreprotect.utility.EntityUtils;
import net.coreprotect.utility.MaterialUtils;
import net.coreprotect.utility.StringUtils;
import net.coreprotect.utility.WorldUtils;
public class ParseResult implements CoreProtectResult {
private final String[] parse;
public ParseResult(String[] data) {
parse = data;
}
public int getActionId() {
return Integer.parseInt(parse[7]);
}
public String getActionString() {
int actionID = Integer.parseInt(parse[7]);
if (parse.length < 13 && Integer.parseInt(parse[6]) == SessionLookup.ID) {
return SessionActions.getActionString(actionID);
}
return LookupActions.getActionString(actionID);
}
@Deprecated
public int getData() {
return Integer.parseInt(parse[6]);
}
public String getPlayer() {
return parse[1];
}
@Deprecated
public int getTime() {
return Integer.parseInt(parse[0]);
}
public long getTimestamp() {
return Long.parseLong(parse[0]) * 1000L;
}
public Material getType() {
if (parse.length < 13) {
return null;
}
int actionID = this.getActionId();
if (isEntityAction(actionID)) {
return null;
}
int type = Integer.parseInt(parse[5]);
Material material = MaterialUtils.getType(type);
if (material == null) {
return null;
}
String typeName = material.name().toLowerCase(Locale.ROOT);
typeName = StringUtils.nameFilter(typeName, this.getData());
return MaterialUtils.getType(typeName);
}
public BlockData getBlockData() {
if (parse.length < 13) {
return null;
}
if (isEntityAction(this.getActionId())) {
return null;
}
String blockData = parse[12];
if (blockData == null || blockData.length() == 0) {
Material type = getType();
if (type != null) {
return type.createBlockData();
}
return BlockUtils.createBlockData(Integer.parseInt(parse[5]));
}
BlockData result = BlockTypeUtils.createBlockDataFromString(blockData);
return result != null ? result : Bukkit.getServer().createBlockData(blockData);
}
public EntityType getEntityType() {
if (parse.length < 13 || !isEntityAction(this.getActionId())) {
return null;
}
int type = Integer.parseInt(parse[5]);
if (this.getActionId() == LookupActions.ENTITY_KILL && type == 0) {
return EntityType.PLAYER;
}
return EntityUtils.getEntityType(type);
}
public int getX() {
return Integer.parseInt(parse[2]);
}
public int getY() {
return Integer.parseInt(parse[3]);
}
public int getZ() {
return Integer.parseInt(parse[4]);
}
public boolean isRolledBack() {
if (parse.length < 13) {
return false;
}
return (Integer.parseInt(parse[8]) == 1 || Integer.parseInt(parse[8]) == 3);
}
public String worldName() {
return WorldUtils.getWorldName(Integer.parseInt(parse.length < 13 ? parse[5] : parse[9]));
}
private boolean isEntityAction(int actionId) {
if (actionId == LookupActions.ENTITY_KILL || actionId == LookupActions.ENTITY_SPAWN) {
return true;
}
return actionId == LookupActions.INTERACTION && parse.length > 14 && parse[13] != null && Integer.parseInt(parse[13]) == InventorySources.ENTITY_INTERACTION;
}
}
@@ -0,0 +1,58 @@
package net.coreprotect.api.result;
import net.coreprotect.model.action.SessionActions;
/**
* Represents a logged player login or logout session event.
*/
public class SessionResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int actionId;
public SessionResult(long time, String username, String world, int x, int y, int z, int actionId) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.actionId = actionId;
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return SessionActions.getActionString(actionId);
}
public String getPlayer() {
return username;
}
public long getTimestamp() {
return time * 1000L;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,129 @@
package net.coreprotect.api.result;
import net.coreprotect.model.action.SignActions;
import net.coreprotect.utility.BlockUtils;
/**
* Represents logged sign text.
*/
public class SignResult implements CoreProtectResult {
private final long time;
private final String username;
private final String world;
private final int x;
private final int y;
private final int z;
private final int actionId;
private final int color;
private final int colorSecondary;
private final int data;
private final boolean waxed;
private final boolean front;
private final String[] lines;
public SignResult(long time, String username, String world, int x, int y, int z, int actionId, int color, int colorSecondary, int data, boolean waxed, boolean front, String[] lines) {
this.time = time;
this.username = username;
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.actionId = actionId;
this.color = color;
this.colorSecondary = colorSecondary;
this.data = data;
this.waxed = waxed;
this.front = front;
this.lines = lines.clone();
}
public int getActionId() {
return actionId;
}
public String getActionString() {
return SignActions.getActionString(actionId);
}
public int getColor() {
return color;
}
public int getColorSecondary() {
return colorSecondary;
}
public int getData() {
return data;
}
public String getLine(int line) {
if (line < 0 || line >= lines.length) {
return "";
}
return lines[line];
}
public String[] getLines() {
return lines.clone();
}
public String getMessage() {
StringBuilder message = new StringBuilder();
int startLine = front ? 0 : 4;
int endLine = front ? 4 : 8;
for (int index = startLine; index < endLine; index++) {
String line = lines[index];
if (line != null && !line.isEmpty()) {
message.append(line);
if (!line.endsWith(" ")) {
message.append(" ");
}
}
}
return message.toString();
}
public String getPlayer() {
return username;
}
public long getTimestamp() {
return time * 1000L;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isBackGlowing() {
return BlockUtils.isSideGlowing(false, data);
}
public boolean isFront() {
return front;
}
public boolean isFrontGlowing() {
return BlockUtils.isSideGlowing(true, data);
}
public boolean isWaxed() {
return waxed;
}
public String worldName() {
return world;
}
}
@@ -0,0 +1,60 @@
package net.coreprotect.api.result;
import net.coreprotect.model.action.LookupActions;
/**
* Represents a logged username used by a player UUID.
*/
public class UsernameResult implements CoreProtectResult {
private final long time;
private final String uuid;
private final String username;
private final String player;
public UsernameResult(long time, String uuid, String username, String player) {
this.time = time;
this.uuid = uuid;
this.username = username;
this.player = player;
}
public int getActionId() {
return LookupActions.USERNAME;
}
public String getActionString() {
return LookupActions.getActionString(getActionId());
}
public String getPlayer() {
return player;
}
public long getTimestamp() {
return time * 1000L;
}
public String getUsername() {
return username;
}
public String getUuid() {
return uuid;
}
public int getX() {
return 0;
}
public int getY() {
return 0;
}
public int getZ() {
return 0;
}
public String worldName() {
return "";
}
}
@@ -1,52 +1,132 @@
package net.coreprotect.bukkit;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.bukkit.Art;
import org.bukkit.Chunk;
import org.bukkit.Color;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
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.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.FaceAttachable;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Painting;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.Event;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionType;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.utility.Util;
import net.coreprotect.utility.BlockUtils;
/**
* Base adapter implementation for Bukkit API compatibility.
* Provides default implementations for methods that work across multiple Minecraft versions.
* Version-specific implementations extend this class to provide specialized behavior.
*/
public class BukkitAdapter implements BukkitInterface {
/** The currently active adapter instance */
public static BukkitInterface ADAPTER;
public static final int BUKKIT_v1_13 = 13;
public static final int BUKKIT_v1_14 = 14;
public static final int BUKKIT_v1_15 = 15;
public static final int BUKKIT_v1_16 = 16;
public static final int BUKKIT_v1_17 = 17;
// Version constants for Bukkit implementations
public static final int BUKKIT_V1_13 = 13;
public static final int BUKKIT_V1_14 = 14;
public static final int BUKKIT_V1_15 = 15;
public static final int BUKKIT_V1_16 = 16;
public static final int BUKKIT_V1_17 = 17;
public static final int BUKKIT_V1_18 = 18;
public static final int BUKKIT_V1_19 = 19;
public static final int BUKKIT_V1_20 = 20;
public static final int BUKKIT_V1_21 = 21;
public static final int BUKKIT_V1_21_5 = 21005;
public static final int BUKKIT_V1_21_11 = 21011;
public static final int BUKKIT_V26_0 = 26000;
public static final int BUKKIT_V26_1 = 26010;
public static final int BUKKIT_V26_2 = 26020;
public static int getAdapterVersion(int major, int minor) {
return getAdapterVersion(major, minor, 0);
}
public static int getAdapterVersion(int major, int minor, int patch) {
if (major == 1) {
return minor == 21 && patch >= 5 ? (minor * 1000) + patch : minor;
}
return (major * 1000) + (minor * 10) + patch;
}
/**
* Initializes the appropriate Bukkit adapter based on the server version.
* This method should be called during plugin initialization.
*/
public static void loadAdapter() {
switch (ConfigHandler.SERVER_VERSION) {
case BUKKIT_v1_13:
case BUKKIT_v1_14:
BukkitAdapter.ADAPTER = new BukkitAdapter();
break;
case BUKKIT_v1_15:
BukkitAdapter.ADAPTER = new Bukkit_v1_15();
break;
case BUKKIT_v1_16:
BukkitAdapter.ADAPTER = new Bukkit_v1_16();
break;
case BUKKIT_v1_17:
default:
BukkitAdapter.ADAPTER = new Bukkit_v1_17();
break;
int bukkitVersion = ConfigHandler.SERVER_VERSION;
if (bukkitVersion >= BUKKIT_V26_2) {
ADAPTER = new Bukkit_v26_2();
}
else if (bukkitVersion >= BUKKIT_V1_21_11) {
ADAPTER = new Bukkit_v1_21_11();
}
else if (bukkitVersion >= BUKKIT_V1_21_5) {
ADAPTER = new Bukkit_v1_21_5();
}
else if (bukkitVersion >= BUKKIT_V1_21) {
ADAPTER = new Bukkit_v1_21();
}
else if (bukkitVersion >= BUKKIT_V1_20) {
ADAPTER = new Bukkit_v1_20();
}
else if (bukkitVersion >= BUKKIT_V1_19) {
ADAPTER = new Bukkit_v1_19();
}
else if (bukkitVersion >= BUKKIT_V1_18) {
ADAPTER = new Bukkit_v1_18();
}
else if (bukkitVersion >= BUKKIT_V1_17) {
ADAPTER = new Bukkit_v1_17();
}
else {
ADAPTER = new BukkitAdapter();
}
}
// -------------------- Basic data conversion methods --------------------
public static Set<Material> EMPTY_SET = new HashSet<>(Arrays.asList());
@Override
public String parseLegacyName(String name) {
return name;
@@ -57,6 +137,13 @@ public class BukkitAdapter implements BukkitInterface {
return -1;
}
// -------------------- Entity methods --------------------
@Override
public boolean isChunkEntitiesLoaded(Chunk chunk) {
return true;
}
@Override
public boolean getEntityMeta(LivingEntity entity, List<Object> info) {
return false;
@@ -67,6 +154,100 @@ public class BukkitAdapter implements BukkitInterface {
return false;
}
/**
* Accesses registry-backed entity variants without exposing the entity interface to legacy
* server bytecode remapping.
*/
public static boolean getRegistryVariant(BukkitInterface adapter, Entity entity, List<Object> info, String getterName) {
try {
Object variant = entity.getClass().getMethod(getterName).invoke(entity);
if (variant == null) {
return false;
}
info.add(adapter.getRegistryKey(variant));
return true;
}
catch (ReflectiveOperationException | LinkageError e) {
return false;
}
}
/**
* Restores registry-backed entity variants without exposing the entity interface to legacy
* server bytecode remapping.
*/
public static boolean setRegistryVariant(BukkitInterface adapter, Entity entity, Object value, String getterName, String setterName) {
try {
Class<?> variantClass = entity.getClass().getMethod(getterName).getReturnType();
Object variant = value instanceof String ? adapter.getRegistryValue((String) value, variantClass) : value;
if (!variantClass.isInstance(variant)) {
return false;
}
entity.getClass().getMethod(setterName, variantClass).invoke(entity, variant);
return true;
}
catch (ReflectiveOperationException | LinkageError e) {
return false;
}
}
public static Object getLegacyEnumValue(Class<?> type, String name) {
if (!type.isEnum()) {
return null;
}
try {
@SuppressWarnings({ "rawtypes", "unchecked" })
Object value = Enum.valueOf((Class<? extends Enum>) type.asSubclass(Enum.class), name);
return value;
}
catch (IllegalArgumentException | LinkageError exception) {
return null;
}
}
@Override
public void addMerchantRecipeMeta(MerchantRecipe recipe, List<Object> recipeData) {
}
@Override
public void setMerchantRecipeMeta(MerchantRecipe recipe, List<?> recipeData) {
}
@Override
public void refreshVillagerBrain(Villager villager) {
try {
Object handle = villager.getClass().getMethod("getHandle").invoke(villager);
Object level = villager.getWorld().getClass().getMethod("getHandle").invoke(villager.getWorld());
invokeSingleArgumentMethod(handle, "refreshBrain", level);
}
catch (Exception e) {
}
}
@Override
public void getWolfVariant(org.bukkit.entity.Wolf wolf, List<Object> info) {
// Base implementation does nothing - Wolf variants only exist in 1.21+
}
@Override
public void setWolfVariant(org.bukkit.entity.Wolf wolf, Object value) {
// Base implementation does nothing - Wolf variants only exist in 1.21+
}
@Override
public EntityType getEntityType(Material material) {
switch (material) {
case END_CRYSTAL:
return EntityType.valueOf("ENDER_CRYSTAL");
default:
return EntityType.UNKNOWN;
}
}
// -------------------- Item handling methods --------------------
@Override
public boolean getItemMeta(ItemMeta itemMeta, List<Map<String, Object>> list, List<List<Map<String, Object>>> metadata, int slot) {
return false;
@@ -77,25 +258,95 @@ public class BukkitAdapter implements BukkitInterface {
return false;
}
@Override
public String getItemName(ItemMeta itemMeta) {
return "";
}
@Override
public Material getPlantSeeds(Material material) {
switch (material) {
case WHEAT:
return Material.WHEAT_SEEDS;
case PUMPKIN_STEM:
return Material.PUMPKIN_SEEDS;
case MELON_STEM:
return Material.MELON_SEEDS;
case BEETROOTS:
return Material.BEETROOT_SEEDS;
default:
return material;
}
}
@Override
public ItemStack adjustIngredient(MerchantRecipe recipe, ItemStack itemStack) {
return null;
}
@Override
public ItemStack getArrowMeta(Arrow arrow, ItemStack itemStack) {
PotionData data = arrow.getBasePotionData();
if (data.getType() != PotionType.valueOf("UNCRAFTABLE")) {
itemStack = new ItemStack(Material.TIPPED_ARROW);
PotionMeta meta = (PotionMeta) itemStack.getItemMeta();
meta.setBasePotionData(data);
for (PotionEffect effect : arrow.getCustomEffects()) {
meta.addCustomEffect(effect, false);
}
itemStack.setItemMeta(meta);
}
return itemStack;
}
private static void invokeSingleArgumentMethod(Object target, String methodName, Object argument) {
if (target == null || argument == null) {
return;
}
for (Method method : target.getClass().getMethods()) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getName().equals(methodName) && parameterTypes.length == 1 && parameterTypes[0].isInstance(argument)) {
try {
method.invoke(target, argument);
}
catch (Exception e) {
}
return;
}
}
}
// -------------------- Block methods --------------------
@Override
public boolean isAttached(Block block, Block scanBlock, BlockData blockData, int scanMin) {
if (blockData instanceof Directional) {
return (scanMin < 5 && scanBlock.getRelative(((Directional) blockData).getFacing().getOppositeFace()).getLocation().equals(block.getLocation()));
if (blockData instanceof Directional && blockData instanceof FaceAttachable) {
Directional directional = (Directional) blockData;
FaceAttachable faceAttachable = (FaceAttachable) blockData;
boolean scanButton = false;
switch (faceAttachable.getAttachedFace()) {
case WALL:
scanButton = (scanMin < 5 && scanBlock.getRelative(directional.getFacing().getOppositeFace()).getLocation().equals(block.getLocation()));
break;
case FLOOR:
scanButton = (scanMin == 5);
break;
case CEILING:
scanButton = (scanMin == 6);
break;
default:
break;
}
return scanButton;
}
return true; // unvalidated attachments default to true
}
@Override
public boolean isWall(BlockData blockData) {
return false;
}
@Override
public void sendSignChange(Player player, Sign sign) {
return;
}
@Override
public int getMinHeight(World world) {
return 0;
@@ -106,9 +357,31 @@ public class BukkitAdapter implements BukkitInterface {
return Material.AIR;
}
@Override
public boolean hasBlockType(String key) {
return false;
}
@Override
public BlockData createBlockData(String key) {
return null;
}
@Override
public BlockData createBlockDataFromString(String blockData) {
return null;
}
@Override
public boolean isInvisible(Material material) {
return BlockUtils.isAir(material);
}
// -------------------- Special block type checkers --------------------
@Override
public boolean isItemFrame(Material material) {
return (material == Material.ITEM_FRAME);
return material == Material.ITEM_FRAME;
}
@Override
@@ -116,24 +389,229 @@ public class BukkitAdapter implements BukkitInterface {
return Material.ITEM_FRAME;
}
@Override
public Material getFrameType(EntityType type) {
return type == EntityType.ITEM_FRAME ? Material.ITEM_FRAME : null;
}
@Override
public Class<?> getFrameClass(Material material) {
return ItemFrame.class;
}
@Override
public boolean isGlowing(Sign sign) {
public <T extends Entity> T spawn(World world, Location location, Class<T> entityClass, Consumer<? super T> function) {
// Bukkit 1.20.1 and earlier only have World#spawn(Location, Class, org.bukkit.util.Consumer), which is deprecated for removal in the current API
try {
Class<?> consumerClass = Class.forName("org.bukkit.util.Consumer");
Object consumer = Proxy.newProxyInstance(consumerClass.getClassLoader(), new Class<?>[] { consumerClass }, (proxy, method, args) -> {
if (method.getDeclaringClass() == Object.class) {
switch (method.getName()) {
case "equals":
return proxy == args[0];
case "hashCode":
return System.identityHashCode(proxy);
default:
return consumerClass.getName();
}
}
function.accept(entityClass.cast(args[0]));
return null;
});
Method spawnMethod = World.class.getMethod("spawn", Location.class, Class.class, consumerClass);
return entityClass.cast(spawnMethod.invoke(world, location, entityClass, consumer));
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new IllegalStateException(cause);
}
catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
@Override
public boolean isDecoratedPot(Material material) {
return false;
}
@Override
public void setGlowing(Sign sign, boolean set) {
return;
public boolean isSuspiciousBlock(Material material) {
return false;
}
@Override
public boolean isInvisible(Material material) {
return Util.isAir(material);
public boolean isSign(Material material) {
return Tag.SIGNS.isTagged(material);
}
@Override
public boolean isChiseledBookshelf(Material material) {
return false;
}
@Override
public boolean isBookshelfBook(Material material) {
return false;
}
@Override
public ItemStack getChiseledBookshelfBook(BlockState blockState, PlayerInteractEvent event) {
return null;
}
// -------------------- Sign handling methods --------------------
@Override
public String getLine(Sign sign, int line) {
if (line < 4) {
return sign.getLine(line);
}
else {
return "";
}
}
@Override
public void setLine(Sign sign, int line, String string) {
if (string == null) {
string = "";
}
if (line < 4) {
sign.setLine(line, string);
}
}
@Override
public boolean isSignFront(SignChangeEvent event) {
return true;
}
@Override
public Boolean getSignInteractionSide(Sign sign, Player player) {
return true;
}
@Override
public boolean isGlowing(Sign sign, boolean isFront) {
return false;
}
@Override
public boolean isWaxed(Sign sign) {
return false;
}
@Override
public boolean shouldLogExplosion(Event event){
return true;
}
@Override
public Material getExplodedBlock(BlockExplodeEvent event){
// accoding to the Bukkit docs this will always return air
return event.getBlock().getType();
}
@Override
public void setGlowing(Sign sign, boolean isFront, boolean isGlowing) {
// Base implementation does nothing
}
@Override
public void setColor(Sign sign, boolean isFront, int color) {
if (!isFront) {
return;
}
sign.setColor(DyeColor.getByColor(Color.fromRGB(color)));
}
@Override
public void setWaxed(Sign sign, boolean isWaxed) {
// Base implementation does nothing
}
@Override
public int getColor(Sign sign, boolean isFront) {
if (isFront) {
return sign.getColor().getColor().asRGB();
}
return 0;
}
// -------------------- Registry methods --------------------
@Override
public Object getRegistryKey(Object value) {
return value;
}
@Override
public Object getRegistryValue(String key, Object tClass) {
return null;
}
@Override
public String getPaintingArtKey(Painting painting) {
try {
return painting.getArt().name();
}
catch (IncompatibleClassChangeError e) {
return painting.getArt().toString();
}
}
@Override
public Art getPaintingArt(String name) {
if (name == null || name.isBlank()) {
return null;
}
return Art.getByName(name.toUpperCase(Locale.ROOT));
}
@Override
public boolean isCrafter(InventoryType type) {
return false;
}
@Override
public boolean isBundle(Material material) {
return false;
}
@Override
public boolean isCopperChest(Material material) {
return false;
}
@Override
public boolean isShelf(Material material){
return false;
}
@Override
public List<Location> getShelfInteractionLocations(Block block, BlockFace blockFace) {
return Collections.emptyList();
}
@Override
public Set<Material> copperChestMaterials() {
return EMPTY_SET;
}
@Override
public Set<Material> shelfMaterials() {
return EMPTY_SET;
}
}
@@ -2,52 +2,565 @@ package net.coreprotect.bukkit;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.bukkit.Art;
import org.bukkit.Chunk;
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.block.data.BlockData;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Painting;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.Event;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.inventory.meta.ItemMeta;
/**
* Interface for Bukkit API compatibility across different Minecraft versions.
* Each method provides version-specific implementations to handle differences
* between Minecraft/Bukkit API versions.
*/
public interface BukkitInterface {
public Material getBucketContents(Material material);
// --------------------------------------------------------------------------
// Block-related methods
// --------------------------------------------------------------------------
public Material getFrameType(Entity entity);
/**
* Checks if a block is attached to another block.
*
* @param block
* The base block
* @param scanBlock
* The block to check for attachment
* @param blockData
* The block data
* @param scanMin
* The minimum scan value
* @return true if the block is attached, false otherwise
*/
boolean isAttached(Block block, Block scanBlock, BlockData blockData, int scanMin);
public Class<?> getFrameClass(Material material);
/**
* Gets the minimum height of a world.
*
* @param world
* The world
* @return The minimum height
*/
int getMinHeight(World world);
public String parseLegacyName(String name);
/**
* Gets the legacy block ID for a material.
*
* @param material
* The material
* @return The legacy block ID, or -1 if not applicable
*/
int getLegacyBlockId(Material material);
public boolean getEntityMeta(LivingEntity entity, List<Object> info);
/**
* Gets the contents of a bucket material.
*
* @param material
* The bucket material
* @return The material inside the bucket, or AIR if not applicable
*/
Material getBucketContents(Material material);
public boolean setEntityMeta(Entity entity, Object value, int count);
/**
* Checks whether the runtime supports and contains a block type key.
*
* @param key
* The namespaced block key
* @return true if the block type exists, false otherwise
*/
boolean hasBlockType(String key);
public boolean getItemMeta(ItemMeta itemMeta, List<Map<String, Object>> list, List<List<Map<String, Object>>> metadata, int slot);
/**
* Creates default block data for a block type key.
*
* @param key
* The namespaced block key
* @return The block data, or null if unsupported/unavailable
*/
BlockData createBlockData(String key);
public boolean setItemMeta(Material rowType, ItemStack itemstack, List<Map<String, Object>> map);
/**
* Creates block data from a serialized block data string.
*
* @param blockData
* The serialized block data
* @return The block data, or null if unsupported/unavailable
*/
BlockData createBlockDataFromString(String blockData);
public boolean isAttached(Block block, Block scanBlock, BlockData blockData, int scanMin);
// --------------------------------------------------------------------------
// Material type checking methods
// --------------------------------------------------------------------------
public boolean isWall(BlockData blockData);
/**
* Checks if a material is an item frame.
*
* @param material
* The material to check
* @return true if the material is an item frame, false otherwise
*/
boolean isItemFrame(Material material);
public boolean isItemFrame(Material material);
/**
* Checks if a material is invisible.
*
* @param material
* The material to check
* @return true if the material is invisible, false otherwise
*/
boolean isInvisible(Material material);
public boolean isGlowing(Sign sign);
/**
* Checks if a material is a decorated pot.
*
* @param material
* The material to check
* @return true if the material is a decorated pot, false otherwise
*/
boolean isDecoratedPot(Material material);
public boolean isInvisible(Material material);
/**
* Checks if a material is a suspicious block.
*
* @param material
* The material to check
* @return true if the material is a suspicious block, false otherwise
*/
boolean isSuspiciousBlock(Material material);
public int getMinHeight(World world);
/**
* Checks if a material is a sign.
*
* @param material
* The material to check
* @return true if the material is a sign, false otherwise
*/
boolean isSign(Material material);
public int getLegacyBlockId(Material material);
/**
* Checks if a material is a chiseled bookshelf.
*
* @param material
* The material to check
* @return true if the material is a chiseled bookshelf, false otherwise
*/
boolean isChiseledBookshelf(Material material);
public void sendSignChange(Player player, Sign sign);
public void setGlowing(Sign sign, boolean b);
/**
* Checks if a material is a shelf of any wood kind.
*
* @param material
* The material to check
* @return true if the material is a shelf, false otherwise
*/
boolean isShelf(Material material);
List<Location> getShelfInteractionLocations(Block block, BlockFace blockFace);
/**
* Checks if a material is a bookshelf book.
*
* @param material
* The material to check
* @return true if the material is a bookshelf book, false otherwise
*/
boolean isBookshelfBook(Material material);
/**
* Checks if a material is a bundle.
*
* @param material
* The material to check
* @return true if the material is a bundle, false otherwise
*/
boolean isBundle(Material material);
/**
* Gets the seeds material for a plant material.
*
* @param material
* The plant material
* @return The seeds material
*/
Material getPlantSeeds(Material material);
// --------------------------------------------------------------------------
// Item and inventory methods
// --------------------------------------------------------------------------
/**
* Adjusts an ingredient in a merchant recipe.
*
* @param recipe
* The merchant recipe
* @param itemStack
* The item stack
* @return The adjusted item stack, or null if not applicable
*/
ItemStack adjustIngredient(MerchantRecipe recipe, ItemStack itemStack);
/**
* Gets metadata from an item meta.
*
* @param itemMeta
* The item meta
* @param list
* The list to populate with metadata
* @param metadata
* The metadata list to populate
* @param slot
* The slot
* @return true if metadata was extracted, false otherwise
*/
boolean getItemMeta(ItemMeta itemMeta, List<Map<String, Object>> list, List<List<Map<String, Object>>> metadata, int slot);
/**
* Sets metadata on an item stack.
*
* @param rowType
* The material type
* @param itemstack
* The item stack
* @param map
* The metadata map
* @return true if metadata was set, false otherwise
*/
boolean setItemMeta(Material rowType, ItemStack itemstack, List<Map<String, Object>> map);
String getItemName(ItemMeta itemMeta);
/**
* Gets a book from a chiseled bookshelf.
*
* @param blockState
* The block state
* @param event
* The player interact event
* @return The book item stack, or null if not applicable
*/
ItemStack getChiseledBookshelfBook(BlockState blockState, PlayerInteractEvent event);
/**
* Gets arrow metadata for an item stack.
*
* @param arrow
* The arrow entity
* @param itemStack
* The item stack
* @return The item stack with arrow metadata
*/
ItemStack getArrowMeta(Arrow arrow, ItemStack itemStack);
// --------------------------------------------------------------------------
// Entity methods
// --------------------------------------------------------------------------
/**
* Checks whether a loaded chunk's entity data is ready to query.
*
* @param chunk
* The loaded chunk
* @return true when entity data is ready
*/
boolean isChunkEntitiesLoaded(Chunk chunk);
/**
* Gets metadata from a living entity.
*
* @param entity
* The living entity
* @param info
* The list to populate with entity metadata
* @return true if metadata was extracted, false otherwise
*/
boolean getEntityMeta(LivingEntity entity, List<Object> info);
/**
* Sets metadata on an entity.
*
* @param entity
* The entity
* @param value
* The metadata value
* @param count
* The count
* @return true if metadata was set, false otherwise
*/
boolean setEntityMeta(Entity entity, Object value, int count);
void addMerchantRecipeMeta(MerchantRecipe recipe, List<Object> recipeData);
void setMerchantRecipeMeta(MerchantRecipe recipe, List<?> recipeData);
void refreshVillagerBrain(Villager villager);
/**
* Gets the wolf variant and adds it to the info list.
* Only implemented in Minecraft 1.21+.
*
* @param wolf
* The wolf entity
* @param info
* The list to add the variant information to
*/
void getWolfVariant(org.bukkit.entity.Wolf wolf, List<Object> info);
/**
* Sets the wolf variant from the provided value.
* Only implemented in Minecraft 1.21+.
*
* @param wolf
* The wolf entity
* @param value
* The variant value to set
*/
void setWolfVariant(org.bukkit.entity.Wolf wolf, Object value);
/**
* Gets the frame type for an entity.
*
* @param entity
* The entity
* @return The frame material type
*/
Material getFrameType(Entity entity);
/**
* Gets the frame type for an entity type.
*
* @param type
* The entity type
* @return The frame material type
*/
Material getFrameType(EntityType type);
/**
* Gets the entity type for a material.
*
* @param material
* The material
* @return The entity type
*/
EntityType getEntityType(Material material);
/**
* Gets the frame class for a material.
*
* @param material
* The material
* @return The frame class
*/
Class<?> getFrameClass(Material material);
/**
* Spawns an entity, running a function on it before it is added to the world.
*
* @param world
* The world
* @param location
* The spawn location
* @param entityClass
* The entity class
* @param function
* The function to run before the entity is added
* @return The spawned entity
*/
<T extends Entity> T spawn(World world, Location location, Class<T> entityClass, Consumer<? super T> function);
// --------------------------------------------------------------------------
// Sign methods
// --------------------------------------------------------------------------
/**
* Checks if a sign is glowing.
*
* @param sign
* The sign
* @param isFront
* Whether to check the front side
* @return true if the sign is glowing, false otherwise
*/
boolean isGlowing(Sign sign, boolean isFront);
/**
* Checks if a sign is waxed.
*
* @param sign
* The sign
* @return true if the sign is waxed, false otherwise
*/
boolean isWaxed(Sign sign);
/**
* Sets whether a sign is glowing.
*
* @param sign
* The sign
* @param isFront
* Whether to set the front side
* @param isGlowing
* Whether the sign should be glowing
*/
void setGlowing(Sign sign, boolean isFront, boolean isGlowing);
/**
* Sets the color of a sign.
*
* @param sign
* The sign
* @param isFront
* Whether to set the front side
* @param color
* The color RGB value
*/
void setColor(Sign sign, boolean isFront, int color);
/**
* Sets whether a sign is waxed.
*
* @param sign
* The sign
* @param isWaxed
* Whether the sign should be waxed
*/
void setWaxed(Sign sign, boolean isWaxed);
/**
* Gets the color of a sign.
*
* @param sign
* The sign
* @param isFront
* Whether to get the front side color
* @return The color RGB value
*/
int getColor(Sign sign, boolean isFront);
/**
* Gets a line of text from a sign.
*
* @param sign
* The sign
* @param line
* The line number (0-based)
* @return The text on the line
*/
String getLine(Sign sign, int line);
/**
* Sets a line of text on a sign.
*
* @param sign
* The sign
* @param line
* The line number (0-based)
* @param string
* The text to set
*/
void setLine(Sign sign, int line, String string);
/**
* Checks if a sign change event is for the front side of the sign.
*
* @param event
* The sign change event
* @return true if the event is for the front side, false otherwise
*/
boolean isSignFront(SignChangeEvent event);
Boolean getSignInteractionSide(Sign sign, Player player);
/**
* Checks whether an explosion event should be logged or not. (i.e. wind charge explosions)
*
* @param event
* The explosion event (Block or Entity ExplodeEvent)
* @return true if the explosion should affect blocks
*/
boolean shouldLogExplosion(Event event);
/**
* Gets the material of the block that exploded
*
* @param event
* The block explosion event
* @return the material of the block that caused the explosion
*/
Material getExplodedBlock(BlockExplodeEvent event);
// --------------------------------------------------------------------------
// Registry methods
// --------------------------------------------------------------------------
/**
* Gets the registry key for a value.
*
* @param value
* The value
* @return The registry key
*/
Object getRegistryKey(Object value);
/**
* Gets a registry value by key and class.
*
* @param key
* The key
* @param tClass
* The class
* @return The registry value
*/
Object getRegistryValue(String key, Object tClass);
String getPaintingArtKey(Painting painting);
Art getPaintingArt(String name);
/**
* Parses a legacy material name.
*
* @param name
* The legacy name
* @return The parsed name
*/
String parseLegacyName(String name);
boolean isCrafter(InventoryType type);
boolean isCopperChest(Material material);
Set<Material> copperChestMaterials();
Set<Material> shelfMaterials();
}
@@ -1,105 +0,0 @@
package net.coreprotect.bukkit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.block.Sign;
import org.bukkit.entity.Bee;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SuspiciousStewMeta;
import org.bukkit.potion.PotionEffect;
public class Bukkit_v1_15 extends BukkitAdapter implements BukkitInterface {
@Override
public boolean getEntityMeta(LivingEntity entity, List<Object> info) {
if (entity instanceof Bee) {
Bee bee = (Bee) entity;
info.add(bee.getAnger());
info.add(bee.hasNectar());
info.add(bee.hasStung());
}
else {
return false;
}
return true;
}
@Override
public boolean setEntityMeta(Entity entity, Object value, int count) {
if (entity instanceof Bee) {
Bee bee = (Bee) entity;
if (count == 0) {
int set = (int) value;
bee.setAnger(set);
}
else if (count == 1) {
boolean set = (Boolean) value;
bee.setHasNectar(set);
}
else if (count == 2) {
boolean set = (Boolean) value;
bee.setHasStung(set);
}
}
else {
return false;
}
return true;
}
@Override
public boolean getItemMeta(ItemMeta itemMeta, List<Map<String, Object>> list, List<List<Map<String, Object>>> metadata, int slot) {
if (itemMeta instanceof SuspiciousStewMeta) {
SuspiciousStewMeta meta = (SuspiciousStewMeta) itemMeta;
SuspiciousStewMeta subMeta = meta.clone();
meta.clearCustomEffects();
list.add(meta.serialize());
metadata.add(list);
if (subMeta.hasCustomEffects()) {
for (PotionEffect effect : subMeta.getCustomEffects()) {
list = new ArrayList<>();
list.add(effect.serialize());
metadata.add(list);
}
}
}
else {
return false;
}
return true;
}
@Override
public boolean setItemMeta(Material rowType, ItemStack itemstack, List<Map<String, Object>> map) {
if ((rowType == Material.SUSPICIOUS_STEW)) {
for (Map<String, Object> suspiciousStewData : map) {
SuspiciousStewMeta meta = (SuspiciousStewMeta) itemstack.getItemMeta();
PotionEffect effect = new PotionEffect(suspiciousStewData);
meta.addCustomEffect(effect, true);
itemstack.setItemMeta(meta);
}
}
else {
return false;
}
return true;
}
@Override
public void sendSignChange(Player player, Sign sign) {
player.sendSignChange(sign.getLocation(), sign.getLines(), sign.getColor());
}
}
@@ -1,118 +0,0 @@
package net.coreprotect.bukkit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.FaceAttachable;
import org.bukkit.block.data.type.Wall;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Piglin;
import org.bukkit.entity.Zoglin;
import net.coreprotect.model.BlockGroup;
public class Bukkit_v1_16 extends Bukkit_v1_15 implements BukkitInterface {
public Bukkit_v1_16() {
BlockGroup.TRACK_TOP = new HashSet<>(Arrays.asList(Material.TORCH, Material.REDSTONE_TORCH, Material.BAMBOO, Material.BAMBOO_SAPLING, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.SCAFFOLDING, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.GRASS, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.REDSTONE_WIRE, Material.WHEAT, Material.ACACIA_SIGN, Material.BIRCH_SIGN, Material.DARK_OAK_SIGN, Material.JUNGLE_SIGN, Material.OAK_SIGN, Material.SPRUCE_SIGN, 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.RAIL, Material.IRON_DOOR, Material.SNOW, Material.CACTUS, Material.SUGAR_CANE, Material.REPEATER, Material.PUMPKIN_STEM, Material.MELON_STEM, Material.CARROT, Material.POTATO, Material.COMPARATOR, Material.ACTIVATOR_RAIL, Material.SUNFLOWER, Material.LILAC, Material.TALL_GRASS, Material.LARGE_FERN, Material.ROSE_BUSH, Material.PEONY, Material.NETHER_WART, Material.CHORUS_PLANT, Material.CHORUS_FLOWER, Material.KELP, Material.SOUL_TORCH, Material.TWISTING_VINES, Material.CRIMSON_FUNGUS, Material.WARPED_FUNGUS, Material.CRIMSON_ROOTS, Material.WARPED_ROOTS, Material.NETHER_SPROUTS, Material.CRIMSON_SIGN, Material.WARPED_SIGN));
BlockGroup.TRACK_BOTTOM = new HashSet<>(Arrays.asList(Material.WEEPING_VINES));
BlockGroup.TRACK_SIDE = new HashSet<>(Arrays.asList(Material.WALL_TORCH, Material.REDSTONE_WALL_TORCH, Material.RAIL, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.ACTIVATOR_RAIL, Material.WHITE_BED, Material.ORANGE_BED, Material.MAGENTA_BED, Material.LIGHT_BLUE_BED, Material.YELLOW_BED, Material.LIME_BED, Material.PINK_BED, Material.GRAY_BED, Material.LIGHT_GRAY_BED, Material.CYAN_BED, Material.PURPLE_BED, Material.BLUE_BED, Material.BROWN_BED, Material.GREEN_BED, Material.RED_BED, Material.BLACK_BED, Material.LADDER, Material.ACACIA_WALL_SIGN, Material.BIRCH_WALL_SIGN, Material.DARK_OAK_WALL_SIGN, Material.JUNGLE_WALL_SIGN, Material.OAK_WALL_SIGN, Material.SPRUCE_WALL_SIGN, Material.VINE, Material.COCOA, Material.TRIPWIRE_HOOK, 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, Material.SOUL_WALL_TORCH, Material.CRIMSON_WALL_SIGN, Material.WARPED_WALL_SIGN));
BlockGroup.DOORS = new HashSet<>(Arrays.asList(Material.OAK_DOOR, Material.SPRUCE_DOOR, Material.BIRCH_DOOR, Material.JUNGLE_DOOR, Material.ACACIA_DOOR, Material.DARK_OAK_DOOR, Material.CRIMSON_DOOR, Material.WARPED_DOOR));
BlockGroup.BUTTONS = new HashSet<>(Arrays.asList(Material.STONE_BUTTON, Material.OAK_BUTTON, Material.ACACIA_BUTTON, Material.BIRCH_BUTTON, Material.DARK_OAK_BUTTON, Material.JUNGLE_BUTTON, Material.SPRUCE_BUTTON, Material.POLISHED_BLACKSTONE_BUTTON, Material.CRIMSON_BUTTON, Material.WARPED_BUTTON));
BlockGroup.PRESSURE_PLATES = new HashSet<>(Arrays.asList(Material.STONE_PRESSURE_PLATE, Material.ACACIA_PRESSURE_PLATE, Material.BIRCH_PRESSURE_PLATE, Material.DARK_OAK_PRESSURE_PLATE, Material.HEAVY_WEIGHTED_PRESSURE_PLATE, Material.JUNGLE_PRESSURE_PLATE, Material.LIGHT_WEIGHTED_PRESSURE_PLATE, Material.OAK_PRESSURE_PLATE, Material.SPRUCE_PRESSURE_PLATE, Material.CRIMSON_PRESSURE_PLATE, Material.WARPED_PRESSURE_PLATE, Material.POLISHED_BLACKSTONE_PRESSURE_PLATE));
BlockGroup.VINES = new HashSet<>(Arrays.asList(Material.VINE, Material.WEEPING_VINES, Material.TWISTING_VINES));
BlockGroup.LIGHTABLES = new HashSet<>(Arrays.asList(Material.CAMPFIRE, Material.SOUL_CAMPFIRE));
BlockGroup.FIRE = new HashSet<>(Arrays.asList(Material.FIRE, Material.SOUL_FIRE));
BlockGroup.LANTERNS = new HashSet<>(Arrays.asList(Material.LANTERN, Material.SOUL_LANTERN));
BlockGroup.SOUL_BLOCKS = new HashSet<>(Arrays.asList(Material.SOUL_SAND, Material.SOUL_SOIL));
BlockGroup.INTERACT_BLOCKS = new HashSet<>(Arrays.asList(Material.SPRUCE_FENCE_GATE, Material.BIRCH_FENCE_GATE, Material.JUNGLE_FENCE_GATE, Material.DARK_OAK_FENCE_GATE, Material.ACACIA_FENCE_GATE, Material.DISPENSER, Material.NOTE_BLOCK, Material.CHEST, Material.FURNACE, Material.LEVER, Material.REPEATER, Material.ACACIA_TRAPDOOR, Material.BIRCH_TRAPDOOR, Material.DARK_OAK_TRAPDOOR, Material.JUNGLE_TRAPDOOR, Material.SPRUCE_TRAPDOOR, Material.OAK_TRAPDOOR, Material.OAK_FENCE_GATE, Material.BREWING_STAND, Material.ANVIL, Material.CHIPPED_ANVIL, Material.DAMAGED_ANVIL, Material.ENDER_CHEST, Material.TRAPPED_CHEST, Material.COMPARATOR, Material.HOPPER, Material.DROPPER, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.BARREL, Material.BLAST_FURNACE, Material.GRINDSTONE, Material.LOOM, Material.SMOKER, Material.CARTOGRAPHY_TABLE, Material.CRIMSON_FENCE_GATE, Material.WARPED_FENCE_GATE, Material.CRIMSON_TRAPDOOR, Material.WARPED_TRAPDOOR));
BlockGroup.SAFE_INTERACT_BLOCKS = new HashSet<>(Arrays.asList(Material.LEVER, Material.ACACIA_TRAPDOOR, Material.BIRCH_TRAPDOOR, Material.DARK_OAK_TRAPDOOR, Material.JUNGLE_TRAPDOOR, Material.SPRUCE_TRAPDOOR, Material.OAK_TRAPDOOR, Material.OAK_FENCE_GATE, Material.SPRUCE_FENCE_GATE, Material.BIRCH_FENCE_GATE, Material.JUNGLE_FENCE_GATE, Material.DARK_OAK_FENCE_GATE, Material.ACACIA_FENCE_GATE, Material.CRIMSON_FENCE_GATE, Material.WARPED_FENCE_GATE, Material.CRIMSON_TRAPDOOR, Material.WARPED_TRAPDOOR));
BlockGroup.UPDATE_STATE = new HashSet<>(Arrays.asList(Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.RAIL, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FURNACE, Material.BLAST_FURNACE, Material.SMOKER, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.GLOWSTONE, Material.JACK_O_LANTERN, Material.REPEATER, Material.REDSTONE_LAMP, Material.BEACON, Material.COMPARATOR, Material.DAYLIGHT_DETECTOR, Material.REDSTONE_BLOCK, Material.HOPPER, Material.CHEST, Material.TRAPPED_CHEST, Material.ACTIVATOR_RAIL, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH, Material.SHROOMLIGHT, Material.RESPAWN_ANCHOR, Material.CRYING_OBSIDIAN, Material.TARGET));
BlockGroup.NON_ATTACHABLE = new HashSet<>(Arrays.asList(Material.AIR, Material.CAVE_AIR, Material.BARRIER, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.WATER, Material.LAVA, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.GRASS, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.LADDER, Material.RAIL, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.SNOW, Material.SUGAR_CANE, Material.NETHER_PORTAL, Material.REPEATER, Material.KELP, Material.CHORUS_FLOWER, Material.CHORUS_PLANT, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH));
}
@Override
public boolean getEntityMeta(LivingEntity entity, List<Object> info) {
if (entity instanceof Piglin) {
Piglin piglin = (Piglin) entity;
info.add(piglin.isBaby());
}
else if (entity instanceof Zoglin) {
Zoglin zoglin = (Zoglin) entity;
info.add(zoglin.isBaby());
}
else if (super.getEntityMeta(entity, info)) {
return true;
}
else {
return false;
}
return true;
}
@Override
public boolean setEntityMeta(Entity entity, Object value, int count) {
if (entity instanceof Piglin) {
Piglin piglin = (Piglin) entity;
if (count == 0) {
boolean set = (Boolean) value;
piglin.setBaby(set);
}
}
else if (entity instanceof Zoglin) {
Zoglin zoglin = (Zoglin) entity;
if (count == 0) {
boolean set = (Boolean) value;
zoglin.setBaby(set);
}
}
else if (super.setEntityMeta(entity, value, count)) {
return true;
}
else {
return false;
}
return true;
}
@Override
public boolean isAttached(Block block, Block scanBlock, BlockData blockData, int scanMin) {
if (blockData instanceof Directional && blockData instanceof FaceAttachable) {
Directional directional = (Directional) blockData;
FaceAttachable faceAttachable = (FaceAttachable) blockData;
boolean scanButton = false;
switch (faceAttachable.getAttachedFace()) {
case WALL:
scanButton = (scanMin < 5 && scanBlock.getRelative(directional.getFacing().getOppositeFace()).getLocation().equals(block.getLocation()));
break;
case FLOOR:
scanButton = (scanMin == 5);
break;
case CEILING:
scanButton = (scanMin == 6);
break;
default:
break;
}
return scanButton;
}
return true; // unvalidated attachments default to true
}
@Override
public boolean isWall(BlockData blockData) {
return (blockData instanceof Wall);
}
}
@@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@@ -15,6 +16,7 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.PointedDripstone;
import org.bukkit.entity.Axolotl;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.GlowItemFrame;
import org.bukkit.entity.Goat;
import org.bukkit.entity.ItemFrame;
@@ -24,20 +26,47 @@ import org.bukkit.inventory.meta.BundleMeta;
import org.bukkit.inventory.meta.ItemMeta;
import net.coreprotect.model.BlockGroup;
import net.coreprotect.utility.Util;
import net.coreprotect.utility.ItemUtils;
import net.coreprotect.utility.MaterialUtils;
public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
/**
* Bukkit adapter implementation for Minecraft 1.17.
* Provides version-specific implementations for the BukkitInterface
* to handle features introduced in the 1.17 update.
*/
public class Bukkit_v1_17 extends BukkitAdapter {
/**
* Initializes the Bukkit_v1_17 adapter with 1.17-specific block groups.
* Sets up collections of blocks with similar behavior for efficient handling.
*/
public Bukkit_v1_17() {
BlockGroup.TRACK_ANY = new HashSet<>(Arrays.asList(Material.MOVING_PISTON, Material.LEVER, Material.BELL, Material.SMALL_AMETHYST_BUD, Material.MEDIUM_AMETHYST_BUD, Material.LARGE_AMETHYST_BUD, Material.AMETHYST_CLUSTER, Material.GLOW_LICHEN));
BlockGroup.TRACK_TOP = new HashSet<>(Arrays.asList(Material.TORCH, Material.REDSTONE_TORCH, Material.BAMBOO, Material.BAMBOO_SAPLING, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.SCAFFOLDING, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.GRASS, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.REDSTONE_WIRE, Material.WHEAT, Material.ACACIA_SIGN, Material.BIRCH_SIGN, Material.DARK_OAK_SIGN, Material.JUNGLE_SIGN, Material.OAK_SIGN, Material.SPRUCE_SIGN, 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.RAIL, Material.IRON_DOOR, Material.SNOW, Material.CACTUS, Material.SUGAR_CANE, Material.REPEATER, Material.PUMPKIN_STEM, Material.MELON_STEM, Material.CARROT, Material.POTATO, Material.COMPARATOR, Material.ACTIVATOR_RAIL, Material.SUNFLOWER, Material.LILAC, Material.TALL_GRASS, Material.LARGE_FERN, Material.ROSE_BUSH, Material.PEONY, Material.NETHER_WART, Material.CHORUS_PLANT, Material.CHORUS_FLOWER, Material.KELP, Material.SOUL_TORCH, Material.TWISTING_VINES, Material.CRIMSON_FUNGUS, Material.WARPED_FUNGUS, Material.CRIMSON_ROOTS, Material.WARPED_ROOTS, Material.NETHER_SPROUTS, Material.CRIMSON_SIGN, Material.WARPED_SIGN, Material.AZALEA, Material.FLOWERING_AZALEA, Material.SMALL_DRIPLEAF, Material.BIG_DRIPLEAF));
initializeBlockGroups();
}
/**
* Initializes all the block groups for Minecraft 1.17.
* This includes new blocks like amethyst, candles, pointed dripstone, etc.
*/
private void initializeBlockGroups() {
BlockGroup.TRACK_ANY = new HashSet<>(Arrays.asList(Material.PISTON_HEAD, Material.LEVER, Material.BELL, Material.SMALL_AMETHYST_BUD, Material.MEDIUM_AMETHYST_BUD, Material.LARGE_AMETHYST_BUD, Material.AMETHYST_CLUSTER, Material.GLOW_LICHEN));
BlockGroup.TRACK_TOP = new HashSet<>(Arrays.asList(Material.TORCH, Material.REDSTONE_TORCH, Material.BAMBOO, Material.BAMBOO_SAPLING, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.SCAFFOLDING, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.REDSTONE_WIRE, Material.WHEAT, Material.ACACIA_SIGN, Material.BIRCH_SIGN, Material.DARK_OAK_SIGN, Material.JUNGLE_SIGN, Material.OAK_SIGN, Material.SPRUCE_SIGN, 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.RAIL, Material.IRON_DOOR, Material.SNOW, Material.CACTUS, Material.SUGAR_CANE, Material.REPEATER, Material.PUMPKIN_STEM, Material.MELON_STEM, Material.CARROT, Material.POTATO, Material.COMPARATOR, Material.ACTIVATOR_RAIL, Material.SUNFLOWER, Material.LILAC, Material.TALL_GRASS, Material.LARGE_FERN, Material.ROSE_BUSH, Material.PEONY, Material.NETHER_WART, Material.CHORUS_PLANT, Material.CHORUS_FLOWER, Material.KELP, Material.SOUL_TORCH, Material.TWISTING_VINES, Material.CRIMSON_FUNGUS, Material.WARPED_FUNGUS, Material.CRIMSON_ROOTS, Material.WARPED_ROOTS, Material.NETHER_SPROUTS, Material.CRIMSON_SIGN, Material.WARPED_SIGN, Material.AZALEA, Material.FLOWERING_AZALEA, Material.SMALL_DRIPLEAF, Material.BIG_DRIPLEAF));
BlockGroup.TRACK_TOP_BOTTOM = new HashSet<>(Arrays.asList(Material.POINTED_DRIPSTONE, Material.BIG_DRIPLEAF_STEM));
BlockGroup.TRACK_BOTTOM = new HashSet<>(Arrays.asList(Material.WEEPING_VINES, Material.CAVE_VINES, Material.CAVE_VINES_PLANT, Material.HANGING_ROOTS));
BlockGroup.VINES = new HashSet<>(Arrays.asList(Material.VINE, Material.WEEPING_VINES, Material.TWISTING_VINES, Material.CAVE_VINES));
BlockGroup.CANDLES = new HashSet<>(Arrays.asList(Material.CANDLE, Material.BLACK_CANDLE, Material.BLUE_CANDLE, Material.BROWN_CANDLE, Material.CYAN_CANDLE, Material.GRAY_CANDLE, Material.GREEN_CANDLE, Material.LIGHT_BLUE_CANDLE, Material.LIGHT_GRAY_CANDLE, Material.LIME_CANDLE, Material.MAGENTA_CANDLE, Material.ORANGE_CANDLE, Material.PINK_CANDLE, Material.PURPLE_CANDLE, Material.RED_CANDLE, Material.WHITE_CANDLE, Material.YELLOW_CANDLE, Material.CANDLE_CAKE, Material.BLACK_CANDLE_CAKE, Material.BLUE_CANDLE_CAKE, Material.BROWN_CANDLE_CAKE, Material.CYAN_CANDLE_CAKE, Material.GRAY_CANDLE_CAKE, Material.GREEN_CANDLE_CAKE, Material.LIGHT_BLUE_CANDLE_CAKE, Material.LIGHT_GRAY_CANDLE_CAKE, Material.LIME_CANDLE_CAKE, Material.MAGENTA_CANDLE_CAKE, Material.ORANGE_CANDLE_CAKE, Material.PINK_CANDLE_CAKE, Material.PURPLE_CANDLE_CAKE, Material.RED_CANDLE_CAKE, Material.WHITE_CANDLE_CAKE, Material.YELLOW_CANDLE_CAKE));
BlockGroup.AMETHYST = new HashSet<>(Arrays.asList(Material.SMALL_AMETHYST_BUD, Material.MEDIUM_AMETHYST_BUD, Material.LARGE_AMETHYST_BUD, Material.AMETHYST_CLUSTER));
BlockGroup.UPDATE_STATE = new HashSet<>(Arrays.asList(Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.RAIL, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FURNACE, Material.BLAST_FURNACE, Material.SMOKER, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.GLOWSTONE, Material.JACK_O_LANTERN, Material.REPEATER, Material.REDSTONE_LAMP, Material.BEACON, Material.COMPARATOR, Material.DAYLIGHT_DETECTOR, Material.REDSTONE_BLOCK, Material.HOPPER, Material.CHEST, Material.TRAPPED_CHEST, Material.ACTIVATOR_RAIL, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH, Material.SHROOMLIGHT, Material.RESPAWN_ANCHOR, Material.CRYING_OBSIDIAN, Material.TARGET, Material.SMALL_AMETHYST_BUD, Material.MEDIUM_AMETHYST_BUD, Material.LARGE_AMETHYST_BUD, Material.AMETHYST_CLUSTER, Material.CAVE_VINES, Material.CAVE_VINES_PLANT, Material.GLOW_LICHEN, Material.LIGHT, Material.LAVA_CAULDRON));
BlockGroup.NON_ATTACHABLE = new HashSet<>(Arrays.asList(Material.AIR, Material.CAVE_AIR, Material.BARRIER, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.WATER, Material.LAVA, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.GRASS, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.LADDER, Material.RAIL, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.SNOW, Material.SUGAR_CANE, Material.NETHER_PORTAL, Material.REPEATER, Material.KELP, Material.CHORUS_FLOWER, Material.CHORUS_PLANT, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH, Material.LIGHT, Material.SMALL_DRIPLEAF, Material.BIG_DRIPLEAF, Material.BIG_DRIPLEAF_STEM, Material.GLOW_LICHEN, Material.HANGING_ROOTS));
BlockGroup.NON_ATTACHABLE = new HashSet<>(Arrays.asList(Material.AIR, Material.CAVE_AIR, Material.BARRIER, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.WATER, Material.LAVA, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.LADDER, Material.RAIL, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.SNOW, Material.SUGAR_CANE, Material.NETHER_PORTAL, Material.REPEATER, Material.KELP, Material.CHORUS_FLOWER, Material.CHORUS_PLANT, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH, Material.LIGHT, Material.SMALL_DRIPLEAF, Material.BIG_DRIPLEAF, Material.BIG_DRIPLEAF_STEM, Material.GLOW_LICHEN, Material.HANGING_ROOTS));
BlockGroup.VERTICAL_TOP_BOTTOM = new HashSet<>(Arrays.asList(Material.BIG_DRIPLEAF_STEM));
}
@@ -45,20 +74,17 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
public String parseLegacyName(String name) {
switch (name) {
case "GRASS_PATH":
name = "DIRT_PATH";
break;
return "DIRT_PATH";
default:
break;
return name;
}
return name;
}
@Override
public int getLegacyBlockId(Material material) {
switch (material) {
case DIRT_PATH:
return Util.getBlockId("GRASS_PATH", false);
return MaterialUtils.getBlockId("GRASS_PATH", false);
default:
return -1;
}
@@ -69,19 +95,15 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
if (entity instanceof Axolotl) {
Axolotl axolotl = (Axolotl) entity;
info.add(axolotl.getVariant());
return true;
}
else if (entity instanceof Goat) {
Goat goat = (Goat) entity;
info.add(goat.isScreaming());
}
else if (super.getEntityMeta(entity, info)) {
return true;
}
else {
return false;
}
return true;
return super.getEntityMeta(entity, info);
}
@Override
@@ -89,25 +111,28 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
if (entity instanceof Axolotl) {
Axolotl axolotl = (Axolotl) entity;
if (count == 0) {
org.bukkit.entity.Axolotl.Variant set = (org.bukkit.entity.Axolotl.Variant) value;
axolotl.setVariant(set);
try {
org.bukkit.entity.Axolotl.Variant variant = value instanceof String
? org.bukkit.entity.Axolotl.Variant.valueOf((String) value)
: (org.bukkit.entity.Axolotl.Variant) value;
axolotl.setVariant(variant);
return true;
}
catch (IllegalArgumentException | ClassCastException exception) {
return false;
}
}
}
else if (entity instanceof Goat) {
Goat goat = (Goat) entity;
if (count == 0) {
boolean set = (Boolean) value;
goat.setScreaming(set);
boolean isScreaming = (Boolean) value;
goat.setScreaming(isScreaming);
return true;
}
}
else if (super.setEntityMeta(entity, value, count)) {
return true;
}
else {
return false;
}
return true;
return super.setEntityMeta(entity, value, count);
}
@Override
@@ -122,44 +147,40 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
if (subMeta.hasItems()) {
list = new ArrayList<>();
for (ItemStack itemStack : subMeta.getItems()) {
Map<String, Object> itemMap = Util.serializeItemStack(itemStack, slot);
Map<String, Object> itemMap = ItemUtils.serializeItemStack(itemStack, null, slot);
if (itemMap.size() > 0) {
list.add(itemMap);
}
}
metadata.add(list);
}
}
else if (super.getItemMeta(itemMeta, list, metadata, slot)) {
return true;
}
else {
return false;
}
return true;
return super.getItemMeta(itemMeta, list, metadata, slot);
}
@Override
public boolean isBundle(Material material) {
return material == Material.BUNDLE;
}
@Override
public boolean setItemMeta(Material rowType, ItemStack itemstack, List<Map<String, Object>> map) {
if ((rowType == Material.BUNDLE)) {
if (BukkitAdapter.ADAPTER.isBundle(rowType)) {
BundleMeta meta = (BundleMeta) itemstack.getItemMeta();
for (Map<String, Object> itemData : map) {
ItemStack itemStack = Util.unserializeItemStack(itemData);
ItemStack itemStack = ItemUtils.unserializeItemStack(itemData);
if (itemStack != null) {
meta.addItem(itemStack);
}
}
itemstack.setItemMeta(meta);
}
else if (super.setItemMeta(rowType, itemstack, map)) {
return true;
}
else {
return false;
}
return true;
return super.setItemMeta(rowType, itemstack, map);
}
@Override
@@ -168,15 +189,10 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
PointedDripstone pointedDripstone = (PointedDripstone) blockData;
BlockFace blockFace = pointedDripstone.getVerticalDirection();
boolean adjacent = scanBlock.getRelative(blockFace.getOppositeFace()).getLocation().equals(block.getLocation());
if (!adjacent) {
return false;
}
}
else if (!super.isAttached(block, scanBlock, blockData, scanMin)) {
return false;
return adjacent;
}
return true;
return super.isAttached(block, scanBlock, blockData, scanMin);
}
@Override
@@ -186,7 +202,7 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
@Override
public Material getBucketContents(Material material) {
return (material == Material.POWDER_SNOW_BUCKET ? Material.POWDER_SNOW : Material.AIR);
return (material == Material.POWDER_SNOW_BUCKET) ? Material.POWDER_SNOW : Material.AIR;
}
@Override
@@ -199,19 +215,39 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
return (entity instanceof GlowItemFrame) ? Material.GLOW_ITEM_FRAME : Material.ITEM_FRAME;
}
@Override
public Material getFrameType(EntityType type) {
switch (type) {
case ITEM_FRAME:
return Material.ITEM_FRAME;
case GLOW_ITEM_FRAME:
return Material.GLOW_ITEM_FRAME;
default:
return null;
}
}
@Override
public Class<?> getFrameClass(Material material) {
return (material == Material.GLOW_ITEM_FRAME) ? GlowItemFrame.class : ItemFrame.class;
}
@Override
public boolean isGlowing(Sign sign) {
public boolean isGlowing(Sign sign, boolean isFront) {
if (!isFront) {
return false;
}
return sign.isGlowingText();
}
@Override
public void setGlowing(Sign sign, boolean set) {
sign.setGlowingText(set);
public void setGlowing(Sign sign, boolean isFront, boolean isGlowing) {
if (!isFront) {
return;
}
sign.setGlowingText(isGlowing);
}
@Override
@@ -219,4 +255,8 @@ public class Bukkit_v1_17 extends Bukkit_v1_16 implements BukkitInterface {
return material.isAir() || material == Material.LIGHT;
}
@Override
public boolean isChunkEntitiesLoaded(Chunk chunk) {
return chunk.isEntitiesLoaded();
}
}
@@ -0,0 +1,62 @@
package net.coreprotect.bukkit;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
/**
* Bukkit adapter implementation for Minecraft 1.18.
* Provides version-specific implementations for the BukkitInterface
* to handle features introduced in the 1.18 update.
*/
public class Bukkit_v1_18 extends Bukkit_v1_17 {
/**
* Flag to track whether the MerchantRecipe.adjust() method is available.
* This is initialized on first use and prevents repeated reflection checks.
* - null: Not yet checked
* - true: Method exists and should be used
* - false: Method doesn't exist or failed, fallback to parent implementation
*/
private Boolean hasAdjustMethod = null;
/**
* Adjusts an ingredient in a merchant recipe for version 1.18+.
* This handles changes to the MerchantRecipe API introduced in Bukkit 1.18.1.
*
* @param recipe
* The merchant recipe
* @param itemStack
* The item stack to adjust
* @return The adjusted item stack, or null if adjustment not supported or fails
*/
@Override
public ItemStack adjustIngredient(MerchantRecipe recipe, ItemStack itemStack) {
if (itemStack == null) {
return null;
}
try {
// First-time detection of adjust method availability
if (hasAdjustMethod == null) {
hasAdjustMethod = true;
// Test if the adjust method exists using reflection
MerchantRecipe.class.getMethod("adjust", ItemStack.class); // Bukkit 1.18.1+
}
// Skip if we've already determined the method isn't available
else if (Boolean.FALSE.equals(hasAdjustMethod)) {
return super.adjustIngredient(recipe, itemStack);
}
// Create a clone to avoid modifying the original itemStack
ItemStack adjustedStack = itemStack.clone();
recipe.adjust(adjustedStack);
return adjustedStack;
}
catch (Exception e) {
// Method doesn't exist or failed, mark it for future calls
hasAdjustMethod = false;
return super.adjustIngredient(recipe, itemStack);
}
}
}
@@ -0,0 +1,250 @@
package net.coreprotect.bukkit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Goat;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Tadpole;
import net.coreprotect.model.BlockGroup;
/**
* Bukkit adapter implementation for Minecraft 1.19.
* Provides version-specific implementations for the BukkitInterface
* to handle features introduced in the 1.19 update, including:
* - New block types (mangrove)
* - New entity types (frogs, tadpoles)
* - Updates to existing entities (goats)
* - Sculk blocks
*/
public class Bukkit_v1_19 extends Bukkit_v1_18 {
/**
* Initializes the Bukkit_v1_19 adapter with 1.19-specific block groups.
* Sets up collections of blocks with similar behavior for efficient handling.
*/
public Bukkit_v1_19() {
initializeBlockGroups();
}
/**
* Initializes all the block groups for Minecraft 1.19.
* This organizes blocks into functional categories for efficient lookups and operations.
*/
private void initializeBlockGroups() {
// Blocks that need tracking from the top face
initializeTrackTopBlocks();
// Blocks that need tracking from the side
initializeTrackSideBlocks();
// Door blocks
initializeDoorBlocks();
// Button blocks
initializeButtonBlocks();
// Pressure plate blocks
initializePressurePlateBlocks();
// Blocks that can be interacted with
initializeInteractBlocks();
// Blocks that can be safely interacted with (won't cause block updates)
initializeSafeInteractBlocks();
// Blocks that can't have other blocks attached to them
initializeNonAttachableBlocks();
// Sculk blocks (new in 1.19)
initializeSculkBlocks();
}
/**
* Initializes blocks that need tracking from their top face.
*/
private void initializeTrackTopBlocks() {
BlockGroup.TRACK_TOP = new HashSet<>(Arrays.asList(Material.TORCH, Material.REDSTONE_TORCH, Material.BAMBOO, Material.BAMBOO_SAPLING, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.SCAFFOLDING, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.MANGROVE_PROPAGULE, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.REDSTONE_WIRE, Material.WHEAT, Material.MANGROVE_SIGN, Material.ACACIA_SIGN, Material.BIRCH_SIGN, Material.DARK_OAK_SIGN, Material.JUNGLE_SIGN, Material.OAK_SIGN, Material.SPRUCE_SIGN, 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.RAIL, Material.IRON_DOOR, Material.SNOW, Material.CACTUS, Material.SUGAR_CANE, Material.REPEATER, Material.PUMPKIN_STEM, Material.MELON_STEM, Material.CARROT, Material.POTATO, Material.COMPARATOR, Material.ACTIVATOR_RAIL, Material.SUNFLOWER, Material.LILAC, Material.TALL_GRASS, Material.LARGE_FERN, Material.ROSE_BUSH, Material.PEONY, Material.NETHER_WART, Material.CHORUS_PLANT, Material.CHORUS_FLOWER, Material.KELP, Material.SOUL_TORCH, Material.TWISTING_VINES, Material.CRIMSON_FUNGUS, Material.WARPED_FUNGUS, Material.CRIMSON_ROOTS, Material.WARPED_ROOTS, Material.NETHER_SPROUTS, Material.CRIMSON_SIGN, Material.WARPED_SIGN, Material.AZALEA, Material.FLOWERING_AZALEA, Material.SMALL_DRIPLEAF, Material.BIG_DRIPLEAF));
}
/**
* Initializes blocks that need tracking from their side.
*/
private void initializeTrackSideBlocks() {
BlockGroup.TRACK_SIDE = new HashSet<>(Arrays.asList(Material.WALL_TORCH, Material.REDSTONE_WALL_TORCH, Material.RAIL, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.ACTIVATOR_RAIL, Material.WHITE_BED, Material.ORANGE_BED, Material.MAGENTA_BED, Material.LIGHT_BLUE_BED, Material.YELLOW_BED, Material.LIME_BED, Material.PINK_BED, Material.GRAY_BED, Material.LIGHT_GRAY_BED, Material.CYAN_BED, Material.PURPLE_BED, Material.BLUE_BED, Material.BROWN_BED, Material.GREEN_BED, Material.RED_BED, Material.BLACK_BED, Material.LADDER, Material.MANGROVE_WALL_SIGN, Material.ACACIA_WALL_SIGN, Material.BIRCH_WALL_SIGN, Material.DARK_OAK_WALL_SIGN, Material.JUNGLE_WALL_SIGN, Material.OAK_WALL_SIGN, Material.SPRUCE_WALL_SIGN, Material.VINE, Material.COCOA, Material.TRIPWIRE_HOOK, 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, Material.SOUL_WALL_TORCH, Material.CRIMSON_WALL_SIGN, Material.WARPED_WALL_SIGN));
}
/**
* Initializes door blocks.
*/
private void initializeDoorBlocks() {
BlockGroup.DOORS = new HashSet<>(Arrays.asList(Material.OAK_DOOR, Material.SPRUCE_DOOR, Material.BIRCH_DOOR, Material.JUNGLE_DOOR, Material.MANGROVE_DOOR, Material.ACACIA_DOOR, Material.DARK_OAK_DOOR, Material.CRIMSON_DOOR, Material.WARPED_DOOR));
}
/**
* Initializes button blocks.
*/
private void initializeButtonBlocks() {
BlockGroup.BUTTONS = new HashSet<>(Arrays.asList(Material.STONE_BUTTON, Material.OAK_BUTTON, Material.MANGROVE_BUTTON, Material.ACACIA_BUTTON, Material.BIRCH_BUTTON, Material.DARK_OAK_BUTTON, Material.JUNGLE_BUTTON, Material.SPRUCE_BUTTON, Material.POLISHED_BLACKSTONE_BUTTON, Material.CRIMSON_BUTTON, Material.WARPED_BUTTON));
}
/**
* Initializes pressure plate blocks.
*/
private void initializePressurePlateBlocks() {
BlockGroup.PRESSURE_PLATES = new HashSet<>(Arrays.asList(Material.STONE_PRESSURE_PLATE, Material.MANGROVE_PRESSURE_PLATE, Material.ACACIA_PRESSURE_PLATE, Material.BIRCH_PRESSURE_PLATE, Material.DARK_OAK_PRESSURE_PLATE, Material.HEAVY_WEIGHTED_PRESSURE_PLATE, Material.JUNGLE_PRESSURE_PLATE, Material.LIGHT_WEIGHTED_PRESSURE_PLATE, Material.OAK_PRESSURE_PLATE, Material.SPRUCE_PRESSURE_PLATE, Material.CRIMSON_PRESSURE_PLATE, Material.WARPED_PRESSURE_PLATE, Material.POLISHED_BLACKSTONE_PRESSURE_PLATE));
}
/**
* Initializes blocks that can be interacted with.
*/
private void initializeInteractBlocks() {
BlockGroup.INTERACT_BLOCKS = new HashSet<>(Arrays.asList(Material.SPRUCE_FENCE_GATE, Material.BIRCH_FENCE_GATE, Material.JUNGLE_FENCE_GATE, Material.DARK_OAK_FENCE_GATE, Material.MANGROVE_FENCE_GATE, Material.ACACIA_FENCE_GATE, Material.DISPENSER, Material.NOTE_BLOCK, Material.CHEST, Material.FURNACE, Material.LEVER, Material.REPEATER, Material.MANGROVE_TRAPDOOR, Material.ACACIA_TRAPDOOR, Material.BIRCH_TRAPDOOR, Material.DARK_OAK_TRAPDOOR, Material.JUNGLE_TRAPDOOR, Material.SPRUCE_TRAPDOOR, Material.OAK_TRAPDOOR, Material.OAK_FENCE_GATE, Material.BREWING_STAND, Material.ANVIL, Material.CHIPPED_ANVIL, Material.DAMAGED_ANVIL, Material.ENDER_CHEST, Material.TRAPPED_CHEST, Material.COMPARATOR, Material.HOPPER, Material.DROPPER, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.BARREL, Material.BLAST_FURNACE, Material.GRINDSTONE, Material.LOOM, Material.SMOKER, Material.CRAFTING_TABLE, Material.CARTOGRAPHY_TABLE, Material.ENCHANTING_TABLE, Material.SMITHING_TABLE, Material.STONECUTTER, Material.CRIMSON_FENCE_GATE, Material.WARPED_FENCE_GATE, Material.CRIMSON_TRAPDOOR, Material.WARPED_TRAPDOOR));
}
/**
* Initializes blocks that can be safely interacted with.
*/
private void initializeSafeInteractBlocks() {
BlockGroup.SAFE_INTERACT_BLOCKS = new HashSet<>(Arrays.asList(Material.LEVER, Material.MANGROVE_TRAPDOOR, Material.ACACIA_TRAPDOOR, Material.BIRCH_TRAPDOOR, Material.DARK_OAK_TRAPDOOR, Material.JUNGLE_TRAPDOOR, Material.SPRUCE_TRAPDOOR, Material.OAK_TRAPDOOR, Material.OAK_FENCE_GATE, Material.SPRUCE_FENCE_GATE, Material.BIRCH_FENCE_GATE, Material.JUNGLE_FENCE_GATE, Material.DARK_OAK_FENCE_GATE, Material.MANGROVE_FENCE_GATE, Material.ACACIA_FENCE_GATE, Material.CRIMSON_FENCE_GATE, Material.WARPED_FENCE_GATE, Material.CRIMSON_TRAPDOOR, Material.WARPED_TRAPDOOR));
}
/**
* Initializes blocks that can't have other blocks attached to them.
*/
private void initializeNonAttachableBlocks() {
BlockGroup.NON_ATTACHABLE = new HashSet<>(Arrays.asList(Material.AIR, Material.CAVE_AIR, Material.BARRIER, Material.CORNFLOWER, Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SWEET_BERRY_BUSH, Material.OAK_SAPLING, Material.SPRUCE_SAPLING, Material.BIRCH_SAPLING, Material.JUNGLE_SAPLING, Material.MANGROVE_PROPAGULE, Material.ACACIA_SAPLING, Material.DARK_OAK_SAPLING, Material.WATER, Material.LAVA, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FERN, Material.DEAD_BUSH, Material.DANDELION, Material.POPPY, Material.BLUE_ORCHID, Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.LADDER, Material.RAIL, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.SNOW, Material.SUGAR_CANE, Material.NETHER_PORTAL, Material.REPEATER, Material.KELP, Material.CHORUS_FLOWER, Material.CHORUS_PLANT, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH, Material.LIGHT, Material.SMALL_DRIPLEAF, Material.BIG_DRIPLEAF, Material.BIG_DRIPLEAF_STEM, Material.GLOW_LICHEN, Material.HANGING_ROOTS));
}
/**
* Initializes sculk blocks (new in 1.19).
*/
private void initializeSculkBlocks() {
BlockGroup.SCULK = new HashSet<>(Arrays.asList(Material.SCULK, Material.SCULK_VEIN, Material.SCULK_SENSOR, Material.SCULK_SHRIEKER));
}
/**
* Gets metadata from a living entity specific to 1.19.
* Handles Frog, Tadpole, and updated Goat entities.
*
* @param entity
* The living entity
* @param info
* The list to populate with entity metadata
* @return true if metadata was extracted, false otherwise
*/
@Override
public boolean getEntityMeta(LivingEntity entity, List<Object> info) {
if (entity == null || info == null) {
return false;
}
try {
if (entity instanceof Frog) {
Frog frog = (Frog) entity;
info.add(BukkitAdapter.ADAPTER.getRegistryKey(frog.getVariant()));
return true;
}
else if (entity instanceof Tadpole) {
Tadpole tadpole = (Tadpole) entity;
info.add(tadpole.getAge());
return true;
}
else if (entity instanceof Goat) {
Goat goat = (Goat) entity;
info.add(goat.isScreaming());
info.add(goat.hasLeftHorn());
info.add(goat.hasRightHorn());
return true;
}
// Fall back to parent implementation if not a known 1.19 entity
return super.getEntityMeta(entity, info);
}
catch (Exception e) {
// Log error or handle exception if needed
return false;
}
}
/**
* Sets metadata on an entity specific to 1.19.
* Handles Frog, Tadpole, and updated Goat entities.
*
* @param entity
* The entity
* @param value
* The metadata value
* @param count
* The index of the metadata property
* @return true if metadata was set, false otherwise
*/
@Override
public boolean setEntityMeta(Entity entity, Object value, int count) {
if (entity == null || value == null) {
return false;
}
try {
if (entity instanceof Frog) {
Frog frog = (Frog) entity;
if (count == 0) {
// Convert string registry key to variant if needed
if (value instanceof String) {
String key = (String) value;
value = BukkitAdapter.ADAPTER.getRegistryValue(key, Frog.Variant.class);
if (value == null) {
int separator = key.indexOf(':');
String name = separator < 0 ? key : key.substring(separator + 1).toUpperCase(Locale.ROOT).replace('.', '_').replace('-', '_');
value = BukkitAdapter.getLegacyEnumValue(Frog.Variant.class, name);
}
if (!(value instanceof Frog.Variant)) {
return false;
}
}
frog.setVariant((Frog.Variant) value);
return true;
}
}
else if (entity instanceof Tadpole) {
Tadpole tadpole = (Tadpole) entity;
if (count == 0) {
tadpole.setAge((int) value);
return true;
}
}
else if (entity instanceof Goat) {
Goat goat = (Goat) entity;
boolean boolValue = (Boolean) value;
switch (count) {
case 0:
goat.setScreaming(boolValue);
return true;
case 1:
goat.setLeftHorn(boolValue);
return true;
case 2:
goat.setRightHorn(boolValue);
return true;
default:
// Invalid count for goat
return false;
}
}
// Fall back to parent implementation if not a known 1.19 entity
return super.setEntityMeta(entity, value, count);
}
catch (Exception e) {
// Log error or handle exception if needed
return false;
}
}
}
@@ -0,0 +1,377 @@
package net.coreprotect.bukkit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.bukkit.Color;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.block.ChiseledBookshelf;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionType;
import net.coreprotect.model.BlockGroup;
/**
* Bukkit adapter implementation for Minecraft 1.20.
* Provides version-specific implementations for the BukkitInterface
* to handle features introduced in the 1.20 update, including:
* - Updated sign handling with front/back sides
* - New block types (chiseled bookshelf, decorated pot)
* - Suspicious blocks (sand/gravel)
* - Arrow potion handling
*/
public class Bukkit_v1_20 extends Bukkit_v1_19 {
private Boolean hasClickedPosition;
private Boolean hasSignTargetSide;
private Boolean hasBasePotionType;
private Boolean hasItemName;
private Boolean hasSpawnFunction;
/**
* Initializes the Bukkit_v1_20 adapter with 1.20-specific block groups and mappings.
* Sets up collections of blocks with similar behavior for efficient handling.
*/
public Bukkit_v1_20() {
initializeBlockGroups();
initializeTaggedBlocks();
}
/**
* Initializes the block groups specific to Minecraft 1.20.
*/
private void initializeBlockGroups() {
BlockGroup.CONTAINERS = new HashSet<>(Arrays.asList(Material.JUKEBOX, Material.DISPENSER, Material.CHEST, Material.FURNACE, Material.BREWING_STAND, Material.TRAPPED_CHEST, Material.HOPPER, Material.DROPPER, Material.ARMOR_STAND, Material.ITEM_FRAME, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.BARREL, Material.BLAST_FURNACE, Material.SMOKER, Material.LECTERN, Material.CHISELED_BOOKSHELF, Material.DECORATED_POT));
BlockGroup.UPDATE_STATE = new HashSet<>(Arrays.asList(Material.TORCH, Material.WALL_TORCH, Material.REDSTONE_WIRE, Material.RAIL, Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.FURNACE, Material.BLAST_FURNACE, Material.SMOKER, Material.LEVER, Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH, Material.GLOWSTONE, Material.JACK_O_LANTERN, Material.REPEATER, Material.REDSTONE_LAMP, Material.BEACON, Material.COMPARATOR, Material.DAYLIGHT_DETECTOR, Material.REDSTONE_BLOCK, Material.HOPPER, Material.CHEST, Material.TRAPPED_CHEST, Material.ACTIVATOR_RAIL, Material.SOUL_TORCH, Material.SOUL_WALL_TORCH, Material.SHROOMLIGHT, Material.RESPAWN_ANCHOR, Material.CRYING_OBSIDIAN, Material.TARGET, Material.SMALL_AMETHYST_BUD, Material.MEDIUM_AMETHYST_BUD, Material.LARGE_AMETHYST_BUD, Material.AMETHYST_CLUSTER, Material.CAVE_VINES, Material.CAVE_VINES_PLANT, Material.GLOW_LICHEN, Material.LIGHT, Material.LAVA_CAULDRON, Material.CHISELED_BOOKSHELF));
}
/**
* Initializes blocks based on Bukkit Tags for 1.20.
*/
private void initializeTaggedBlocks() {
// Clear and update button blocks from tag
BlockGroup.BUTTONS.clear();
BlockGroup.BUTTONS.addAll(Tag.BUTTONS.getValues());
// Clear and update pressure plate blocks from tag
BlockGroup.PRESSURE_PLATES.clear();
BlockGroup.PRESSURE_PLATES.addAll(Tag.PRESSURE_PLATES.getValues());
// Add all doors from tag
addMissingTaggedBlocks(Tag.DOORS.getValues(), BlockGroup.DOORS);
// Add all fence gates to interaction blocks
addMissingTaggedBlocks(Tag.FENCE_GATES.getValues(), BlockGroup.INTERACT_BLOCKS);
addMissingTaggedBlocks(Tag.FENCE_GATES.getValues(), BlockGroup.SAFE_INTERACT_BLOCKS);
// Add all wooden trapdoors to interaction blocks
addMissingTaggedBlocks(Tag.WOODEN_TRAPDOORS.getValues(), BlockGroup.INTERACT_BLOCKS);
addMissingTaggedBlocks(Tag.WOODEN_TRAPDOORS.getValues(), BlockGroup.SAFE_INTERACT_BLOCKS);
// Add hanging signs to bottom-tracked blocks
addMissingTaggedBlocks(Tag.CEILING_HANGING_SIGNS.getValues(), BlockGroup.TRACK_BOTTOM);
// Add wall signs to side-tracked blocks
addMissingTaggedBlocks(Tag.WALL_SIGNS.getValues(), BlockGroup.TRACK_SIDE);
// Add saplings to top-tracked and non-attachable blocks
addMissingTaggedBlocks(Tag.SAPLINGS.getValues(), BlockGroup.TRACK_TOP);
addMissingTaggedBlocks(Tag.SAPLINGS.getValues(), BlockGroup.NON_ATTACHABLE);
// Add flowers to top-tracked and non-attachable blocks
addMissingTaggedBlocks(Tag.FLOWERS.getValues(), BlockGroup.TRACK_TOP);
addMissingTaggedBlocks(Tag.FLOWERS.getValues(), BlockGroup.NON_ATTACHABLE);
// Add signs (except wall signs) to top-tracked blocks
for (Material value : Tag.SIGNS.getValues()) {
if (!Tag.WALL_SIGNS.isTagged(value) && !BlockGroup.TRACK_TOP.contains(value)) {
BlockGroup.TRACK_TOP.add(value);
}
}
}
/**
* Helper method to add missing blocks from a tag to a block group.
*
* @param taggedBlocks
* The collection of blocks from a tag
* @param targetGroup
* The block group to add missing blocks to
*/
private void addMissingTaggedBlocks(Iterable<Material> taggedBlocks, Set<Material> targetGroup) {
for (Material value : taggedBlocks) {
if (!targetGroup.contains(value)) {
targetGroup.add(value);
}
}
}
@Override
public Boolean getSignInteractionSide(Sign sign, Player player) {
if (hasSignTargetSide == null) {
try {
Sign.class.getMethod("getTargetSide", Player.class);
hasSignTargetSide = true;
}
catch (NoSuchMethodException e) {
hasSignTargetSide = false;
}
}
return hasSignTargetSide ? sign.getTargetSide(player) == sign.getSide(Side.FRONT) : null;
}
@Override
public void setGlowing(Sign sign, boolean isFront, boolean isGlowing) {
Side side = isFront ? Side.FRONT : Side.BACK;
sign.getSide(side).setGlowingText(isGlowing);
}
@Override
public String parseLegacyName(String name) {
switch (name) {
case "GRASS_PATH":
return "DIRT_PATH";
case "GRASS":
return "SHORT_GRASS";
case "SCUTE":
return "TURTLE_SCUTE";
default:
// Fallback until this method is moved up into v1_21
if ("SHORT_GRASS".equals(name) && Material.getMaterial(name) == null) {
return "GRASS";
}
return name;
}
}
@Override
public void setColor(Sign sign, boolean isFront, int color) {
Side side = isFront ? Side.FRONT : Side.BACK;
sign.getSide(side).setColor(DyeColor.getByColor(Color.fromRGB(color)));
}
@Override
public void setWaxed(Sign sign, boolean isWaxed) {
sign.setWaxed(isWaxed);
}
@Override
public int getColor(Sign sign, boolean isFront) {
Side side = isFront ? Side.FRONT : Side.BACK;
return sign.getSide(side).getColor().getColor().asRGB();
}
@Override
public boolean isGlowing(Sign sign, boolean isFront) {
Side side = isFront ? Side.FRONT : Side.BACK;
return sign.getSide(side).isGlowingText();
}
@Override
public boolean isWaxed(Sign sign) {
return sign.isWaxed();
}
@Override
public Material getPlantSeeds(Material material) {
switch (material) {
case WHEAT:
return Material.WHEAT_SEEDS;
case PUMPKIN_STEM:
return Material.PUMPKIN_SEEDS;
case MELON_STEM:
return Material.MELON_SEEDS;
case BEETROOTS:
return Material.BEETROOT_SEEDS;
case TORCHFLOWER_CROP:
return Material.TORCHFLOWER_SEEDS;
default:
return material;
}
}
@Override
public boolean isDecoratedPot(Material material) {
return material == Material.DECORATED_POT;
}
@Override
public boolean isSuspiciousBlock(Material material) {
return material == Material.SUSPICIOUS_GRAVEL || material == Material.SUSPICIOUS_SAND;
}
@Override
public boolean isSign(Material material) {
return Tag.ALL_SIGNS.isTagged(material);
}
@Override
public boolean isChiseledBookshelf(Material material) {
return material == Material.CHISELED_BOOKSHELF;
}
@Override
public boolean isBookshelfBook(Material material) {
return Tag.ITEMS_BOOKSHELF_BOOKS.isTagged(material);
}
@Override
public ItemStack getChiseledBookshelfBook(BlockState blockState, PlayerInteractEvent event) {
try {
if (hasClickedPosition == null) {
hasClickedPosition = true;
// Check if getClickedPosition method exists (Bukkit 1.20.1+)
PlayerInteractEvent.class.getMethod("getClickedPosition");
}
else if (Boolean.FALSE.equals(hasClickedPosition)) {
return null; // Method doesn't exist, return null
}
ChiseledBookshelf bookshelf = (ChiseledBookshelf) blockState;
int slot = bookshelf.getSlot(event.getClickedPosition());
ItemStack book = bookshelf.getInventory().getItem(slot);
return book == null ? new ItemStack(Material.AIR) : book;
}
catch (Exception e) {
hasClickedPosition = false;
return null;
}
}
@Override
public String getLine(Sign sign, int line) {
if (line < 4) {
return sign.getSide(Side.FRONT).getLine(line);
}
else {
return sign.getSide(Side.BACK).getLine(line - 4);
}
}
@Override
public void setLine(Sign sign, int line, String string) {
string = string == null ? "" : string;
if (line < 4) {
sign.getSide(Side.FRONT).setLine(line, string);
}
else {
sign.getSide(Side.BACK).setLine(line - 4, string);
}
}
@Override
public boolean isSignFront(SignChangeEvent event) {
return event.getSide().equals(Side.FRONT);
}
@Override
public String getItemName(ItemMeta itemMeta) {
if (hasItemName == null) {
try {
ItemMeta.class.getMethod("getItemName");
hasItemName = true;
}
catch (NoSuchMethodException e) {
hasItemName = false;
}
}
return hasItemName && itemMeta.hasItemName() ? itemMeta.getItemName() : "";
}
@Override
public <T extends Entity> T spawn(World world, Location location, Class<T> entityClass, Consumer<? super T> function) {
if (hasSpawnFunction == null) {
try {
// Check if World#spawn accepts java.util.function.Consumer (Bukkit 1.20.2+)
World.class.getMethod("spawn", Location.class, Class.class, Consumer.class);
hasSpawnFunction = true;
}
catch (NoSuchMethodException e) {
hasSpawnFunction = false;
}
}
return hasSpawnFunction ? world.spawn(location, entityClass, function) : super.spawn(world, location, entityClass, function);
}
@Override
public ItemStack getArrowMeta(Arrow arrow, ItemStack itemStack) {
try {
if (hasBasePotionType == null) {
hasBasePotionType = true;
// Check if getBasePotionType method exists (Bukkit 1.20.2+)
Arrow.class.getMethod("getBasePotionType");
}
else if (Boolean.FALSE.equals(hasBasePotionType)) {
return super.getArrowMeta(arrow, itemStack);
}
// Apply potion effects to the arrow
PotionType potionType = arrow.getBasePotionType();
Color color = arrow.getColor();
if (potionType != null || color != null) {
itemStack = new ItemStack(Material.TIPPED_ARROW);
PotionMeta meta = (PotionMeta) itemStack.getItemMeta();
meta.setBasePotionType(potionType);
meta.setColor(color);
for (PotionEffect effect : arrow.getCustomEffects()) {
meta.addCustomEffect(effect, false);
}
itemStack.setItemMeta(meta);
}
return itemStack;
}
catch (Exception e) {
hasBasePotionType = false;
return super.getArrowMeta(arrow, itemStack);
}
}
@Override
public Material getExplodedBlock(BlockExplodeEvent event){
// accoding to the Bukkit docs this will always return air
return event.getExplodedBlockState().getType();
}
@Override
public void addMerchantRecipeMeta(MerchantRecipe recipe, List<Object> recipeData) {
recipeData.add(recipe.getDemand());
}
@Override
public void setMerchantRecipeMeta(MerchantRecipe recipe, List<?> recipeData) {
if (recipeData.size() > 7 && recipeData.get(7) instanceof Number) {
recipe.setDemand(((Number) recipeData.get(7)).intValue());
}
}
}
@@ -0,0 +1,447 @@
package net.coreprotect.bukkit;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.bukkit.Art;
import org.bukkit.Bukkit;
import org.bukkit.ExplosionResult;
import org.bukkit.Keyed;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockType;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Painting;
import org.bukkit.event.Event;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.inventory.InventoryType;
import net.coreprotect.model.BlockGroup;
import net.coreprotect.utility.BlockTypeUtils;
/**
* Bukkit adapter implementation for Minecraft 1.21.
* Provides version-specific implementations for the BukkitInterface
* to handle features introduced in the 1.21 update, including:
* - New block types (crafter)
* - Registry handling for named objects
* - Updated interaction blocks
*/
public class Bukkit_v1_21 extends Bukkit_v1_20 {
public static Set<Material> COPPER_CHESTS = new HashSet<>(Arrays.asList());
public static Set<Material> SHELVES = new HashSet<>(Arrays.asList());
public static Set<Material> BUNDLES = new HashSet<>(Arrays.asList());
/**
* Initializes the Bukkit_v1_21 adapter with 1.21-specific block groups and mappings.
* Sets up collections of blocks with similar behavior for efficient handling.
*/
public Bukkit_v1_21() {
initializeBlockGroups();
initializeTrapdoorBlocks();
initializeBundles();
BlockGroup.INTERACT_BLOCKS.addAll(copperChestMaterials());
BlockGroup.CONTAINERS.addAll(copperChestMaterials());
BlockGroup.UPDATE_STATE.addAll(copperChestMaterials());
BlockGroup.CONTAINERS.addAll(shelfMaterials());
}
/**
* Initializes the block groups specific to Minecraft 1.21.
* Updates container, interaction, and update state blocks with new 1.21 blocks.
*/
private void initializeBlockGroups() {
addToBlockGroupIfMissing(Material.CRAFTER, BlockGroup.CONTAINERS);
addToBlockGroupIfMissing(Material.CRAFTER, BlockGroup.INTERACT_BLOCKS);
addToBlockGroupIfMissing(Material.CRAFTER, BlockGroup.UPDATE_STATE);
}
/**
* Initializes trapdoor blocks for interaction handling.
* Excludes iron trapdoors as they cannot be directly interacted with.
*/
private void initializeTrapdoorBlocks() {
for (Material value : Tag.TRAPDOORS.getValues()) {
// Iron trapdoors are not directly interactable
if (value == Material.IRON_TRAPDOOR) {
continue;
}
// Add to interaction blocks if not already present
addToBlockGroupIfMissing(value, BlockGroup.INTERACT_BLOCKS);
addToBlockGroupIfMissing(value, BlockGroup.SAFE_INTERACT_BLOCKS);
}
}
/**
* Initializes the bundles group to enable the ability to roll back dyed bundles correctly.
* It needs to check whether dyed bundles exist because they were added in 1.21.2.
*/
public void initializeBundles(){
if (BUNDLES.isEmpty()) {
Tag<Material> bundleTag = getMaterialTag(Tag.REGISTRY_ITEMS, "bundles");
if (bundleTag != null) {
BUNDLES.addAll(bundleTag.getValues());
}
if (BUNDLES.isEmpty()) {
addMaterialsEndingWith(BUNDLES, "_BUNDLE");
}
BUNDLES.add(Material.BUNDLE);
}
}
/**
* Helper method to add a block to a block group if it's not already present.
*
* @param block
* The block to add
* @param group
* The group to add the block to
*/
private void addToBlockGroupIfMissing(Material block, Set<Material> group) {
if (!group.contains(block)) {
group.add(block);
}
}
@Override
public boolean hasBlockType(String key) {
return getBlockType(key) != null;
}
@Override
public BlockData createBlockData(String key) {
try {
BlockType blockType = getBlockType(key);
return blockType == null ? null : blockType.createBlockData();
}
catch (Exception e) {
return null;
}
}
@Override
public BlockData createBlockDataFromString(String blockData) {
try {
BlockType blockType = getBlockType(BlockTypeUtils.getBlockDataKey(blockData));
if (blockType == null) {
return null;
}
String states = BlockTypeUtils.getBlockDataStates(blockData);
return states == null ? blockType.createBlockData() : blockType.createBlockData(states);
}
catch (Exception e) {
return null;
}
}
private BlockType getBlockType(String key) {
NamespacedKey namespacedKey = NamespacedKey.fromString(BlockTypeUtils.normalizeKey(key));
return namespacedKey == null ? null : Registry.BLOCK.get(namespacedKey);
}
/**
* Gets the EntityType corresponding to a Material.
* Maps Material to its equivalent EntityType for entity handling.
*
* @param material
* The material to convert
* @return The corresponding EntityType, or UNKNOWN if not mappable
*/
@Override
public EntityType getEntityType(Material material) {
switch (material) {
case END_CRYSTAL:
return EntityType.valueOf("END_CRYSTAL");
default:
return EntityType.UNKNOWN;
}
}
/**
* Gets a registry key string from a keyed object.
* Used for serializing objects that implement Keyed.
*
* @param value
* The keyed object
* @return The string representation of the registry key
*/
@Override
public Object getRegistryKey(Object value) {
return ((Keyed) value).getKey().toString();
}
/**
* Gets a registry value from a key string and class.
* Used for deserializing registry objects.
*
* @param key
* The registry key as a string
* @param tClass
* The class of the registry
* @return The registry value
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Object getRegistryValue(String key, Object tClass) {
NamespacedKey namespacedKey = NamespacedKey.fromString(key);
// return RegistryAccess.registryAccess().getRegistry(RegistryKey.CAT_VARIANT).get((NamespacedKey)value);
return Bukkit.getRegistry((Class) tClass).get(namespacedKey);
}
@Override
public String getPaintingArtKey(Painting painting) {
try {
NamespacedKey key = Registry.ART.getKey(painting.getArt());
if (key != null) {
return normalizePaintingArtKey(key.toString());
}
}
catch (Exception e) {
}
return normalizePaintingArtKey(super.getPaintingArtKey(painting));
}
@Override
public Art getPaintingArt(String name) {
NamespacedKey key = NamespacedKey.fromString(normalizePaintingArtLookupKey(name));
if (key != null) {
Art art = Registry.ART.get(key);
if (art != null) {
return art;
}
}
return super.getPaintingArt(name);
}
private String normalizePaintingArtKey(String name) {
if (name == null) {
return "";
}
String normalized = name.toLowerCase(Locale.ROOT).trim();
if (normalized.startsWith("minecraft:")) {
return normalized.substring("minecraft:".length());
}
return normalized;
}
private String normalizePaintingArtLookupKey(String name) {
String normalized = normalizePaintingArtKey(name);
return normalized.contains(":") ? normalized : "minecraft:" + normalized;
}
/**
* Gets the wolf variant and adds it to the info list.
* This functionality is specific to Minecraft 1.21+.
*
* @param wolf
* The wolf entity
* @param info
* The list to add the variant information to
*/
@Override
public void getWolfVariant(org.bukkit.entity.Wolf wolf, List<Object> info) {
// Add the variant to the info list
info.add(getRegistryKey(wolf.getVariant()));
}
/**
* Sets the wolf variant from the provided value.
* This functionality is specific to Minecraft 1.21+.
*
* @param wolf
* The wolf entity
* @param value
* The variant value to set
*/
@Override
public void setWolfVariant(org.bukkit.entity.Wolf wolf, Object value) {
if (value instanceof String) {
value = getRegistryValue((String) value, org.bukkit.entity.Wolf.Variant.class);
}
org.bukkit.entity.Wolf.Variant variant = (org.bukkit.entity.Wolf.Variant) value;
wolf.setVariant(variant);
}
@Override
public boolean isCrafter(InventoryType type) {
return type == InventoryType.CRAFTER;
}
@Override
public boolean isCopperChest(Material material) {
if (COPPER_CHESTS.contains(material) && material != Material.CHEST) {
return true;
}
return false;
}
@Override
public boolean isShelf(Material material) {
return SHELVES.contains(material);
}
@Override
public boolean isBundle(Material material) {
return BUNDLES.contains(material);
}
@Override
public Set<Material> copperChestMaterials() {
if (COPPER_CHESTS.isEmpty()) {
Material copperChest = Material.getMaterial("COPPER_CHEST");
if (copperChest == null) {
COPPER_CHESTS.add(Material.CHEST);
}
else {
COPPER_CHESTS.add(Material.getMaterial("COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("EXPOSED_COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("WEATHERED_COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("OXIDIZED_COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("WAXED_COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("WAXED_EXPOSED_COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("WAXED_WEATHERED_COPPER_CHEST"));
COPPER_CHESTS.add(Material.getMaterial("WAXED_OXIDIZED_COPPER_CHEST"));
}
}
return COPPER_CHESTS;
}
@Override
public Set<Material> shelfMaterials() {
if (SHELVES.isEmpty()) {
Tag<Material> shelfTag = getMaterialTag(Tag.REGISTRY_BLOCKS, "wooden_shelves");
if (shelfTag != null) {
SHELVES.addAll(shelfTag.getValues());
}
if (SHELVES.isEmpty()) {
addMaterialsEndingWith(SHELVES, "_SHELF");
}
}
return SHELVES;
}
@Override
public List<Location> getShelfInteractionLocations(Block block, BlockFace blockFace) {
List<Location> locations = new ArrayList<>();
BlockData blockData = block.getBlockData();
BlockFace facing = getShelfFacing(blockData);
if (facing == null) {
locations.add(block.getLocation());
return locations;
}
if (blockFace != facing) {
return locations;
}
String sideChain = getShelfSideChain(blockData);
if ("UNCONNECTED".equals(sideChain)) {
locations.add(block.getLocation());
return locations;
}
Block center = block;
int directionX = facing.getModX();
int directionZ = facing.getModZ();
if ("LEFT".equals(sideChain)) {
center = center.getRelative(directionZ, 0, -directionX);
}
else if ("RIGHT".equals(sideChain)) {
center = center.getRelative(-directionZ, 0, directionX);
}
String centerSideChain = getShelfSideChain(center.getBlockData());
if (centerSideChain == null) {
locations.add(block.getLocation());
return locations;
}
locations.add(center.getLocation());
if (!"CENTER".equals(centerSideChain)) {
locations.add(block.getLocation());
return locations;
}
locations.add(center.getRelative(-directionZ, 0, directionX).getLocation());
locations.add(center.getRelative(directionZ, 0, -directionX).getLocation());
return locations;
}
private static Tag<Material> getMaterialTag(String registry, String key) {
return Bukkit.getTag(registry, NamespacedKey.minecraft(key), Material.class);
}
private static void addMaterialsEndingWith(Set<Material> materials, String suffix) {
for (Material material : Material.values()) {
if (material.name().endsWith(suffix)) {
materials.add(material);
}
}
}
private static BlockFace getShelfFacing(BlockData blockData) {
Object facing = invokeNoArgumentMethod(blockData, "getFacing");
return facing instanceof BlockFace ? (BlockFace) facing : null;
}
private static String getShelfSideChain(BlockData blockData) {
Object sideChain = invokeNoArgumentMethod(blockData, "getSideChain");
return sideChain == null ? null : sideChain.toString();
}
private static Object invokeNoArgumentMethod(Object target, String methodName) {
if (target == null) {
return null;
}
try {
Method method = target.getClass().getMethod(methodName);
return method.invoke(target);
}
catch (ReflectiveOperationException | LinkageError e) {
return null;
}
}
@Override
public boolean shouldLogExplosion(Event event){
ExplosionResult result = null;
if (event instanceof EntityExplodeEvent){
result = ((EntityExplodeEvent)event).getExplosionResult();
} else if (event instanceof BlockExplodeEvent){
result = ((BlockExplodeEvent)event).getExplosionResult();
}
return !(result == ExplosionResult.KEEP ||
result == ExplosionResult.TRIGGER_BLOCK
);
}
}
@@ -0,0 +1,50 @@
package net.coreprotect.bukkit;
import java.util.List;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.ZombieNautilus;
/**
* Bukkit adapter implementation for Minecraft 1.21.11.
*/
public class Bukkit_v1_21_11 extends Bukkit_v1_21_5 {
@Override
public boolean getEntityMeta(LivingEntity entity, List<Object> info) {
if (entity instanceof ZombieNautilus) {
ZombieNautilus nautilus = (ZombieNautilus) entity;
info.add(getRegistryKey(nautilus.getVariant()));
return true;
}
return super.getEntityMeta(entity, info);
}
@Override
public boolean setEntityMeta(Entity entity, Object value, int count) {
if (entity instanceof ZombieNautilus && count == 0) {
ZombieNautilus.Variant variant = getZombieNautilusVariant(value);
if (variant == null) {
return false;
}
ZombieNautilus nautilus = (ZombieNautilus) entity;
nautilus.setVariant(variant);
return true;
}
return super.setEntityMeta(entity, value, count);
}
private ZombieNautilus.Variant getZombieNautilusVariant(Object value) {
try {
Object variant = value instanceof String ? getRegistryValue((String) value, ZombieNautilus.Variant.class) : value;
return variant instanceof ZombieNautilus.Variant ? (ZombieNautilus.Variant) variant : null;
}
catch (IllegalArgumentException exception) {
return null;
}
}
}
@@ -0,0 +1,72 @@
package net.coreprotect.bukkit;
import java.util.List;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Pig;
/**
* Bukkit adapter implementation for Minecraft 1.21.5.
*/
public class Bukkit_v1_21_5 extends Bukkit_v1_21 {
@Override
public boolean getEntityMeta(LivingEntity entity, List<Object> info) {
if (entity instanceof Chicken) {
Chicken chicken = (Chicken) entity;
info.add(getRegistryKey(chicken.getVariant()));
return true;
}
else if (entity.getType() == EntityType.COW) {
return BukkitAdapter.getRegistryVariant(this, entity, info, "getVariant");
}
else if (entity instanceof Pig) {
Pig pig = (Pig) entity;
info.add(getRegistryKey(pig.getVariant()));
return true;
}
return super.getEntityMeta(entity, info);
}
@Override
public boolean setEntityMeta(Entity entity, Object value, int count) {
if (entity instanceof Chicken && count == 0) {
Chicken.Variant variant = getChickenVariant(value);
if (variant == null) {
return false;
}
Chicken chicken = (Chicken) entity;
chicken.setVariant(variant);
return true;
}
else if (entity.getType() == EntityType.COW && count == 0) {
return BukkitAdapter.setRegistryVariant(this, entity, value, "getVariant", "setVariant");
}
else if (entity instanceof Pig && count == 1) {
Pig.Variant variant = getPigVariant(value);
if (variant == null) {
return false;
}
Pig pig = (Pig) entity;
pig.setVariant(variant);
return true;
}
return super.setEntityMeta(entity, value, count);
}
private Chicken.Variant getChickenVariant(Object value) {
Object variant = value instanceof String ? getRegistryValue((String) value, Chicken.Variant.class) : value;
return variant instanceof Chicken.Variant ? (Chicken.Variant) variant : null;
}
private Pig.Variant getPigVariant(Object value) {
Object variant = value instanceof String ? getRegistryValue((String) value, Pig.Variant.class) : value;
return variant instanceof Pig.Variant ? (Pig.Variant) variant : null;
}
}
@@ -0,0 +1,20 @@
package net.coreprotect.bukkit;
import org.bukkit.Material;
import net.coreprotect.model.BlockGroup;
/**
* Bukkit adapter implementation for Minecraft 26.2.
*/
public class Bukkit_v26_2 extends Bukkit_v1_21_11 {
public Bukkit_v26_2() {
initializeBlockGroups();
}
private void initializeBlockGroups() {
BlockGroup.TRACK_TOP_BOTTOM.add(Material.SULFUR_SPIKE);
}
}
@@ -11,16 +11,17 @@ import net.coreprotect.language.Phrase;
import net.coreprotect.language.Selector;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.Util;
import net.coreprotect.utility.ErrorReporter;
public class ApplyCommand {
protected static void runCommand(CommandSender user, Command command, boolean permission, String[] args) {
try {
if (ConfigHandler.lastRollback.get(user.getName()) != null) {
List<Object> list = ConfigHandler.lastRollback.get(user.getName());
int time = (Integer) list.get(0);
args = (String[]) list.get(1);
Location location = (Location) list.get(2);
long startTime = (Long) list.get(0);
long endTime = (Long) list.get(1);
args = (String[]) list.get(2);
Location location = (Location) list.get(3);
boolean valid = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("#preview")) {
@@ -33,7 +34,7 @@ public class ApplyCommand {
}
else {
ConfigHandler.lastRollback.remove(user.getName());
RollbackRestoreCommand.runCommand(user, command, permission, args, location, time);
RollbackRestoreCommand.runCommand(user, command, permission, args, location, startTime, endTime);
}
}
else {
@@ -41,7 +42,7 @@ public class ApplyCommand {
}
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
}
}
@@ -11,16 +11,17 @@ import net.coreprotect.language.Phrase;
import net.coreprotect.language.Selector;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.Util;
import net.coreprotect.utility.ErrorReporter;
public class CancelCommand {
protected static void runCommand(CommandSender user, Command command, boolean permission, String[] args) {
try {
if (ConfigHandler.lastRollback.get(user.getName()) != null) {
List<Object> list = ConfigHandler.lastRollback.get(user.getName());
int time = (Integer) list.get(0);
args = (String[]) list.get(1);
Location location = (Location) list.get(2);
long startTime = (Long) list.get(0);
long endTime = (Long) list.get(1);
args = (String[]) list.get(2);
Location location = (Location) list.get(3);
boolean valid = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("#preview")) {
@@ -33,7 +34,7 @@ public class CancelCommand {
}
else {
ConfigHandler.lastRollback.remove(user.getName());
RollbackRestoreCommand.runCommand(user, command, permission, args, location, time);
RollbackRestoreCommand.runCommand(user, command, permission, args, location, startTime, endTime);
}
}
else {
@@ -41,7 +42,7 @@ public class CancelCommand {
}
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,346 @@
package net.coreprotect.command;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.coreprotect.command.parser.*;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
/**
* Main parser class for CoreProtect commands.
* Delegates to specialized parser classes for specific functionality.
*/
public class CommandParser {
/**
* Parse page number from command arguments
*
* @param argumentArray
* The command arguments
* @return The modified argument array
*/
protected static String[] parsePage(String[] argumentArray) {
return ActionParser.parsePage(argumentArray);
}
/**
* Parse action type from command arguments
*
* @param inputArguments
* The command arguments
* @return A list of action type integers
*/
protected static List<Integer> parseAction(String[] inputArguments) {
return ActionParser.parseAction(inputArguments);
}
protected static List<Integer> parseAction(String[] inputArguments, boolean allowMultiple) {
return ActionParser.parseAction(inputArguments, allowMultiple);
}
protected static ActionParser.ParseResult parseActions(String[] inputArguments, boolean allowMultiple) {
return ActionParser.parseActions(inputArguments, allowMultiple);
}
protected static MessageFilterParser.ParseResult parseMessageFilters(String[] inputArguments) {
return MessageFilterParser.parse(inputArguments);
}
protected static RollbackStateParser.ParseResult parseRollbackState(String[] inputArguments) {
return RollbackStateParser.parse(inputArguments);
}
/**
* Parse coordinates from command arguments
*
* @param location
* The base location
* @param inputArguments
* The command arguments
* @param worldId
* The world ID
* @return The parsed location
*/
protected static Location parseCoordinates(Location location, String[] inputArguments, int worldId) {
return LocationParser.parseCoordinates(location, inputArguments, worldId);
}
/**
* Parse count flag from command arguments
*
* @param inputArguments
* The command arguments
* @return true if the count flag is present
*/
protected static boolean parseCount(String[] inputArguments) {
return ActionParser.parseCount(inputArguments);
}
protected static boolean parseSummary(String[] inputArguments) {
return ActionParser.parseSummary(inputArguments);
}
/**
* Parse excluded materials and entities from command arguments
*
* @param player
* The command sender
* @param inputArguments
* The command arguments
* @param argAction
* The list of actions to include
* @return A map of excluded materials and entities
*/
protected static Map<Object, Boolean> parseExcluded(CommandSender player, String[] inputArguments, List<Integer> argAction) {
return MaterialParser.parseExcluded(player, inputArguments, argAction);
}
/**
* Parse excluded users from command arguments
*
* @param player
* The command sender
* @param inputArguments
* The command arguments
* @return A list of excluded users
*/
protected static List<String> parseExcludedUsers(CommandSender player, String[] inputArguments) {
return UserParser.parseExcludedUsers(player, inputArguments);
}
/**
* Parse force global flag from command arguments
*
* @param inputArguments
* The command arguments
* @return true if global search should be forced
*/
protected static boolean parseForceGlobal(String[] inputArguments) {
return WorldParser.parseForceGlobal(inputArguments);
}
/**
* Parse location from command sender and command arguments
*
* @param user
* The command sender
* @param argumentArray
* The command arguments
* @return The parsed location
*/
protected static Location parseLocation(CommandSender user, String[] argumentArray) {
return LocationParser.parseLocation(user, argumentArray);
}
/**
* Parse noisy/verbose flag from command arguments
*
* @param inputArguments
* The command arguments
* @return 1 if noisy/verbose mode is enabled, 0 otherwise
*/
protected static int parseNoisy(String[] inputArguments) {
return ActionParser.parseNoisy(inputArguments);
}
/**
* Parse preview flag from command arguments
*
* @param inputArguments
* The command arguments
* @return 1 for preview, 2 for preview cancel, 0 otherwise
*/
protected static int parsePreview(String[] inputArguments) {
return ActionParser.parsePreview(inputArguments);
}
/**
* Parse radius from command arguments
*
* @param inputArguments
* The command arguments
* @param user
* The command sender
* @param location
* The base location
* @return The parsed radius
*/
protected static Integer[] parseRadius(String[] inputArguments, CommandSender user, Location location) {
Integer[] result = LocationParser.parseRadius(inputArguments, user, location);
// Handle WorldEdit case which LocationParser returned as null
if (result == null && WorldParser.parseWorldEdit(inputArguments)) {
if (user.getServer().getPluginManager().getPlugin("WorldEdit") != null) {
Integer[] worldEditResult = WorldEditHandler.runWorldEditCommand(user);
if (worldEditResult != null) {
result = worldEditResult;
}
}
}
return result;
}
/**
* Parse restricted materials and entities from command arguments
*
* @param player
* The command sender
* @param inputArguments
* The command arguments
* @param argAction
* The list of actions to include
* @return A list of restricted materials and entities
*/
protected static List<Object> parseRestricted(CommandSender player, String[] inputArguments, List<Integer> argAction) {
return MaterialParser.parseRestricted(player, inputArguments, argAction);
}
/**
* Parse time from command arguments
*
* @param inputArguments
* The command arguments
* @return An array of two longs - [time1, time2]
*/
protected static long[] parseTime(String[] inputArguments) {
return TimeParser.parseTime(inputArguments);
}
/**
* Parse time string from command arguments for display
*
* @param inputArguments
* The command arguments
* @return A formatted time string
*/
protected static String parseTimeString(String[] inputArguments) {
return TimeParser.parseTimeString(inputArguments);
}
/**
* Parse rows from command arguments
*
* @param inputArguments
* The command arguments
* @return The number of rows
*/
protected static int parseRows(String[] inputArguments) {
return TimeParser.parseRows(inputArguments);
}
/**
* Parse world from command arguments
*
* @param inputArguments
* The command arguments
* @param processWorldEdit
* Whether to process WorldEdit arguments
* @param requireLoaded
* Whether the world must be loaded
* @return The world ID
*/
protected static int parseWorld(String[] inputArguments, boolean processWorldEdit, boolean requireLoaded) {
return WorldParser.parseWorld(inputArguments, processWorldEdit, requireLoaded);
}
/**
* Parse whether to use WorldEdit for radius
*
* @param inputArguments
* The command arguments
* @return true if WorldEdit should be used
*/
protected static boolean parseWorldEdit(String[] inputArguments) {
return WorldParser.parseWorldEdit(inputArguments);
}
/**
* Parse world name from command arguments
*
* @param inputArguments
* The command arguments
* @param processWorldEdit
* Whether to process WorldEdit arguments
* @return The world name
*/
protected static String parseWorldName(String[] inputArguments, boolean processWorldEdit) {
return WorldParser.parseWorldName(inputArguments, processWorldEdit);
}
/**
* Get a map of block tags and their associated materials
*
* @return A map of block tags and their associated materials
*/
protected static Map<String, Set<Material>> getTags() {
return MaterialParser.getTags();
}
/**
* Check if an argument matches a block tag
*
* @param argument
* The argument to check
* @return true if the argument matches a block tag
*/
protected static boolean checkTags(String argument) {
return MaterialParser.checkTags(argument);
}
/**
* Check if an argument matches a block tag and add the associated materials to the list
*
* @param argument
* The argument to check
* @param list
* The list to add the associated materials to
* @return true if the argument matches a block tag
*/
protected static boolean checkTags(String argument, Map<Object, Boolean> list) {
return MaterialParser.checkTags(argument, list);
}
/**
* Check if an argument matches a block tag and add the associated materials to the list
*
* @param argument
* The argument to check
* @param list
* The list to add the associated materials to
* @return true if the argument matches a block tag
*/
protected static boolean checkTags(String argument, List<Object> list) {
return MaterialParser.checkTags(argument, list);
}
/**
* Parse users from command arguments
*
* @param inputArguments
* The command arguments
* @return A list of parsed users
*/
protected static List<String> parseUsers(String[] inputArguments) {
return UserParser.parseUsers(inputArguments);
}
/**
* Helper method for formatting BigDecimal values
*
* @param input
* The BigDecimal value to format
* @return The formatted string
*/
private static String timeString(BigDecimal input) {
return input.stripTrailingZeros().toPlainString();
}
protected static Integer parseGivableItemId(String[] inputArguments) {
return GivableItemIdParser.parseGivableItemId(inputArguments);
}
}
@@ -0,0 +1,69 @@
package net.coreprotect.command;
import java.util.Locale;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.language.Phrase;
import net.coreprotect.language.Selector;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
public class ConsumerCommand {
private ConsumerCommand() {
throw new IllegalStateException("Command class");
}
protected static void runCommand(final CommandSender player, boolean permission, String[] args) {
if (!permission) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (!(player instanceof ConsoleCommandSender)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.COMMAND_CONSOLE));
return;
}
if (ConfigHandler.converterRunning) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.UPGRADE_IN_PROGRESS));
return;
}
if (ConfigHandler.purgeRunning) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PURGE_IN_PROGRESS));
return;
}
if (args.length == 2) {
String action = args[1].toLowerCase(Locale.ROOT);
boolean pauseCommand = (action.equals("pause") || action.equals("disable") || action.equals("stop"));
boolean resumeCommand = (action.equals("resume") || action.equals("enable") || action.equals("start"));
if (pauseCommand || resumeCommand) {
if (ConfigHandler.pauseConsumer) {
if (pauseCommand) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.CONSUMER_ERROR, Selector.FIRST)); // already paused
}
else {
ConfigHandler.pauseConsumer = false;
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.CONSUMER_TOGGLED, Selector.SECOND)); // now started
}
}
else {
if (resumeCommand) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.CONSUMER_ERROR, Selector.SECOND)); // already running
}
else {
ConfigHandler.pauseConsumer = true;
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.CONSUMER_TOGGLED, Selector.FIRST)); // now paused
}
}
return;
}
}
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, Color.WHITE, "/co consumer <pause|resume>"));
}
}
@@ -0,0 +1,40 @@
package net.coreprotect.command;
import net.coreprotect.language.Phrase;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.ChatMessage;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.ItemUtils;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class GiveCommand {
public static void runCommand(CommandSender sender, Command command, boolean permission, String[] args) {
if (!permission) {
Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.NO_PERMISSION)).build());
return;
}
Integer itemId = CommandParser.parseGivableItemId(args);
if (itemId == null) {
Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.MISSING_PARAMETERS, Color.WHITE, "/" + command.getName() + " give <itemId>")).build());
return;
}
ItemStack item = ItemUtils.getGivableItem(itemId);
if (item == null) {
Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.INVALID_ITEM_ID)).build());
return;
}
if (!(sender instanceof Player)) {
Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.ACTION_NOT_SUPPORTED)).build());
return;
}
Player player = (Player) sender;
player.getInventory().addItem(item);
}
}
@@ -38,6 +38,7 @@ public class HelpCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "a:<action> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PARAMS_5, Selector.FIRST));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "i:<include> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PARAMS_6, Selector.FIRST));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "e:<exclude> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PARAMS_7, Selector.FIRST));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "f:<filter> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_FILTER_1));
Chat.sendMessage(player, Color.GREY + Color.ITALIC + Phrase.build(Phrase.HELP_PARAMETER, "/co help <param>"));
}
else if (helpcommand.equals("rollback") || helpcommand.equals("rollbacks") || helpcommand.equals("rb") || helpcommand.equals("ro")) {
@@ -67,8 +68,13 @@ public class HelpCommand {
Chat.sendMessage(player, Color.GREY + Color.ITALIC + Phrase.build(Phrase.HELP_PARAMETER, "/co help params"));
}
else if (helpcommand.equals("purge") || helpcommand.equals("purges")) {
Chat.sendMessage(player, Color.DARK_AQUA + "/co purge t:<time> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_1));
Chat.sendMessage(player, Color.GREY + Color.ITALIC + "" + Phrase.build(Phrase.HELP_PURGE_2, "/co purge t:30d"));
Chat.sendMessage(player, Color.DARK_AQUA + "/co purge " + Color.GREY + "<params> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_1));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "t:<time> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_3));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "r:<world/radius> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_4));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "i:<include> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_5));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "e:<exclude> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_6));
Chat.sendMessage(player, Color.DARK_AQUA + "| " + Color.GREY + "a:kill " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_PURGE_7));
Chat.sendMessage(player, Color.GREY + Color.ITALIC + Phrase.build(Phrase.HELP_PURGE_2, "/co purge t:30d"));
}
else if (helpcommand.equals("reload")) {
Chat.sendMessage(player, Color.DARK_AQUA + "/co reload " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_RELOAD_COMMAND));
@@ -95,6 +101,10 @@ public class HelpCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "/co lookup a:<action> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_ACTION_1));
Chat.sendMessage(player, Color.GREY + Color.ITALIC + Phrase.build(Phrase.HELP_ACTION_2));
}
else if (helpcommand.equals("f") || helpcommand.equals("filter") || helpcommand.equals("ffilter")) {
Chat.sendMessage(player, Color.DARK_AQUA + "/co lookup a:<chat|command|sign> f:<filter> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_FILTER_1));
Chat.sendMessage(player, Color.GREY + Color.ITALIC + Phrase.build(Phrase.HELP_FILTER_2));
}
else if (helpcommand.equals("i") || helpcommand.equals("include") || helpcommand.equals("iinclude") || helpcommand.equals("b") || helpcommand.equals("block") || helpcommand.equals("blocks") || helpcommand.equals("bblock") || helpcommand.equals("bblocks")) {
Chat.sendMessage(player, Color.DARK_AQUA + "/co lookup i:<include> " + Color.WHITE + "- " + Phrase.build(Phrase.HELP_INCLUDE_1));
Chat.sendMessage(player, Color.GREY + Color.ITALIC + Phrase.build(Phrase.HELP_INCLUDE_2));
@@ -7,7 +7,6 @@ import net.coreprotect.language.Phrase;
import net.coreprotect.language.Selector;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.Util;
public class InspectCommand {
protected static void runCommand(CommandSender player, boolean permission, String[] args) {
@@ -1,13 +1,10 @@
package net.coreprotect.command;
import java.sql.Connection;
import java.sql.Statement;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -19,49 +16,74 @@ import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import net.coreprotect.command.lookup.BlockLookupThread;
import net.coreprotect.command.lookup.ChestTransactionLookupThread;
import net.coreprotect.command.lookup.EntityInteractionLookupThread;
import net.coreprotect.command.lookup.StandardLookupThread;
import net.coreprotect.command.parser.ActionParser;
import net.coreprotect.command.parser.MessageFilterParser;
import net.coreprotect.command.parser.RollbackStateParser;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.database.Database;
import net.coreprotect.database.Lookup;
import net.coreprotect.database.lookup.BlockLookup;
import net.coreprotect.database.lookup.ChestTransactionLookup;
import net.coreprotect.database.lookup.InteractionLookup;
import net.coreprotect.database.lookup.PlayerLookup;
import net.coreprotect.database.lookup.SignMessageLookup;
import net.coreprotect.database.statement.UserStatement;
import net.coreprotect.language.Phrase;
import net.coreprotect.language.Selector;
import net.coreprotect.model.action.EntityActionFilter;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.model.lookup.LookupOutputMode;
import net.coreprotect.model.lookup.LookupRollbackState;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.ChatMessage;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.Util;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.ErrorReporter;
public class LookupCommand {
protected static void runCommand(CommandSender player, Command command, boolean permission, String[] args) {
public static void runCommand(CommandSender player, Command command, boolean permission, String[] args) {
int resultc = args.length;
args = CommandHandler.parsePage(args);
Location lo = CommandHandler.parseLocation(player, args);
MessageFilterParser.ParseResult messageFilterResult = CommandParser.parseMessageFilters(args);
args = messageFilterResult.getArguments();
List<String> argFilters = messageFilterResult.getFilters();
args = CommandParser.parsePage(args);
Location lo = CommandParser.parseLocation(player, args);
// List<String> arg_uuids = new ArrayList<String>();
List<String> argUsers = CommandHandler.parseUsers(args);
Integer[] argRadius = CommandHandler.parseRadius(args, player, lo);
int argNoisy = CommandHandler.parseNoisy(args);
List<Integer> argAction = CommandHandler.parseAction(args);
List<Object> argBlocks = CommandHandler.parseRestricted(player, args, argAction);
List<Object> argExclude = CommandHandler.parseExcluded(player, args, argAction);
List<String> argExcludeUsers = CommandHandler.parseExcludedUsers(player, args);
String ts = CommandHandler.parseTimeString(args);
int rbseconds = CommandHandler.parseTime(args);
int argWid = CommandHandler.parseWorld(args, true, true);
int parseRows = CommandHandler.parseRows(args);
boolean count = CommandHandler.parseCount(args);
boolean worldedit = CommandHandler.parseWorldEdit(args);
boolean forceglobal = CommandHandler.parseForceGlobal(args);
List<String> argUsers = CommandParser.parseUsers(args);
Integer[] argRadius = CommandParser.parseRadius(args, player, lo);
int argNoisy = CommandParser.parseNoisy(args);
ActionParser.ParseResult actionResult = CommandParser.parseActions(args, true);
List<Integer> argAction = actionResult.getActions();
EntityActionFilter argEntityActionFilter = actionResult.getEntityActionFilter();
List<Object> argBlocks = CommandParser.parseRestricted(player, args, argAction);
Map<Object, Boolean> argExclude = CommandParser.parseExcluded(player, args, argAction);
List<String> argExcludeUsers = CommandParser.parseExcludedUsers(player, args);
String ts = CommandParser.parseTimeString(args);
long[] argTime = CommandParser.parseTime(args);
long startTime = argTime[0];
long endTime = argTime[1];
int argWid = CommandParser.parseWorld(args, true, true);
int parseRows = CommandParser.parseRows(args);
boolean summary = CommandParser.parseSummary(args);
LookupOutputMode outputMode = summary ? LookupOutputMode.SUMMARY
: CommandParser.parseCount(args) ? LookupOutputMode.COUNT : LookupOutputMode.DETAIL;
RollbackStateParser.ParseResult rollbackStateResult = CommandParser.parseRollbackState(args);
LookupRollbackState rollbackState = rollbackStateResult.getState();
boolean worldedit = CommandParser.parseWorldEdit(args);
boolean forceglobal = CommandParser.parseForceGlobal(args);
boolean pageLookup = false;
if (argBlocks == null || argExclude == null || argExcludeUsers == null) {
return;
}
if (rollbackStateResult.isConflicting()) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_PARAMETER, "#rolledback + #restored"));
return;
}
if (args[0].toLowerCase(Locale.ROOT).equals("page") && (args.length != 2 || !args[1].equals(args[1].replaceAll("[^0-9]", "")))) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, Color.WHITE, "/co page <page>"));
return;
}
int argExcluded = argExclude.size();
int argRestricted = argBlocks.size();
@@ -69,15 +91,16 @@ public class LookupCommand {
boolean hasBlock = false;
boolean hasEntity = false;
for (Object arg : argBlocks) {
if (arg instanceof Material) {
if (arg instanceof Material || arg instanceof String) {
hasBlock = true;
}
else if (arg instanceof EntityType) {
hasEntity = true;
if (argAction.size() == 0) {
argAction.add(3);
argAction.add(LookupActions.ENTITY_KILL);
argAction.add(LookupActions.ENTITY_SPAWN);
}
else if (!argAction.contains(3)) {
else if (!argEntityActionFilter.includesAnyEntity(argAction, true) && !argAction.contains(LookupActions.INTERACTION)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_INCLUDE_COMBO));
return;
}
@@ -85,16 +108,17 @@ public class LookupCommand {
}
/* check for invalid block/entity combinations (exclude) */
for (Object arg : argExclude) {
if (arg instanceof Material) {
for (Object arg : argExclude.keySet()) {
if (arg instanceof Material || arg instanceof String) {
hasBlock = true;
}
else if (arg instanceof EntityType) {
hasEntity = true;
if (argAction.size() == 0) {
argAction.add(3);
argAction.add(LookupActions.ENTITY_KILL);
argAction.add(LookupActions.ENTITY_SPAWN);
}
else if (!argAction.contains(3)) {
else if (!argEntityActionFilter.includesAnyEntity(argAction, true) && !argAction.contains(LookupActions.INTERACTION)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_INCLUDE_COMBO));
return;
}
@@ -107,7 +131,7 @@ public class LookupCommand {
}
if (argWid == -1) {
String worldName = CommandHandler.parseWorldName(args, true);
String worldName = CommandParser.parseWorldName(args, true);
Chat.sendMessage(player, new ChatMessage(Phrase.build(Phrase.WORLD_NOT_FOUND, worldName)).build());
return;
}
@@ -145,7 +169,7 @@ public class LookupCommand {
}
}
}
if (argAction.contains(6) || argAction.contains(7) || argAction.contains(8) || argAction.contains(9) || argAction.contains(10)) {
if (argAction.contains(LookupActions.CHAT) || argAction.contains(LookupActions.COMMAND) || argAction.contains(LookupActions.SESSION) || argAction.contains(LookupActions.USERNAME) || argAction.contains(LookupActions.SIGN)) {
pageLookup = true;
}
@@ -171,6 +195,22 @@ public class LookupCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_ACTION));
return;
}
if (rollbackState != LookupRollbackState.ANY && (argAction.contains(LookupActions.CHAT) || argAction.contains(LookupActions.COMMAND) || argAction.contains(LookupActions.SESSION) || argAction.contains(LookupActions.USERNAME) || argAction.contains(LookupActions.SIGN))) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INCOMPATIBLE_ACTION, rollbackState == LookupRollbackState.ROLLED_BACK ? "#rolledback" : "#restored"));
return;
}
if (messageFilterResult.isSpecified()) {
if (messageFilterResult.hasInvalidLength()) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MESSAGE_FILTER_TOO_SHORT, Integer.toString(MessageFilterParser.MINIMUM_FILTER_CODE_POINTS)));
return;
}
boolean messageLookup = !argAction.isEmpty() && argAction.stream().allMatch(action -> action == LookupActions.CHAT || action == LookupActions.COMMAND);
boolean signLookup = !argAction.isEmpty() && argAction.stream().allMatch(action -> action == LookupActions.SIGN);
if (!messageLookup && !signLookup) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INCOMPATIBLE_ACTION, "f:<filter>"));
return;
}
}
if (worldedit && argRadius == null) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_SELECTION, "WorldEdit"));
return;
@@ -191,49 +231,57 @@ public class LookupCommand {
allPermission = true;
}
if (!allPermission) {
if (!pageLookup && (argAction.size() == 0 || (argAction.size() == 1 && (argAction.contains(0) || argAction.contains(1)))) && !player.hasPermission("coreprotect.lookup.block")) {
if (!pageLookup && (argAction.isEmpty() || argAction.contains(LookupActions.BLOCK_BREAK) || argAction.contains(LookupActions.BLOCK_PLACE)) && !player.hasPermission("coreprotect.lookup.block")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(2) && !player.hasPermission("coreprotect.lookup.click")) {
if (argAction.contains(LookupActions.INTERACTION) && !player.hasPermission("coreprotect.lookup.click")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(3) && !player.hasPermission("coreprotect.lookup.kill")) {
if (argAction.contains(LookupActions.ENTITY_KILL) && !player.hasPermission("coreprotect.lookup.kill")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(4) && !player.hasPermission("coreprotect.lookup.container")) {
if (argAction.contains(LookupActions.ENTITY_SPAWN) && !player.hasPermission("coreprotect.lookup.spawn")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(6) && !player.hasPermission("coreprotect.lookup.chat")) {
if (argAction.contains(LookupActions.CONTAINER) && !argAction.contains(LookupActions.ITEM) && !player.hasPermission("coreprotect.lookup.container")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(7) && !player.hasPermission("coreprotect.lookup.command")) {
if (argAction.contains(LookupActions.CHAT) && !player.hasPermission("coreprotect.lookup.chat")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(8) && !player.hasPermission("coreprotect.lookup.session")) {
if (argAction.contains(LookupActions.COMMAND) && !player.hasPermission("coreprotect.lookup.command")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(9) && !player.hasPermission("coreprotect.lookup.username")) {
if (argAction.contains(LookupActions.SESSION) && !player.hasPermission("coreprotect.lookup.session")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(10) && !player.hasPermission("coreprotect.lookup.sign")) {
if (argAction.contains(LookupActions.USERNAME) && !player.hasPermission("coreprotect.lookup.username")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(11) && !player.hasPermission("coreprotect.lookup.item")) {
if (argAction.contains(LookupActions.SIGN) && !player.hasPermission("coreprotect.lookup.sign")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(LookupActions.ITEM) && !argAction.contains(LookupActions.CONTAINER) && !player.hasPermission("coreprotect.lookup.item")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (LookupActions.isInventoryLookup(argAction) && !player.hasPermission("coreprotect.lookup.inventory")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
}
if (argAction.contains(6) || argAction.contains(7) || argAction.contains(8) || argAction.contains(9) || argAction.contains(10)) {
if (argAction.contains(9) && (argRadius != null || argWid > 0 || worldedit)) {
if (argAction.contains(LookupActions.CHAT) || argAction.contains(LookupActions.COMMAND) || argAction.contains(LookupActions.SESSION) || argAction.contains(LookupActions.USERNAME) || argAction.contains(LookupActions.SIGN)) {
if (argAction.contains(LookupActions.USERNAME) && (argRadius != null || argWid > 0 || worldedit)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INCOMPATIBLE_ACTION, "r:"));
return;
}
@@ -247,11 +295,23 @@ public class LookupCommand {
}
}
if (rbseconds <= 0 && !pageLookup && type == 4 && (argBlocks.size() > 0 || argUsers.size() > 0)) {
if (startTime <= 0 && argRadius == null && !pageLookup && type == 4 && (argBlocks.size() > 0 || argUsers.size() > 0)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_LOOKUP_TIME, Selector.FIRST));
return;
}
if (LookupActions.isInventoryLookup(argAction)) {
if (argUsers.size() == 0) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_ACTION_USER));
return;
}
argExclude.put(Material.FIRE, false);
argExclude.put(Material.WATER, false);
argExclude.put(Material.FARMLAND, false);
argExcludeUsers.add("#hopper");
}
if (type == 1) {
boolean defaultRe = true;
int p = 0;
@@ -312,51 +372,17 @@ public class LookupCommand {
String bc = x + "." + y + "." + z + "." + wid + "." + x2 + "." + y2 + "." + z2 + "." + re;
ConfigHandler.lookupCommand.put(player.getName(), bc);
String world = Util.getWorldName(wid);
String world = WorldUtils.getWorldName(wid);
double dx = 0.5 * (x + x2);
double dy = 0.5 * (y + y2);
double dz = 0.5 * (z + z2);
final Location location = new Location(Bukkit.getServer().getWorld(world), dx, dy, dz);
final CommandSender player2 = player;
final int p2 = p;
final int finalLimit = re;
class BasicThread implements Runnable {
@Override
public void run() {
try {
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { true, System.currentTimeMillis() });
Connection connection = Database.getConnection(true);
if (connection != null) {
Statement statement = connection.createStatement();
String blockdata = ChestTransactionLookup.performLookup(command.getName(), statement, location, player2, p2, finalLimit, false);
if (blockdata.contains("\n")) {
for (String b : blockdata.split("\n")) {
Chat.sendComponent(player2, b);
}
}
else {
Chat.sendComponent(player2, blockdata);
}
statement.close();
connection.close();
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
}
}
catch (Exception e) {
e.printStackTrace();
}
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { false, System.currentTimeMillis() });
}
}
Runnable runnable = new BasicThread();
Runnable runnable = new ChestTransactionLookupThread(player, command, location, p, re);
Thread thread = new Thread(runnable);
thread.start();
}
else if (type == 2 || type == 3 || type == 7 || type == 8) {
else if (type == 2 || type == 3 || type == 7 || type == 8 || type == 9) {
boolean defaultRe = true;
int page = 1;
int re = 7;
@@ -398,6 +424,13 @@ public class LookupCommand {
}
// String bc = x+"."+y+"."+z+"."+wid+"."+rstring+"."+lookup_user;
if (type == 9) {
Runnable runnable = new EntityInteractionLookupThread(player, command, page, re);
Thread thread = new Thread(runnable);
thread.start();
return;
}
String lcommand = ConfigHandler.lookupCommand.get(player.getName());
String[] data = lcommand.split("\\.");
int x = Integer.parseInt(data[0]);
@@ -410,79 +443,25 @@ public class LookupCommand {
}
String bc = x + "." + y + "." + z + "." + wid + "." + lookupType + "." + re;
Integer entitySpawnRowId = type == 2 && data.length > 6 ? Integer.valueOf(data[6]) : null;
if (entitySpawnRowId != null) {
bc += "." + entitySpawnRowId;
}
ConfigHandler.lookupCommand.put(player.getName(), bc);
String world = Util.getWorldName(wid);
final Block fblock = Bukkit.getServer().getWorld(world).getBlockAt(x, y, z);// .getLocation();
final BlockState fblockstate = fblock.getState();
final CommandSender player2 = player;
final int p2 = page;
final int finalLimit = re;
final int t = type;
class BasicThread implements Runnable {
@Override
public void run() {
try {
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { true, System.currentTimeMillis() });
Connection connection = Database.getConnection(true);
if (connection != null) {
Statement statement = connection.createStatement();
if (t == 8) {
List<String> signData = SignMessageLookup.performLookup(command.getName(), statement, fblockstate.getLocation(), player2, p2, finalLimit);
for (String signMessage : signData) {
String bypass = null;
String world = WorldUtils.getWorldName(wid);
final Block block = Bukkit.getServer().getWorld(world).getBlockAt(x, y, z);
final BlockState blockState = block.getState();
if (signMessage.contains("\n")) {
String[] split = signMessage.split("\n");
signMessage = split[0];
bypass = split[1];
}
if (signMessage.length() > 0) {
Chat.sendComponent(player2, signMessage, bypass);
}
}
}
else {
String blockdata = null;
if (t == 7) {
blockdata = InteractionLookup.performLookup(command.getName(), statement, fblock, player2, 0, p2, finalLimit);
}
else {
blockdata = BlockLookup.performLookup(command.getName(), statement, fblockstate, player2, 0, p2, finalLimit);
}
if (blockdata.contains("\n")) {
for (String b : blockdata.split("\n")) {
Chat.sendComponent(player2, b);
}
}
else if (blockdata.length() > 0) {
Chat.sendComponent(player2, blockdata);
}
}
statement.close();
connection.close();
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
}
}
catch (Exception e) {
e.printStackTrace();
}
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { false, System.currentTimeMillis() });
}
}
Runnable runnable = new BasicThread();
Runnable runnable = new BlockLookupThread(player, command, block, blockState, page, re, type, entitySpawnRowId);
Thread thread = new Thread(runnable);
thread.start();
}
else if (type == 4 || type == 5) {
boolean defaultRe = true;
int pa = 1;
int re = 4;
if (argAction.contains(6) || argAction.contains(7) || argAction.contains(9)) {
int re = outputMode == LookupOutputMode.SUMMARY ? 8 : 4;
if (outputMode != LookupOutputMode.SUMMARY && (argAction.contains(LookupActions.CHAT) || argAction.contains(LookupActions.COMMAND) || argAction.contains(LookupActions.USERNAME) || LookupActions.isInventoryLookup(argAction))) {
re = 7;
}
if (parseRows > 0) {
@@ -548,16 +527,26 @@ public class LookupCommand {
List<String> rollbackusers = argUsers;
int c = 0;
for (String ruser : rollbackusers) {
List<Player> players = Bukkit.getServer().matchPlayer(ruser);
for (Player p : players) {
if (p.getName().equalsIgnoreCase(ruser)) {
rollbackusers.set(c, p.getName());
if (Bukkit.getServer() != null) {
List<Player> players = Bukkit.getServer().matchPlayer(ruser);
for (Player p : players) {
if (p.getName().equalsIgnoreCase(ruser)) {
rollbackusers.set(c, p.getName());
}
}
}
c++;
if (LookupActions.isInventoryLookup(argAction)) {
if (ruser.startsWith("#")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_USERNAME, ruser));
return;
}
}
}
int cs = -1;
long timeStart = -1;
long timeEnd = 0;
int x = 0;
int y = 0;
int z = 0;
@@ -570,14 +559,14 @@ public class LookupCommand {
y = Integer.parseInt(data[1]);
z = Integer.parseInt(data[2]);
wid = Integer.parseInt(data[3]);
cs = Integer.parseInt(data[4]);
// arg_radius = Integer.parseInt(data[5]);
argNoisy = Integer.parseInt(data[5]);
argExcluded = Integer.parseInt(data[6]);
argRestricted = Integer.parseInt(data[7]);
argWid = Integer.parseInt(data[8]);
timeStart = Long.parseLong(data[4]);
timeEnd = Long.parseLong(data[5]);
argNoisy = Integer.parseInt(data[6]);
argExcluded = Integer.parseInt(data[7]);
argRestricted = Integer.parseInt(data[8]);
argWid = Integer.parseInt(data[9]);
if (defaultRe) {
re = Integer.parseInt(data[9]);
re = Integer.parseInt(data[10]);
}
rollbackusers = ConfigHandler.lookupUlist.get(player.getName());
@@ -585,24 +574,31 @@ public class LookupCommand {
argExclude = ConfigHandler.lookupElist.get(player.getName());
argExcludeUsers = ConfigHandler.lookupEUserlist.get(player.getName());
argAction = ConfigHandler.lookupAlist.get(player.getName());
argEntityActionFilter = ConfigHandler.lookupEntityActionFilter.getOrDefault(player.getName(), EntityActionFilter.DEFAULT);
argFilters = ConfigHandler.lookupFlist.getOrDefault(player.getName(), Collections.emptyList());
argRadius = ConfigHandler.lookupRadius.get(player.getName());
ts = ConfigHandler.lookupTime.get(player.getName());
rbseconds = 1;
outputMode = ConfigHandler.lookupOutputMode.getOrDefault(player.getName(), LookupOutputMode.DETAIL);
rollbackState = ConfigHandler.lookupRollbackState.getOrDefault(player.getName(), LookupRollbackState.ANY);
startTime = 1;
endTime = 0;
}
else {
if (lo != null) {
x = lo.getBlockX();
z = lo.getBlockZ();
wid = Util.getWorldId(lo.getWorld().getName());
if (lo.getWorld() != null) {
wid = WorldUtils.getWorldId(lo.getWorld().getName());
}
}
if (rollbackusers.size() == 1 && rollbackusers.contains("#global") && argAction.contains(9)) {
if (rollbackusers.size() == 1 && rollbackusers.contains("#global") && argAction.contains(LookupActions.USERNAME)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co l a:username u:<user>"));
return;
}
if (rollbackusers.contains("#container")) {
if (argAction.contains(6) || argAction.contains(7) || argAction.contains(8) || argAction.contains(9) || argAction.contains(10)) {
if (argAction.contains(LookupActions.CHAT) || argAction.contains(LookupActions.COMMAND) || argAction.contains(LookupActions.SESSION) || argAction.contains(LookupActions.USERNAME) || argAction.contains(LookupActions.SIGN) || argAction.contains(LookupActions.ITEM)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_USERNAME, "#container"));
return;
}
@@ -642,333 +638,30 @@ public class LookupCommand {
}
}
final List<String> rollbackusers2 = rollbackusers;
int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
if (cs == -1) {
if (rbseconds <= 0) {
cs = 0;
}
else {
cs = unixtimestamp - rbseconds;
}
}
final int stime = cs;
final Integer[] radius = argRadius;
try {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.LOOKUP_SEARCHING));
final CommandSender player2 = player;
final int finalX = x;
final int finalY = y;
final int finalZ = z;
final int finalWid = wid;
final int finalArgWid = argWid;
final int noisy = argNoisy;
final String rtime = ts;
final int excluded = argExcluded;
final int restricted = argRestricted;
// final List<String> uuid_list = arg_uuids;
final List<Object> blist = argBlocks;
final List<Object> elist = argExclude;
final List<String> euserlist = argExcludeUsers;
final int page = pa;
final int displayResults = re;
final int typeLookup = type;
final Location finalLocation = lo;
final List<Integer> finalArgAction = argAction;
final boolean finalCount = count;
class BasicThread2 implements Runnable {
@Override
public void run() {
try {
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { true, System.currentTimeMillis() });
List<String> uuidList = new ArrayList<>();
Location location = finalLocation;
boolean exists = false;
String bc = finalX + "." + finalY + "." + finalZ + "." + finalWid + "." + stime + "." + noisy + "." + excluded + "." + restricted + "." + finalArgWid + "." + displayResults;
ConfigHandler.lookupCommand.put(player2.getName(), bc);
ConfigHandler.lookupPage.put(player2.getName(), page);
ConfigHandler.lookupTime.put(player2.getName(), rtime);
ConfigHandler.lookupType.put(player2.getName(), 5);
ConfigHandler.lookupElist.put(player2.getName(), elist);
ConfigHandler.lookupEUserlist.put(player2.getName(), euserlist);
ConfigHandler.lookupBlist.put(player2.getName(), blist);
ConfigHandler.lookupUlist.put(player2.getName(), rollbackusers2);
ConfigHandler.lookupAlist.put(player2.getName(), finalArgAction);
ConfigHandler.lookupRadius.put(player2.getName(), radius);
Connection connection = Database.getConnection(true);
if (connection != null) {
Statement statement = connection.createStatement();
String baduser = "";
for (String check : rollbackusers2) {
if ((!check.equals("#global") && !check.equals("#container")) || finalArgAction.contains(9)) {
exists = PlayerLookup.playerExists(connection, check);
if (!exists) {
baduser = check;
break;
}
else if (finalArgAction.contains(9)) {
if (ConfigHandler.uuidCache.get(check.toLowerCase(Locale.ROOT)) != null) {
String uuid = ConfigHandler.uuidCache.get(check.toLowerCase(Locale.ROOT));
uuidList.add(uuid);
}
}
}
else {
exists = true;
}
}
if (exists) {
for (String check : euserlist) {
if (!check.equals("#global")) {
exists = PlayerLookup.playerExists(connection, check);
if (!exists) {
baduser = check;
break;
}
}
else {
baduser = "#global";
exists = false;
}
}
}
if (exists) {
List<String> userList = new ArrayList<>();
if (!finalArgAction.contains(9)) {
userList = rollbackusers2;
}
int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
boolean restrict_world = false;
if (radius != null) {
restrict_world = true;
}
if (location == null) {
restrict_world = false;
}
if (finalArgWid > 0) {
restrict_world = true;
location = new Location(Bukkit.getServer().getWorld(Util.getWorldName(finalArgWid)), finalX, finalY, finalZ);
}
else if (location != null) {
location = new Location(Bukkit.getServer().getWorld(Util.getWorldName(finalWid)), finalX, finalY, finalZ);
}
long rowMax = (long) page * displayResults;
long pageStart = rowMax - displayResults;
int rows = 0;
boolean checkRows = true;
if (typeLookup == 5 && page > 1) {
rows = ConfigHandler.lookupRows.get(player2.getName());
if (pageStart < rows) {
checkRows = false;
}
}
if (checkRows) {
rows = Lookup.countLookupRows(statement, player2, uuidList, userList, blist, elist, euserlist, finalArgAction, location, radius, stime, restrict_world, true);
ConfigHandler.lookupRows.put(player2.getName(), rows);
}
if (finalCount) {
String row_format = NumberFormat.getInstance().format(rows);
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.LOOKUP_ROWS_FOUND, row_format, (rows == 1 ? Selector.FIRST : Selector.SECOND)));
}
else if (pageStart < rows) {
List<String[]> lookupList = Lookup.performPartialLookup(statement, player2, uuidList, userList, blist, elist, euserlist, finalArgAction, location, radius, stime, (int) pageStart, displayResults, restrict_world, true);
Chat.sendMessage(player2, Color.WHITE + "----- " + Color.DARK_AQUA + Phrase.build(Phrase.LOOKUP_HEADER, "CoreProtect") + Color.WHITE + " -----");
if (finalArgAction.contains(6) || finalArgAction.contains(7)) { // Chat/command
for (String[] data : lookupList) {
String time = data[0];
String dplayer = data[1];
String message = data[2];
String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true);
Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Color.DARK_AQUA + dplayer + ": " + Color.WHITE, message);
}
}
else if (finalArgAction.contains(8)) { // login/logouts
for (String[] data : lookupList) {
String time = data[0];
String dplayer = data[1];
int wid = Integer.parseInt(data[2]);
int x = Integer.parseInt(data[3]);
int y = Integer.parseInt(data[4]);
int z = Integer.parseInt(data[5]);
int action = Integer.parseInt(data[6]);
String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true);
double time_length = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, false).replaceAll("[^0-9]", "").length() * 1.50;
int padding = (int) (time_length + 12.50);
String leftPadding = StringUtils.leftPad("", padding, ' ');
Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Color.DARK_AQUA + Phrase.build(Phrase.LOOKUP_LOGIN, Color.DARK_AQUA + dplayer + Color.WHITE, (action != 0 ? Selector.FIRST : Selector.SECOND)));
Chat.sendComponent(player2, Color.WHITE + leftPadding + Color.GREY + "^ " + Util.getCoordinates(command.getName(), wid, x, y, z, true, true) + "");
}
}
else if (finalArgAction.contains(9)) { // username-changes
for (String[] data : lookupList) {
String time = data[0];
String user = ConfigHandler.uuidCacheReversed.get(data[1]);
String username = data[2];
String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true);
Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Phrase.build(Phrase.LOOKUP_USERNAME, Color.DARK_AQUA + user + Color.WHITE, Color.DARK_AQUA + username + Color.WHITE));
}
}
else if (finalArgAction.contains(10)) { // sign messages
for (String[] data : lookupList) {
String time = data[0];
String dplayer = data[1];
int wid = Integer.parseInt(data[2]);
int x = Integer.parseInt(data[3]);
int y = Integer.parseInt(data[4]);
int z = Integer.parseInt(data[5]);
String message = data[6];
String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true);
double time_length = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, false).replaceAll("[^0-9]", "").length() * 1.50;
int padding = (int) (time_length + 12.50);
String leftPadding = StringUtils.leftPad("", padding, ' ');
Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Color.DARK_AQUA + dplayer + ": " + Color.WHITE, message);
Chat.sendComponent(player2, Color.WHITE + leftPadding + Color.GREY + "^ " + Util.getCoordinates(command.getName(), wid, x, y, z, true, true) + "");
}
}
else {
for (String[] data : lookupList) {
int drb = Integer.parseInt(data[8]);
String rbd = "";
if (drb == 1) {
rbd = Color.STRIKETHROUGH;
}
String time = data[0];
String dplayer = data[1];
int x = Integer.parseInt(data[2]);
int y = Integer.parseInt(data[3]);
int z = Integer.parseInt(data[4]);
String dtype = data[5];
int ddata = Integer.parseInt(data[6]);
int daction = Integer.parseInt(data[7]);
int wid = Integer.parseInt(data[9]);
int amount = Integer.parseInt(data[10]);
String tag = Color.WHITE + "-";
String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true);
double timeLength = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, false).replaceAll("[^0-9]", "").length() * 1.50;
int padding = (int) (timeLength + 12.50);
String leftPadding = StringUtils.leftPad("", padding, ' ');
String dname = "";
boolean isPlayer = false;
if (daction == 3 && !finalArgAction.contains(11) && amount == -1) {
int dTypeInt = Integer.parseInt(dtype);
if (dTypeInt == 0) {
if (ConfigHandler.playerIdCacheReversed.get(ddata) == null) {
UserStatement.loadName(connection, ddata);
}
dname = ConfigHandler.playerIdCacheReversed.get(ddata);
isPlayer = true;
}
else {
dname = Util.getEntityType(dTypeInt).name();
}
}
else {
dname = Util.getType(Integer.parseInt(dtype)).name().toLowerCase(Locale.ROOT);
dname = Util.nameFilter(dname, ddata);
}
if (dname.length() > 0 && !isPlayer) {
dname = "minecraft:" + dname.toLowerCase(Locale.ROOT) + "";
}
// Hide "minecraft:" for now.
if (dname.contains("minecraft:")) {
String[] blockNameSplit = dname.split(":");
dname = blockNameSplit[1];
}
// Functions.sendMessage(player2, timeago+" " + ChatColors.WHITE + "- " + ChatColors.DARK_AQUA+rbd+""+dplayer+" " + ChatColors.WHITE+rbd+""+a+" " + ChatColors.DARK_AQUA+rbd+"#"+dtype+ChatColors.WHITE + ". " + ChatColors.GREY + "(x"+x+"/y"+y+"/z"+z+")");
Phrase phrase = Phrase.LOOKUP_BLOCK;
String selector = Selector.FIRST;
String action = "a:block";
if (finalArgAction.contains(4) || finalArgAction.contains(5) || finalArgAction.contains(11) || amount > -1) {
if (daction == 2 || daction == 3) {
phrase = Phrase.LOOKUP_ITEM; // {picked up|dropped}
selector = (daction != 2 ? Selector.FIRST : Selector.SECOND);
action = "a:inventory";
}
else if (daction == 4 || daction == 5) {
phrase = Phrase.LOOKUP_STORAGE; // {deposited|withdrew}
selector = (daction != 4 ? Selector.FIRST : Selector.SECOND);
action = "a:inventory";
}
else {
phrase = Phrase.LOOKUP_CONTAINER; // {added|removed}
selector = (daction != 0 ? Selector.FIRST : Selector.SECOND);
action = "a:container";
}
Chat.sendComponent(player2, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector));
}
else {
if (daction == 2 || daction == 3) {
phrase = Phrase.LOOKUP_INTERACTION; // {clicked|killed}
selector = (daction != 3 ? Selector.FIRST : Selector.SECOND);
action = (daction == 2 ? "a:click" : "a:kill");
}
else {
phrase = Phrase.LOOKUP_BLOCK; // {placed|broke}
selector = (daction != 0 ? Selector.FIRST : Selector.SECOND);
}
// tag = ChatColors.RED + "-";
Chat.sendComponent(player2, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector));
}
boolean itemLookup = finalArgAction.contains(4) && finalArgAction.contains(11);
action = (finalArgAction.size() == 0 || itemLookup ? " (" + action + ")" : "");
Chat.sendComponent(player2, Color.WHITE + leftPadding + Color.GREY + "^ " + Util.getCoordinates(command.getName(), wid, x, y, z, true, true) + Color.GREY + Color.ITALIC + action);
}
}
if (rows > displayResults) {
int total_pages = (int) Math.ceil(rows / (displayResults + 0.0));
if (finalArgAction.contains(6) || finalArgAction.contains(7) || finalArgAction.contains(9)) {
Chat.sendMessage(player2, "-----");
}
Chat.sendComponent(player2, Util.getPageNavigation(command.getName(), page, total_pages) + "| " + Phrase.build(Phrase.LOOKUP_VIEW_PAGE, Color.WHITE, "/co l <page>"));
}
}
else if (rows > 0) {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_RESULTS_PAGE, Selector.FIRST));
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_RESULTS));
}
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.USER_NOT_FOUND, baduser));
}
statement.close();
connection.close();
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
}
}
catch (Exception e) {
e.printStackTrace();
}
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { false, System.currentTimeMillis() });
if (timeStart == -1) {
if (startTime <= 0) {
timeStart = 0;
}
else {
timeStart = (System.currentTimeMillis() / 1000L) - startTime;
}
if (endTime <= 0) {
timeEnd = 0;
}
else {
timeEnd = (System.currentTimeMillis() / 1000L) - endTime;
}
}
Runnable runnable = new BasicThread2();
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Color.ITALIC + Phrase.build(Phrase.LOOKUP_SEARCHING));
Runnable runnable = new StandardLookupThread(player, command, rollbackusers, argBlocks, argExclude, argExcludeUsers, argAction, argEntityActionFilter, argFilters, argRadius, lo, x, y, z, wid, argWid, timeStart, timeEnd, argNoisy, argExcluded, argRestricted, pa, re, type, ts, outputMode, rollbackState);
Thread thread = new Thread(runnable);
thread.start();
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
}
else {
@@ -990,4 +683,5 @@ public class LookupCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co l <params>"));
}
}
}
@@ -0,0 +1,26 @@
package net.coreprotect.command;
import org.bukkit.command.CommandSender;
import net.coreprotect.config.Config;
import net.coreprotect.language.Phrase;
import net.coreprotect.listener.channel.PluginChannelListener;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.ErrorReporter;
public class NetworkDebugCommand {
protected static void runCommand(CommandSender player, boolean permission, String[] args) {
if (!permission || !Config.getGlobal().NETWORK_DEBUG) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
try {
PluginChannelListener.getInstance().sendTest(player, args.length == 2 ? args[1] : "");
}
catch (Exception e) {
ErrorReporter.report(e);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,15 +1,25 @@
package net.coreprotect.command;
import java.util.concurrent.CancellationException;
import org.bukkit.command.CommandSender;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.consumer.Consumer;
import net.coreprotect.database.Database;
import net.coreprotect.database.DuckDBRecovery;
import net.coreprotect.language.Phrase;
import net.coreprotect.thread.NetworkHandler;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.EntitySpawnTracking;
import net.coreprotect.utility.ErrorReporter;
import net.coreprotect.utility.LookupThrottle;
public class ReloadCommand {
private static final long CONNECTION_DRAIN_TIMEOUT_MILLIS = 60_000L;
protected static void runCommand(final CommandSender player, boolean permission, String[] args) {
if (permission) {
if (ConfigHandler.converterRunning) {
@@ -20,47 +30,91 @@ public class ReloadCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PURGE_IN_PROGRESS));
return;
}
if (ConfigHandler.lookupThrottle.get(player.getName()) != null) {
Object[] lookupThrottle = ConfigHandler.lookupThrottle.get(player.getName());
if ((boolean) lookupThrottle[0] || ((System.currentTimeMillis() - (long) lookupThrottle[1])) < 100) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
return;
}
if (!LookupThrottle.tryAcquire(player.getName(), 100)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
return;
}
ConfigHandler.lookupThrottle.put(player.getName(), new Object[] { true, System.currentTimeMillis() });
class BasicThread implements Runnable {
@Override
public void run() {
try {
if (Consumer.isPaused) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_STARTED));
}
while (Consumer.isPaused) {
Thread.sleep(1);
}
Consumer.isPaused = true;
ConfigHandler.performInitialization(false);
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_SUCCESS));
Thread networkHandler = new Thread(new NetworkHandler(false, false));
networkHandler.start();
performReload(player);
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
finally {
LookupThrottle.release(player.getName());
}
Consumer.isPaused = false;
ConfigHandler.lookupThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() });
}
}
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
try {
Runnable runnable = new BasicThread();
Thread thread = new Thread(runnable);
thread.start();
}
catch (RuntimeException | Error e) {
LookupThrottle.release(player.getName());
throw e;
}
}
else {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
}
}
static synchronized boolean performReload(CommandSender player) throws InterruptedException {
final boolean resumePersistenceAfterCancellation = !Consumer.isDatabaseReloadPaused();
boolean resumePersistence = resumePersistenceAfterCancellation;
Consumer.OperationStartResult startResult = Consumer.beginDatabaseReload();
if (startResult != Consumer.OperationStartResult.STARTED) {
Phrase phrase = startResult == Consumer.OperationStartResult.PURGE_RUNNING ? Phrase.PURGE_IN_PROGRESS
: startResult == Consumer.OperationStartResult.ROLLBACK_RUNNING ? Phrase.ROLLBACK_IN_PROGRESS
: startResult == Consumer.OperationStartResult.PERSISTENCE_HALTED ? Phrase.DATABASE_PERSISTENCE_HALTED : Phrase.DATABASE_BUSY;
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(phrase));
return false;
}
long deadline = System.nanoTime() + CONNECTION_DRAIN_TIMEOUT_MILLIS * 1_000_000L;
try {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_STARTED));
if (!Consumer.lockDatabaseReload(CONNECTION_DRAIN_TIMEOUT_MILLIS)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_FAILED));
return false;
}
if (!Database.awaitConnectionDrain(remainingMillis(deadline))) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_FAILED));
return false;
}
resumePersistence = false;
boolean initialized;
try {
initialized = ConfigHandler.performInitialization(false);
}
catch (CancellationException e) {
resumePersistence = resumePersistenceAfterCancellation;
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_FAILED));
return false;
}
if (!initialized) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_FAILED));
return false;
}
EntitySpawnTracking.invalidateDatabaseVerification();
DuckDBRecovery.reset();
resumePersistence = true;
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.RELOAD_SUCCESS));
Thread networkHandler = new Thread(new NetworkHandler(false, false));
networkHandler.start();
return true;
}
finally {
Consumer.endDatabaseReload(resumePersistence);
}
}
private static long remainingMillis(long deadline) {
return Math.max(0L, (deadline - System.nanoTime()) / 1_000_000L);
}
}
@@ -5,6 +5,7 @@ import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -18,36 +19,51 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import net.coreprotect.command.parser.ActionParser;
import net.coreprotect.config.Config;
import net.coreprotect.config.ConfigHandler;
import net.coreprotect.consumer.Consumer;
import net.coreprotect.database.ContainerRollback;
import net.coreprotect.database.Database;
import net.coreprotect.database.Rollback;
import net.coreprotect.database.lookup.PlayerLookup;
import net.coreprotect.database.rollback.Rollback;
import net.coreprotect.language.Phrase;
import net.coreprotect.language.Selector;
import net.coreprotect.model.action.EntityActionFilter;
import net.coreprotect.model.action.LookupActions;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.Util;
import net.coreprotect.utility.WorldUtils;
import net.coreprotect.utility.ErrorReporter;
import net.coreprotect.utility.LookupThrottle;
public class RollbackRestoreCommand {
protected static void runCommand(CommandSender player, Command command, boolean permission, String[] args, Location argLocation, int forceSeconds) {
Location lo = (argLocation != null ? argLocation : CommandHandler.parseLocation(player, args));
public static void runCommand(CommandSender player, Command command, boolean permission, String[] args, Location argLocation, long forceStart, long forceEnd) {
String lookupOnlyFlag = findLookupOnlyFlag(args);
if (lookupOnlyFlag != null) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_PARAMETER, lookupOnlyFlag));
return;
}
Location lo = (argLocation != null ? argLocation : CommandParser.parseLocation(player, args));
List<String> argUuids = new ArrayList<>();
List<String> argUsers = CommandHandler.parseUsers(args);
Integer[] argRadius = CommandHandler.parseRadius(args, player, lo);
int argNoisy = CommandHandler.parseNoisy(args);
List<Integer> argAction = CommandHandler.parseAction(args);
List<Object> argBlocks = CommandHandler.parseRestricted(player, args, argAction);
List<Object> argExclude = CommandHandler.parseExcluded(player, args, argAction);
List<String> argExcludeUsers = CommandHandler.parseExcludedUsers(player, args);
String ts = CommandHandler.parseTimeString(args);
int rbSeconds = CommandHandler.parseTime(args);
int argWid = CommandHandler.parseWorld(args, true, true);
boolean count = CommandHandler.parseCount(args);
boolean worldedit = CommandHandler.parseWorldEdit(args);
boolean forceglobal = CommandHandler.parseForceGlobal(args);
int preview = CommandHandler.parsePreview(args);
List<String> argUsers = CommandParser.parseUsers(args);
Integer[] argRadius = CommandParser.parseRadius(args, player, lo);
int argNoisy = CommandParser.parseNoisy(args);
ActionParser.ParseResult actionResult = CommandParser.parseActions(args, false);
List<Integer> argAction = actionResult.getActions();
EntityActionFilter argEntityActionFilter = actionResult.getEntityActionFilter();
List<Object> argBlocks = CommandParser.parseRestricted(player, args, argAction);
Map<Object, Boolean> argExclude = CommandParser.parseExcluded(player, args, argAction);
List<String> argExcludeUsers = CommandParser.parseExcludedUsers(player, args);
String ts = CommandParser.parseTimeString(args);
long[] argTime = CommandParser.parseTime(args);
long startTime = argTime[0];
long endTime = argTime[1];
int argWid = CommandParser.parseWorld(args, true, true);
boolean worldedit = CommandParser.parseWorldEdit(args);
boolean forceglobal = CommandParser.parseForceGlobal(args);
int preview = CommandParser.parsePreview(args);
String corecommand = args[0].toLowerCase(Locale.ROOT);
if (argBlocks == null || argExclude == null || argExcludeUsers == null) {
@@ -58,15 +74,16 @@ public class RollbackRestoreCommand {
boolean hasBlock = false;
boolean hasEntity = false;
for (Object arg : argBlocks) {
if (arg instanceof Material) {
if (arg instanceof Material || arg instanceof String) {
hasBlock = true;
}
else if (arg instanceof EntityType) {
hasEntity = true;
if (argAction.size() == 0) {
argAction.add(3);
argAction.add(LookupActions.ENTITY_KILL);
argAction.add(LookupActions.ENTITY_SPAWN);
}
else if (!argAction.contains(3)) {
else if (!argEntityActionFilter.includesAnyEntity(argAction, true)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_INCLUDE_COMBO));
return;
}
@@ -74,16 +91,17 @@ public class RollbackRestoreCommand {
}
/* check for invalid block/entity combinations (exclude) */
for (Object arg : argExclude) {
if (arg instanceof Material) {
for (Object arg : argExclude.keySet()) {
if (arg instanceof Material || arg instanceof String) {
hasBlock = true;
}
else if (arg instanceof EntityType) {
hasEntity = true;
if (argAction.size() == 0) {
argAction.add(3);
argAction.add(LookupActions.ENTITY_KILL);
argAction.add(LookupActions.ENTITY_SPAWN);
}
else if (!argAction.contains(3)) {
else if (!argEntityActionFilter.includesAnyEntity(argAction, true)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_INCLUDE_COMBO));
return;
}
@@ -95,10 +113,6 @@ public class RollbackRestoreCommand {
return;
}
if (count) {
LookupCommand.runCommand(player, command, permission, args);
return;
}
if (ConfigHandler.converterRunning) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.UPGRADE_IN_PROGRESS));
return;
@@ -108,7 +122,7 @@ public class RollbackRestoreCommand {
return;
}
if (argWid == -1) {
String worldName = CommandHandler.parseWorldName(args, true);
String worldName = CommandParser.parseWorldName(args, true);
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.WORLD_NOT_FOUND, worldName));
return;
}
@@ -116,6 +130,10 @@ public class RollbackRestoreCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PREVIEW_IN_GAME));
return;
}
if (preview > 0 && argAction.contains(LookupActions.SIGN)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ACTION_NOT_SUPPORTED));
return;
}
if (argAction.contains(-1)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_ACTION));
return;
@@ -139,7 +157,7 @@ public class RollbackRestoreCommand {
return;
}
}
if (preview > 1 && forceSeconds <= 0) {
if (preview > 1 && forceStart <= 0) {
preview = 1;
}
@@ -151,13 +169,14 @@ public class RollbackRestoreCommand {
final int finalAction = a;
int DEFAULT_RADIUS = Config.getGlobal().DEFAULT_RADIUS;
if ((player instanceof Player || player instanceof BlockCommandSender) && argRadius == null && DEFAULT_RADIUS > 0 && !forceglobal) {
boolean exactEntityContainer = argUsers.contains("#container") && ConfigHandler.lookupEntityContainer.get(player.getName()) != null;
if ((player instanceof Player || player instanceof BlockCommandSender) && argRadius == null && DEFAULT_RADIUS > 0 && !forceglobal && !argAction.contains(LookupActions.ITEM) && !argAction.contains(5) && !exactEntityContainer) {
Location location = lo;
int xmin = location.getBlockX() - DEFAULT_RADIUS;
int xmax = location.getBlockX() + DEFAULT_RADIUS;
int zmin = location.getBlockZ() - DEFAULT_RADIUS;
int zmax = location.getBlockZ() + DEFAULT_RADIUS;
argRadius = new Integer[] { DEFAULT_RADIUS, xmin, xmax, -1, -1, zmin, zmax, 0 };
argRadius = new Integer[] { DEFAULT_RADIUS, xmin, xmax, null, null, zmin, zmax, 0 };
}
// if (arg_radius==-2)arg_radius = -1;
@@ -178,6 +197,21 @@ public class RollbackRestoreCommand {
return;
}
if (LookupActions.isInventoryLookup(argAction)) {
if (argUsers.size() == 0) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_ACTION_USER));
return;
}
argExclude.put(Material.FIRE, false);
argExclude.put(Material.WATER, false);
argExclude.put(Material.FARMLAND, false);
argExcludeUsers.add("#hopper");
}
else if (!argAction.contains(LookupActions.CONTAINER) && !argAction.contains(LookupActions.SIGN) && Config.getGlobal().EXCLUDE_TNT && !argExclude.containsKey(Material.TNT) && !argBlocks.contains(Material.TNT)) {
argExclude.put(Material.TNT, true);
}
if (g == 1 && (argUsers.size() > 0 || (argUsers.size() == 0 && argRadius != null))) {
Integer MAX_RADIUS = Config.getGlobal().MAX_RADIUS;
if (argRadius != null) {
@@ -189,17 +223,17 @@ public class RollbackRestoreCommand {
}
}
if (argAction.size() > 0) {
if (argAction.contains(4)) {
if (argAction.contains(LookupActions.CONTAINER)) {
if (argUsers.contains("#global") || (argUsers.size() == 0 && argRadius == null)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_ACTION_USER));
return;
}
else if (preview > 0) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PREVIEW_CONTAINER));
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PREVIEW_TRANSACTION, !argAction.contains(LookupActions.ITEM) ? Selector.FIRST : Selector.SECOND));
return;
}
}
if (argAction.contains(8) || argAction.contains(11) || (!argAction.contains(0) && !argAction.contains(1) && !argAction.contains(3) && !argAction.contains(4))) {
if (argAction.contains(LookupActions.SESSION) || (argAction.contains(LookupActions.ITEM) && !argAction.contains(LookupActions.CONTAINER)) || (!argAction.contains(LookupActions.BLOCK_BREAK) && !argAction.contains(LookupActions.BLOCK_PLACE) && !argAction.contains(LookupActions.ENTITY_KILL) && !argAction.contains(LookupActions.ENTITY_SPAWN) && !argAction.contains(LookupActions.CONTAINER) && !argAction.contains(LookupActions.SIGN))) {
if (finalAction == 0) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ACTION_NOT_SUPPORTED));
}
@@ -213,16 +247,26 @@ public class RollbackRestoreCommand {
if (argUsers.size() == 0) {
argUsers.add("#global");
}
List<String> rollbackusers = argUsers;
int c = 0;
for (String ruser : rollbackusers) {
List<Player> players = Bukkit.getServer().matchPlayer(ruser);
List<Player> players = Bukkit.getServer().matchPlayer(ruser); // here
for (Player p : players) {
if (p.getName().equalsIgnoreCase(ruser)) {
rollbackusers.set(c, p.getName());
ruser = p.getName();
rollbackusers.set(c, ruser);
}
}
c++;
if (LookupActions.isInventoryLookup(argAction)) {
Player onlineUser = Bukkit.getServer().getPlayer(ruser);
if (onlineUser == null || !onlineUser.isOnline()) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.USER_OFFLINE, ruser));
return;
}
}
}
int wid = 0;
@@ -230,6 +274,11 @@ public class RollbackRestoreCommand {
int y = 0;
int z = 0;
if (rollbackusers.contains("#container")) {
if (argAction.contains(LookupActions.ITEM)) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_USERNAME, "#container"));
return;
}
boolean valid = false;
if (ConfigHandler.lookupType.get(player.getName()) != null) {
int lookupType = ConfigHandler.lookupType.get(player.getName());
@@ -244,7 +293,7 @@ public class RollbackRestoreCommand {
}
if (valid) {
if (preview > 0) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PREVIEW_CONTAINER));
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.PREVIEW_TRANSACTION, Selector.FIRST));
return;
}
else {
@@ -257,20 +306,22 @@ public class RollbackRestoreCommand {
argAction.add(5);
argRadius = null;
argWid = 0;
lo = new Location(Bukkit.getServer().getWorld(Util.getWorldName(wid)), x, y, z);
Block block = lo.getBlock();
if (block.getState() instanceof Chest) {
BlockFace[] blockFaces = new BlockFace[] { BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST };
for (BlockFace face : blockFaces) {
if (block.getRelative(face, 1).getState() instanceof Chest) {
Block relative = block.getRelative(face, 1);
int x2 = relative.getX();
int z2 = relative.getZ();
double newX = (x + x2) / 2.0;
double newZ = (z + z2) / 2.0;
lo.setX(newX);
lo.setZ(newZ);
break;
lo = new Location(Bukkit.getServer().getWorld(WorldUtils.getWorldName(wid)), x, y, z);
if (!exactEntityContainer) {
Block block = lo.getBlock();
if (block.getState() instanceof Chest) {
BlockFace[] blockFaces = new BlockFace[] { BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST };
for (BlockFace face : blockFaces) {
if (block.getRelative(face, 1).getState() instanceof Chest) {
Block relative = block.getRelative(face, 1);
int x2 = relative.getX();
int z2 = relative.getZ();
double newX = (x + x2) / 2.0;
double newZ = (z + z2) / 2.0;
lo.setX(newX);
lo.setZ(newZ);
break;
}
}
}
}
@@ -283,13 +334,16 @@ public class RollbackRestoreCommand {
}
final List<String> rollbackusers2 = rollbackusers;
if (rbSeconds > 0) {
int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
int seconds = unixtimestamp - rbSeconds;
if (forceSeconds > 0) {
seconds = forceSeconds;
if (startTime > 0) {
long unixtimestamp = (System.currentTimeMillis() / 1000L);
long timeStart = unixtimestamp - startTime;
long timeEnd = endTime > 0 ? (unixtimestamp - endTime) : 0;
if (forceStart > 0) {
timeStart = forceStart;
timeEnd = forceEnd;
}
final int stime = seconds;
final long finalTimeStart = timeStart;
final long finalTimeEnd = timeEnd;
final Integer[] radius = argRadius;
try {
final CommandSender player2 = player;
@@ -297,24 +351,35 @@ public class RollbackRestoreCommand {
final String rtime = ts;
final List<String> uuidList = argUuids;
final List<Object> blist = argBlocks;
final List<Object> elist = argExclude;
final Map<Object, Boolean> elist = argExclude;
final List<String> euserlist = argExcludeUsers;
final Location locationFinal = lo;
final int finalArgWid = argWid;
final List<Integer> finalArgAction = argAction;
final EntityActionFilter finalEntityActionFilter = argEntityActionFilter;
final String[] finalArgs = args;
final int finalPreview = preview;
ConfigHandler.activeRollbacks.put(player.getName(), true);
Consumer.OperationStartResult startResult = Consumer.claimRollback(player.getName(), finalPreview == 0);
if (startResult != Consumer.OperationStartResult.STARTED) {
Phrase phrase = startResult == Consumer.OperationStartResult.PURGE_RUNNING ? Phrase.PURGE_IN_PROGRESS
: startResult == Consumer.OperationStartResult.PERSISTENCE_HALTED ? Phrase.DATABASE_PERSISTENCE_HALTED
: startResult == Consumer.OperationStartResult.RELOAD_RUNNING ? Phrase.DATABASE_BUSY : Phrase.ROLLBACK_IN_PROGRESS;
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(phrase));
return;
}
class BasicThread2 implements Runnable {
@Override
public void run() {
try {
ConfigHandler.lookupThrottle.put(player.getName(), new Object[] { true, System.currentTimeMillis() });
if (!LookupThrottle.tryAcquire(player.getName(), 100)) {
Consumer.releaseRollback(player.getName());
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
return;
}
try (Connection connection = Database.getConnection(false, 1000)) {
int action = finalAction;
Location location = locationFinal;
Connection connection = Database.getConnection(false, 1000);
if (connection != null) {
Statement statement = connection.createStatement();
String baduser = "";
@@ -333,14 +398,14 @@ public class RollbackRestoreCommand {
}
if (exists) {
for (String check : euserlist) {
if (!check.equals("#global")) {
if (!check.equals("#global") && !check.equals("#hopper")) {
exists = PlayerLookup.playerExists(connection, check);
if (!exists) {
baduser = check;
break;
}
}
else {
else if (check.equals("#global")) {
baduser = "#global";
exists = false;
}
@@ -356,7 +421,7 @@ public class RollbackRestoreCommand {
}
if (finalArgWid > 0) {
restrictWorld = true;
location = new Location(Bukkit.getServer().getWorld(Util.getWorldName(finalArgWid)), 0, 0, 0);
location = new Location(Bukkit.getServer().getWorld(WorldUtils.getWorldName(finalArgWid)), 0, 0, 0);
}
boolean verbose = false;
if (noisy == 1) {
@@ -389,45 +454,52 @@ public class RollbackRestoreCommand {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_STARTED, users, Selector.SECOND));
}
if (finalArgAction.contains(5)) {
ContainerRollback.performContainerRollbackRestore(statement, player2, uuidList, rollbackusers2, rtime, blist, elist, euserlist, finalArgAction, location, radius, stime, restrictWorld, false, verbose, action);
boolean completed = true;
if (finalArgAction.contains(5) && ConfigHandler.lookupEntityContainer.get(player2.getName()) == null) {
completed = ContainerRollback.performContainerRollbackRestore(statement, player2, uuidList, rollbackusers2, rtime, blist, elist, euserlist, finalArgAction, location, radius, finalTimeStart, finalTimeEnd, restrictWorld, false, verbose, action);
}
else {
Rollback.performRollbackRestore(statement, player2, uuidList, rollbackusers2, rtime, blist, elist, euserlist, finalArgAction, location, radius, stime, restrictWorld, false, verbose, action, finalPreview);
if (finalPreview < 2) {
List<Object> list = new ArrayList<>();
list.add(stime);
list.add(finalArgs);
list.add(locationFinal);
ConfigHandler.lastRollback.put(player2.getName(), list);
}
completed = Rollback.performRollbackRestore(statement, player2, uuidList, rollbackusers2, rtime, blist, elist, euserlist, finalArgAction, finalEntityActionFilter, location, radius, finalTimeStart, finalTimeEnd, restrictWorld, false, verbose, action, finalPreview) != null;
}
if (completed && finalPreview < 2) {
List<Object> list = new ArrayList<>();
list.add(finalTimeStart);
list.add(finalTimeEnd);
list.add(finalArgs);
list.add(locationFinal);
ConfigHandler.lastRollback.put(player2.getName(), list);
}
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.USER_NOT_FOUND, baduser));
}
statement.close();
connection.close();
}
else {
Chat.sendMessage(player2, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
}
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
if (ConfigHandler.activeRollbacks.get(player2.getName()) != null) {
ConfigHandler.activeRollbacks.remove(player2.getName());
finally {
Consumer.releaseRollback(player2.getName());
LookupThrottle.release(player2.getName());
}
ConfigHandler.lookupThrottle.put(player2.getName(), new Object[] { false, System.currentTimeMillis() });
}
}
Runnable runnable = new BasicThread2();
Thread thread = new Thread(runnable);
thread.start();
try {
thread.start();
}
catch (Exception e) {
Consumer.releaseRollback(player.getName());
throw e;
}
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
}
else {
@@ -452,4 +524,15 @@ public class RollbackRestoreCommand {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
}
}
private static String findLookupOnlyFlag(String[] args) {
for (int index = 1; index < args.length; index++) {
String raw = args[index];
String argument = raw.trim().toLowerCase(Locale.ROOT).replace("\\", "").replace("'", "");
if (argument.equals("#count") || argument.equals("#sum") || argument.equals("#summary") || argument.equals("count") || argument.equals("sum")) {
return raw.trim();
}
}
return null;
}
}
@@ -17,6 +17,9 @@ import net.coreprotect.patch.Patch;
import net.coreprotect.thread.NetworkHandler;
import net.coreprotect.utility.Chat;
import net.coreprotect.utility.Color;
import net.coreprotect.utility.SystemUtils;
import net.coreprotect.utility.VersionUtils;
import net.coreprotect.utility.ErrorReporter;
public class StatusCommand {
private static ConcurrentHashMap<String, Boolean> alert = new ConcurrentHashMap<>();
@@ -37,12 +40,16 @@ public class StatusCommand {
String versionCheck = "";
if (Config.getGlobal().CHECK_UPDATES) {
String latestVersion = NetworkHandler.latestVersion();
String latestEdgeVersion = NetworkHandler.latestEdgeVersion();
if (latestVersion != null) {
versionCheck = " (" + Phrase.build(Phrase.LATEST_VERSION, "v" + latestVersion) + ")";
}
else if (latestEdgeVersion != null && !VersionUtils.isCommunityEdition()) {
versionCheck = " (" + Phrase.build(Phrase.LATEST_VERSION, "v" + latestEdgeVersion) + ")";
}
}
Chat.sendMessage(player, Color.WHITE + "----- " + Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "-----");
Chat.sendMessage(player, Color.WHITE + "----- " + Color.DARK_AQUA + "CoreProtect" + (VersionUtils.isCommunityEdition() ? " " + ConfigHandler.COMMUNITY_EDITION : "") + Color.WHITE + " -----");
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_VERSION, Color.WHITE, ConfigHandler.EDITION_NAME + " v" + pdfFile.getVersion() + ".") + versionCheck);
String donationKey = NetworkHandler.donationKey();
@@ -54,23 +61,24 @@ public class StatusCommand {
}
/*
CoreProtect show RAM usage
Items processed (since server start)
Items processed (last 60 minutes)
*/
// Using MySQL/SQLite (Database Size: 587MB)
String firstVersion = Patch.getFirstVersion();
String firstVersion = ConfigHandler.databaseType.isClickHouse() ? "" : Patch.getFirstVersion();
if (firstVersion.length() > 0) {
firstVersion = " (" + Phrase.build(Phrase.FIRST_VERSION, firstVersion) + ")";
}
if (Config.getGlobal().MYSQL) {
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_DATABASE, Color.WHITE, "MySQL") + firstVersion);
String databaseName = ConfigHandler.databaseType.getDisplayName();
if (Consumer.isPersistenceHalted()) {
databaseName += " " + Color.RED + Phrase.build(Phrase.STATUS_DATABASE_STATE, Selector.FIRST) + Color.WHITE;
}
else {
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_DATABASE, Color.WHITE, "SQLite") + firstVersion);
else if (!ConfigHandler.databaseReachable) {
databaseName += " " + Color.RED + Phrase.build(Phrase.STATUS_DATABASE_STATE, Selector.SECOND) + Color.WHITE;
}
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_DATABASE, Color.WHITE, databaseName) + firstVersion);
if (ConfigHandler.worldeditEnabled) {
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_INTEGRATION, Color.WHITE, "WorldEdit", Selector.FIRST));
@@ -99,7 +107,55 @@ public class StatusCommand {
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_CONSUMER, Color.WHITE, String.format("%,d", consumerCount), (consumerCount == 1 ? Selector.FIRST : Selector.SECOND)));
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
long autoPurgeRowsPurged = ConfigHandler.autoPurgeRowsPurged.get();
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_AUTO_PURGE, Color.WHITE, String.format("%,d", autoPurgeRowsPurged), (autoPurgeRowsPurged == 1 ? Selector.FIRST : Selector.SECOND)));
try {
String cpuInfo = "x" + Runtime.getRuntime().availableProcessors() + " " + Phrase.build(Phrase.CPU_CORES);
if (ConfigHandler.processorInfo != null) {
String modelName = ConfigHandler.processorInfo.getName();
if (modelName.contains(" CPU")) {
String[] split = modelName.split(" CPU")[0].split(" ");
modelName = split[split.length - 1];
}
else if (modelName.contains(" Processor")) {
String[] split = modelName.split(" Processor")[0].split(" ");
modelName = split[split.length - 1];
}
String cpuSpeed = String.valueOf(ConfigHandler.processorInfo.getMaxFrequency());
double speedVal = Long.valueOf(cpuSpeed) / 1000000000.0;
// Fix for Apple Silicon processors reporting 0 GHz
if (speedVal < 0.01 && SystemUtils.isAppleSilicon()) {
Double appleSiliconSpeed = SystemUtils.getAppleSiliconSpeed();
if (appleSiliconSpeed != null) {
speedVal = appleSiliconSpeed;
}
}
cpuSpeed = String.format("%.2f", speedVal);
if (speedVal >= 0.01) {
cpuInfo = "x" + Runtime.getRuntime().availableProcessors() + " " + cpuSpeed + "GHz " + modelName + ".";
}
}
int mb = 1024 * 1024;
Runtime runtime = Runtime.getRuntime();
String usedRAM = String.format("%.2f", Double.valueOf((runtime.totalMemory() - runtime.freeMemory()) / mb) / 1000.0);
String totalRAM = String.format("%.2f", Double.valueOf(runtime.maxMemory() / mb) / 1000.0);
String systemInformation = Phrase.build(Phrase.RAM_STATS, usedRAM, totalRAM);
if (cpuInfo.length() > 0) {
systemInformation = cpuInfo + " (" + systemInformation + ")";
}
Chat.sendMessage(player, Color.DARK_AQUA + Phrase.build(Phrase.STATUS_SYSTEM, Color.WHITE, systemInformation));
}
catch (Exception e) {
ErrorReporter.report(e);
}
// Functions.sendMessage(player, Color.DARK_AQUA + "Website: " + Color.WHITE + "www.coreprotect.net/updates/");
@@ -122,7 +178,7 @@ public class StatusCommand {
}
}
catch (Exception e) {
e.printStackTrace();
ErrorReporter.report(e);
}
}
}
@@ -17,16 +17,17 @@ import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import net.coreprotect.command.parser.RollbackStateParser;
import net.coreprotect.config.ConfigHandler;
public class TabHandler implements TabCompleter {
// private static String[] COMMANDS = new String[] { "help", "inspect", "rollback", "restore", "lookup", "purge", "reload", "status", "near", "undo" }; // max 10!
private static String[] HELP = new String[] { "inspect", "rollback", "restore", "lookup", "purge", "teleport", "status", "params", "users", "time", "radius", "action", "include", "exclude" };
private static String[] PARAMS = new String[] { "user:", "time:", "radius:", "action:", "include:", "exclude:", "#container" };
private static String[] ACTIONS = new String[] { "block", "+block", "-block", "click", "kill", "container", "chat", "command", "+inventory", "-inventory", "inventory", "item", "+item", "-item", "sign", "session", "+session", "-session", "username" };
private static String[] NUMBERS = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
private static String[] TIMES = new String[] { "w", "d", "h", "m", "s" };
private static final String[] HELP = new String[] { "inspect", "rollback", "restore", "lookup", "purge", "teleport", "status", "params", "users", "time", "radius", "action", "include", "exclude", "filter" };
private static final String[] PARAMS = new String[] { "user:", "time:", "radius:", "action:", "include:", "exclude:", "filter:", "#container" };
private static final String[] ACTIONS = new String[] { "block", "+block", "-block", "click", "kill", "spawn", "+container", "-container", "container", "chat", "command", "+inventory", "-inventory", "inventory", "item", "+item", "-item", "sign", "session", "+session", "-session", "username" };
private static final String[] NUMBERS = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
private static final String[] TIMES = new String[] { "w", "d", "h", "m", "s" };
private static ArrayList<String> materials = null;
@Override
@@ -34,370 +35,492 @@ public class TabHandler implements TabCompleter {
if (!(sender instanceof Player) || args.length == 0) {
return null;
}
if (args.length == 1) {
String argument = args[0].toLowerCase(Locale.ROOT);
List<String> completions = new ArrayList<>();
if (sender.hasPermission("coreprotect.help")) {
completions.add("help");
}
if (sender.hasPermission("coreprotect.inspect")) {
completions.add("inspect");
}
if (sender.hasPermission("coreprotect.rollback")) {
completions.add("rollback");
}
if (sender.hasPermission("coreprotect.restore")) {
completions.add("restore");
}
if (sender.hasPermission("coreprotect.lookup")) {
completions.add("lookup");
}
if (sender.hasPermission("coreprotect.purge")) {
completions.add("purge");
}
if (sender.hasPermission("coreprotect.reload")) {
completions.add("reload");
}
if (sender.hasPermission("coreprotect.status")) {
completions.add("status");
}
if (sender.hasPermission("coreprotect.lookup.near")) {
completions.add("near");
}
if (sender.hasPermission("coreprotect.restore")) {
completions.add("undo");
}
return StringUtil.copyPartialMatches(argument, completions, new ArrayList<>(completions.size()));
return getFirstLevelCompletions(sender, args[0]);
}
else if (args.length > 1) {
String argument0 = args[0].toLowerCase(Locale.ROOT);
String argument1 = args[1].toLowerCase(Locale.ROOT);
String currentArg = args[args.length - 1].toLowerCase(Locale.ROOT).trim();
String lastArg = args[args.length - 2].toLowerCase(Locale.ROOT).trim();
boolean hasUser = false;
boolean hasAction = false;
boolean hasInclude = false;
boolean hasExclude = false;
boolean hasRadius = false;
boolean hasTime = false;
boolean hasContainer = false;
boolean hasCount = false;
boolean hasPreview = false;
boolean hasPage = false;
boolean validContainer = false;
boolean pageLookup = false;
String argument0 = args[0].toLowerCase(Locale.ROOT);
String currentArg = args[args.length - 1].toLowerCase(Locale.ROOT).trim();
String lastArg = args.length > 1 ? args[args.length - 2].toLowerCase(Locale.ROOT).trim() : "";
if (ConfigHandler.lookupType.get(sender.getName()) != null && ConfigHandler.lookupPage.get(sender.getName()) != null) {
pageLookup = true;
}
ParamState paramState = getParamState(sender, args);
for (int i = 1; i < args.length; i++) {
String arg = args[i].toLowerCase(Locale.ROOT);
if (arg.equals("#container")) {
hasContainer = true;
}
else if (arg.equals("#count") || arg.equals("#sum")) {
hasCount = true;
}
else if (arg.equals("#preview")) {
hasPreview = true;
}
else if ((!arg.contains(":") && !args[i - 1].contains(":") && args.length > (i + 1)) || arg.contains("u:") || arg.contains("user:") || arg.contains("users:") || arg.contains("p:")) {
hasUser = true;
}
else if (arg.contains("page:")) {
hasPage = true;
}
else if (arg.contains("a:") || arg.contains("action:")) {
hasAction = true;
}
else if (arg.contains("i:") || arg.contains("include:") || arg.contains("item:") || arg.contains("items:") || arg.contains("b:") || arg.contains("block:") || arg.contains("blocks:")) {
hasInclude = true;
}
else if (arg.contains("t:") || arg.contains("time:")) {
hasTime = true;
}
else if (arg.contains("e:") || arg.contains("exclude:")) {
hasExclude = true;
}
else if (arg.contains("r:") || arg.contains("radius:")) {
hasRadius = true;
}
}
if (!hasContainer) {
if (ConfigHandler.lookupType.get(sender.getName()) != null) {
int lookupType = ConfigHandler.lookupType.get(sender.getName());
if (lookupType == 1) {
validContainer = true;
}
else if (lookupType == 5) {
if (ConfigHandler.lookupUlist.get(sender.getName()).contains("#container")) {
validContainer = true;
}
}
}
}
if (lastArg.equals("a:") || lastArg.equals("action:")) {
List<String> completions = new ArrayList<>(Arrays.asList(ACTIONS));
return StringUtil.copyPartialMatches(currentArg, completions, new ArrayList<>(completions.size()));
}
else if (currentArg.startsWith("a:") || currentArg.startsWith("action:")) {
String arg = "";
String[] split = currentArg.split(":", 2);
String filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
List<String> completions = new ArrayList<>(Arrays.asList(ACTIONS));
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
else if (lastArg.equals("u:") || lastArg.equals("user:") || lastArg.equals("users:") || lastArg.equals("p:")) {
return null;
}
else if (currentArg.startsWith("u:") || currentArg.startsWith("user:") || currentArg.startsWith("users:") || currentArg.startsWith("p:")) {
String arg = "";
String[] split = currentArg.split(":", 2);
String filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
List<String> completions = Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList());
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
else if (lastArg.equals("t:") || lastArg.equals("time:") || currentArg.startsWith("t:") || currentArg.startsWith("time:")) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
List<String> completions = new ArrayList<>();
if (arg.chars().allMatch(Character::isDigit)) {
boolean addNumbers = true;
if (currentArg.length() > 0) {
char lastChar = currentArg.charAt(currentArg.length() - 1);
if (Character.isDigit(lastChar)) {
completions.addAll(Arrays.asList(TIMES));
addNumbers = false;
}
}
if (addNumbers) {
completions.addAll(Arrays.asList(NUMBERS));
}
}
completions = new ArrayList<>(completions);
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + arg + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
else if (lastArg.equals("page:") || currentArg.startsWith("page:")) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
if (arg.chars().allMatch(Character::isDigit)) {
List<String> completions = new ArrayList<>(Arrays.asList(NUMBERS));
if (arg.length() < 1) {
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + arg + completions.get(index));
}
if (arg.length() == 0) {
completions.remove(0);
}
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
}
else if (lastArg.equals("r:") || lastArg.equals("radius:") || currentArg.startsWith("r:") || currentArg.startsWith("radius:")) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
if (!argument0.equals("purge") && arg.chars().allMatch(Character::isDigit)) {
List<String> completions = new ArrayList<>(Arrays.asList(NUMBERS));
if (arg.length() < 2) {
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + arg + completions.get(index));
}
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
else if (argument0.equals("purge") || arg.startsWith("#")) {
ArrayList<String> params = new ArrayList<>();
params.add("#global");
if (!argument0.equals("purge") && sender.getServer().getPluginManager().getPlugin("WorldEdit") != null) {
params.add("#worldedit");
}
List<World> worlds = sender.getServer().getWorlds();
for (World world : worlds) {
params.add("#" + world.getName());
}
List<String> completions = new ArrayList<>(params);
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
}
else if (lastArg.equals("i:") || lastArg.equals("include:") || lastArg.equals("item:") || lastArg.equals("items:") || lastArg.equals("b:") || lastArg.equals("block:") || lastArg.equals("blocks:") || currentArg.startsWith("i:") || currentArg.startsWith("include:") || currentArg.startsWith("item:") || currentArg.startsWith("items:") || currentArg.startsWith("b:") || currentArg.startsWith("block:") || currentArg.startsWith("blocks:") || lastArg.equals("e:") || lastArg.equals("exclude:") || currentArg.startsWith("e:") || currentArg.startsWith("exclude:")) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
if (materials == null) {
List<Material> addList = Arrays.asList(Material.ARMOR_STAND);
List<Material> excludeList = Arrays.asList(Material.GRASS);
Set<String> materialList = new HashSet<>();
Material[] materialValues = Material.values();
for (Material material : materialValues) {
if (material.isBlock() || material.isItem()) {
materialList.add(material.name().toLowerCase(Locale.ROOT));
}
}
for (Material exclude : excludeList) {
materialList.remove(exclude.name().toLowerCase(Locale.ROOT));
}
for (Material add : addList) {
materialList.add(add.name().toLowerCase(Locale.ROOT));
}
materials = new ArrayList<>(materialList);
}
List<String> completions = new ArrayList<>(materials);
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
else if (args.length == 2) {
if (argument0.equals("help")) {
List<String> completions = new ArrayList<>(Arrays.asList(HELP));
return StringUtil.copyPartialMatches(argument1, completions, new ArrayList<>(completions.size()));
}
else if (argument0.equals("purge")) {
List<String> completions = new ArrayList<>(Arrays.asList("t:", "r:"));
return StringUtil.copyPartialMatches(argument1, completions, new ArrayList<>(completions.size()));
}
else if (argument0.equals("l") || argument0.equals("lookup") || argument0.equals("rollback") || argument0.equals("rb") || argument0.equals("ro") || argument0.equals("restore") || argument0.equals("rs") || argument0.equals("re")) {
List<String> completions = new ArrayList<>(filterParams(true, argument0, argument1, hasUser, hasAction, hasInclude, hasExclude, hasRadius, hasTime, hasContainer, hasCount, hasPreview, pageLookup, validContainer));
completions.addAll(Bukkit.getOnlinePlayers().stream().filter(player -> player.getName().toLowerCase(Locale.ROOT).startsWith(argument1)).map(Player::getName).collect(Collectors.toList()));
return StringUtil.copyPartialMatches(argument1, completions, new ArrayList<>(completions.size()));
}
}
else if (args.length == 3 && argument0.equals("purge")) {
if (argument1.startsWith("t:")) {
List<String> completions = new ArrayList<>(Arrays.asList("r:"));
return StringUtil.copyPartialMatches(args[2].toLowerCase(Locale.ROOT), completions, new ArrayList<>(completions.size()));
}
else if (argument1.startsWith("r:")) {
List<String> completions = new ArrayList<>(Arrays.asList("t:"));
return StringUtil.copyPartialMatches(args[2].toLowerCase(Locale.ROOT), completions, new ArrayList<>(completions.size()));
}
return Arrays.asList("");
}
else if (argument0.equals("l") || argument0.equals("lookup") || argument0.equals("rollback") || argument0.equals("rb") || argument0.equals("ro") || argument0.equals("restore") || argument0.equals("rs") || argument0.equals("re")) {
if ((!argument0.equals("l") && !argument0.equals("lookup")) || !hasPage) {
ArrayList<String> params = filterParams(false, argument0, currentArg, hasUser, hasAction, hasInclude, hasExclude, hasRadius, hasTime, hasContainer, hasCount, hasPreview, pageLookup, validContainer);
List<String> completions = new ArrayList<>(params);
return StringUtil.copyPartialMatches(currentArg, completions, new ArrayList<>(completions.size()));
}
}
// Handle param-specific completions
if (isActionParam(lastArg, currentArg) && hasLookupPermission(sender)) {
return handleActionParamCompletions(currentArg, lastArg);
}
else if (isUserParam(lastArg, currentArg) && hasLookupPermission(sender)) {
return handleUserParamCompletions(currentArg, lastArg);
}
else if (isTimeParam(lastArg, currentArg) && hasTimePermission(sender)) {
return handleTimeParamCompletions(currentArg, lastArg);
}
else if (isPageParam(lastArg, currentArg) && hasPagePermission(sender)) {
return handlePageParamCompletions(currentArg, lastArg);
}
else if (isRadiusParam(lastArg, currentArg) && hasRadiusPermission(sender)) {
return handleRadiusParamCompletions(currentArg, lastArg, argument0);
}
else if (isMaterialParam(lastArg, currentArg) && hasLookupPermission(sender)) {
return handleMaterialParamCompletions(currentArg, lastArg);
}
else if (args.length == 2) {
return handleSecondArgCompletions(sender, argument0, args[1], paramState);
}
else if (args.length == 3 && argument0.equals("purge") && sender.hasPermission("coreprotect.purge")) {
return handlePurgeThirdArgCompletions(args[1], args[2]);
}
else if (hasLookupCommand(argument0, sender) && (!argument0.equals("l") && !argument0.equals("lookup") || !paramState.hasPage)) {
return handleGenericLookupCompletions(argument0, currentArg, paramState);
}
return Arrays.asList("");
}
private ArrayList<String> filterParams(boolean firstParam, String lastArgument, String argument, boolean hasUser, boolean hasAction, boolean hasInclude, boolean hasExclude, boolean hasRadius, boolean hasTime, boolean hasContainer, boolean hasCount, boolean hasPreview, boolean pageLookup, boolean validContainer) {
private List<String> getFirstLevelCompletions(CommandSender sender, String argument) {
String arg = argument.toLowerCase(Locale.ROOT);
List<String> completions = new ArrayList<>();
addCompletionIfPermitted(sender, "coreprotect.help", "help", completions);
addCompletionIfPermitted(sender, "coreprotect.inspect", "inspect", completions);
addCompletionIfPermitted(sender, "coreprotect.rollback", "rollback", completions);
addCompletionIfPermitted(sender, "coreprotect.restore", "restore", completions);
addCompletionIfPermitted(sender, "coreprotect.lookup", "lookup", completions);
addCompletionIfPermitted(sender, "coreprotect.purge", "purge", completions);
addCompletionIfPermitted(sender, "coreprotect.reload", "reload", completions);
addCompletionIfPermitted(sender, "coreprotect.status", "status", completions);
addCompletionIfPermitted(sender, "coreprotect.lookup.near", "near", completions);
addCompletionIfPermitted(sender, "coreprotect.restore", "undo", completions);
return StringUtil.copyPartialMatches(arg, completions, new ArrayList<>(completions.size()));
}
private void addCompletionIfPermitted(CommandSender sender, String permission, String completion, List<String> completions) {
if (sender.hasPermission(permission)) {
completions.add(completion);
}
}
private boolean hasLookupPermission(CommandSender sender) {
return sender.hasPermission("coreprotect.lookup") || sender.hasPermission("coreprotect.rollback") || sender.hasPermission("coreprotect.restore");
}
private boolean hasTimePermission(CommandSender sender) {
return hasLookupPermission(sender) || sender.hasPermission("coreprotect.purge");
}
private boolean hasRadiusPermission(CommandSender sender) {
return hasTimePermission(sender);
}
private boolean hasPagePermission(CommandSender sender) {
return sender.hasPermission("coreprotect.lookup") || sender.hasPermission("coreprotect.lookup.near") || sender.hasPermission("coreprotect.inspect");
}
private boolean hasLookupCommand(String cmd, CommandSender sender) {
return (sender.hasPermission("coreprotect.lookup") && (cmd.equals("l") || cmd.equals("lookup"))) || (sender.hasPermission("coreprotect.rollback") && (cmd.equals("rollback") || cmd.equals("rb") || cmd.equals("ro"))) || (sender.hasPermission("coreprotect.restore") && (cmd.equals("restore") || cmd.equals("rs") || cmd.equals("re")));
}
private boolean isActionParam(String lastArg, String currentArg) {
return lastArg.equals("a:") || lastArg.equals("action:") || currentArg.startsWith("a:") || currentArg.startsWith("action:");
}
private boolean isUserParam(String lastArg, String currentArg) {
return lastArg.equals("u:") || lastArg.equals("user:") || lastArg.equals("users:") || lastArg.equals("p:") || currentArg.startsWith("u:") || currentArg.startsWith("user:") || currentArg.startsWith("users:") || currentArg.startsWith("p:");
}
private boolean isTimeParam(String lastArg, String currentArg) {
return lastArg.equals("t:") || lastArg.equals("time:") || currentArg.startsWith("t:") || currentArg.startsWith("time:");
}
private boolean isPageParam(String lastArg, String currentArg) {
return lastArg.equals("page:") || currentArg.startsWith("page:");
}
private boolean isRadiusParam(String lastArg, String currentArg) {
return lastArg.equals("r:") || lastArg.equals("radius:") || currentArg.startsWith("r:") || currentArg.startsWith("radius:");
}
private boolean isMaterialParam(String lastArg, String currentArg) {
return lastArg.equals("i:") || lastArg.equals("include:") || lastArg.equals("item:") || lastArg.equals("items:") || lastArg.equals("b:") || lastArg.equals("block:") || lastArg.equals("blocks:") || currentArg.startsWith("i:") || currentArg.startsWith("include:") || currentArg.startsWith("item:") || currentArg.startsWith("items:") || currentArg.startsWith("b:") || currentArg.startsWith("block:") || currentArg.startsWith("blocks:") || lastArg.equals("e:") || lastArg.equals("exclude:") || currentArg.startsWith("e:") || currentArg.startsWith("exclude:");
}
private static class ParamState {
boolean hasUser;
boolean hasAction;
boolean hasInclude;
boolean hasExclude;
boolean hasFilter;
boolean hasRadius;
boolean hasTime;
boolean hasContainer;
boolean hasCount;
boolean hasSummary;
boolean hasRollbackState;
boolean hasPreview;
boolean hasPage;
boolean validContainer;
boolean pageLookup;
}
private ParamState getParamState(CommandSender sender, String[] args) {
ParamState state = new ParamState();
String senderName = sender.getName();
if (ConfigHandler.lookupType.get(senderName) != null && ConfigHandler.lookupPage.get(senderName) != null) {
state.pageLookup = true;
}
for (int i = 1; i < args.length; i++) {
String arg = args[i].toLowerCase(Locale.ROOT);
if (arg.equals("#container")) {
state.hasContainer = true;
}
else if (arg.equals("#count") || arg.equals("#sum")) {
state.hasCount = true;
}
else if (arg.equals("#summary")) {
state.hasSummary = true;
}
else if (RollbackStateParser.isModifier(arg)) {
state.hasRollbackState = true;
}
else if (arg.equals("#preview")) {
state.hasPreview = true;
}
else if (arg.startsWith("f:") || arg.startsWith("filter:")) {
state.hasFilter = true;
}
else if ((!arg.contains(":") && !args[i - 1].contains(":") && args.length > (i + 1)) || arg.contains("u:") || arg.contains("user:") || arg.contains("users:") || arg.contains("p:")) {
state.hasUser = true;
}
else if (arg.contains("page:")) {
state.hasPage = true;
}
else if (arg.contains("a:") || arg.contains("action:")) {
state.hasAction = true;
}
else if (arg.contains("i:") || arg.contains("include:") || arg.contains("item:") || arg.contains("items:") || arg.contains("b:") || arg.contains("block:") || arg.contains("blocks:")) {
state.hasInclude = true;
}
else if (arg.contains("t:") || arg.contains("time:")) {
state.hasTime = true;
}
else if (arg.contains("e:") || arg.contains("exclude:")) {
state.hasExclude = true;
}
else if (arg.contains("r:") || arg.contains("radius:")) {
state.hasRadius = true;
}
}
if (!state.hasContainer) {
Integer lookupType = ConfigHandler.lookupType.get(senderName);
if (lookupType != null) {
if (lookupType == 1) {
state.validContainer = true;
}
else if (lookupType == 5) {
List<String> lookupUsers = ConfigHandler.lookupUlist.get(senderName);
if (lookupUsers != null && lookupUsers.contains("#container")) {
state.validContainer = true;
}
}
}
}
return state;
}
private List<String> handleActionParamCompletions(String currentArg, String lastArg) {
String arg = "";
String filter = lastArg;
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
List<String> completions = new ArrayList<>(Arrays.asList(ACTIONS));
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
private List<String> handleUserParamCompletions(String currentArg, String lastArg) {
if (lastArg.equals("u:") || lastArg.equals("user:") || lastArg.equals("users:") || lastArg.equals("p:")) {
return null;
}
String arg = "";
String[] split = currentArg.split(":", 2);
String filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
List<String> completions = Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList());
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
private List<String> handleTimeParamCompletions(String currentArg, String lastArg) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
List<String> completions = new ArrayList<>();
if (arg.chars().allMatch(Character::isDigit)) {
boolean addNumbers = true;
if (currentArg.length() > 0) {
char lastChar = currentArg.charAt(currentArg.length() - 1);
if (Character.isDigit(lastChar)) {
completions.addAll(Arrays.asList(TIMES));
addNumbers = false;
}
}
if (addNumbers) {
completions.addAll(Arrays.asList(NUMBERS));
}
}
completions = new ArrayList<>(completions);
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + arg + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
private List<String> handlePageParamCompletions(String currentArg, String lastArg) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
if (arg.chars().allMatch(Character::isDigit)) {
List<String> completions = new ArrayList<>(Arrays.asList(NUMBERS));
if (arg.length() < 1) {
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + arg + completions.get(index));
}
if (arg.length() == 0) {
completions.remove(0);
}
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
return null;
}
private List<String> handleRadiusParamCompletions(String currentArg, String lastArg, String baseCommand) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
if (!baseCommand.equals("purge") && arg.chars().allMatch(Character::isDigit)) {
List<String> completions = new ArrayList<>(Arrays.asList(NUMBERS));
if (arg.length() < 2) {
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + arg + completions.get(index));
}
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
else if (baseCommand.equals("purge") || arg.startsWith("#")) {
ArrayList<String> params = new ArrayList<>();
params.add("#global");
if (!baseCommand.equals("purge") && Bukkit.getServer().getPluginManager().getPlugin("WorldEdit") != null) {
params.add("#worldedit");
}
List<World> worlds = Bukkit.getServer().getWorlds();
for (World world : worlds) {
params.add("#" + world.getName());
}
List<String> completions = new ArrayList<>(params);
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
return null;
}
private List<String> handleMaterialParamCompletions(String currentArg, String lastArg) {
String filter = lastArg;
String arg = "";
if (currentArg.contains(":")) {
String[] split = currentArg.split(":", 2);
filter = split[0] + ":";
if (split.length > 1) {
arg = split[1];
}
}
else {
filter = "";
arg = currentArg;
}
initializeMaterialsIfNeeded();
List<String> completions = new ArrayList<>(materials);
for (int index = 0; index < completions.size(); index++) {
completions.set(index, filter + completions.get(index));
}
return StringUtil.copyPartialMatches(filter + arg, completions, new ArrayList<>(completions.size()));
}
private void initializeMaterialsIfNeeded() {
if (materials == null) {
List<Material> addList = Arrays.asList(Material.ARMOR_STAND);
List<Material> excludeList = Arrays.asList();
Set<String> materialList = new HashSet<>();
Material[] materialValues = Material.values();
for (Material material : materialValues) {
if (material.isBlock() || material.isItem()) {
materialList.add(material.name().toLowerCase(Locale.ROOT));
}
}
for (Material exclude : excludeList) {
materialList.remove(exclude.name().toLowerCase(Locale.ROOT));
}
for (Material add : addList) {
materialList.add(add.name().toLowerCase(Locale.ROOT));
}
// add custom tags
for (String tag : CommandParser.getTags().keySet()) {
materialList.add(tag);
}
materials = new ArrayList<>(materialList);
}
}
private List<String> handleSecondArgCompletions(CommandSender sender, String cmd0, String cmd1, ParamState paramState) {
String argument0 = cmd0.toLowerCase(Locale.ROOT);
String argument1 = cmd1.toLowerCase(Locale.ROOT);
if (argument0.equals("help") && sender.hasPermission("coreprotect.help")) {
List<String> completions = new ArrayList<>(Arrays.asList(HELP));
return StringUtil.copyPartialMatches(argument1, completions, new ArrayList<>(completions.size()));
}
else if (argument0.equals("purge") && sender.hasPermission("coreprotect.purge")) {
List<String> completions = new ArrayList<>(Arrays.asList("t:", "r:", "i:", "e:", "a:kill"));
return StringUtil.copyPartialMatches(argument1, completions, new ArrayList<>(completions.size()));
}
else if (hasLookupCommand(argument0, sender)) {
List<String> completions = new ArrayList<>(filterParams(true, argument0, argument1, paramState));
completions.addAll(Bukkit.getOnlinePlayers().stream().filter(player -> player.getName().toLowerCase(Locale.ROOT).startsWith(argument1)).map(Player::getName).collect(Collectors.toList()));
return StringUtil.copyPartialMatches(argument1, completions, new ArrayList<>(completions.size()));
}
return null;
}
private List<String> handlePurgeThirdArgCompletions(String arg1, String arg2) {
String argument1 = arg1.toLowerCase(Locale.ROOT);
String argument2 = arg2.toLowerCase(Locale.ROOT);
if (argument1.startsWith("t:")) {
List<String> completions = new ArrayList<>(Arrays.asList("r:", "i:", "e:", "a:kill"));
return StringUtil.copyPartialMatches(argument2, completions, new ArrayList<>(completions.size()));
}
else if (argument1.startsWith("r:") || argument1.startsWith("i:") || argument1.startsWith("e:") || argument1.startsWith("a:")) {
List<String> completions = new ArrayList<>(Arrays.asList("t:"));
return StringUtil.copyPartialMatches(argument2, completions, new ArrayList<>(completions.size()));
}
return Arrays.asList("");
}
private List<String> handleGenericLookupCompletions(String cmd, String currentArg, ParamState paramState) {
ArrayList<String> params = filterParams(false, cmd, currentArg, paramState);
List<String> completions = new ArrayList<>(params);
return StringUtil.copyPartialMatches(currentArg, completions, new ArrayList<>(completions.size()));
}
private ArrayList<String> filterParams(boolean firstParam, String lastArgument, String argument, ParamState state) {
ArrayList<String> params = new ArrayList<>();
for (String param : PARAMS) {
if (param.equals("user:") && !hasUser) {
if (param.equals("user:") && !state.hasUser) {
params.add(param);
}
else if (param.equals("action:") && !hasAction) {
else if (param.equals("action:") && !state.hasAction) {
params.add(param);
}
else if (param.equals("include:") && !hasInclude) {
else if (param.equals("include:") && !state.hasInclude) {
params.add(param);
}
else if (param.equals("exclude:") && !hasExclude) {
else if (param.equals("exclude:") && !state.hasExclude) {
params.add(param);
}
else if (param.equals("radius:") && !hasRadius) {
else if (param.equals("filter:") && !state.hasFilter && (lastArgument.equals("l") || lastArgument.equals("lookup"))) {
params.add(param);
}
else if (param.equals("time:") && !hasTime) {
else if (param.equals("radius:") && !state.hasRadius) {
params.add(param);
}
else if (param.equals("#container") && !hasContainer && !hasRadius && validContainer) {
else if (param.equals("time:") && !state.hasTime) {
params.add(param);
}
else if (param.equals("#container") && !state.hasContainer && !state.hasRadius && state.validContainer) {
params.add(param);
}
}
if (firstParam && pageLookup && (lastArgument.equals("l") || lastArgument.equals("lookup"))) {
if (firstParam && state.pageLookup && (lastArgument.equals("l") || lastArgument.equals("lookup"))) {
params.add("page:");
}
else if (!firstParam && argument.startsWith("#")) {
if (!hasCount) {
if (argument.startsWith("#")) {
if (!state.hasCount && !state.hasSummary && (lastArgument.equals("l") || lastArgument.equals("lookup"))) {
params.add("#count");
params.add("#summary");
}
if (!hasPreview) {
if (!state.hasRollbackState && (lastArgument.equals("l") || lastArgument.equals("lookup"))) {
params.add("#rolledback");
params.add("#restored");
}
if (!state.hasPreview) {
params.add("#preview");
}
}
Loaded 100 of 441 files, more files were not shown because too many files have changed in this diff. Show more