Compare commits

..
Author SHA1 Message Date
YoyoNow f63390915b Add glyph flight for enchant particles and blend team links
Pull Request Build / Build (pull_request) Successful in 1m57s
Entries with the enchant particle get a Glyph flight setting: float down as
usual, fly into the player or fly out of them, the latter two unlocked by the
WarGear Season placement and participation. Team link lines now start in the
drawing player's particle and blend into the partner's along the way.
2026-10-26 15:13:39 +01:00
YoyoNow db939e27d9 Turn the touches back into reward designs styled like any other
The Touch setting is gone. Its unique mechanics become reward designs that use
the entry's particle: Burst (on jumping, landing or sneaking, as a puff or a
ring), Footprints, Team link, Shimmer and Heartbeat. Each is unlocked by any
of the rewards that had it, and Burst's triggers are locked separately. The
wing beat becomes the Beat on jump wing motion, unlocked by WarGear Season
2022. December snow and the glyphs are dropped: the first is a plain particle
with a condition, the second only works with enchanting glyphs.
2026-10-26 15:08:08 +01:00
YoyoNow 36e6329f2b Redraw beating wings every tick only after a jump
The wing beat touch no longer lowers the entry's interval. Its wings are drawn
every tick during the beat and at their usual interval otherwise; the extra
redraws do not count towards the slot budget.
2026-10-26 15:02:56 +01:00
YoyoNow b2300c128e Let the wing beat touch beat the entry's own wings
On a Wings entry the touch adds a beat after each jump to the wings' motion
and draws nothing of its own; picking it redraws the wings every 2 ticks like
a motion does. Other designs keep the dragon breath wings.
2026-10-26 14:59:32 +01:00
YoyoNow 9cb4e0b7db Unlock everything for administrators
Every particle requirement passes for users with the administration
permission. Lore keeps naming the regular requirement.
2026-10-26 14:49:53 +01:00
YoyoNow 8e4730bd12 Turn reward touches into an unlockable Touch setting
Every entry gets a Touch setting next to its design, offering the 16 touches
the rewards carried, each unlocked by its reward. The reward designs that the
builder can now make are removed; only the underwater rain cloud and the
medium, hard and advanced egg hunt designs stay. Reward requirements move to
Unlocks, shared by touches, wing images and cosy smoke. The trail duration
moves to the particle row to make room in the design row.
2026-10-26 14:48:29 +01:00
YoyoNow baa278a1f5 Cap particle speed at 0.05 and give each wing shape its own icon
Speed steps by 0.005 up to 0.05; presets keep their faster particles. Number
values now show up to three decimals.
2026-10-26 14:43:44 +01:00
YoyoNow 8fd6fdf81e Unlock tournament wing images and cosy smoke for building
The MiniWarGear league, Underwater MWG, WarGear season 2022 and WarGear clash
wing images become Wings shapes, locked behind the placement that earns their
reward. Cosy campfire smoke becomes a buildable particle for Hells Bells
participants. Choice options can now carry a requirement: locked options show
it in the picker, are skipped when cycling and hide entries that use them.
2026-10-26 14:37:40 +01:00
YoyoNow 7d0f5aceae Give the rebuildable reward designs unique touches
Rewards whose look the basic designs can build now carry a touch the builder
cannot make: bursts on jumping, landing or sneaking, footprints, a line to
nearby players with the same reward, a December extra, aimed enchanting
glyphs, a heartbeat, a jump-beat wing or an orbiting shape. BodyMotion tracks
the jump, land and sneak events the touches react to.
2026-10-26 14:34:50 +01:00
YoyoNow ad9087c038 Remove the breathe wing motion
Wings keep the flap motions only; saved breathing wings load as none.
2026-10-26 13:57:59 +01:00
YoyoNow 872da5f62a Open wings with speed and let them flap or breathe
All wings stand 5 degrees open when idle and open up to 15 degrees the
faster the player moves. The wings design gets a motion setting to flap
slowly, flap quickly or breathe; picking a motion redraws the wing every
2 ticks so it looks smooth, and going back to none restores every 20.
Body rotation tracking now also tracks speed, as BodyMotion.
2026-10-26 13:55:58 +01:00
YoyoNow e830d087c9 Merge wing designs, turn wings with the body, limit leaves
Wing and Drawn wings become one Wings design whose shapes include the
images and the drawn bat, butterfly and fairy; for drawn shapes the points
per wing replace the amount. Body rotation is tracked per player the way
the client turns the body, and wings, the heart beat and body placement
follow it instead of the head. Leaves particles are for the server team
only, and typed hex colours show the closest dye.
2026-10-26 13:39:28 +01:00
YoyoNow b4661ea345 Add colour along the wing and sparkle to lobby wings
Both wing designs can colour their points from the spine to the tips
instead of changing colour over time, and a sparkle setting shows only a
share of the points each tick, which also lowers their cost. The angel
and phoenix drawn shapes are removed.
2026-10-26 13:34:23 +01:00
YoyoNow 4e5945d2bc Lower drawn wings to the shoulder blades
The drawn shapes had their shoulder joint at head height. They now sit
about 1.3 blocks above the feet at the default size.
2026-10-26 10:50:57 +01:00
YoyoNow a5b8f00c58 Add drawn wing shapes to the slot builder
A new Drawn wings design draws angel, bat, butterfly, fairy and phoenix
wings from lines and curves instead of images. The points per wing can be
set from 6 to 60, so sparse wings stay cheap enough to show often.
2026-10-26 10:48:32 +01:00
YoyoNow 8e23490b04 Settle open particle slot questions
Slots hold up to 9 entries, so the slot editor is one entry row plus the
bottom bar, and the server team may spawn 90 particles per tick. The
water and lava rings for taking part in any event return as reward
designs, and locked slots can be deleted from their read-only view.
2026-10-26 10:33:15 +01:00
YoyoNow 4c6b5e654a Quiet no-op particle setting clicks and show entry particle counts
Clicking a step past the minimum or maximum, or setting an unchanged
value, now does nothing instead of showing the budget message, and
changes that lower a slot's particles are allowed even over budget.
Entries show their particles per tick in the slot editor list, and the
entry editor's paper shows the edited entry's share.
2026-10-26 10:23:24 +01:00
YoyoNow 38a33b7298 Show the resulting value on number step buttons
Each step button on the number screen shows the value it leads to, and
marks when that value is the minimum or maximum.
2026-10-26 10:20:50 +01:00
YoyoNow 32797bd2a8 Limit lobby particle speed to 0.2
Speed went up to 1, which makes most particles shoot several blocks away.
It now goes up to 0.2; presets use at most 0.1.
2026-10-26 10:12:14 +01:00
YoyoNow 28b62bf27b Limit lobby particle spread to half a block
Spread went up to 2 blocks, which scatters a design's particles far
around the player. It now goes up to 0.5 in steps of 0.01.
2026-10-26 10:12:10 +01:00
YoyoNow db915f7be7 Keep large and lingering particles out of the slot builder
Explosion, sonic boom, sweep attack, gusts, campfire and large smoke,
dust pillar, block marker and shriek are a block or more in size or rise
for seconds and wall players in once a design spawns many of them. They
stay available to presets only.
2026-10-26 10:10:21 +01:00
YoyoNow aef26b5e9f Add the trail particle with duration and target
Trail particles fly from where they spawn to a target offset over the
chosen number of ticks, in the entry's colour. The target follows the
body like other offsets when the entry is placed relative to it.
2026-10-26 10:10:17 +01:00
YoyoNow 8009098965 Space out entry editor rows and show empty slots as firework stars
Settings in the entry editor start one column after the row headers, and
empty particle slots show a firework star instead of a gray pane.
2026-10-26 09:49:20 +01:00
YoyoNow 47f0d9858b Place lobby particle entries relative to the body
Entries get a World/Body setting. In body mode offsets, heights and wings
follow the body's facing and lie along the body while gliding or swimming,
so wings stay on the back. Rings circle the body but keep their world
angle, so they do not turn with the player. New wings default to body;
presets and rewards stay in world mode and look exactly as before.
2026-10-26 09:49:11 +01:00
YoyoNow c3eda01e20 Cycle particle settings with up to five options
Choices with up to five options cycle forward on left click and backward
on right click, listing all options in the lore. Two-option choices still
read as a toggle; longer choices keep their list.
2026-10-26 09:49:07 +01:00
YoyoNow 5e81081620 Toggle two-option particle settings on click
Choices with exactly two options switch on click like on/off settings,
and their lore lists both options with the current one highlighted.
2026-10-26 09:49:02 +01:00
YoyoNow ed70d65bc4 Size particle choice menus by their options
Lists that fit on one page get one row per nine options plus the bottom
bar, so a condition with three options opens a two row menu. Longer lists
keep the paged 6 row menu.
2026-10-26 09:48:57 +01:00
YoyoNow 2ce8799a19 Use black panes for the particle menu bottom bar
Unused bottom bar columns are black glass panes, empty places above it
are gray, including the free entry places of the slot editor.
2026-10-26 09:48:51 +01:00
YoyoNow 15036a7ba0 Allow hex colour codes for lobby particles
The colour screen gets a button to type a colour as #RRGGBB or #RGB,
next to picking one of the dye colours.
2026-10-26 09:48:46 +01:00
YoyoNow d77c014409 Translate lobby particle types and blocks
Add English and German names for all 104 selectable particle types and
the 43 blocks of the block setting.
2026-10-26 09:48:41 +01:00
YoyoNow 6b5441bb04 Offer nearly every vanilla particle in the slot builder
Add the remaining particles, including colour, colour transition, block
and item particles, which take their colours or a new block setting from
the entry. Only particles that cover the screen, show only under water or
need a target stay out.
2026-10-26 09:48:36 +01:00
YoyoNow 54d30f4a42 Fill unused particle menu columns with gray panes
Unused bottom bar columns show a gray glass pane instead of a barrier,
and the slot editor drops its use button since the particle menu
toggles slots with a click.
2026-10-26 09:48:32 +01:00
YoyoNow e41f76937e Give particle menus a shared bottom bar
Back, use, add, rename, icon, particle count and delete or reset now sit
in the same column of the last row in every particle menu, and columns a
menu does not use show a barrier.
2026-10-26 09:48:28 +01:00
YoyoNow 0c7ee052e8 Toggle particle slots with a click and label entry editor rows
A slot with entries is switched on or off with a click in the particle
menu and edited with a shift click; empty slots still open the editor.
The entry editor rows get distinct glowing header icons.
2026-10-26 09:48:25 +01:00
YoyoNow b60cdfc88d Simplify lobby particle menus
Every menu item now acts on a plain click: slots open their editor with a
use button, entries open their editor, and duplicating is gone. The entry
editor groups settings into labelled rows, numbers and colours get their
own screens instead of relying on right click or Q, and slot names show
the particles per tick used out of the maximum.
2026-10-26 09:48:20 +01:00
YoyoNow 98f04451a4 Polish lobby particle slots
Count the real particle amount of reward designs, set slot budgets to
30 particles per tick (60 for the server team), let requirements test
only against an UnlockContext, and drop requirements and messages the
old particle menu used.
2026-10-26 09:48:05 +01:00
YoyoNow c95901bc42 Add lobby particle slot menus
Replace the particle inventory with a main menu showing the 7 slots and
unlocked presets, a slot editor with a read-only view for locked slots,
and an entry editor with pickers for designs, choices, colours and icons.
Slots are limited to 10 entries and a particles-per-tick budget.
2026-10-26 09:48:00 +01:00
YoyoNow 211e2693cf Move lobby presets and rewards onto the particle slot model
Team- and user-specific particles become presets built from entries,
tournament and egg hunt particles become fixed reward designs, and the
old particle enums, ParticleEnum and ParticleData are removed. The
particle menu temporarily lists only unlocked presets.
2026-10-26 09:47:55 +01:00
YoyoNow 1980375e73 Add configurable particle slot model
Introduce parameters, particle types, designs and entries that build the
existing particle elements, store each of the 7 particle slots in its own
UserConfig key, and let LobbyPlayer select a slot next to the old enum
particles. Slots 4-5 need a team, 6-7 the server team.
2026-10-26 09:47:49 +01:00
YoyoNow ef041272bb Refactor lobby particle runtime for multi-entry particles
Tick particles from per-player cached always/move lists, drop the
per-instance listeners in Delayed and RandomParticle, and compute
particle viewers once per tick instead of once per spawned point.
2026-10-26 09:47:43 +01:00
YoyoNow bfbf98c1d6 Fix GUI.schemDialog for overriden Public list
Deploy / Build (push) Successful in 2m57s
Deploy / Deploy (push) Successful in 8s
2026-10-23 11:05:28 +02:00
YoyoNow c4b46e4431 Add override for Publics in GameModeConfig.Schematic
Deploy / Build (push) Successful in 2m22s
Deploy / Deploy (push) Successful in 8s
2026-10-21 08:22:05 +02:00
YoyoNow c238b9bdf2 Improve CheckCommand
Deploy / Build (push) Successful in 2m26s
Deploy / Deploy (push) Successful in 9s
2026-10-21 08:02:05 +02:00
YoyoNow fec4007120 Merge pull request 'Fixed Replay not starting for Normal Users' (#510) from 12312/SteamWar:main into main
Deploy / Build (push) Successful in 2m34s
Deploy / Deploy (push) Successful in 9s
Reviewed-on: #510
2026-10-20 19:37:39 +02:00
12312 11798c3ec0 Fixed Replay not starting for Normal Users
Pull Request Build / Build (pull_request) Successful in 1m30s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 6s
Fixed Replay not starting for Normal Users.
2026-10-19 22:19:33 +02:00
YoyoNow 9935ab785d Fix NoClipCommand
Deploy / Build (push) Successful in 2m29s
Deploy / Deploy (push) Successful in 8s
2026-10-19 11:21:15 +02:00
YoyoNow 8e3e71dd96 CLine: render as a single display shown by view distance
Deploy / Build (push) Successful in 2m24s
Deploy / Deploy (push) Successful in 9s
The line no longer needs start, middle and end parts that were moved
around the player. It is one display that is visible for a player as soon
as any chunk the line passes through is in their view distance.
REntity#canView now receives the chunk positions of player and entity.
2026-10-19 10:41:34 +02:00
YoyoNow 3fe2e006b9 REntityServer: use per-entity view range instead of chunk view distance
Entities now decide per player whether they are visible. REntity defaults
to the player view radius, RDisplay derives it from its viewRange (1 = 64
blocks) capped at the client view distance, so values like Float.MAX_VALUE
stay cheap. Changing the viewRange re-evaluates the viewers. Fixes #488
2026-10-19 10:40:03 +02:00
YoyoNow 9d5fcc07e8 Only notify the owner's dev server about Bauwelt member changes
Deploy / Build (push) Successful in 2m25s
Deploy / Deploy (push) Successful in 9s
Dev servers are registered per owner (lowercase username), so there is
no need to wake every dev server.
2026-10-19 10:25:42 +02:00
YoyoNow 0c3e63d521 Restore BauweltMember cache and notify all dev servers on member changes
getBauMember never read its cache after the Exposed migration, so every
scoreboard render hit the DB (Too many connections, #479). Cache by
(bauwelt, member) incl. negative results, evict on addMember/remove, and
send BaumemberUpdatePacket to the Bauserver and all dev servers (also on
addmember). BauMemberUpdate clears the cache itself to not depend on
handler order.
2026-10-19 10:24:41 +02:00
YoyoNow 27d260a8d2 Delete CRIU checkpoint on /bau delete
Deploy / Build (push) Successful in 2m24s
Deploy / Deploy (push) Successful in 8s
/bau delete only removed the world folder. The checkpoint of the build
server stayed in place, so the next start restored the old JVM state
against a freshly copied world (superflat world, plugins inactive).
Extract ServerStarter#checkpointDir and delete the checkpoint together
with the world.
Fixes #438
2026-10-19 10:14:10 +02:00
YoyoNow 37f017b84c Improve action bar loading bar for server start
Deploy / Build (push) Successful in 2m24s
Deploy / Deploy (push) Successful in 8s
2026-10-19 09:59:02 +02:00
YoyoNow 21d0a4359d Update .gitignore
Deploy / Build (push) Successful in 2m49s
Deploy / Deploy (push) Successful in 10s
2026-10-19 09:31:38 +02:00
YoyoNow 8318d408b6 Fix REntity relative move rotation in 1.21
Deploy / Build (push) Successful in 2m26s
Deploy / Deploy (push) Successful in 9s
Pass yaw before pitch to ClientboundMoveEntityPacket.Rot/PosRot and stop
converting the already byte-encoded rotation a second time.
2026-10-19 09:27:27 +02:00
YoyoNow d14ddc4fab Fix lag from PistonCalculator at high TPS
Deploy / Build (push) Successful in 2m27s
Deploy / Deploy (push) Successful in 9s
Bundle piston order recalculations into one run every 200ms (wall clock,
independent of TPS) instead of one full recalculation and entity rebuild
per piston/place/break event. Tracked pistons that get moved are removed
from the visualization instead of being moved along.

Fixes #227
2026-10-19 09:19:41 +02:00
YoyoNow 5cc5529533 Reenable ReplayCommand
Deploy / Build (push) Successful in 2m18s
Deploy / Deploy (push) Successful in 9s
2026-10-19 09:07:08 +02:00
YoyoNow 380693a2b7 Fix REntity position desync and velocity in 1.21 replays
Deploy / Build (push) Successful in 2m22s
Deploy / Deploy (push) Successful in 10s
Send ClientboundEntityPositionSyncPacket instead of the teleport packet so
relative moves keep working afterwards, and pass rotation in degrees.
Pass the velocity unscaled to Vec3 instead of pre-multiplying by 8000.
Fixes #247
2026-10-19 09:03:52 +02:00
YoyoNow 69aef9dfb6 Fix ClassCastException for replay player equipment
Deploy / Build (push) Successful in 2m21s
Deploy / Deploy (push) Successful in 8s
PacketProcessor passes Bukkit EquipmentSlots to REntity#setItem, which cast
them to the NMS EquipmentSlot. Take the Bukkit slot and map it to the NMS
slot when building the equipment packet.
2026-10-19 08:38:42 +02:00
YoyoNow 097df865e5 Resend replay player tab entry when skin arrives
Deploy / Build (push) Successful in 2m29s
Deploy / Deploy (push) Successful in 8s
RPlayer requests the skin asynchronously, so the first tab list packet is
usually sent without textures and viewers already in range kept the default
skin. Re-send despawn/delist/list/spawn to the current viewers once the
skin response is received.
2026-10-19 08:21:52 +02:00
YoyoNow 230f8c521a Use PistonStructureResolver in PistonCalculator
Deploy / Build (push) Successful in 2m35s
Deploy / Deploy (push) Successful in 9s
Numbering now comes directly from the vanilla resolver instead of a
custom breadth-first search, so the order matches the real move order
(including backtracking through slime/honey chains). The display of
more than 12 blocks is removed; a blocked piston is shown in red.

Refs: #158
2026-10-18 20:09:10 +02:00
YoyoNow 253a975c73 Show no player hover for players of the public team
Deploy / Build (push) Successful in 2m47s
Deploy / Deploy (push) Failing after 5s
2026-10-18 20:01:24 +02:00
YoyoNow 12dd46176b Suggest /team info SW when clicking the SW prefix 2026-10-18 20:01:19 +02:00
YoyoNow 2111d72387 Suggest /team info when clicking a team prefix
Clicking the team prefix in the global/local chat or an event team
prefix in the fight chat puts /team info <shorthand> into the chat
input; the hover explains the click.
2026-10-18 20:01:15 +02:00
YoyoNow 3aa471c3ab Read global chat on fight servers after /chat global join
Deploy / Build (push) Successful in 2m25s
Deploy / Deploy (push) Successful in 12s
2026-10-16 11:29:02 +02:00
YoyoNow 47557d7bb8 Keep global chat readable after writing into it on fight servers
Deploy / Build (push) Successful in 2m30s
Deploy / Deploy (push) Successful in 9s
Writing /global <text> on a fight server shows the global chat until
the player leaves the server or runs /chat global leave, without
changing the default channel.
2026-10-16 11:17:30 +02:00
YoyoNow 62850b2e11 Let moderators read all fight chats automatically
Deploy / Build (push) Successful in 2m32s
Deploy / Deploy (push) Successful in 8s
/chat arenas join|leave lets moderators read the fight and team chats
of every arena and event spectate server; the choice is saved across
relogs. /chat <arena> leave excludes a single arena for the session.
2026-10-16 11:09:52 +02:00
YoyoNow 7f47d687d3 Hide /chat msg from players without moderation permission 2026-10-16 11:09:45 +02:00
YoyoNow 362e1cebff Name the fight channel Fight
The server name is already shown in the second line of the hover.
2026-10-16 11:09:41 +02:00
YoyoNow 39cacbd701 Show team on hover for @name mentions 2026-10-16 11:09:36 +02:00
YoyoNow 503261ce6d Name team chat channels TeamChat and ServerTeamChat 2026-10-16 11:09:30 +02:00
YoyoNow 8a86ebde83 Make DM names hoverable and channel icons clickable
- Both names in direct messages show their team on hover
- Clicking a channel icon suggests the command to write a single
  message into that channel
2026-10-16 11:09:24 +02:00
YoyoNow be915770b1 Show server name in local channel hover
Deploy / Build (push) Successful in 2m27s
Deploy / Deploy (push) Successful in 9s
2026-10-16 09:46:22 +02:00
YoyoNow 70c4225827 Merge pull request 'Chat channels' (#506) from feature/chat-channels into main
Deploy / Build (push) Successful in 3m4s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #506
2026-10-16 09:19:54 +02:00
YoyoNow 904a850a3f Remove duplicated chat channel logic
Pull Request Build / Build (pull_request) Successful in 1m29s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
- Shared send-or-switch helper for /global, /local, /tc and /stc
- Shared helpers for "is on server" and the player's current local
  channel
- Read fight infos from TablistManager instead of keeping a second map
- /chat resolves fight servers (arenas and spectate servers) by name in
  one place
- Reuse the global and participant formats instead of identical keys
2026-10-16 09:17:01 +02:00
YoyoNow 4c75879915 Keep spectators in fight chat and treat spectate servers as fight servers
Pull Request Build / Build (pull_request) Successful in 1m30s
- '+' from a spectator on a fight server stays in the fight chat
- Event spectate servers (static servers with a spectate port) use a
  local fight chat by default and hide the global chat like arenas
2026-10-16 09:07:58 +02:00
YoyoNow 65cc6119b0 Show default channel notice with its icon
The notice on join, on every default change and for /chat is now
"<icon> is your default channel" in green without the SteamWar prefix;
hovering the icon shows the channel name.
2026-10-16 09:00:41 +02:00
YoyoNow 975d42c17f Add team hovers to fight chat and make DMs non-default
- Names in the fight chat show the sender's team on hover like
  everywhere else; the Blue/Red team prefix is only hoverable in event
  fights, where it is the actual team
- Server team members using the SW prefix show the SteamWar team
- Direct messages can no longer be the default channel; /msg and /r
  require a message again
2026-10-16 08:59:26 +02:00
YoyoNow 317cb0adf8 Add channel icons and hovers to chat messages
Every chat message now starts with a fixed icon of its channel
(🌍 Global, ⌂ Local, ⚔ fight, ⚑ fight team, 👥 TC, ⚙ STC, ✉ DM),
highlighted yellow if it is the receiver's default channel. Hovering
the icon shows the channel name, hovering the sender shows their team
name and shorthand.
2026-10-16 08:48:30 +02:00
YoyoNow 4266a7f35b Merge pull request 'fix: Remove Help Text for /team server' (#499) from 12312/SteamWar:main into main
Deploy / Build (push) Successful in 2m49s
Deploy / Deploy (push) Successful in 10s
Reviewed-on: #499
2026-10-15 20:25:47 +02:00
YoyoNow 26d5ff7aad Merge pull request 'Fix SmartPlace reversing debug stick' (#505) from fix/smartplace-debugstick into main
Deploy / Build (push) Successful in 2m53s
Deploy / Deploy (push) Successful in 10s
Reviewed-on: #505
2026-10-15 20:09:49 +02:00
YoyoNow b21ff415a8 Show channel tags as plain gray prefix
Pull Request Build / Build (pull_request) Successful in 1m32s
Messages from a channel that is not the receiver's default are now
prefixed with the plain channel name in dark gray instead of brackets.
Direct messages get no tag, as their format already shows sender and
receiver.
2026-10-15 20:06:58 +02:00
YoyoNow 9fe05b59e4 Rework chat into Velocity-managed channels
Chat is now handled entirely by VelocityCore through channels with
separate readers and writers (Global, Local/Fight, Blue/Red fight team,
Team, ServerTeam, Alert and 1:1 direct messages). Each player has a
saved default channel; messages from other channels are tagged with
the channel name, and every default change is announced.

- /global, /local, /tc, /stc, /msg and /r switch the default channel
  when called without a message
- /chat <channel> switches, /chat <channel> join|leave toggles reading
  (saved); moderators can join arenas by name and write with
  /chat <channel> msg <text>
- '+' sends from global to local and from local/fight to the fight team
- Referees write yellow in the fight chat
- Subservers no longer receive chat; arenas get a record-only
  ChatRecordPacket so replays keep their chat
- Remove the chat listeners of SpigotCore, FightSystem, MissileWars,
  TowerRun and TNTLeague

Closes #460, #129
2026-10-15 19:55:25 +02:00
YoyoNow 2302f9aafb Fix SmartPlace reversing debug stick
Pull Request Build / Build (pull_request) Successful in 1m32s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-10-15 18:14:19 +02:00
YoyoNow d727982ce0 Fix AutoChecker inventory rules
Deploy / Build (push) Successful in 9m6s
Deploy / Deploy (push) Successful in 20s
Count every inventory block towards exactly one Schematic.Limited rule:
filled blocks towards a rule whose Content allows all their items, empty
blocks towards a rule without Content (unlimited if there is none). Each
rule's Amount now caps how many blocks it covers, instead of taking the
highest Amount of all rules for that block type.

Inventory blocks whose items fit no rule are now checked and reported,
items from different rules in one block are reported as mixed, and the
auto-fix trims or removes them accordingly.

Remove the hardcoded wargearseason26 wind charge handling and its 2048
limit, wind charges are configured via Limited rules now.

Log a warning for Limited rules that match no blocks, e.g. a misspelled
'Materials' key.

Fixes #458, #481, #502
2026-10-15 17:53:06 +02:00
YoyoNow 2b42f0d13c Remove YAPION from BauSystem_Main module
Deploy / Build (push) Successful in 40m13s
Deploy / Deploy (push) Successful in 5m54s
2026-09-29 14:26:57 +02:00
12312 3e3a209c80 Removed Extra Line
Pull Request Build / Build (pull_request) Successful in 1m36s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 5s
2026-09-24 21:49:20 +02:00
12312 1d43021853 Remove TEAM_HELP_SERVER Deutsch 2026-09-24 21:47:22 +02:00
12312 5a224c1bcc Remove TEAM_HELP_SERVER English 2026-09-24 21:46:18 +02:00
12312 6330dbc2b1 VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java aktualisiert 2026-09-24 21:43:20 +02:00
YoyoNow 4306fe7bc9 Merge pull request 'Fix SteamwarUser.setLocale' (#489) from Hut512/SteamWar:fix/setlocale-typo into main
Deploy / Build (push) Successful in 18m57s
Deploy / Deploy (push) Successful in 26s
Reviewed-on: #489
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-09-14 12:18:40 +02:00
YoyoNow 58df6087f0 Merge pull request 'Fix NoClip' (#491) from Hut512/SteamWar:fix/swplayer-invalid-reference into main
Deploy / Build (push) Successful in 19m39s
Deploy / Deploy (push) Successful in 32s
Reviewed-on: #491
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-09-14 12:18:16 +02:00
YoyoNow b9b7869124 Merge pull request 'Fix region water level' (#494) from Hut512/SteamWar:fix/testblock-water-level into main
Deploy / Build (push) Successful in 19m18s
Deploy / Deploy (push) Successful in 24s
Reviewed-on: #494
2026-09-14 12:17:30 +02:00
YoyoNow 1e85b14667 Improve SteamEditWand
Deploy / Build (push) Successful in 12m55s
Deploy / Deploy (push) Successful in 23s
2026-09-14 10:02:39 +02:00
Hut512 35c27923aa Fix region water level
Pull Request Build / Build (pull_request) Failing after 4m56s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 10s
2026-09-12 20:07:07 +02:00
Hut512 c3b3e3e60f Fix NoClip instant block break
Pull Request Build / Build (pull_request) Successful in 1m25s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-09-07 22:21:53 +02:00
Hut512 3aa1bf7ffd Fix scoreboard cleanup 2026-09-07 21:15:01 +02:00
Hut512 5a02a58437 Fix SteamwarUser.setLocale
Pull Request Build / Build (pull_request) Successful in 1m25s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-09-07 15:24:32 +02:00
YoyoNow 6209b86149 Fix TeamCommand.info
Deploy / Build (push) Successful in 2m25s
Deploy / Deploy (push) Successful in 11s
2026-09-07 14:03:06 +02:00
300 changed files with 10334 additions and 9780 deletions
+6
View File
@@ -7,8 +7,14 @@
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
steamwar.properties steamwar.properties
# Kotlin
.kotlin
# IntelliJ IDEA # IntelliJ IDEA
*.iml *.iml
.idea
!.idea/copyright
!.idea/runConfigurations
# VSCode # VSCode
bin/ bin/
+1 -1
View File
@@ -47,7 +47,7 @@ dependencies {
compileOnly(libs.netty) compileOnly(libs.netty)
implementation(libs.luaj) implementation(libs.luaj)
implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) // implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
} }
widener { widener {
@@ -20,8 +20,7 @@
package de.steamwar.bausystem; package de.steamwar.bausystem;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.configplayer.ConfigConverter;
import de.steamwar.bausystem.features.gui.BauGUI; import de.steamwar.bausystem.features.gui.BauGUI;
import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin; import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin;
import de.steamwar.bausystem.features.script.lua.libs.LuaLib; import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
@@ -36,9 +35,9 @@ import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionSystem; import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.bausystem.utils.TickManager; import de.steamwar.bausystem.utils.TickManager;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.command.AbstractValidator; import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.core.WorldIdentifier; import de.steamwar.core.WorldIdentifier;
import de.steamwar.core.worldedit.SteamEdit; import de.steamwar.core.worldedit.SteamEdit;
@@ -53,6 +52,8 @@ import org.bukkit.GameRule;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
@@ -65,7 +66,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
public class BauSystem extends JavaPlugin { public class BauSystem extends JavaPlugin implements Listener {
// This should be treated as final! // This should be treated as final!
public static Message MESSAGE; public static Message MESSAGE;
@@ -109,9 +110,6 @@ public class BauSystem extends JavaPlugin {
if (any instanceof PanzernAlgorithm) { if (any instanceof PanzernAlgorithm) {
Panzern.add((PanzernAlgorithm) any); Panzern.add((PanzernAlgorithm) any);
} }
if (any instanceof ConfigConverter) {
Config.addConfigConverter((ConfigConverter) any);
}
if (any instanceof BoundingBoxLoader) { if (any instanceof BoundingBoxLoader) {
((BoundingBoxLoader) any).load(); ((BoundingBoxLoader) any).load();
} }
@@ -149,11 +147,16 @@ public class BauSystem extends JavaPlugin {
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier); WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
} }
@EventHandler
public void onCRIUSleep(CRIUSleepEvent event) {
RegionSystem.INSTANCE.save();
}
@Override @Override
public void onDisable() { public void onDisable() {
linker.unlink(); linker.unlink();
WorldData.write(); RegionSystem.INSTANCE.save();
Config.getInstance().saveAll(); ConfigStore.saveAll();
} }
private <T extends CommandSender> AbstractValidator<T, ?> validator(Permission permission) { private <T extends CommandSender> AbstractValidator<T, ?> validator(Permission permission) {
@@ -0,0 +1,47 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config;
/**
* Converts a config of one version into the next version, e.g. ConfigV1 to
* ConfigV2. The converter for the second newest version converts into the
* current version. Converters are registered in the {@link ConfigStore}
* of the config.
*
* @param <F> the config version to convert from
* @param <T> the config version to convert to
*/
public interface ConfigConverter<F extends VersionedConfig, T extends VersionedConfig> {
/**
* @return the version this converter converts from
*/
int version();
/**
* Converts the config to the next version. If anything goes wrong do not
* silently exit this method, throw an Exception. The {@link ConfigStore}
* will deal with it.
*
* @param config the config to convert
* @return the converted config
*/
T convert(F config);
}
@@ -0,0 +1,364 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.steamwar.sql.UserConfig;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Level;
/**
* Loads, converts and saves a versioned config stored as JSON. Older versions
* are converted with the registered {@link ConfigConverter}s, legacy formats
* that are not JSON can be read with {@link #legacy(Predicate, Function)}.
* <p>
* A store can be bound to where the config is stored:
* <ul>
* <li>{@link #userConfig(String)}: one config per player in the {@link UserConfig}, use {@link #get(Player)}</li>
* <li>{@link #file(File)}: one config in a file, use {@link #get()}</li>
* <li>unbound: use {@link #load(File)} and {@link #save(File, VersionedConfig)}</li>
* </ul>
*
* @param <T> the current config version
*/
public class ConfigStore<T extends VersionedConfig> {
private static final List<ConfigStore<?>> STORES = new ArrayList<>();
private final Class<T> type;
private final int version;
private final Supplier<T> defaultConfig;
private final Map<Integer, Class<? extends VersionedConfig>> jsonVersions = new HashMap<>();
private final Map<Integer, ConfigConverter<?, ?>> converters = new HashMap<>();
private final List<LegacyFormat> legacyFormats = new ArrayList<>();
private Gson gson = new Gson();
private Integer unversionedVersion = null;
private String userConfigKey = null;
private Predicate<T> removeIf = config -> false;
private final Map<UUID, T> playerConfigs = new ConcurrentHashMap<>();
private File file = null;
private File legacyFile = null;
private Function<String, ? extends VersionedConfig> legacyFileReader = null;
private T fileConfig = null;
private record LegacyFormat(Predicate<String> matches, Function<String, ? extends VersionedConfig> reader) {
}
public ConfigStore(Class<T> type, int version, Supplier<T> defaultConfig) {
this.type = type;
this.version = version;
this.defaultConfig = defaultConfig;
jsonVersions.put(version, type);
STORES.add(this);
}
public ConfigStore<T> gson(Gson gson) {
this.gson = gson;
return this;
}
/**
* Registers an older config version that is stored as JSON.
*/
public ConfigStore<T> jsonVersion(int version, Class<? extends VersionedConfig> type) {
jsonVersions.put(version, type);
return this;
}
public ConfigStore<T> converter(ConfigConverter<?, ?> converter) {
if (converters.putIfAbsent(converter.version(), converter) != null) {
throw new IllegalStateException("Duplicate converter for version " + converter.version() + " of " + type.getSimpleName());
}
return this;
}
/**
* Registers a legacy format that is not JSON, stored strings matching the predicate are read by the reader.
*/
public ConfigStore<T> legacy(Predicate<String> matches, Function<String, ? extends VersionedConfig> reader) {
legacyFormats.add(new LegacyFormat(matches, reader));
return this;
}
/**
* The version of stored JSON without a version field.
*/
public ConfigStore<T> unversioned(int version) {
this.unversionedVersion = version;
return this;
}
/**
* Binds this store to the {@link UserConfig} with the given key, one config per player.
*/
public ConfigStore<T> userConfig(String key) {
this.userConfigKey = key;
return this;
}
/**
* Removes the config from the {@link UserConfig} instead of saving it, if the predicate matches.
*/
public ConfigStore<T> removeIf(Predicate<T> removeIf) {
this.removeIf = removeIf;
return this;
}
/**
* Binds this store to a single file.
*/
public ConfigStore<T> file(File file) {
this.file = file;
return this;
}
/**
* Legacy file to read, if the file of this store does not exist yet.
*/
public ConfigStore<T> legacyFile(File legacyFile, Function<String, ? extends VersionedConfig> reader) {
this.legacyFile = legacyFile;
this.legacyFileReader = reader;
return this;
}
/**
* Reads a stored config and converts it to the current version.
*
* @throws RuntimeException if the config could not be read or converted
*/
public T read(String stored) {
for (LegacyFormat legacyFormat : legacyFormats) {
if (legacyFormat.matches().test(stored)) {
return update(legacyFormat.reader().apply(stored));
}
}
JsonObject json = JsonParser.parseString(stored).getAsJsonObject();
int storedVersion;
if (json.has("version")) {
storedVersion = json.get("version").getAsInt();
} else if (unversionedVersion != null) {
storedVersion = unversionedVersion;
json.addProperty("version", storedVersion);
} else {
throw new IllegalStateException("Missing version of " + type.getSimpleName());
}
Class<? extends VersionedConfig> storedType = jsonVersions.get(storedVersion);
if (storedType == null) {
throw new IllegalStateException("Unknown version " + storedVersion + " of " + type.getSimpleName());
}
return update(gson.fromJson(json, storedType));
}
public String write(T config) {
return gson.toJson(config);
}
/**
* Converts a config to the current version.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public T update(VersionedConfig config) {
while (config.getVersion() < version) {
ConfigConverter converter = converters.get(config.getVersion());
if (converter == null) {
throw new IllegalStateException("No converter found for version " + config.getVersion() + " of " + type.getSimpleName());
}
VersionedConfig converted = converter.convert(config);
if (converted.getVersion() <= config.getVersion()) {
throw new IllegalStateException("Version did not increase after conversion of version " + config.getVersion() + " of " + type.getSimpleName());
}
config = converted;
}
if (!type.isInstance(config)) {
throw new IllegalStateException("Version " + config.getVersion() + " is not the current version of " + type.getSimpleName());
}
return type.cast(config);
}
/**
* @return the config stored in the file, or the default config if the file is missing or empty
* @throws RuntimeException if the config could not be read or converted
*/
public T load(File file) throws IOException {
if (file.length() == 0) {
return defaultConfig.get();
}
return read(Files.readString(file.toPath(), StandardCharsets.UTF_8));
}
public void save(File file, T config) throws IOException {
Files.writeString(file.toPath(), write(config), StandardCharsets.UTF_8);
}
/**
* Get the config of a player, loads it from the {@link UserConfig} and converts it if necessary.
*/
public T get(Player player) {
requireUserConfig();
return playerConfigs.computeIfAbsent(player.getUniqueId(), this::loadPlayer);
}
/**
* Save the config of a player, this does not unload the config.
*/
public void save(Player player) {
requireUserConfig();
T config = playerConfigs.get(player.getUniqueId());
if (config != null) {
savePlayer(player.getUniqueId(), config);
}
}
/**
* Save and unload the config of a player.
*/
public void unload(Player player) {
if (userConfigKey == null) return;
T config = playerConfigs.remove(player.getUniqueId());
if (config != null) {
savePlayer(player.getUniqueId(), config);
}
}
private T loadPlayer(UUID uuid) {
String stored = UserConfig.getConfig(uuid, userConfigKey);
if (stored == null) {
return defaultConfig.get();
}
try {
return read(stored);
} catch (Exception e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not load " + type.getSimpleName() + " of " + uuid, e);
return defaultConfig.get();
}
}
private void savePlayer(UUID uuid, T config) {
if (removeIf.test(config)) {
UserConfig.removePlayerConfig(uuid, userConfigKey);
} else {
UserConfig.updatePlayerConfig(uuid, userConfigKey, write(config));
}
}
private void requireUserConfig() {
if (userConfigKey == null) {
throw new IllegalStateException(type.getSimpleName() + " is not stored in the UserConfig");
}
}
/**
* Get the config of the file of this store, loads it and converts it if necessary.
*/
public T get() {
requireFile();
if (fileConfig == null) {
fileConfig = loadFile();
save();
}
return fileConfig;
}
/**
* Save the config to the file of this store, if it was loaded.
*/
public void save() {
requireFile();
if (fileConfig == null) return;
try {
save(file, fileConfig);
} catch (IOException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not write " + file, e);
}
}
private T loadFile() {
try {
if (file.length() != 0) {
return load(file);
}
if (legacyFile != null && legacyFile.length() != 0) {
return update(legacyFileReader.apply(Files.readString(legacyFile.toPath(), StandardCharsets.UTF_8)));
}
return defaultConfig.get();
} catch (Exception e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not load " + file + ", using default " + type.getSimpleName(), e);
backupBrokenFile();
return defaultConfig.get();
}
}
private void backupBrokenFile() {
if (file.length() == 0) return;
try {
Files.copy(file.toPath(), new File(file.getParentFile(), file.getName() + ".broken").toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not backup broken " + file, e);
}
}
private void requireFile() {
if (file == null) {
throw new IllegalStateException(type.getSimpleName() + " is not bound to a file");
}
}
static void unloadAll(Player player) {
STORES.forEach(store -> store.unload(player));
}
/**
* Saves every loaded config and unloads the configs of the players.
*/
public static void saveAll() {
for (ConfigStore<?> store : STORES) {
if (store.userConfigKey != null) {
store.playerConfigs.forEach((uuid, config) -> saveUnchecked(store, uuid, config));
store.playerConfigs.clear();
}
if (store.file != null) {
store.save();
}
}
}
@SuppressWarnings("unchecked")
private static <T extends VersionedConfig> void saveUnchecked(ConfigStore<T> store, UUID uuid, Object config) {
store.savePlayer(uuid, (T) config);
}
}
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2025 SteamWar.de-Serverteam * Copyright (C) 2026 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@@ -17,21 +17,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.core.events; package de.steamwar.bausystem.config;
import de.steamwar.core.Core; import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerQuitEvent;
/**
* Saves and unloads the configs of the {@link ConfigStore}s bound to the UserConfig
* when a player quits, configs are loaded on first use.
*/
@Linked @Linked
public class ChattingEvent implements Listener { public class ConfigStoreListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR)
private void onChat(AsyncPlayerChatEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
event.setCancelled(true); ConfigStore.unloadAll(event.getPlayer());
Core.MESSAGE.broadcastPrefixless("LOCAL_CHAT", event.getPlayer().getDisplayName(), event.getMessage()); }
@EventHandler
public void onCRIUSleep(CRIUSleepEvent event) {
ConfigStore.saveAll();
} }
} }
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2025 SteamWar.de-Serverteam * Copyright (C) 2026 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@@ -17,11 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.bausystem.region; package de.steamwar.bausystem.config;
public interface RegionDataStore { /**
void save(); * Common interface of every version of a config stored by a {@link ConfigStore},
void load(RegionData regionData); * the current version (e.g. Config) and every older version (ConfigV1, ConfigV2, ...).
default void delete() { */
} public interface VersionedConfig {
int getVersion();
} }
@@ -0,0 +1,95 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config.legacy;
import lombok.experimental.UtilityClass;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Deserializes {@link ItemStack}s that were serialized by YAPION, read with {@link YapionReader}.
*/
@UtilityClass
@Deprecated
public class YapionItemDeserializer {
private static final String TYPE_IDENTIFIER = "@type";
public ItemStack deserializeItem(Object yapion) {
if (yapion == null) return null;
Object result = deserialize(yapion);
if (result instanceof ItemStack itemStack) return itemStack;
throw new IllegalArgumentException("Not an ItemStack: " + result);
}
private Object deserialize(Object yapion) {
if (yapion instanceof List<?> list) {
List<Object> result = new ArrayList<>();
list.forEach(o -> result.add(deserialize(o)));
return result;
}
if (yapion instanceof YapionReader.YapionMap map) {
Map<Object, Object> result = new LinkedHashMap<>();
map.entries().forEach((key, value) -> result.put(deserialize(key), deserialize(value)));
return result;
}
if (yapion instanceof Map<?, ?> object) {
return deserializeObject(object);
}
return yapion;
}
private Object deserializeObject(Map<?, ?> object) {
Map<String, Object> data = new LinkedHashMap<>();
object.forEach((key, value) -> {
if (!TYPE_IDENTIFIER.equals(key)) data.put((String) key, deserialize(value));
});
if (!(object.get(TYPE_IDENTIFIER) instanceof String type)) {
return data;
}
Class<?> javaType = javaUtilClass(type);
if (javaType != null && Collection.class.isAssignableFrom(javaType)) {
return data.getOrDefault("values", new ArrayList<>());
}
if (javaType != null && Map.class.isAssignableFrom(javaType)) {
return data.containsKey("values") ? data.get("values") : data;
}
data.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, type.equals("org.bukkit.inventory.meta.ItemMeta") ? "ItemMeta" : type);
return ConfigurationSerialization.deserializeObject(data);
}
private Class<?> javaUtilClass(String type) {
if (!type.startsWith("java.util.")) return null;
try {
return Class.forName(type);
} catch (ClassNotFoundException e) {
return null;
}
}
}
@@ -0,0 +1,323 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config.legacy;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Minimal reader for the legacy YAPION format, only used to migrate old data.
* <p>
* The result is built from plain java objects:
* <ul>
* <li>YAPION objects {@code {key(value)}} become a {@link LinkedHashMap} of String to value</li>
* <li>YAPION arrays {@code [a,b]} become a {@link List}</li>
* <li>YAPION maps {@code <(a):(b)>} become a {@link YapionMap}</li>
* <li>YAPION values {@code (value)} become null, Boolean, Number or String</li>
* </ul>
*/
@Deprecated
public class YapionReader {
private static final Pattern INTEGER = Pattern.compile("[-+]?\\d+");
private static final Pattern DECIMAL = Pattern.compile("[-+]?(\\d+\\.\\d*|\\.\\d+|\\d+)([eE][-+]?\\d+)?");
public record YapionMap(Map<Object, Object> entries) {
}
public static Map<String, Object> parse(String yapion) {
YapionReader reader = new YapionReader(yapion);
reader.skipWhitespace();
Map<String, Object> result = reader.readObject();
reader.skipWhitespace();
if (reader.index != yapion.length()) {
throw reader.error("Unexpected trailing content");
}
return result;
}
public static double getDouble(Map<?, ?> object, String key, double defaultValue) {
return object.get(key) instanceof Number value ? value.doubleValue() : defaultValue;
}
public static int getInt(Map<?, ?> object, String key, int defaultValue) {
return object.get(key) instanceof Number value ? value.intValue() : defaultValue;
}
public static boolean getBoolean(Map<?, ?> object, String key, boolean defaultValue) {
return object.get(key) instanceof Boolean value ? value : defaultValue;
}
public static String getString(Map<?, ?> object, String key, String defaultValue) {
return object.get(key) instanceof String value ? value : defaultValue;
}
/**
* @return the entries of the array that are YAPION objects
*/
public static List<Map<?, ?>> getObjects(Map<?, ?> object, String key) {
List<Map<?, ?>> objects = new ArrayList<>();
if (object.get(key) instanceof List<?> array) {
array.forEach(o -> {
if (o instanceof Map<?, ?> map) objects.add(map);
});
}
return objects;
}
private final String input;
private int index = 0;
private YapionReader(String input) {
this.input = input;
}
private Object readAny() {
return switch (peek()) {
case '{' -> readObject();
case '[' -> readArray();
case '<' -> readMap();
case '(' -> readValue();
default -> throw error("Expected '{', '[', '<' or '('");
};
}
private Map<String, Object> readObject() {
expect('{');
Map<String, Object> object = new LinkedHashMap<>();
while (true) {
skipWhitespace();
if (peek() == '}') {
index++;
return object;
}
StringBuilder key = new StringBuilder();
while (true) {
char c = peek();
if (c == '(' || c == '{' || c == '[' || c == '<') break;
if (c == '}') throw error("Key without value");
key.append(readChar());
}
object.put(key.toString(), readAny());
}
}
private List<Object> readArray() {
expect('[');
List<Object> array = new ArrayList<>();
skipWhitespace();
if (peek() == ']') {
index++;
return array;
}
while (true) {
int start = index;
skipWhitespace();
char c = peek();
if (c == '{' || c == '[' || c == '<' || c == '(') {
array.add(readAny());
skipWhitespace();
} else {
// Whitespace is part of plain values
index = start;
StringBuilder value = new StringBuilder();
while (peek() != ',' && peek() != ']') {
value.append(readChar());
}
array.add(typed(value.toString()));
}
char separator = input.charAt(index++);
if (separator == ']') return array;
if (separator != ',') throw error("Expected ',' or ']'");
}
}
private YapionMap readMap() {
expect('<');
Map<Object, Object> map = new LinkedHashMap<>();
while (true) {
skipWhitespace();
if (peek() == '>') {
index++;
return new YapionMap(map);
}
Object key = readAny();
skipWhitespace();
expect(':');
skipWhitespace();
map.put(key, readAny());
}
}
/**
* YAPION does not escape parentheses in values, balanced parentheses are part of the value.
* A backslash only escapes a backslash, a unicode sequence or a control character, otherwise it is kept.
*/
private Object readValue() {
expect('(');
StringBuilder value = new StringBuilder();
int depth = 0;
while (true) {
char c = peek();
if (c == '\\') {
value.append(readValueEscape());
continue;
}
index++;
if (c == '(') {
depth++;
} else if (c == ')') {
if (depth == 0) break;
depth--;
}
value.append(c);
}
return typed(value.toString());
}
private String readValueEscape() {
index++;
if (index >= input.length()) return "\\";
char escaped = input.charAt(index);
switch (escaped) {
case '\\', '(', ')':
index++;
return String.valueOf(escaped);
case 'n':
index++;
return "\n";
case 't':
index++;
return "\t";
case 'r':
index++;
return "\r";
case 'u':
if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) {
char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16);
index += 5;
return String.valueOf(unicode);
}
return "\\";
default:
return "\\";
}
}
/**
* Reads one character of a key or plain array value, resolving backslash escapes.
* A backslash that does not escape anything is kept, as the old player config
* collapsed escaped backslashes into a single one.
*/
private char readChar() {
char c = input.charAt(index++);
if (c != '\\') return c;
if (index >= input.length()) throw error("Unterminated escape");
char escaped = input.charAt(index);
switch (escaped) {
case 'n':
index++;
return '\n';
case 't':
index++;
return '\t';
case 'r':
index++;
return '\r';
case 'u':
if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) {
char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16);
index += 5;
return unicode;
}
return '\\';
case '\\', ',', ' ', '(', ')', '{', '}', '[', ']', '<', '>', '"', ':', '@':
index++;
return escaped;
default:
return '\\';
}
}
private static Object typed(String value) {
if (value.equals("null")) return null;
if (value.equals("true")) return true;
if (value.equals("false")) return false;
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
return value.substring(1, value.length() - 1);
}
try {
if (INTEGER.matcher(value).matches()) {
long l = Long.parseLong(value);
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) return (int) l;
return l;
}
if (value.length() > 1) {
String number = value.substring(0, value.length() - 1);
switch (value.charAt(value.length() - 1)) {
case 'L':
if (INTEGER.matcher(number).matches()) return Long.parseLong(number);
break;
case 'S':
if (INTEGER.matcher(number).matches()) return Short.parseShort(number);
break;
case 'B':
if (INTEGER.matcher(number).matches()) return Byte.parseByte(number);
break;
case 'F':
if (DECIMAL.matcher(number).matches()) return Float.parseFloat(number);
break;
case 'D':
if (DECIMAL.matcher(number).matches()) return Double.parseDouble(number);
break;
default:
break;
}
}
if (DECIMAL.matcher(value).matches()) return Double.parseDouble(value);
} catch (NumberFormatException e) {
// Not a number, fall through to String
}
return value;
}
private void skipWhitespace() {
while (index < input.length() && Character.isWhitespace(input.charAt(index))) {
index++;
}
}
private char peek() {
if (index >= input.length()) throw error("Unexpected end of input");
return input.charAt(index);
}
private void expect(char c) {
if (peek() != c) throw error("Expected '" + c + "'");
index++;
}
private IllegalArgumentException error(String message) {
return new IllegalArgumentException(message + " at index " + index);
}
}
@@ -0,0 +1,63 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config.serializer;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.io.IOException;
import java.util.Base64;
import java.util.logging.Level;
/**
* Stores an {@link ItemStack} as Base64 of {@link ItemStack#serializeAsBytes()}, which
* contains the data version and gets upgraded on newer Minecraft versions.
* Empty items are stored as null, items that can't be read are deserialized as null.
*/
public class ItemStackTypeAdapter extends TypeAdapter<ItemStack> {
@Override
public void write(JsonWriter out, ItemStack itemStack) throws IOException {
if (itemStack == null || itemStack.isEmpty()) {
out.nullValue();
return;
}
out.value(Base64.getEncoder().encodeToString(itemStack.serializeAsBytes()));
}
@Override
public ItemStack read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String value = in.nextString();
try {
return ItemStack.deserializeBytes(Base64.getDecoder().decode(value));
} catch (Exception e) {
Bukkit.getLogger().log(Level.WARNING, "Could not deserialize ItemStack", e);
return null;
}
}
}
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2025 SteamWar.de-Serverteam * Copyright (C) 2026 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@@ -19,129 +19,77 @@
package de.steamwar.bausystem.configplayer; package de.steamwar.bausystem.configplayer;
import de.steamwar.bausystem.configplayer.serializer.ConfigurationSerializableSerializer; import com.google.gson.GsonBuilder;
import de.steamwar.linkage.Linked; import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.sql.UserConfig; import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.serializer.ItemStackTypeAdapter;
import de.steamwar.bausystem.configplayer.converter.ConfigV1ToV2Converter;
import de.steamwar.bausystem.features.hotbar.DefaultHotbar;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import lombok.Setter;
import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import yapion.hierarchy.output.StringOutput;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import yapion.serializing.SerializeManager;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
@Linked /**
public class Config implements Listener { * The current player config. It is stored as JSON, fields missing in the
* stored JSON keep the default value of their initializer, so adding a new
* field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to ConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION},
* write a {@link ConfigConverter} from the copy to this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class Config implements VersionedConfig {
static { public static final int VERSION = 2;
SerializeManager.add(new ConfigurationSerializableSerializer());
}
@Getter public static final ConfigStore<Config> STORE = new ConfigStore<>(Config.class, VERSION, Config::new)
private static Config instance; .gson(new GsonBuilder().registerTypeHierarchyAdapter(ItemStack.class, new ItemStackTypeAdapter()).create())
.legacy(s -> s.startsWith("{@version(1)"), ConfigV1::parse)
.converter(new ConfigV1ToV2Converter())
.userConfig("bausystem");
{ @Setter(AccessLevel.NONE)
instance = this; protected int version = VERSION;
}
private final Map<UUID, YAPIONObject> playerConfigurations = new HashMap<>();
private static final Map<Integer, ConfigConverter> CONFIG_CONVERTER_MAP = new HashMap<>();
public static void addConfigConverter(ConfigConverter configConverter) {
CONFIG_CONVERTER_MAP.putIfAbsent(configConverter.version(), configConverter);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
get(event.getPlayer());
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
save(event.getPlayer());
playerConfigurations.remove(event.getPlayer().getUniqueId());
}
/** /**
* Get a PlayerConfig, optionally loads it from the DataBase and migrates it if necessary. * BauGui item id to inventory slot
*
* @param player the player from whom to get the config.
* @return the config object
*/ */
public YAPIONObject get(Player player) { protected Map<Integer, Integer> bauGui = defaultBauGui();
UUID uuid = player.getUniqueId(); protected int bauGuiSize = 45;
if (!playerConfigurations.containsKey(uuid)) {
String s = UserConfig.getConfig(uuid, "bausystem");
YAPIONObject yapionObject;
if (s == null) {
yapionObject = ConfigCreator.createDefaultConfig();
} else {
yapionObject = YAPIONParser.parse(s);
}
yapionObject = update(yapionObject);
playerConfigurations.put(uuid, yapionObject);
return yapionObject;
}
return playerConfigurations.get(uuid);
}
public void saveAll() {
playerConfigurations.forEach((uuid, yapionObject) -> {
String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\+", "\\");
UserConfig.updatePlayerConfig(uuid, "bausystem", string);
});
playerConfigurations.clear();
}
/** /**
* Save a PlayerConfig, this does not remove the key value mapping from the map. * Slots 0-8 are the hotbar, slots 9-12 the armor contents
*
* @param player the player to save the config.
*/ */
public void save(Player player) { protected ItemStack[] hotbar = DefaultHotbar.defaultHotbar();
UUID uuid = player.getUniqueId();
if (playerConfigurations.containsKey(uuid)) {
YAPIONObject yapionObject = playerConfigurations.get(uuid);
String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\\\+", "\\\\");
UserConfig.updatePlayerConfig(uuid, "bausystem", string);
}
}
private YAPIONObject update(YAPIONObject yapionObject) { protected boolean nightVision = false;
int version = yapionObject.getPlainValue("@version"); protected boolean tntClickDetails = true;
while (version < ConfigCreator.currentVersion) { protected boolean detonatorAutostart = false;
ConfigConverter configConverter = CONFIG_CONVERTER_MAP.getOrDefault(version, null); protected boolean inventoryFill = false;
if (configConverter == null) { protected boolean rayVisualizer = false;
Bukkit.getLogger().log(Level.SEVERE, "No updater found for version " + version); protected boolean smartPlace = false;
return ConfigCreator.createDefaultConfig(); protected boolean blockCounter = false;
} protected boolean depthMessage = true;
try {
configConverter.update(yapionObject); public static Map<Integer, Integer> defaultBauGui() {
} catch (Exception e) { // 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ?
Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e); // 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16
return ConfigCreator.createDefaultConfig(); // 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11
} // 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18
int newVersion = yapionObject.getPlainValue("@version"); // 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ?
if (version == newVersion) { Map<Integer, Integer> bauGui = new HashMap<>();
Bukkit.getLogger().log(Level.SEVERE, "Version Tag was the same after conversion"); bauGui.put(10, 2); bauGui.put(3, 3); bauGui.put(7, 4); bauGui.put(17, 5); bauGui.put(15, 6);
return ConfigCreator.createDefaultConfig(); bauGui.put(5, 9); bauGui.put(4, 18); bauGui.put(6, 27);
} bauGui.put(16, 17); bauGui.put(11, 26); bauGui.put(18, 35);
if (newVersion < version) { bauGui.put(19, 20); bauGui.put(21, 21); bauGui.put(9, 22); bauGui.put(0, 23); bauGui.put(1, 24);
Bukkit.getLogger().log(Level.SEVERE, "Version Tag was earlier after conversion"); bauGui.put(23, 37); bauGui.put(20, 38); bauGui.put(8, 39); bauGui.put(22, 40); bauGui.put(26, 41); bauGui.put(12, 42); bauGui.put(14, 43);
return ConfigCreator.createDefaultConfig(); return bauGui;
}
version = newVersion;
}
return yapionObject;
} }
} }
@@ -1,56 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer;
import yapion.hierarchy.types.YAPIONObject;
/**
* A new {@link ConfigConverter} should be written when you remove anything
* from the Config or modify any mayor part. When you move anything from
* any key to any other key you should write a new {@link ConfigConverter}.
* For adding any new key you should be able to get the default without
* having it in the Config. Anything you need to change you should also
* change the {@link ConfigCreator} accordingly, to produce the new Config.
*/
public interface ConfigConverter {
/**
* This describes the version this Converter can convert from. The version
* it should convert to is the version 1 above this number. But this is not
* a necessity. In the config Object as parameter given in {@link #update(YAPIONObject)}
* you should update the <b>@version</b> variable in the root object to the
* new version this converter produced.
*
* @return the version number
*/
int version();
/**
* This method should update everything needed to go from a lower config
* version to a higher. It should update the <b>@version</b> variable
* accordingly. Anything else is up the implementation. If anything goes wrong
* do not silently exit this method, throw an Exception. The updater Code will
* deal with it. Never leave the inputted object in a corrupted state.
*
* @param config the config object to update
*/
void update(YAPIONObject config);
}
@@ -1,61 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer;
import de.steamwar.bausystem.features.hotbar.DefaultHotbar;
import lombok.experimental.UtilityClass;
import yapion.hierarchy.types.YAPIONObject;
@UtilityClass
public class ConfigCreator {
public static final int currentVersion = 1;
public YAPIONObject createDefaultConfig() {
YAPIONObject yapionObject = new YAPIONObject();
// This call should never be touched
yapionObject.add("@version", currentVersion);
// Any initialising goes into here
yapionObject.add("baugui", defaultBauGui());
// Default Hotbar Gui
yapionObject.add("hotbar", DefaultHotbar.defaultHotbar());
return yapionObject;
}
private YAPIONObject defaultBauGui() {
YAPIONObject yapionObject = new YAPIONObject();
// 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ?
// 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16
// 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11
// 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18
// 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ?
yapionObject.add("10", 2).add("3", 3).add("7", 4).add("17", 5).add("15", 6);
yapionObject.add("5", 9).add("4", 18).add("6", 27);
yapionObject.add("16", 17).add("11", 26).add("18", 35);
yapionObject.add("19", 20).add("21", 21).add("9", 22).add("0", 23).add("1", 24);
yapionObject.add("23", 37).add("20", 38).add("8", 39).add("22", 40).add("26", 41).add("12", 42).add("14", 43);
yapionObject.add("size", 45);
return yapionObject;
}
}
@@ -0,0 +1,104 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.legacy.YapionReader;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The legacy config that was stored in the YAPION format.
*/
@Getter
@Setter
public class ConfigV1 implements VersionedConfig {
public static final int VERSION = 1;
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* BauGui item id to inventory slot
*/
protected Map<Integer, Integer> bauGui = new HashMap<>();
protected int bauGuiSize = 45;
/**
* Minecraft version to the hotbar serialized by YAPION, read with {@link YapionReader}.
* Use {@link de.steamwar.bausystem.config.legacy.YapionItemDeserializer} to get the items.
*/
protected Map<Integer, List<?>> hotbars = new HashMap<>();
protected boolean nightVision = false;
protected boolean tntClickDetails = true;
protected boolean detonatorAutostart = false;
protected boolean inventoryFill = false;
protected boolean rayVisualizer = false;
protected boolean smartPlace = false;
protected boolean blockCounter = false;
protected boolean depthMessage = true;
public static ConfigV1 parse(String yapion) {
Map<String, Object> object = YapionReader.parse(yapion);
ConfigV1 config = new ConfigV1();
config.version = (Integer) object.get("@version");
if (object.get("baugui") instanceof Map<?, ?> bauGui) {
bauGui.forEach((key, value) -> {
if (key.equals("size")) {
config.bauGuiSize = (Integer) value;
} else {
config.bauGui.put(Integer.parseInt((String) key), (Integer) value);
}
});
}
object.forEach((key, value) -> {
if (!(value instanceof List<?> hotbar)) return;
if (key.equals("hotbar")) {
// Hotbars were stored without version prior to 1.19
config.hotbars.putIfAbsent(19, hotbar);
} else if (key.startsWith("hotbar-")) {
config.hotbars.put(Integer.parseInt(key.substring("hotbar-".length())), hotbar);
}
});
config.nightVision = getBoolean(object, "nightvision", config.nightVision);
config.tntClickDetails = getBoolean(object, "tnt_click_details", config.tntClickDetails);
config.detonatorAutostart = getBoolean(object, "detonator-autostart", config.detonatorAutostart);
config.inventoryFill = getBoolean(object, "inventoryfill", config.inventoryFill);
config.rayVisualizer = getBoolean(object, "rayvisualizer", config.rayVisualizer);
config.smartPlace = getBoolean(object, "smartPlace", config.smartPlace);
config.blockCounter = getBoolean(object, "blockCounter", config.blockCounter);
config.depthMessage = getBoolean(object, "depth_message", config.depthMessage);
return config;
}
private static boolean getBoolean(Map<String, Object> object, String key, boolean defaultValue) {
return object.get(key) instanceof Boolean value ? value : defaultValue;
}
}
@@ -0,0 +1,75 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer.converter;
import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.bausystem.config.legacy.YapionItemDeserializer;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.configplayer.ConfigV1;
import de.steamwar.core.Core;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
@Deprecated
public class ConfigV1ToV2Converter implements ConfigConverter<ConfigV1, Config> {
@Override
public int version() {
return ConfigV1.VERSION;
}
@Override
public Config convert(ConfigV1 old) {
Config config = new Config();
config.setBauGui(new HashMap<>(old.getBauGui()));
config.setBauGuiSize(old.getBauGuiSize());
List<?> hotbar = old.getHotbars().get(Core.getVersion());
if (hotbar != null) {
config.setHotbar(convertHotbar(hotbar));
}
config.setNightVision(old.isNightVision());
config.setTntClickDetails(old.isTntClickDetails());
config.setDetonatorAutostart(old.isDetonatorAutostart());
config.setInventoryFill(old.isInventoryFill());
config.setRayVisualizer(old.isRayVisualizer());
config.setSmartPlace(old.isSmartPlace());
config.setBlockCounter(old.isBlockCounter());
config.setDepthMessage(old.isDepthMessage());
return config;
}
private ItemStack[] convertHotbar(List<?> hotbar) {
ItemStack[] items = new ItemStack[13];
for (int i = 0; i < Math.min(hotbar.size(), items.length); i++) {
try {
items[i] = YapionItemDeserializer.deserializeItem(hotbar.get(i));
} catch (Exception e) {
Bukkit.getLogger().log(Level.WARNING, "Could not convert hotbar item " + i, e);
}
}
return items;
}
}
@@ -1,90 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer.serializer;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import yapion.hierarchy.api.groups.YAPIONAnyType;
import yapion.hierarchy.types.YAPIONObject;
import yapion.serializing.api.SerializerObject;
import yapion.serializing.data.DeserializeData;
import yapion.serializing.data.SerializeData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import static yapion.utils.IdentifierUtils.TYPE_IDENTIFIER;
public class ConfigurationSerializableSerializer extends SerializerObject<ConfigurationSerializable> {
@Override
public Class<ConfigurationSerializable> type() {
return ConfigurationSerializable.class;
}
@Override
public boolean isInterface() {
return true;
}
@Override
public YAPIONObject serialize(SerializeData<ConfigurationSerializable> serializeData) {
YAPIONObject yapionObject = new YAPIONObject();
yapionObject.add(TYPE_IDENTIFIER, serializeData.object.getClass().getTypeName());
if (serializeData.object instanceof ItemStack) {
yapionObject.add(TYPE_IDENTIFIER, ItemStack.class.getTypeName());
}
if (serializeData.object instanceof ItemMeta) {
yapionObject.add(TYPE_IDENTIFIER, ItemMeta.class.getTypeName());
}
Map<String, Object> serializeDataMap = serializeData.object.serialize();
serializeDataMap.forEach((s, o) -> {
YAPIONAnyType yapionAnyType = serializeData.serialize(o);
if (yapionAnyType instanceof YAPIONObject) {
YAPIONObject object = (YAPIONObject) yapionAnyType;
if (object.containsKey(TYPE_IDENTIFIER) && object.getPlainValue(TYPE_IDENTIFIER).equals("com.google.common.collect.RegularImmutableList")) {
object.put(TYPE_IDENTIFIER, ArrayList.class.getTypeName());
}
}
yapionObject.add(s, yapionAnyType);
});
return yapionObject;
}
@Override
public ConfigurationSerializable deserialize(DeserializeData<YAPIONObject> deserializeData) {
Map<String, Object> deserializeDataMap = new HashMap<>();
deserializeData.object.forEach((s, yapionAnyType) -> {
if (s.equals(TYPE_IDENTIFIER)) {
if (yapionAnyType.toString().equals("(org.bukkit.inventory.meta.ItemMeta)")) {
deserializeDataMap.put("==", "ItemMeta");
} else {
deserializeDataMap.put("==", deserializeData.deserialize(yapionAnyType));
}
return;
}
deserializeDataMap.put(s, deserializeData.deserialize(yapionAnyType));
});
return ConfigurationSerialization.deserializeObject(deserializeDataMap);
}
}
@@ -56,7 +56,6 @@ public class BauInfoBauGuiItem extends BauGuiItem {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
List<String> stringList = new ArrayList<>(); List<String> stringList = new ArrayList<>();
for (Flag flag : Flag.getFlags()) { for (Flag flag : Flag.getFlags()) {
if (flag == Flag.CHANGED) continue;
if (!region.getRegionData().has(flag).isApplicable()) continue; if (!region.getRegionData().has(flag).isApplicable()) continue;
FlagOptional<?> value = region.getRegionData().get(flag); FlagOptional<?> value = region.getRegionData().get(flag);
if (value.isPresent()) { if (value.isPresent()) {
@@ -64,7 +64,7 @@ public class Depth {
dimensions.setZ(Math.abs(dimensions.getZ())); dimensions.setZ(Math.abs(dimensions.getZ()));
RegionUtils.message(region, player -> { RegionUtils.message(region, player -> {
if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) { if (Config.STORE.get(player).isDepthMessage()) {
player.spigot().sendMessage(getMessage(player, dimensions.getBlockX() + 1, dimensions.getBlockY() + 1, dimensions.getBlockZ() + 1, tntCount)); player.spigot().sendMessage(getMessage(player, dimensions.getBlockX() + 1, dimensions.getBlockY() + 1, dimensions.getBlockZ() + 1, tntCount));
} }
}); });
@@ -34,11 +34,11 @@ public class DepthCommand extends SWCommand {
@Register @Register
public void toggle(Player player) { public void toggle(Player player) {
if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) { if (Config.STORE.get(player).isDepthMessage()) {
Config.getInstance().get(player).put("depth_message", false); Config.STORE.get(player).setDepthMessage(false);
BauSystem.MESSAGE.send("DEPTH_COUNTER_DISABLE", player); BauSystem.MESSAGE.send("DEPTH_COUNTER_DISABLE", player);
} else { } else {
Config.getInstance().get(player).put("depth_message", true); Config.STORE.get(player).setDepthMessage(true);
BauSystem.MESSAGE.send("DEPTH_COUNTER_ENABLE", player); BauSystem.MESSAGE.send("DEPTH_COUNTER_ENABLE", player);
} }
} }
@@ -95,7 +95,7 @@ public class Detonator {
public static void activateDetonator(DetonatorStorage detonator) { public static void activateDetonator(DetonatorStorage detonator) {
Player p = detonator.getPlayer(); Player p = detonator.getPlayer();
if (Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false)) { if (Config.STORE.get(p).isDetonatorAutostart()) {
AutostartListener.instance.activate(p); AutostartListener.instance.activate(p);
} }
@@ -76,8 +76,8 @@ public class DetonatorCommand extends SWCommand {
@Register(value = "autostart", description = "DETONATOR_HELP_AUTOSTART") @Register(value = "autostart", description = "DETONATOR_HELP_AUTOSTART")
public void toggleAutostartTimer(Player p) { public void toggleAutostartTimer(Player p) {
boolean current = Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false); boolean current = Config.STORE.get(p).isDetonatorAutostart();
Config.getInstance().get(p).put("detonator-autostart", !current); Config.STORE.get(p).setDetonatorAutostart(!current);
if (!current) { if (!current) {
BauSystem.MESSAGE.send("DETONATOR_AUTOSTART_ENABLE", p); BauSystem.MESSAGE.send("DETONATOR_AUTOSTART_ENABLE", p);
} else { } else {
@@ -28,7 +28,6 @@ import lombok.Setter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import yapion.hierarchy.types.YAPIONObject;
import java.util.*; import java.util.*;
@@ -52,15 +51,15 @@ public class BauGuiMapping {
} }
@Getter @Getter
private final YAPIONObject object; private final Config config;
@Getter @Getter
private final Player owner; private final Player owner;
@Getter @Getter
@Setter @Setter
private boolean saved; private boolean saved;
protected BauGuiMapping(YAPIONObject object, Player p) { protected BauGuiMapping(Config config, Player p) {
this.object = object; this.config = config;
this.owner = p; this.owner = p;
fromUUID.put(p.getUniqueId(), this); fromUUID.put(p.getUniqueId(), this);
mappings.add(this); mappings.add(this);
@@ -70,19 +69,18 @@ public class BauGuiMapping {
BauGuiMapping mapping = fromUUID.get(p.getUniqueId()); BauGuiMapping mapping = fromUUID.get(p.getUniqueId());
if (mapping == null) { if (mapping == null) {
YAPIONObject yapionObject = Config.getInstance().get(p); mapping = new BauGuiMapping(Config.STORE.get(p), p);
mapping = new BauGuiMapping(yapionObject.getObject("baugui"), p);
} }
return mapping; return mapping;
} }
public boolean isShown(int id) { public boolean isShown(int id) {
return object.getPlainValueOrDefault(Integer.toString(id), -1) >= 0; return config.getBauGui().getOrDefault(id, -1) >= 0;
} }
public int getSlot(int id) { public int getSlot(int id) {
return object.getPlainValue(Integer.toString(id)); return config.getBauGui().get(id);
} }
public Map<Integer, Integer> getMapping() { public Map<Integer, Integer> getMapping() {
@@ -94,7 +92,7 @@ public class BauGuiMapping {
} }
public int getSize() { public int getSize() {
return object.getPlainValueOrDefault("size", 45); return config.getBauGuiSize();
} }
public void setSize(int size) { public void setSize(int size) {
@@ -102,7 +100,7 @@ public class BauGuiMapping {
return; return;
} }
object.add("size", size); config.setBauGuiSize(size);
} }
private void tick() { private void tick() {
@@ -111,16 +109,17 @@ public class BauGuiMapping {
public void save() { public void save() {
if (saved) return; if (saved) return;
YAPIONObject config = Config.getInstance().get(owner); Config current = Config.STORE.get(owner);
config.add("baugui", object); current.setBauGui(config.getBauGui());
Config.getInstance().save(owner); current.setBauGuiSize(config.getBauGuiSize());
Config.STORE.save(owner);
saved = true; saved = true;
} }
private Map<Integer, Integer> internalReadMap() { private Map<Integer, Integer> internalReadMap() {
Map<Integer, Integer> map = new HashMap<>(); Map<Integer, Integer> map = new HashMap<>();
for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) { for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) {
Integer value = object.getPlainValueOrDefault(e.getKey().toString(), -1); Integer value = config.getBauGui().getOrDefault(e.getKey(), -1);
if (value == -1) continue; if (value == -1) continue;
map.put(e.getKey(), value); map.put(e.getKey(), value);
} }
@@ -129,10 +128,10 @@ public class BauGuiMapping {
private void internalWriteMap(Map<Integer, Integer> map) { private void internalWriteMap(Map<Integer, Integer> map) {
for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) { for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) {
object.remove(e.getKey().toString()); config.getBauGui().remove(e.getKey());
Integer value = map.getOrDefault(e.getKey(), -1); Integer value = map.getOrDefault(e.getKey(), -1);
if (value == -1) continue; if (value == -1) continue;
object.add(e.getKey().toString(), value); config.getBauGui().put(e.getKey(), value);
} }
} }
} }
@@ -20,39 +20,23 @@
package de.steamwar.bausystem.features.hotbar; package de.steamwar.bausystem.features.hotbar;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.core.Core;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import yapion.hierarchy.types.YAPIONArray; import org.bukkit.inventory.meta.ItemMeta;
import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONValue;
import yapion.parser.YAPIONParser;
import yapion.serializing.YAPIONDeserializer;
import yapion.serializing.YAPIONSerializer;
import java.util.HashSet; import java.util.List;
import java.util.Set;
@UtilityClass @UtilityClass
public class DefaultHotbar { public class DefaultHotbar {
private final YAPIONArray DEFAULT_HOTBAR = YAPIONParser.parse("{[{@type(org.bukkit.inventory.ItemStack)v(2230)type(WOODEN_AXE)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(WorldEdit Wand)lore{@type(java.util.ArrayList)values[Left click: select pos #1,Right click: select pos #2]}}},{@type(org.bukkit.inventory.ItemStack)v(2230)type(COMPASS)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(Navigation Wand)lore{@type(java.util.ArrayList)values[Left click: jump to location,Right click: pass through walls]}}},null,null,null,null,null,null,{@type(org.bukkit.inventory.ItemStack)v(2230)type(NETHER_STAR)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(§eBau GUI)}},null,null,{@type(org.bukkit.inventory.ItemStack)v(3117)type(ELYTRA)},null]}").getArray("");
public void updateHotbar(Player p) { public void updateHotbar(Player p) {
ItemStack[] hotbar = new ItemStack[13]; ItemStack[] hotbar = new ItemStack[13];
System.arraycopy(p.getInventory().getContents(), 0, hotbar, 0, 9); System.arraycopy(p.getInventory().getContents(), 0, hotbar, 0, 9);
System.arraycopy(p.getInventory().getArmorContents(), 0, hotbar, 9, 4); System.arraycopy(p.getInventory().getArmorContents(), 0, hotbar, 9, 4);
YAPIONArray yapionArray = new YAPIONArray(); Config.STORE.get(p).setHotbar(copy(hotbar));
for (ItemStack itemStack : hotbar) { Config.STORE.save(p);
if (itemStack != null) {
yapionArray.add(YAPIONSerializer.serialize(itemStack));
} else {
yapionArray.add(new YAPIONValue<>(null));
}
}
Config.getInstance().get(p).add("hotbar-" + Core.getVersion(), yapionArray);
Config.getInstance().save(p);
} }
public void setHotbar(Player p) { public void setHotbar(Player p) {
@@ -66,28 +50,34 @@ public class DefaultHotbar {
} }
public ItemStack[] getItems(Player p) { public ItemStack[] getItems(Player p) {
Config.getInstance().get(p).remap("hotbar", "hotbar-19"); return copy(Config.STORE.get(p).getHotbar());
YAPIONArray yapionArray = Config.getInstance().get(p).getYAPIONArrayOrSetDefault("hotbar-" + Core.getVersion(), defaultHotbar()); }
public ItemStack[] defaultHotbar() {
ItemStack[] hotbar = new ItemStack[13]; ItemStack[] hotbar = new ItemStack[13];
Set<Integer> invalid = new HashSet<>(); hotbar[0] = item(Material.WOODEN_AXE, "WorldEdit Wand", "Left click: select pos #1", "Right click: select pos #2");
yapionArray.forEach((integer, yapionAnyType) -> { hotbar[1] = item(Material.COMPASS, "Navigation Wand", "Left click: jump to location", "Right click: pass through walls");
if (yapionAnyType instanceof YAPIONValue) { hotbar[8] = item(Material.NETHER_STAR, "§eBau GUI");
hotbar[integer] = null; hotbar[11] = new ItemStack(Material.ELYTRA);
} else {
try {
hotbar[integer] = YAPIONDeserializer.deserialize((YAPIONObject) yapionAnyType);
} catch (Exception e) {
invalid.add(integer);
hotbar[integer] = null;
}
}
});
invalid.forEach(i -> yapionArray.set(i, new YAPIONValue<>(null)));
if (!invalid.isEmpty()) Config.getInstance().save(p);
return hotbar; return hotbar;
} }
public YAPIONArray defaultHotbar() { private ItemStack item(Material material, String name, String... lore) {
return DEFAULT_HOTBAR; ItemStack itemStack = new ItemStack(material);
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName(name);
if (lore.length > 0) {
meta.setLore(List.of(lore));
}
itemStack.setItemMeta(meta);
return itemStack;
}
private ItemStack[] copy(ItemStack[] hotbar) {
ItemStack[] copy = new ItemStack[13];
for (int i = 0; i < Math.min(hotbar.length, copy.length); i++) {
copy[i] = hotbar[i] == null ? null : hotbar[i].clone();
}
return copy;
} }
} }
@@ -49,7 +49,7 @@ public class InventoryFillBauGuiItem extends BauGuiItem {
@Override @Override
public ItemStack getItem(Player player) { public ItemStack getItem(Player player) {
String loreKey = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false) ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE"; String loreKey = Config.STORE.get(player).isInventoryFill() ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE";
return new SWItem(Material.HOPPER, BauSystem.MESSAGE.parse("OTHER_ITEMS_INVENTORY_FILL_NAME", player), Collections.singletonList(BauSystem.MESSAGE.parse(loreKey, player)), false, clickType -> { return new SWItem(Material.HOPPER, BauSystem.MESSAGE.parse("OTHER_ITEMS_INVENTORY_FILL_NAME", player), Collections.singletonList(BauSystem.MESSAGE.parse(loreKey, player)), false, clickType -> {
}).getItemStack(); }).getItemStack();
} }
@@ -40,7 +40,7 @@ public class InventoryFiller implements Listener {
@EventHandler @EventHandler
public void onPlayerDropItem(PlayerDropItemEvent event) { public void onPlayerDropItem(PlayerDropItemEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return; if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return;
if (!event.getPlayer().isSneaking()) return; if (!event.getPlayer().isSneaking()) return;
Block block = event.getPlayer().getTargetBlockExact(5); Block block = event.getPlayer().getTargetBlockExact(5);
if (block == null) return; if (block == null) return;
@@ -62,7 +62,7 @@ public class InventoryFiller implements Listener {
@EventHandler @EventHandler
public void onPlayerItemHeld(PlayerItemHeldEvent event) { public void onPlayerItemHeld(PlayerItemHeldEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return; if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return;
if (!event.getPlayer().isSneaking()) return; if (!event.getPlayer().isSneaking()) return;
ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand(); ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand();
if (itemStack.getType() == Material.AIR) return; if (itemStack.getType() == Material.AIR) return;
@@ -68,8 +68,8 @@ public class InventoryFillerCommand extends SWCommand {
@Register(description = {"INVENTORY_FILL_HELP", "INVENTORY_FILL_INFO"}) @Register(description = {"INVENTORY_FILL_HELP", "INVENTORY_FILL_INFO"})
public void toggle(Player player) { public void toggle(Player player) {
boolean inventoryFill = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false); boolean inventoryFill = Config.STORE.get(player).isInventoryFill();
Config.getInstance().get(player).put("inventoryfill", !inventoryFill); Config.STORE.get(player).setInventoryFill(!inventoryFill);
if (!inventoryFill) { if (!inventoryFill) {
SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("INVENTORY_FILL_ENABLE", player)); SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("INVENTORY_FILL_ENABLE", player));
BauSystem.MESSAGE.send("INVENTORY_FILL_INFO", player); BauSystem.MESSAGE.send("INVENTORY_FILL_INFO", player);
@@ -167,7 +167,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
boolean rayvisualizer = Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("rayvisualizer", false); boolean rayvisualizer = Config.STORE.get(event.getPlayer()).isRayVisualizer();
if (rayvisualizer) server.addPlayer(event.getPlayer()); if (rayvisualizer) server.addPlayer(event.getPlayer());
} }
@@ -181,7 +181,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
if (Permission.BUILD.hasPermission(player)) { if (Permission.BUILD.hasPermission(player)) {
boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false); boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer();
if (rayvisualizer) server.addPlayer(player); if (rayvisualizer) server.addPlayer(player);
} else { } else {
server.removePlayer(player); server.removePlayer(player);
@@ -192,8 +192,8 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
@Register @Register
public void toggle(@Validator Player player) { public void toggle(@Validator Player player) {
boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false); boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer();
Config.getInstance().get(player).put("rayvisualizer", !rayvisualizer); Config.STORE.get(player).setRayVisualizer(!rayvisualizer);
if (!rayvisualizer) { if (!rayvisualizer) {
SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player)); SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player));
server.addPlayer(player); server.addPlayer(player);
@@ -63,7 +63,7 @@ public class ColorCommand extends SWCommand {
} }
region.getRegionData().set(Flag.COLOR, color); region.getRegionData().set(Flag.COLOR, color);
try { try {
PasteBuilder pasteBuilder = new PasteBuilder() PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile()))
.ignoreAir(true) .ignoreAir(true)
.onlyColors(true) .onlyColors(true)
.color(color); .color(color);
@@ -60,7 +60,6 @@ public class FireListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.FIRE).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.FIRE).isWithDefault(FireMode.DENY)) return null; if (region.getRegionData().get(Flag.FIRE).isWithDefault(FireMode.DENY)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.FIRE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FIRE).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.FIRE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FIRE).getWithDefault().getChatValue(), p);
} }
@@ -249,7 +249,6 @@ public class FreezeListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.FREEZE).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.FREEZE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FREEZE).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.FREEZE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FREEZE).getWithDefault().getChatValue(), p);
} }
@@ -52,7 +52,6 @@ public class ItemsListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.ITEMS).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.ITEMS).isWithDefault(ItemMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.ITEMS).isWithDefault(ItemMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.ITEMS.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.ITEMS).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.ITEMS.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.ITEMS).getWithDefault().getChatValue(), p);
} }
@@ -54,7 +54,6 @@ public class NoGravityListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.NO_GRAVITY).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.NO_GRAVITY).isWithDefault(NoGravityMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.NO_GRAVITY).isWithDefault(NoGravityMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.NO_GRAVITY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.NO_GRAVITY).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.NO_GRAVITY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.NO_GRAVITY).getWithDefault().getChatValue(), p);
} }
@@ -70,7 +70,6 @@ public class ProtectListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.PROTECT).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.PROTECT).isWithDefault(ProtectMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.PROTECT).isWithDefault(ProtectMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.PROTECT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.PROTECT).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.PROTECT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.PROTECT).getWithDefault().getChatValue(), p);
} }
@@ -30,7 +30,6 @@ import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.util.SelectCommand; import de.steamwar.bausystem.features.util.SelectCommand;
import de.steamwar.bausystem.region.Point; import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionHistory;
import de.steamwar.bausystem.region.RegionUtils; import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair; import de.steamwar.bausystem.shared.Pair;
@@ -72,10 +71,7 @@ public class RegionCommand extends SWCommand {
@Register(value = "undo", description = "REGION_REGION_HELP_UNDO") @Register(value = "undo", description = "REGION_REGION_HELP_UNDO")
public void undoCommand(@Validator Player p) { public void undoCommand(@Validator Player p) {
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
if (region.getHistory() == RegionHistory.EMPTY) { if (checkGlobalRegion(region, p)) return;
BauSystem.MESSAGE.send("REGION_REGION_NO_REGION", p);
return;
}
if (region.getHistory().undo()) { if (region.getHistory().undo()) {
RegionUtils.message(region, "REGION_REGION_UNDID"); RegionUtils.message(region, "REGION_REGION_UNDID");
@@ -87,8 +83,7 @@ public class RegionCommand extends SWCommand {
@Register(value = "redo", description = "REGION_REGION_HELP_REDO") @Register(value = "redo", description = "REGION_REGION_HELP_REDO")
public void redoCommand(@Validator Player p) { public void redoCommand(@Validator Player p) {
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
if (region.getHistory() == RegionHistory.EMPTY) { if (checkGlobalRegion(region, p)) {
BauSystem.MESSAGE.send("REGION_REGION_NO_REGION", p);
return; return;
} }
@@ -105,7 +100,7 @@ public class RegionCommand extends SWCommand {
if (checkGlobalRegion(region, p)) return; if (checkGlobalRegion(region, p)) return;
try { try {
PasteBuilder pasteBuilder = new PasteBuilder() PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile()))
.ignoreAir(true) .ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false); region.getArea().reset(pasteBuilder, false);
@@ -127,7 +122,7 @@ public class RegionCommand extends SWCommand {
} }
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(PasteBuilder.ClipboardProvider.schematic(node)) PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.SchematicProvider(node))
.ignoreAir(true) .ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false); region.getArea().reset(pasteBuilder, false);
@@ -148,7 +143,7 @@ public class RegionCommand extends SWCommand {
BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p); BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p);
return; return;
} }
p.teleport(region.getBuildArea().getCopyPoint(false).toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND); p.teleport(region.getBuildArea().getCopyPoint().toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND);
BauSystem.MESSAGE.send("REGION_REGION_TP_COPY", p); BauSystem.MESSAGE.send("REGION_REGION_TP_COPY", p);
} }
@@ -163,7 +158,7 @@ public class RegionCommand extends SWCommand {
BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p); BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p);
return; return;
} }
p.teleport(region.getTestblockArea().getCopyPoint(false).toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND); p.teleport(region.getTestblockArea().getCopyPoint().toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND);
BauSystem.MESSAGE.send("REGION_REGION_TP_TEST_BLOCK", p); BauSystem.MESSAGE.send("REGION_REGION_TP_TEST_BLOCK", p);
} }
@@ -196,7 +191,7 @@ public class RegionCommand extends SWCommand {
break; break;
} }
Clipboard clipboard = FlatteningWrapper.impl.copy(minPoint, maxPoint, region.getBuildArea().getCopyPoint(false)); Clipboard clipboard = FlatteningWrapper.impl.copy(minPoint, maxPoint, region.getBuildArea().getCopyPoint());
WorldEdit.getInstance() WorldEdit.getInstance()
.getSessionManager() .getSessionManager()
.get(BukkitAdapter.adapt(p)) .get(BukkitAdapter.adapt(p))
@@ -241,13 +236,13 @@ public class RegionCommand extends SWCommand {
} }
try (EditSession e = WorldEditUtils.getEditSession(p)) { try (EditSession e = WorldEditUtils.getEditSession(p)) {
Operations.completeBlindly(clipboardHolder.createPaste(e).ignoreAirBlocks(ignoreAir).to(toBlockVector3(region.getBuildArea().getCopyPoint(false))).build()); Operations.completeBlindly(clipboardHolder.createPaste(e).ignoreAirBlocks(ignoreAir).to(toBlockVector3(region.getBuildArea().getCopyPoint())).build());
WorldEditUtils.addToPlayer(p, e); WorldEditUtils.addToPlayer(p, e);
if (selectPasted) { if (selectPasted) {
Clipboard clipboard = clipboardHolder.getClipboards().get(0); Clipboard clipboard = clipboardHolder.getClipboards().get(0);
BlockVector3 minPointSelection = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint(false))); BlockVector3 minPointSelection = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint()));
BlockVector3 maxPointSelection = clipboard.getRegion().getMaximumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint(false))); BlockVector3 maxPointSelection = clipboard.getRegion().getMaximumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint()));
FlatteningWrapper.impl.setSelection(p, Point.fromBlockVector3(minPointSelection), Point.fromBlockVector3(maxPointSelection)); FlatteningWrapper.impl.setSelection(p, Point.fromBlockVector3(minPointSelection), Point.fromBlockVector3(maxPointSelection));
} }
BauSystem.MESSAGE.send("REGION_REGION_PASTE_DONE", p); BauSystem.MESSAGE.send("REGION_REGION_PASTE_DONE", p);
@@ -29,6 +29,9 @@ import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance; import de.steamwar.linkage.LinkedInstance;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -49,7 +52,7 @@ public class ResetCommand extends SWCommand {
Region region = regionCheck(p); Region region = regionCheck(p);
if (region == null) return; if (region == null) return;
try { try {
PasteBuilder pasteBuilder = new PasteBuilder() PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile()))
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false); region.getArea().reset(pasteBuilder, false);
region.getRegionData().clear(); region.getRegionData().clear();
@@ -60,6 +63,35 @@ public class ResetCommand extends SWCommand {
} }
} }
@Register(description = "REGION_RESET_HELP_SCHEMATIC")
public void schematicResetCommand(@Validator Player p, SchematicNode node) {
Region region = regionCheck(p);
if (region == null) return;
if (!p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return;
}
if (Punishment.isPunished(SteamwarUser.get(p.getUniqueId()), Punishment.PunishmentType.NoSchemSharing, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMSHARING", p, punishment.getEndTime()))) {
return;
}
}
if (node.isDir()) {
BauSystem.MESSAGE.send("ONLY_SCHEMS", p);
return;
}
try {
PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.SchematicProvider(node))
.color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, true);
RegionUtils.message(region, "REGION_RESET_RESETED");
} catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_RESET_ERROR", p);
Bukkit.getLogger().log(Level.WARNING, "Failed reset", e);
}
}
private Region regionCheck(Player player) { private Region regionCheck(Player player) {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
if (region == RegionSystem.INSTANCE.getGlobalRegion()) { if (region == RegionSystem.INSTANCE.getGlobalRegion()) {
@@ -82,7 +82,6 @@ public class TNTListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.TNT).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.TNT).isWithDefault(TNTMode.ALLOW)) return null; if (region.getRegionData().get(Flag.TNT).isWithDefault(TNTMode.ALLOW)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.TNT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.TNT).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.TNT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.TNT).getWithDefault().getChatValue(), p);
} }
@@ -21,11 +21,9 @@ package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils; import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.region.utils.RegionExtensionType; import de.steamwar.bausystem.region.utils.RegionExtensionType;
import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.PreviousArguments; import de.steamwar.command.PreviousArguments;
@@ -57,12 +55,12 @@ public class TestblockCommand extends SWCommand {
super("testblock", "tb", "dummy"); super("testblock", "tb", "dummy");
} }
@Register(description = {"REGION_TB_HELP_RESET", "REGION_TB_HELP_RESET_EXTENSION"}) @Register
public void schematicTestblockCommand(@Validator Player p, TestblockParameterType... testblockParameterTypes) { public void schematicTestblockCommand(@Validator Player p, TestblockParameterType... testblockParameterTypes) {
schematicTestblockCommand(p, null, testblockParameterTypes); schematicTestblockCommand(p, null, testblockParameterTypes);
} }
@Register(description = {"REGION_TB_HELP_SCHEMATIC", "REGION_TB_HELP_SCHEMATIC_EXTENSION"}) @Register
public void schematicTestblockCommand(@Validator Player p, @Mapper("withPublic") SchematicNode node, TestblockParameterType... testblockParameterTypes) { public void schematicTestblockCommand(@Validator Player p, @Mapper("withPublic") SchematicNode node, TestblockParameterType... testblockParameterTypes) {
Set<TestblockParameterType> testblockParameterTypesSet = new HashSet<>(Arrays.asList(testblockParameterTypes)); Set<TestblockParameterType> testblockParameterTypesSet = new HashSet<>(Arrays.asList(testblockParameterTypes));
boolean isExtension = testblockParameterTypesSet.contains(TestblockParameterType.EXTENSION); boolean isExtension = testblockParameterTypesSet.contains(TestblockParameterType.EXTENSION);
@@ -108,7 +106,13 @@ public class TestblockCommand extends SWCommand {
region.getRegionData().setTestblockSchematic(node); region.getRegionData().setTestblockSchematic(node);
} }
PasteBuilder.ClipboardProvider clipboardProvider = node == null ? PasteBuilder.ClipboardProvider.EMPTY : PasteBuilder.ClipboardProvider.schematic(node); PasteBuilder.ClipboardProvider clipboardProvider;
if (node == null) {
clipboardProvider = new PasteBuilder.FileProvider(region.getTestblockArea().getResetFile());
} else {
clipboardProvider = new PasteBuilder.SchematicProvider(node);
}
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider) PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider)
.ignoreAir(ignoreAir) .ignoreAir(ignoreAir)
@@ -169,17 +173,6 @@ public class TestblockCommand extends SWCommand {
private Region regionCheck(Player player) { private Region regionCheck(Player player) {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
if (region.getRegionData().has(Flag.TESTBLOCK).isWritable() && region.getRegionData().get(Flag.TESTBLOCK).isWithDefault(TestblockMode.NO_VALUE)) {
Point minPoint = region.getArea().getMinPoint(false);
Point maxPoint = region.getArea().getMaxPoint(false);
// TODO: Check if empty!
int half = minPoint.getZ() + (maxPoint.getZ() - minPoint.getZ()) / 2;
if (player.getLocation().getBlockZ() <= half) {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.SOUTH);
} else {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.NORTH);
}
}
if (region.getTestblockArea().isEmpty()) { if (region.getTestblockArea().isEmpty()) {
BauSystem.MESSAGE.send("REGION_TB_NO_REGION", player); BauSystem.MESSAGE.send("REGION_TB_NO_REGION", player);
return null; return null;
@@ -1,49 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.linkage.Linked;
import org.bukkit.entity.Player;
@Linked
public class TestblockScoreboardElement implements ScoreboardElement {
@Override
public ScoreboardGroup getGroup() {
return ScoreboardGroup.REGION;
}
@Override
public int order() {
return 6;
}
@Override
public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.TESTBLOCK).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.TESTBLOCK).is(TestblockMode.NO_VALUE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.TESTBLOCK.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.TESTBLOCK).getWithDefault().getChatValue(), p);
}
}
@@ -53,7 +53,6 @@ public class WaterDestroyListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.WATER_DESTROY).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.ALLOW)) return null; if (region.getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.ALLOW)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.WATER_DESTROY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.WATER_DESTROY).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.WATER_DESTROY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.WATER_DESTROY).getWithDefault().getChatValue(), p);
} }
@@ -50,8 +50,16 @@ public class ResetBauGuiItem extends BauGuiItem {
@Override @Override
public boolean click(ClickType click, Player p) { public boolean click(ClickType click, Player p) {
p.closeInventory(); if (click == ClickType.LEFT) {
resetCommand.genericResetCommand(p); p.closeInventory();
resetCommand.genericResetCommand(p);
} else {
SchematicSelector selector = new SchematicSelector(p, SchematicSelector.selectSchematic(), node -> {
p.closeInventory();
resetCommand.schematicResetCommand(p, node);
});
selector.open();
}
return false; return false;
} }
@@ -79,12 +79,12 @@ public class RegionLib implements LuaLib {
Loader loader = Loader.getLoader(player); Loader loader = Loader.getLoader(player);
table.set("loader", getter(() -> loader == null ? "OFF" : loader.getStage().name())); table.set("loader", getter(() -> loader == null ? "OFF" : loader.getStage().name()));
table.set("copyPoint", getter(() -> toPos(region.get().getBuildArea().getCopyPoint(false)))); table.set("copyPoint", getter(() -> toPos(region.get().getBuildArea().getCopyPoint())));
table.set("minPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false)))); table.set("minPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false))));
table.set("maxPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false)))); table.set("maxPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false))));
table.set("minPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true)))); table.set("minPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true))));
table.set("maxPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true)))); table.set("maxPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true))));
table.set("testblockPoint", getter(() -> toPos(region.get().getTestblockArea().getCopyPoint(false)))); table.set("testblockPoint", getter(() -> toPos(region.get().getTestblockArea().getCopyPoint())));
table.set("minPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false)))); table.set("minPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false))));
table.set("maxPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false)))); table.set("maxPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false))));
table.set("minPointTestblockExtension", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(true)))); table.set("minPointTestblockExtension", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(true))));
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui; import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import de.steamwar.inventory.InvCallback; import de.steamwar.inventory.InvCallback;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import lombok.Getter; import lombok.Getter;
@@ -28,7 +29,6 @@ import lombok.Setter;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@@ -121,9 +121,9 @@ public abstract class SimulatorElement<T extends SimulatorPhase> {
public abstract String getType(); public abstract String getType();
public void saveExtra(YAPIONObject elementObject) { public void saveExtra(SimulatorConfig.Element element) {
} }
public void loadExtra(YAPIONObject elementObject) { public void loadExtra(SimulatorConfig.Element element) {
} }
} }
@@ -20,10 +20,10 @@
package de.steamwar.bausystem.features.simulator.data; package de.steamwar.bausystem.features.simulator.data;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -38,9 +38,9 @@ public abstract class SimulatorPhase {
public abstract void toSimulatorActions(Vector position, BiConsumer<Integer, SimulatorAction> tickStart, BiConsumer<Integer, SimulatorAction> tickEnd); public abstract void toSimulatorActions(Vector position, BiConsumer<Integer, SimulatorAction> tickStart, BiConsumer<Integer, SimulatorAction> tickEnd);
public void saveExtra(YAPIONObject phaseObject) { public void saveExtra(SimulatorConfig.Phase phase) {
} }
public void loadExtra(YAPIONObject phaseObject) { public void loadExtra(SimulatorConfig.Phase phase) {
} }
} }
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.observer;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase; import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@@ -31,7 +32,6 @@ import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.data.type.Observer; import org.bukkit.block.data.type.Observer;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -70,12 +70,12 @@ public final class ObserverPhase extends SimulatorPhase {
} }
@Override @Override
public void saveExtra(YAPIONObject phaseObject) { public void saveExtra(SimulatorConfig.Phase phase) {
phaseObject.add("orientation", orientation.name()); phase.setOrientation(orientation);
} }
@Override @Override
public void loadExtra(YAPIONObject phaseObject) { public void loadExtra(SimulatorConfig.Phase phase) {
orientation = BlockFace.valueOf(phaseObject.getPlainValue("orientation")); if (phase.getOrientation() != null) orientation = phase.getOrientation();
} }
} }
@@ -24,13 +24,13 @@ import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.gui.SimulatorTNTGui; import de.steamwar.bausystem.features.simulator.gui.SimulatorTNTGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui; import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import de.steamwar.inventory.InvCallback; import de.steamwar.inventory.InvCallback;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
public final class TNTElement extends SimulatorElement<TNTPhase> { public final class TNTElement extends SimulatorElement<TNTPhase> {
@@ -100,14 +100,14 @@ public final class TNTElement extends SimulatorElement<TNTPhase> {
} }
@Override @Override
public void saveExtra(YAPIONObject elementObject) { public void saveExtra(SimulatorConfig.Element element) {
elementObject.add("alignmentX", alignment.getX()); element.setAlignmentX(alignment.getX());
elementObject.add("alignmentZ", alignment.getZ()); element.setAlignmentZ(alignment.getZ());
} }
@Override @Override
public void loadExtra(YAPIONObject elementObject) { public void loadExtra(SimulatorConfig.Element element) {
alignment.setX(elementObject.getDoubleOrSetDefault("alignmentX", 0)); alignment.setX(element.getAlignmentX() != null ? element.getAlignmentX() : 0);
alignment.setZ(elementObject.getDoubleOrSetDefault("alignmentZ", 0)); alignment.setZ(element.getAlignmentZ() != null ? element.getAlignmentZ() : 0);
} }
} }
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.tnt;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase; import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.FreezeMode; import de.steamwar.bausystem.region.flags.FreezeMode;
@@ -31,7 +32,6 @@ import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.TNTPrimed;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -83,18 +83,18 @@ public final class TNTPhase extends SimulatorPhase {
} }
@Override @Override
public void saveExtra(YAPIONObject phaseObject) { public void saveExtra(SimulatorConfig.Phase phase) {
phaseObject.add("count", count); phase.setCount(count);
phaseObject.add("xJump", xJump); phase.setXJump(xJump);
phaseObject.add("yJump", yJump); phase.setYJump(yJump);
phaseObject.add("zJump", zJump); phase.setZJump(zJump);
} }
@Override @Override
public void loadExtra(YAPIONObject phaseObject) { public void loadExtra(SimulatorConfig.Phase phase) {
count = phaseObject.getPlainValue("count"); if (phase.getCount() != null) count = phase.getCount();
xJump = phaseObject.getPlainValue("xJump"); if (phase.getXJump() != null) xJump = phase.getXJump();
yJump = phaseObject.getPlainValue("yJump"); if (phase.getYJump() != null) yJump = phase.getYJump();
zJump = phaseObject.getPlainValue("zJump"); if (phase.getZJump() != null) zJump = phase.getZJump();
} }
} }
@@ -33,7 +33,7 @@ public class StabFinalizer extends StabStep {
@Override @Override
protected void start() { protected void start() {
try { try {
PasteBuilder.ClipboardProvider clipboardProvider = PasteBuilder.ClipboardProvider.clipboard(data.clipboard); PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(data.clipboard);
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider); PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider);
if (data.region.getRegionData().has(Flag.COLOR).isReadable()) { if (data.region.getRegionData().has(Flag.COLOR).isReadable()) {
pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault()); pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault());
@@ -72,7 +72,7 @@ public class StabGenerator extends StabStep implements Listener {
@Override @Override
protected void start() { protected void start() {
try { try {
PasteBuilder.ClipboardProvider clipboardProvider = PasteBuilder.ClipboardProvider.clipboard(data.clipboard); PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(data.clipboard);
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider); PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider);
if (data.region.getRegionData().has(Flag.COLOR).isReadable()) { if (data.region.getRegionData().has(Flag.COLOR).isReadable()) {
pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault()); pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault());
@@ -72,7 +72,7 @@ public class StabSetup extends StabStep {
if (TraceRecorder.instance.isAutoTraceEnabledInRegion(data.region)) { if (TraceRecorder.instance.isAutoTraceEnabledInRegion(data.region)) {
TraceRecorder.instance.removeAutoTraceRegion(data.region); TraceRecorder.instance.removeAutoTraceRegion(data.region);
} }
data.clipboard = FlatteningWrapper.impl.copy(data.region.getTestblockArea().getMinPoint(true), data.region.getTestblockArea().getMaxPoint(true), data.region.getTestblockArea().getCopyPoint(false)); data.clipboard = FlatteningWrapper.impl.copy(data.region.getTestblockArea().getMinPoint(true), data.region.getTestblockArea().getMaxPoint(true), data.region.getTestblockArea().getCopyPoint());
new StabDirection(data); new StabDirection(data);
} }
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator.storage; package de.steamwar.bausystem.features.simulator.storage;
import com.google.gson.JsonParseException;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement; import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
@@ -29,12 +30,7 @@ import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase; import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -51,44 +47,41 @@ public class SimFormatSimulatorLoader implements SimulatorLoader {
return Optional.empty(); return Optional.empty();
} }
YAPIONObject yapionObject; SimulatorConfig config;
try { try {
yapionObject = YAPIONParser.parse(file); config = SimulatorConfig.STORE.load(file);
} catch (YAPIONException | IOException e) { } catch (JsonParseException | IllegalStateException | IOException e) {
return Optional.empty(); return Optional.empty();
} }
String name = file.getName().substring(0, file.getName().length() - ".sim".length()); String name = file.getName().substring(0, file.getName().length() - ".sim".length());
Simulator simulator = new Simulator(name); Simulator simulator = new Simulator(name);
loadSimulator(yapionObject, simulator); loadSimulator(config, simulator);
return Optional.of(Collections.singletonList(simulator)); return Optional.of(Collections.singletonList(simulator));
} }
private void loadSimulator(YAPIONObject simulatorObject, Simulator simulator) { private void loadSimulator(SimulatorConfig config, Simulator simulator) {
simulator.setMaterial(Material.valueOf(simulatorObject.getPlainValue("material"))); if (config.getMaterial() != null) simulator.setMaterial(config.getMaterial());
simulator.setAutoTrace(simulatorObject.getPlainValue("autoTrace")); simulator.setAutoTrace(config.isAutoTrace());
simulator.setAutoTestblock(simulatorObject.getPlainValueOrDefault("autoTestblock", false)); simulator.setAutoTestblock(config.isAutoTestblock());
YAPIONArray groups = simulatorObject.getArray("groups"); config.getGroups().forEach(groupConfig -> {
groups.streamObject().forEach(groupObject -> {
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
loadGroup(groupObject, simulatorGroup); loadGroup(groupConfig, simulatorGroup);
simulator.add(simulatorGroup); simulator.add(simulatorGroup);
}); });
} }
private void loadGroup(YAPIONObject groupObject, SimulatorGroup group) { private void loadGroup(SimulatorConfig.Group groupConfig, SimulatorGroup group) {
group.setMaterial(Material.valueOf(groupObject.getPlainValue("material"))); if (groupConfig.getMaterial() != null) group.setMaterial(groupConfig.getMaterial());
group.setDisabled(groupObject.getPlainValue("disabled")); group.setDisabled(groupConfig.isDisabled());
YAPIONArray elements = groupObject.getArray("elements"); groupConfig.getElements().forEach(elementConfig -> {
elements.streamObject().forEach(elementObject -> {
SimulatorElement<?> element; SimulatorElement<?> element;
Supplier<SimulatorPhase> phaseConstructor; Supplier<SimulatorPhase> phaseConstructor;
Vector position = new Vector(elementObject.getDouble("x"), elementObject.getDouble("y"), elementObject.getDouble("z")); Vector position = new Vector(elementConfig.getX(), elementConfig.getY(), elementConfig.getZ());
String type = elementObject.getPlainValue("type"); switch (String.valueOf(elementConfig.getType())) {
switch (type) {
case "TNT": case "TNT":
element = new TNTElement(position); element = new TNTElement(position);
phaseConstructor = TNTPhase::new; phaseConstructor = TNTPhase::new;
@@ -109,28 +102,27 @@ public class SimFormatSimulatorLoader implements SimulatorLoader {
if (element == null) { if (element == null) {
return; return;
} }
loadElement(elementObject, element, phaseConstructor); loadElement(elementConfig, element, phaseConstructor);
group.add(element); group.add(element);
}); });
} }
private void loadElement(YAPIONObject elementObject, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) { private void loadElement(SimulatorConfig.Element elementConfig, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) {
element.setMaterial(Material.valueOf(elementObject.getPlainValue("material"))); if (elementConfig.getMaterial() != null) element.setMaterial(elementConfig.getMaterial());
element.setDisabled(elementObject.getPlainValue("disabled")); element.setDisabled(elementConfig.isDisabled());
element.loadExtra(elementObject); element.loadExtra(elementConfig);
YAPIONArray phases = elementObject.getArray("phases"); elementConfig.getPhases().forEach(phaseConfig -> {
phases.streamObject().forEach(phaseObject -> {
SimulatorPhase phase = phaseConstructor.get(); SimulatorPhase phase = phaseConstructor.get();
loadPhase(phaseObject, phase); loadPhase(phaseConfig, phase);
((SimulatorElement) element).add(phase); ((SimulatorElement) element).add(phase);
}); });
} }
private void loadPhase(YAPIONObject phaseObject, SimulatorPhase phase) { private void loadPhase(SimulatorConfig.Phase phaseConfig, SimulatorPhase phase) {
phase.setTickOffset(phaseObject.getPlainValue("tickOffset")); phase.setTickOffset(phaseConfig.getTickOffset());
phase.setLifetime(phaseObject.getPlainValue("lifetime")); phase.setLifetime(phaseConfig.getLifetime());
phase.setOrder(phaseObject.getPlainValue("order")); phase.setOrder(phaseConfig.getOrder());
phase.loadExtra(phaseObject); phase.loadExtra(phaseConfig);
} }
} }
@@ -0,0 +1,117 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import java.util.ArrayList;
import java.util.List;
/**
* The current stored form of a simulator, saved as JSON in a .sim file. Files
* saved before the version was stored have no version and are version 1.
* Fields missing in the stored JSON keep the default value of their initializer,
* so adding a new field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to SimulatorConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to
* this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class SimulatorConfig implements VersionedConfig {
public static final int VERSION = 1;
/**
* Files saved before the version was stored are version 1
*/
public static final ConfigStore<SimulatorConfig> STORE = new ConfigStore<>(SimulatorConfig.class, VERSION, SimulatorConfig::new)
.unversioned(1);
@Setter(AccessLevel.NONE)
protected int version = VERSION;
protected Material material = Material.BARREL;
protected boolean autoTrace = false;
protected boolean autoTestblock = false;
protected List<Group> groups = new ArrayList<>();
@Getter
@Setter
public static class Group {
protected Material material = Material.CHEST;
protected boolean disabled = false;
protected List<Element> elements = new ArrayList<>();
}
@Getter
@Setter
public static class Element {
/**
* One of "TNT", "Redstone" or "Observer"
*/
protected String type;
protected Material material;
protected boolean disabled = false;
protected double x;
protected double y;
protected double z;
/**
* Only used by TNT
*/
protected Double alignmentX;
protected Double alignmentZ;
protected List<Phase> phases = new ArrayList<>();
}
@Getter
@Setter
public static class Phase {
protected int tickOffset;
protected int lifetime;
protected int order;
/**
* Only used by TNT
*/
protected Integer count;
protected Boolean xJump;
protected Boolean yJump;
protected Boolean zJump;
/**
* Only used by Observer
*/
protected BlockFace orientation;
}
}
@@ -19,24 +19,30 @@
package de.steamwar.bausystem.features.simulator.storage; package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.config.legacy.YapionReader;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static de.steamwar.bausystem.config.legacy.YapionReader.*;
/**
* Loads the legacy .simulator files stored as YAPION, they are deleted after loading
* and saved in the current format by the {@link SimulatorSaver}.
*/
@Deprecated
public class SimulatorFormatSimulatorLoader implements SimulatorLoader { public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
@Override @Override
@@ -45,20 +51,19 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return Optional.empty(); return Optional.empty();
} }
YAPIONObject yapionObject; Map<String, Object> yapionObject;
try { try {
yapionObject = YAPIONParser.parse(file); yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8));
} catch (YAPIONException | IOException e) { } catch (IllegalArgumentException | IOException e) {
return Optional.empty(); return Optional.empty();
} }
String name = file.getName().substring(0, file.getName().length() - ".simulator".length()); String name = file.getName().substring(0, file.getName().length() - ".simulator".length());
Simulator simulator = new Simulator(name); Simulator simulator = new Simulator(name);
simulator.setMaterial(Material.valueOf(yapionObject.getStringOrDefault("material", "BARREL"))); simulator.setMaterial(Material.valueOf(getString(yapionObject, "material", "BARREL")));
YAPIONArray tntElements = yapionObject.getArrayOrDefault("tntElements", new YAPIONArray()); for (Map<?, ?> element : getObjects(yapionObject, "tntElements")) {
for (YAPIONObject element : tntElements.streamObject().collect(Collectors.toList())) {
if (element.containsKey("elements")) { if (element.containsKey("elements")) {
simulator.add(loadGroup(element)); simulator.add(loadGroup(element));
} else { } else {
@@ -70,27 +75,26 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return Optional.of(Collections.singletonList(simulator)); return Optional.of(Collections.singletonList(simulator));
} }
private SimulatorGroup loadGroup(YAPIONObject element) { private SimulatorGroup loadGroup(Map<?, ?> element) {
double x = element.getDoubleOrDefault("x", 0); double x = getDouble(element, "x", 0);
double y = element.getDoubleOrDefault("y", 0); double y = getDouble(element, "y", 0);
double z = element.getDoubleOrDefault("z", 0); double z = getDouble(element, "z", 0);
int tickOffset = element.getIntOrDefault("tickOffset", 0); int tickOffset = getInt(element, "tickOffset", 0);
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
simulatorGroup.setDisabled(element.getBooleanOrDefault("disabled", false)); simulatorGroup.setDisabled(getBoolean(element, "disabled", false));
YAPIONArray elements = element.getArrayOrDefault("elements", new YAPIONArray()); for (Map<?, ?> e : getObjects(element, "elements")) {
for (YAPIONObject e : elements.streamObject().collect(Collectors.toList())) {
simulatorGroup.add(loadElement(e, x, y, z, tickOffset)); simulatorGroup.add(loadElement(e, x, y, z, tickOffset));
} }
return simulatorGroup; return simulatorGroup;
} }
private TNTElement loadElement(YAPIONObject element, double dx, double dy, double dz, int dTickOffset) { private TNTElement loadElement(Map<?, ?> element, double dx, double dy, double dz, int dTickOffset) {
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x") + dx, element.getDouble("y") + dy, element.getDouble("z") + dz)); TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x") + dx, requireDouble(element, "y") + dy, requireDouble(element, "z") + dz));
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); tntElement.setDisabled(getBoolean(element, "disabled", false));
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
TNTPhase tntPhase = loadPhase(element); TNTPhase tntPhase = loadPhase(element);
tntPhase.setTickOffset(tntPhase.getTickOffset() + dTickOffset); tntPhase.setTickOffset(tntPhase.getTickOffset() + dTickOffset);
@@ -99,12 +103,12 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return tntElement; return tntElement;
} }
private SimulatorGroup loadElement(YAPIONObject element) { private SimulatorGroup loadElement(Map<?, ?> element) {
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x"), element.getDouble("y"), element.getDouble("z"))); TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x"), requireDouble(element, "y"), requireDouble(element, "z")));
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); tntElement.setDisabled(getBoolean(element, "disabled", false));
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
simulatorGroup.add(tntElement); simulatorGroup.add(tntElement);
tntElement.add(loadPhase(element)); tntElement.add(loadPhase(element));
@@ -112,15 +116,15 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return simulatorGroup; return simulatorGroup;
} }
private TNTPhase loadPhase(YAPIONObject element) { private TNTPhase loadPhase(Map<?, ?> element) {
TNTPhase tntPhase = new TNTPhase(); TNTPhase tntPhase = new TNTPhase();
tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80)); tntPhase.setLifetime(getInt(element, "fuseTicks", 80));
tntPhase.setCount(element.getIntOrDefault("count", 1)); tntPhase.setCount(getInt(element, "count", 1));
tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0)); tntPhase.setTickOffset(getInt(element, "tickOffset", 0));
tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false)); tntPhase.setXJump(getBoolean(element, "xVelocity", false));
tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false)); tntPhase.setYJump(getBoolean(element, "yVelocity", false));
tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false)); tntPhase.setZJump(getBoolean(element, "zVelocity", false));
switch (element.getStringOrDefault("order", "REPEATER")) { switch (getString(element, "order", "REPEATER")) {
case "REPEATER": case "REPEATER":
tntPhase.setOrder(0); tntPhase.setOrder(0);
break; break;
@@ -133,4 +137,8 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
} }
return tntPhase; return tntPhase;
} }
private double requireDouble(Map<?, ?> element, String key) {
return ((Number) element.get(key)).doubleValue();
}
} }
@@ -21,9 +21,6 @@ package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import yapion.hierarchy.output.FileOutput;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -36,48 +33,42 @@ public class SimulatorSaver {
directory.mkdirs(); directory.mkdirs();
} }
YAPIONObject simulatorObject = new YAPIONObject(); SimulatorConfig config = new SimulatorConfig();
simulatorObject.add("material", simulator.getMaterial().name()); config.setMaterial(simulator.getMaterial());
simulatorObject.add("autoTrace", simulator.isAutoTrace()); config.setAutoTrace(simulator.isAutoTrace());
simulatorObject.add("autoTestblock", simulator.isAutoTestblock()); config.setAutoTestblock(simulator.isAutoTestblock());
YAPIONArray groups = new YAPIONArray();
simulator.getGroups().forEach(group -> { simulator.getGroups().forEach(group -> {
YAPIONObject groupObject = new YAPIONObject(); SimulatorConfig.Group groupConfig = new SimulatorConfig.Group();
groupObject.add("material", group.getMaterial().name()); groupConfig.setMaterial(group.getMaterial());
groupObject.add("disabled", group.isDisabled()); groupConfig.setDisabled(group.isDisabled());
YAPIONArray elements = new YAPIONArray();
group.getElements().forEach(element -> { group.getElements().forEach(element -> {
YAPIONObject elementObject = new YAPIONObject(); SimulatorConfig.Element elementConfig = new SimulatorConfig.Element();
elementObject.add("type", element.getType()); elementConfig.setType(element.getType());
elementObject.add("material", element.getMaterial().name()); elementConfig.setMaterial(element.getMaterial());
elementObject.add("disabled", element.isDisabled()); elementConfig.setDisabled(element.isDisabled());
elementObject.add("x", element.getPosition().getX()); elementConfig.setX(element.getPosition().getX());
elementObject.add("y", element.getPosition().getY()); elementConfig.setY(element.getPosition().getY());
elementObject.add("z", element.getPosition().getZ()); elementConfig.setZ(element.getPosition().getZ());
element.saveExtra(elementObject); element.saveExtra(elementConfig);
YAPIONArray phases = new YAPIONArray();
element.getPhases().forEach(phase -> { element.getPhases().forEach(phase -> {
YAPIONObject phaseObject = new YAPIONObject(); SimulatorConfig.Phase phaseConfig = new SimulatorConfig.Phase();
phaseObject.add("tickOffset", phase.getTickOffset()); phaseConfig.setTickOffset(phase.getTickOffset());
phaseObject.add("lifetime", phase.getLifetime()); phaseConfig.setLifetime(phase.getLifetime());
phaseObject.add("order", phase.getOrder()); phaseConfig.setOrder(phase.getOrder());
phase.saveExtra(phaseObject); phase.saveExtra(phaseConfig);
phases.add(phaseObject); elementConfig.getPhases().add(phaseConfig);
}); });
elementObject.add("phases", phases); groupConfig.getElements().add(elementConfig);
elements.add(elementObject);
}); });
groupObject.add("elements", elements); config.getGroups().add(groupConfig);
groups.add(groupObject);
}); });
simulatorObject.add("groups", groups);
File file = new File(directory, simulator.getName() + ".sim"); File file = new File(directory, simulator.getName() + ".sim");
try { try {
simulatorObject.toJSONLossy(new FileOutput(file)).close(); SimulatorConfig.STORE.save(file, config);
} catch (IOException e) { } catch (IOException e) {
// Ignore // Ignore
} }
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator.storage; package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.config.legacy.YapionReader;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
@@ -26,18 +27,23 @@ import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static de.steamwar.bausystem.config.legacy.YapionReader.*;
/**
* Loads the legacy per player .yapion files containing multiple simulators, they are
* deleted after loading and saved in the current format by the {@link SimulatorSaver}.
*/
@Deprecated
public class YAPIONFormatSimulatorLoader implements SimulatorLoader { public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
@Override @Override
@@ -46,10 +52,10 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
return Optional.empty(); return Optional.empty();
} }
YAPIONObject yapionObject; Map<String, Object> yapionObject;
try { try {
yapionObject = YAPIONParser.parse(file); yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8));
} catch (YAPIONException | IOException e) { } catch (IllegalArgumentException | IOException e) {
return Optional.empty(); return Optional.empty();
} }
@@ -57,28 +63,28 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name)); SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name));
List<Simulator> simulators = new ArrayList<>(); List<Simulator> simulators = new ArrayList<>();
for (String s : yapionObject.getKeys()) { for (String s : yapionObject.keySet()) {
String newName = steamwarUser.getUserName() + (s.isEmpty() ? "" : "_" + s); String newName = steamwarUser.getUserName() + (s.isEmpty() ? "" : "_" + s);
YAPIONArray content = yapionObject.getArray(s); List<Map<?, ?>> content = getObjects(yapionObject, s);
if (content.isEmpty()) continue; if (content.isEmpty()) continue;
Simulator simulator = new Simulator(newName); Simulator simulator = new Simulator(newName);
for (YAPIONObject element : content.streamObject().collect(Collectors.toList())) { for (Map<?, ?> element : content) {
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
simulator.add(simulatorGroup); simulator.add(simulatorGroup);
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("positionX"), element.getDouble("positionY"), element.getDouble("positionZ"))); TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "positionX"), requireDouble(element, "positionY"), requireDouble(element, "positionZ")));
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); // IDK if this existed back then? tntElement.setDisabled(getBoolean(element, "disabled", false)); // IDK if this existed back then?
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
simulatorGroup.add(tntElement); simulatorGroup.add(tntElement);
TNTPhase tntPhase = new TNTPhase(); TNTPhase tntPhase = new TNTPhase();
tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80)); tntPhase.setLifetime(getInt(element, "fuseTicks", 80));
tntPhase.setCount(element.getIntOrDefault("count", 1)); tntPhase.setCount(getInt(element, "count", 1));
tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0)); tntPhase.setTickOffset(getInt(element, "tickOffset", 0));
tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false)); tntPhase.setXJump(getBoolean(element, "xVelocity", false));
tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false)); tntPhase.setYJump(getBoolean(element, "yVelocity", false));
tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false)); tntPhase.setZJump(getBoolean(element, "zVelocity", false));
tntPhase.setOrder(element.getBooleanOrDefault("comparator", false) ? 2 : 0); tntPhase.setOrder(getBoolean(element, "comparator", false) ? 2 : 0);
tntElement.add(tntPhase); tntElement.add(tntPhase);
} }
simulators.add(simulator); simulators.add(simulator);
@@ -87,4 +93,8 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
file.delete(); file.delete();
return Optional.of(simulators); return Optional.of(simulators);
} }
private double requireDouble(Map<?, ?> element, String key) {
return ((Number) element.get(key)).doubleValue();
}
} }
@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.slaves.laufbau;
import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.features.slaves.laufbau.converter.LaufbauConfigV1ToV2Converter;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
/**
* The current laufbau config of a player. It is stored as JSON, fields missing
* in the stored JSON keep the default value of their initializer, so adding a
* new field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to LaufbauConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to
* this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class LaufbauConfig implements VersionedConfig {
public static final int VERSION = 2;
/**
* LaufbauConfigV1 is stored as Base64 of gzipped YAPION, the current version as JSON
*/
public static final ConfigStore<LaufbauConfig> STORE = new ConfigStore<>(LaufbauConfig.class, VERSION, LaufbauConfig::new)
.legacy(s -> !s.startsWith("{"), LaufbauConfigV1::parse)
.converter(new LaufbauConfigV1ToV2Converter())
.userConfig("bausystem-laufbau")
.removeIf(config -> config.getDeactivated().isEmpty());
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* Identifiers of the deactivated {@link BlockBoundingBox}es, see {@link LaufbauUtils#identifier(BlockBoundingBox)}
*/
protected Set<String> deactivated = new HashSet<>();
}
@@ -0,0 +1,65 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.slaves.laufbau;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.legacy.YapionReader;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* The legacy laufbau config that was stored as Base64 of the gzipped YAPION,
* an object with the deactivated identifiers as keys.
*/
@Getter
@Setter
@Deprecated
public class LaufbauConfigV1 implements VersionedConfig {
public static final int VERSION = 1;
@Setter(AccessLevel.NONE)
protected int version = VERSION;
protected Set<String> deactivated = new HashSet<>();
public static LaufbauConfigV1 parse(String base64) {
String yapion;
try (InputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64)))) {
yapion = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
LaufbauConfigV1 config = new LaufbauConfigV1();
config.deactivated.addAll(YapionReader.parse(yapion).keySet());
return config;
}
}
@@ -20,63 +20,22 @@
package de.steamwar.bausystem.features.slaves.laufbau; package de.steamwar.bausystem.features.slaves.laufbau;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.core.SWPlayer;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import de.steamwar.sql.UserConfig;
import lombok.Cleanup;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import yapion.hierarchy.output.StreamOutput;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import yapion.parser.options.StreamOptions;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@UtilityClass @UtilityClass
public class LaufbauUtils { public class LaufbauUtils {
public static class LaufbauComponent implements SWPlayer.Component {
private YAPIONObject yapionObject = new YAPIONObject();
@Override
@SneakyThrows
public void onMount(SWPlayer player) {
String config = UserConfig.getConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau");
if (config == null) {
return;
}
byte[] bytes = Base64.getDecoder().decode(config);
@Cleanup GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(bytes));
yapionObject = new YAPIONParser(gzipInputStream, new StreamOptions().stopOnStreamEnd(true)).parse().result();
}
@Override
@SneakyThrows
public void onUnmount(SWPlayer player) {
if (yapionObject.isEmpty()) {
UserConfig.removePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau");
return;
}
@Cleanup ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@Cleanup GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
yapionObject.toYAPION(new StreamOutput(gzipOutputStream)).close();
UserConfig.updatePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau", Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
}
}
public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) { public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) {
return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ)); return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ));
} }
@@ -123,7 +82,7 @@ public class LaufbauUtils {
return false; return false;
} }
String identifier = identifier(bb); String identifier = identifier(bb);
return SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject.containsKey(identifier); return LaufbauConfig.STORE.get(p).getDeactivated().contains(identifier);
} }
@SneakyThrows @SneakyThrows
@@ -132,11 +91,9 @@ public class LaufbauUtils {
return; return;
} }
String identifier = identifier(bb); String identifier = identifier(bb);
YAPIONObject yapionObject = SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject; Set<String> deactivated = LaufbauConfig.STORE.get(p).getDeactivated();
if (yapionObject.containsKey(identifier)) { if (!deactivated.remove(identifier)) {
yapionObject.remove(identifier); deactivated.add(identifier);
} else {
yapionObject.put(identifier, new YAPIONObject());
} }
} }
} }
@@ -17,19 +17,24 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.bausystem.region.dynamic.modes.wargear_45; package de.steamwar.bausystem.features.slaves.laufbau.converter;
import de.steamwar.sql.GameModeConfig; import de.steamwar.bausystem.config.ConfigConverter;
import lombok.experimental.UtilityClass; import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfig;
import org.bukkit.Material; import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfigV1;
@UtilityClass @Deprecated
public class WG45Utils { public class LaufbauConfigV1ToV2Converter implements ConfigConverter<LaufbauConfigV1, LaufbauConfig> {
public static final GameModeConfig<Material, String> GAME_MODE_CONFIG; @Override
public int version() {
return LaufbauConfigV1.VERSION;
}
static { @Override
GameModeConfig<Material, String> config = GameModeConfig.getByGameName("WarGear"); public LaufbauConfig convert(LaufbauConfigV1 old) {
GAME_MODE_CONFIG = config != null ? config : GameModeConfig.getDefaults(); LaufbauConfig config = new LaufbauConfig();
config.getDeactivated().addAll(old.getDeactivated());
return config;
} }
} }
@@ -35,8 +35,8 @@ public class SmartPlaceCommand extends SWCommand {
@Register(description = {"SMART_PLACE_HELP", "SMART_PLACE_INFO"}) @Register(description = {"SMART_PLACE_HELP", "SMART_PLACE_INFO"})
public void genericToggle(Player p) { public void genericToggle(Player p) {
boolean smartPlace = Config.getInstance().get(p).getPlainValueOrDefault("smartPlace", false); boolean smartPlace = Config.STORE.get(p).isSmartPlace();
Config.getInstance().get(p).put("smartPlace", !smartPlace); Config.STORE.get(p).setSmartPlace(!smartPlace);
if (!smartPlace) { if (!smartPlace) {
SWUtils.sendToActionbar(p, BauSystem.MESSAGE.parse("SMART_PLACE_ENABLE", p)); SWUtils.sendToActionbar(p, BauSystem.MESSAGE.parse("SMART_PLACE_ENABLE", p));
BauSystem.MESSAGE.send("SMART_PLACE_INFO", p); BauSystem.MESSAGE.send("SMART_PLACE_INFO", p);
@@ -25,6 +25,7 @@ import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import net.minecraft.world.InteractionHand;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
@@ -87,7 +88,9 @@ public class SmartPlaceListener implements Listener {
public SmartPlaceListener() { public SmartPlaceListener() {
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> { TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
if (!Permission.BUILD.hasPermission(player)) return packet; if (!Permission.BUILD.hasPermission(player)) return packet;
if (!Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false)) return packet; if (!Config.STORE.get(player).isSmartPlace()) return packet;
ItemStack usedItem = packet.getHand() == InteractionHand.MAIN_HAND ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand();
if (usedItem.getType() == Material.DEBUG_STICK) return packet;
RayTraceResult rayTraceResult = player.rayTraceBlocks(6); RayTraceResult rayTraceResult = player.rayTraceBlocks(6);
Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null; Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null;
BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null; BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null;
@@ -127,7 +130,7 @@ public class SmartPlaceListener implements Listener {
@EventHandler @EventHandler
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return; if (!Config.STORE.get(event.getPlayer()).isSmartPlace()) return;
WAS_EXECUTED.add(event.getPlayer()); WAS_EXECUTED.add(event.getPlayer());
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return; if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return;
@@ -146,7 +149,7 @@ public class SmartPlaceListener implements Listener {
@EventHandler @EventHandler
public void onBlockPlace(BlockPlaceEvent event) { public void onBlockPlace(BlockPlaceEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return; if (!Config.STORE.get(event.getPlayer()).isSmartPlace()) return;
if (!SMART_PLACING.contains(event.getPlayer())) { if (!SMART_PLACING.contains(event.getPlayer())) {
if (CONTAINERS.contains(event.getBlockAgainst().getType())) { if (CONTAINERS.contains(event.getBlockAgainst().getType())) {
SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup(); SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup();
@@ -35,18 +35,15 @@ public class BlockCounter {
public final Map<Region, BlockCount> blockCountMap = new HashMap<>(); public final Map<Region, BlockCount> blockCountMap = new HashMap<>();
public boolean isActive(final Player p) { public boolean isActive(final Player p) {
if (!Config.getInstance().get(p).containsKey("blockCounter")) { return Config.STORE.get(p).isBlockCounter();
return false;
}
return Config.getInstance().get(p).getPlainValue("blockCounter");
} }
public void enable(final Player p) { public void enable(final Player p) {
Config.getInstance().get(p).put("blockCounter", true); Config.STORE.get(p).setBlockCounter(true);
} }
public void disable(final Player p) { public void disable(final Player p) {
Config.getInstance().get(p).put("blockCounter", false); Config.STORE.get(p).setBlockCounter(false);
} }
public String getMessage(Player player, int count, int tntCount, long tick, long lastTick) { public String getMessage(Player player, int count, int tntCount, long tick, long lastTick) {
@@ -36,13 +36,10 @@ import org.bukkit.entity.Player;
import org.bukkit.util.Transformation; import org.bukkit.util.Transformation;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import yapion.hierarchy.types.YAPIONValue;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DETAILS;
/** /**
* Wrapper for the rendering of a record bundle * Wrapper for the rendering of a record bundle
*/ */
@@ -102,7 +99,7 @@ public class TraceEntity extends RBlockDisplay {
* @param player the player the message should be printed for * @param player the player the message should be printed for
*/ */
public void printIntoChat(Player player) { public void printIntoChat(Player player) {
if (!Config.getInstance().get(player).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { if (!Config.STORE.get(player).isTntClickDetails()) {
return; return;
} }
TNTPoint representative = records.get(0); TNTPoint representative = records.get(0);
@@ -30,7 +30,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import yapion.hierarchy.types.YAPIONObject;
@Linked @Linked
public class NightVisionCommand extends SWCommand implements Listener { public class NightVisionCommand extends SWCommand implements Listener {
@@ -41,15 +40,15 @@ public class NightVisionCommand extends SWCommand implements Listener {
@Register(description = "NIGHT_VISION_HELP") @Register(description = "NIGHT_VISION_HELP")
public void genericCommand(Player p) { public void genericCommand(Player p) {
YAPIONObject yapionObject = Config.getInstance().get(p); Config config = Config.STORE.get(p);
boolean value = !yapionObject.getBooleanOrDefault("nightvision", false); boolean value = !config.isNightVision();
yapionObject.put("nightvision", value); config.setNightVision(value);
setNightVision(p, value); setNightVision(p, value);
} }
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
setNightVision(event.getPlayer(), Config.getInstance().get(event.getPlayer()).getBooleanOrDefault("nightvision", false)); setNightVision(event.getPlayer(), Config.STORE.get(event.getPlayer()).isNightVision());
} }
private void setNightVision(Player p, boolean value) { private void setNightVision(Player p, boolean value) {
@@ -22,122 +22,71 @@ package de.steamwar.bausystem.features.util;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.core.ProtocolWrapper; import de.steamwar.core.ProtocolWrapper;
import de.steamwar.core.SWPlayer; import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.*; import io.papermc.paper.event.player.PlayerFailMoveEvent;
import net.minecraft.world.entity.player.Abilities; import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockCanBuildEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent; import org.bukkit.event.player.PlayerToggleFlightEvent;
import java.util.function.BiFunction;
@Linked @Linked
public class NoClipCommand extends SWCommand implements Listener { public class NoClipCommand extends SWCommand implements Listener {
public static class NoClipData implements SWPlayer.Component { public static class NoClipData implements SWPlayer.Component {
private long lastTick = -1;
@Override @Override
public void onUnmount(SWPlayer player) { public void onUnmount(SWPlayer swPlayer) {
player.getPlayer().setGameMode(GameMode.CREATIVE); Player player = swPlayer.getPlayer();
showClientGameMode(player, GameMode.CREATIVE, GameMode.CREATIVE);
} }
} }
public NoClipCommand() { public NoClipCommand() {
super("noclip", "nc"); super("noclip", "nc");
BiFunction<Player, ServerboundMovePlayerPacket, Object> first = (player, o) -> {
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
if (noClipData == null) return o;
if (noClipData.lastTick == TPSUtils.currentTick.get()) return o;
setInternalGameMode(player, GameMode.SPECTATOR);
noClipData.lastTick = TPSUtils.currentTick.get();
return o;
};
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, first);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, first);
BiFunction<Player, Object, Object> second = (player, o) -> {
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
if (noClipData == null) return o;
setInternalGameMode(player, GameMode.CREATIVE);
noClipData.lastTick = TPSUtils.currentTick.get();
return o;
};
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, second);
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, second);
TinyProtocol.instance.addFilter(ServerboundContainerClickPacket.class, second);
BiFunction<Player, ServerboundSetCreativeModeSlotPacket, Object> third = (player, o) -> {
if (SWPlayer.of(player).hasComponent(NoClipData.class)) {
int index = o.slotNum();
if (index >= 36 && index <= 44) {
index -= 36;
} else if (index > 44) {
index -= 5;
} else if (index <= 8) {
index = index - 8 + 36;
}
player.getInventory().setItem(index, CraftItemStack.asBukkitCopy(o.itemStack()));
if (index < 9) player.getInventory().setHeldItemSlot(index);
player.updateInventory();
}
return o;
};
TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third);
} }
private void setInternalGameMode(Player player, GameMode gameMode) { @Register
((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue());
}
@Register(help = true)
public void genericCommand(@Validator Player player) { public void genericCommand(@Validator Player player) {
SWPlayer swPlayer = SWPlayer.of(player); SWPlayer swPlayer = SWPlayer.of(player);
if (swPlayer.hasComponent(NoClipData.class)) { if (swPlayer.hasComponent(NoClipData.class)) {
swPlayer.removeComponent(NoClipData.class); swPlayer.removeComponent(NoClipData.class);
} else { } else {
player.setGameMode(GameMode.SPECTATOR); player.setGameMode(GameMode.CREATIVE);
Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities(); player.setAllowFlight(true);
abilities.mayBuild = true; player.setFlying(true);
abilities.mayfly = true;
swPlayer.setComponent(new NoClipData()); swPlayer.setComponent(new NoClipData());
BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player); BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player);
TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, 1F)); showClientGameMode(player, GameMode.CREATIVE, GameMode.SPECTATOR);
pseudoGameMode(player, GameMode.SPECTATOR);
} }
} }
@EventHandler @EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) { public void onPlayerFailMove(PlayerFailMoveEvent event) {
event.getNewSpectator().forEach(player -> { if (event.getFailReason() != PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK) return;
SWPlayer.of(player).removeComponent(NoClipData.class); if (!SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) return;
}); event.setAllowed(true);
} }
@EventHandler(ignoreCancelled = true) @EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
event.getNewSpectator().forEach(player -> SWPlayer.of(player).removeComponent(NoClipData.class));
}
@EventHandler
public void onPlayerGameModeChange(PlayerGameModeChangeEvent event) { public void onPlayerGameModeChange(PlayerGameModeChangeEvent event) {
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true); event.setCancelled(true);
} }
} }
@EventHandler(ignoreCancelled = true) @EventHandler
public void onBlock(BlockCanBuildEvent event) { public void onBlock(BlockCanBuildEvent event) {
if (event.getPlayer() == null) return; if (event.getPlayer() == null) return;
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
@@ -145,7 +94,7 @@ public class NoClipCommand extends SWCommand implements Listener {
} }
} }
@EventHandler(ignoreCancelled = true) @EventHandler
public void onPlayerToggleFlight(PlayerToggleFlightEvent event) { public void onPlayerToggleFlight(PlayerToggleFlightEvent event) {
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true); event.setCancelled(true);
@@ -153,20 +102,13 @@ public class NoClipCommand extends SWCommand implements Listener {
} }
} }
@EventHandler(ignoreCancelled = true) /**
public void onPlayerTeleport(PlayerTeleportEvent event) { * @param player player
if (event.getCause() != PlayerTeleportEvent.TeleportCause.SPECTATE) { * @param localMode GameMode for breaking and placing
return; * @param listedMode GameMode for noPhysics
} */
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { private static void showClientGameMode(Player player, GameMode localMode, GameMode listedMode) {
event.setCancelled(true); TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, localMode.getValue()));
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), listedMode));
event.getPlayer().setSpectatorTarget(null);
}, 1L);
}
} }
}
private static void pseudoGameMode(Player player, GameMode gameMode) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), gameMode));
}
}
@@ -23,15 +23,17 @@ import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.Permission;
import de.steamwar.entity.*; import de.steamwar.entity.*;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.piston.PistonStructureResolver;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.TileState;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Piston; import org.bukkit.block.data.type.Piston;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Display; import org.bukkit.entity.Display;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay; import org.bukkit.entity.TextDisplay;
@@ -92,41 +94,48 @@ public class PistonCalculator implements Listener {
@EventHandler @EventHandler
public void onBlockPistonExtend(BlockPistonExtendEvent event) { public void onBlockPistonExtend(BlockPistonExtendEvent event) {
if (pistOrders.isEmpty()) return; if (pistOrders.isEmpty()) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { removeMovedPistOrders(event.getBlocks());
movePistOrders(event.getDirection(), event.getBlocks()); scheduleRecalculate();
pistOrders.values().forEach(PistOrder::calculate);
}, 3);
} }
@EventHandler @EventHandler
public void onBlockPistonRetract(BlockPistonRetractEvent event) { public void onBlockPistonRetract(BlockPistonRetractEvent event) {
if (pistOrders.isEmpty()) return; if (pistOrders.isEmpty()) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { removeMovedPistOrders(event.getBlocks());
movePistOrders(event.getDirection(), event.getBlocks()); scheduleRecalculate();
pistOrders.values().forEach(PistOrder::calculate);
}, 3);
} }
private void movePistOrders(BlockFace direction, List<Block> blocks) { private void removeMovedPistOrders(List<Block> blocks) {
Set<PistOrder> orders = new HashSet<>();
blocks.forEach(block -> { blocks.forEach(block -> {
PistOrder pistOrder = pistOrders.get(block.getLocation()); PistOrder pistOrder = pistOrders.remove(block.getLocation());
if (pistOrder == null) return; if (pistOrder != null) pistOrder.server.close();
pistOrders.remove(block.getLocation());
pistOrder.piston = pistOrder.piston.getRelative(direction);
orders.add(pistOrder);
});
orders.forEach(pistOrder -> {
pistOrders.put(pistOrder.piston.getLocation(), pistOrder);
}); });
} }
private static final long RECALCULATE_INTERVAL_MS = 200;
private boolean recalculationScheduled = false;
private long lastRecalculation = 0;
private void scheduleRecalculate() {
if (recalculationScheduled) return;
recalculationScheduled = true;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), this::recalculateIfDue, 1);
}
private void recalculateIfDue() {
if (System.currentTimeMillis() - lastRecalculation < RECALCULATE_INTERVAL_MS) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), this::recalculateIfDue, 1);
return;
}
recalculationScheduled = false;
lastRecalculation = System.currentTimeMillis();
pistOrders.values().forEach(PistOrder::calculate);
}
@EventHandler @EventHandler
public void onBlockPlace(BlockPlaceEvent event) { public void onBlockPlace(BlockPlaceEvent event) {
if (pistOrders.isEmpty()) return; if (pistOrders.isEmpty()) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { scheduleRecalculate();
pistOrders.values().forEach(PistOrder::calculate);
}, 1);
} }
@EventHandler @EventHandler
@@ -136,9 +145,7 @@ public class PistonCalculator implements Listener {
PistOrder pistOrder = pistOrders.remove(event.getBlock().getLocation()); PistOrder pistOrder = pistOrders.remove(event.getBlock().getLocation());
pistOrder.server.close(); pistOrder.server.close();
} }
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { scheduleRecalculate();
pistOrders.values().forEach(PistOrder::calculate);
}, 1);
} }
@EventHandler @EventHandler
@@ -164,13 +171,13 @@ public class PistonCalculator implements Listener {
private final Map<Location, PistOrder> pistOrders = new HashMap<>(); private final Map<Location, PistOrder> pistOrders = new HashMap<>();
private final class PistOrder { private final class PistOrder {
private Block piston; private final Block piston;
private REntityServer server = new REntityServer(); private REntityServer server = new REntityServer();
private boolean pulling = false; private boolean pulling = false;
private List<Location> movedBlocks = new ArrayList<>(); private List<Location> movedBlocks = new ArrayList<>();
private List<Location> brokenBlocks = new ArrayList<>(); private List<Location> brokenBlocks = new ArrayList<>();
private Set<Location> immovableBlocks = new HashSet<>(); private boolean blocked = false;
public PistOrder(Block piston) { public PistOrder(Block piston) {
this.piston = piston; this.piston = piston;
@@ -179,7 +186,7 @@ public class PistonCalculator implements Listener {
public void calculate() { public void calculate() {
movedBlocks.clear(); movedBlocks.clear();
brokenBlocks.clear(); brokenBlocks.clear();
immovableBlocks.clear(); blocked = false;
if (piston.isEmpty()) { if (piston.isEmpty()) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
@@ -199,7 +206,7 @@ public class PistonCalculator implements Listener {
pulling = piston.getType() == Material.STICKY_PISTON && (piston.getRelative(pistonData.getFacing()).getType() == Material.AIR || pistonData.isExtended()); pulling = piston.getType() == Material.STICKY_PISTON && (piston.getRelative(pistonData.getFacing()).getType() == Material.AIR || pistonData.isExtended());
calculate(piston, pistonData.getFacing(), (pulling ? pistonData.getFacing().getOppositeFace() : pistonData.getFacing())); resolve(pistonData.getFacing());
Collections.reverse(movedBlocks); Collections.reverse(movedBlocks);
Collections.reverse(brokenBlocks); Collections.reverse(brokenBlocks);
@@ -207,86 +214,23 @@ public class PistonCalculator implements Listener {
visualize(); visualize();
} }
private void calculate(Block origin, BlockFace facing, BlockFace direction) { private void resolve(BlockFace facing) {
Block calcOrigin = origin; PistonStructureResolver resolver = new PistonStructureResolver(
if (facing != direction) calcOrigin = origin.getRelative(facing, 3); ((CraftWorld) piston.getWorld()).getHandle(),
new BlockPos(piston.getX(), piston.getY(), piston.getZ()),
List<Block> toCalc = new LinkedList<>(); Direction.valueOf(facing.name()),
calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, toCalc); !pulling
);
while (!toCalc.isEmpty()) { if (!resolver.resolve()) {
Block current = toCalc.remove(0); blocked = true;
if (!movedBlocks.contains(current.getLocation())) { return;
movedBlocks.add(current.getLocation());
}
Material type = current.getType();
if (type != Material.SLIME_BLOCK && type != Material.HONEY_BLOCK) continue;
Material oppositeType = type == Material.SLIME_BLOCK ? Material.HONEY_BLOCK : Material.SLIME_BLOCK;
for (BlockFace face : FACES) {
Block block = current.getRelative(face);
if (block.getType().isAir()) continue;
if (isImmovable(block) || block.getPistonMoveReaction() == PistonMoveReaction.BREAK) continue;
if (face != direction || face == direction.getOppositeFace()) {
switch (block.getType()) {
case BLACK_GLAZED_TERRACOTTA:
case GRAY_GLAZED_TERRACOTTA:
case BLUE_GLAZED_TERRACOTTA:
case BROWN_GLAZED_TERRACOTTA:
case CYAN_GLAZED_TERRACOTTA:
case GREEN_GLAZED_TERRACOTTA:
case LIGHT_BLUE_GLAZED_TERRACOTTA:
case LIGHT_GRAY_GLAZED_TERRACOTTA:
case LIME_GLAZED_TERRACOTTA:
case MAGENTA_GLAZED_TERRACOTTA:
case ORANGE_GLAZED_TERRACOTTA:
case PINK_GLAZED_TERRACOTTA:
case PURPLE_GLAZED_TERRACOTTA:
case RED_GLAZED_TERRACOTTA:
case WHITE_GLAZED_TERRACOTTA:
case YELLOW_GLAZED_TERRACOTTA:
continue;
default:
break;
}
}
if (block.getType() != oppositeType) {
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
calcDirection(null, origin, block, null, facing, direction, toCalc);
}
}
} }
resolver.getToPush().forEach(pos -> movedBlocks.add(toLocation(pos)));
movedBlocks.remove(origin.getLocation()); resolver.getToDestroy().forEach(pos -> brokenBlocks.add(toLocation(pos)));
if (facing != direction) movedBlocks.remove(origin.getRelative(facing, 1).getLocation());
if (pulling) immovableBlocks.remove(origin.getRelative(facing).getLocation());
} }
private void calcDirection(Block origin, Block blockOrigin, Block calcOrigin, Block ignore, BlockFace facing, BlockFace direction, List<Block> toCalc) { private Location toLocation(BlockPos pos) {
for (int i = 1; i < 24; i++) { return new Location(piston.getWorld(), pos.getX(), pos.getY(), pos.getZ());
Block block = calcOrigin.getRelative(direction, i);
if (block.equals(ignore)) return;
if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) {
if (!brokenBlocks.contains(block.getLocation())) {
brokenBlocks.add(block.getLocation());
}
return;
}
if (isImmovable(block)) {
immovableBlocks.add(block.getLocation());
return;
}
if (block.getType().isAir()) return;
if (facing == direction && block.equals(blockOrigin)) {
immovableBlocks.add(block.getLocation());
return;
}
if (facing != direction && (block.equals(origin) || block.getRelative(facing.getOppositeFace()).equals(origin))) {
return;
}
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
}
} }
private void visualize() { private void visualize() {
@@ -321,21 +265,11 @@ public class PistonCalculator implements Listener {
textDisplay.setBrightness(new Display.Brightness(15, 15)); textDisplay.setBrightness(new Display.Brightness(15, 15));
} }
for (Location location : immovableBlocks) {
CWireframe wireframe = new CWireframe(server);
wireframe.setPos1(location);
wireframe.setPos2(location);
wireframe.setWidth(1 / 32f);
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
}
CWireframe wireframe = new CWireframe(server); CWireframe wireframe = new CWireframe(server);
wireframe.setPos1(piston.getLocation()); wireframe.setPos1(piston.getLocation());
wireframe.setPos2(piston.getLocation()); wireframe.setPos2(piston.getLocation());
if (!immovableBlocks.isEmpty()) { if (blocked) {
wireframe.setBlock(Material.RED_CONCRETE.createBlockData()); wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
} else if (movedBlocks.size() > 12) {
wireframe.setBlock(Material.YELLOW_CONCRETE.createBlockData());
} else { } else {
wireframe.setBlock(Material.LIME_CONCRETE.createBlockData()); wireframe.setBlock(Material.LIME_CONCRETE.createBlockData());
} }
@@ -348,7 +282,11 @@ public class PistonCalculator implements Listener {
} else { } else {
text.append("§ePush\n"); text.append("§ePush\n");
} }
text.append("§f").append(movedBlocks.size()).append(" §eBlocks"); if (blocked) {
text.append("§cBlocked");
} else {
text.append("§f").append(movedBlocks.size()).append(" §eBlocks");
}
textDisplay.setText(text.toString()); textDisplay.setText(text.toString());
textDisplay.setBillboard(Display.Billboard.CENTER); textDisplay.setBillboard(Display.Billboard.CENTER);
textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER); textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER);
@@ -358,26 +296,4 @@ public class PistonCalculator implements Listener {
textDisplay.setBrightness(new Display.Brightness(15, 15)); textDisplay.setBrightness(new Display.Brightness(15, 15));
} }
} }
private boolean isImmovable(Block block) {
if (block.isEmpty() || block.isLiquid()) {
return false;
}
BlockData blockData = block.getBlockData();
if (blockData instanceof Piston) {
return ((Piston) blockData).isExtended();
}
if (block.getState() instanceof TileState) {
return true;
}
PistonMoveReaction reaction = block.getPistonMoveReaction();
if (reaction == PistonMoveReaction.IGNORE) return true;
if (reaction == PistonMoveReaction.BLOCK) return true;
switch (block.getType()) {
case OBSIDIAN:
return true;
default:
return false;
}
}
} }
@@ -32,26 +32,22 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONValue;
@Linked @Linked
public class TNTClickListener extends SWCommand implements Listener { public class TNTClickListener extends SWCommand implements Listener {
public static final String TNT_CLICK_DETAILS = "tnt_click_details";
public TNTClickListener() { public TNTClickListener() {
super("tntdetails"); super("tntdetails");
} }
@Register(description = "TNT_DETAILS_COMMAND") @Register(description = "TNT_DETAILS_COMMAND")
public void toggle(Player player) { public void toggle(Player player) {
YAPIONObject yapionObject = Config.getInstance().get(player); Config config = Config.STORE.get(player);
if (yapionObject.getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { if (config.isTntClickDetails()) {
yapionObject.put(TNT_CLICK_DETAILS, false); config.setTntClickDetails(false);
BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player); BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player);
} else { } else {
yapionObject.put(TNT_CLICK_DETAILS, true); config.setTntClickDetails(true);
BauSystem.MESSAGE.send("TNT_DETAILS_ON", player); BauSystem.MESSAGE.send("TNT_DETAILS_ON", player);
} }
} }
@@ -60,7 +56,7 @@ public class TNTClickListener extends SWCommand implements Listener {
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (event.getHand() != EquipmentSlot.HAND) return; if (event.getHand() != EquipmentSlot.HAND) return;
if (!Config.getInstance().get(event.getPlayer()).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { if (!Config.STORE.get(event.getPlayer()).isTntClickDetails()) {
return; return;
} }
@@ -22,11 +22,9 @@ package de.steamwar.bausystem.features.warp;
import de.steamwar.bausystem.region.RegionSystem; import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.worlddata.WorldData; import de.steamwar.bausystem.worlddata.WorldData;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
import yapion.hierarchy.types.YAPIONObject;
import java.util.*; import java.util.*;
@@ -36,20 +34,14 @@ public class Warp {
private static Map<String, Warp> warpMap = new HashMap<>(); private static Map<String, Warp> warpMap = new HashMap<>();
public static void enable() { public static void enable() {
Warp worldSpawn = new Warp("WorldSpawn") { Warp worldSpawn = new Warp("WorldSpawn");
@Override worldSpawn.setLocation(RegionSystem.INSTANCE.getWorldSpawn());
public Location getLocation() {
return RegionSystem.INSTANCE.getWorldSpawn();
}
};
worldSpawn.setMat(Material.NETHER_STAR); worldSpawn.setMat(Material.NETHER_STAR);
warpMap.put("WorldSpawn", worldSpawn); warpMap.put("WorldSpawn", worldSpawn);
} }
private String name; private String name;
@Setter
private Location location; private Location location;
@Setter
private Material mat; private Material mat;
private Warp(String name) { private Warp(String name) {
@@ -63,27 +55,22 @@ public class Warp {
warpMap.put(name, this); warpMap.put(name, this);
} }
public Warp(String name, YAPIONObject object) { public Warp(String name, WorldData.WarpEntry warp) {
this.name = name; this.name = name;
double x = object.getPlainValue("x"); location = new Location(Bukkit.getWorlds().get(0), warp.getX(), warp.getY(), warp.getZ(), warp.getYaw(), warp.getPitch());
double y = object.getPlainValue("y"); mat = Material.getMaterial(warp.getMaterial());
double z = object.getPlainValue("z");
float yaw = object.getPlainValue("yaw");
float pitch = object.getPlainValue("pitch");
location = new Location(Bukkit.getWorlds().get(0), x, y, z, yaw, pitch);
mat = Material.getMaterial(object.getPlainValue("material"));
warpMap.put(name, this); warpMap.put(name, this);
} }
public YAPIONObject output() { public WorldData.WarpEntry output() {
YAPIONObject yapionObject = new YAPIONObject(); WorldData.WarpEntry warp = new WorldData.WarpEntry();
yapionObject.add("x", location.getX()); warp.setX(location.getX());
yapionObject.add("y", location.getY()); warp.setY(location.getY());
yapionObject.add("z", location.getZ()); warp.setZ(location.getZ());
yapionObject.add("yaw", location.getYaw()); warp.setYaw(location.getYaw());
yapionObject.add("pitch", location.getPitch()); warp.setPitch(location.getPitch());
yapionObject.add("material", mat.toString()); warp.setMaterial(mat.toString());
return yapionObject; return warp;
} }
public static Set<String> getWarpNames() { public static Set<String> getWarpNames() {
@@ -98,13 +85,21 @@ public class Warp {
return warpMap.get(name); return warpMap.get(name);
} }
public void setMat(Material mat) {
this.mat = mat;
}
public void setLocation(Location location) {
this.location = location;
}
public void delete() { public void delete() {
warpMap.remove(name); warpMap.remove(name);
WorldData.getWarpData().remove(name); WorldData.STORE.get().getWarps().remove(name);
} }
public void teleport(Player player) { public void teleport(Player player) {
player.teleport(getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN);
player.playSound(getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1, 1); player.playSound(location, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1, 1);
} }
} }
@@ -34,9 +34,9 @@ import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import yapion.hierarchy.types.YAPIONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
@Linked @Linked
public class WarpCommand extends SWCommand implements Disable, Enable { public class WarpCommand extends SWCommand implements Disable, Enable {
@@ -135,17 +135,15 @@ public class WarpCommand extends SWCommand implements Disable, Enable {
@Override @Override
public void enable() { public void enable() {
WorldData.getWarpData().forEach((name, yapionAnyType) -> { WorldData.STORE.get().getWarps().forEach(Warp::new);
new Warp(name, (YAPIONObject) yapionAnyType);
});
Warp.enable(); Warp.enable();
} }
@Override @Override
public void disable() { public void disable() {
YAPIONObject yapionObject = WorldData.getWarpData(); Map<String, WorldData.WarpEntry> warps = WorldData.STORE.get().getWarps();
for (Warp warp : Warp.getWarps()) { for (Warp warp : Warp.getWarps()) {
yapionObject.add(warp.getName(), warp.output()); warps.put(warp.getName(), warp.output());
} }
} }
} }
@@ -99,10 +99,10 @@ public class WarpListener implements Listener {
}); });
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
if (!region.getBuildArea().isEmpty()) { if (!region.getBuildArea().isEmpty()) {
locations.put("Copy", region.getBuildArea().getCopyPoint(false).toLocation(p).add(0.5, 0, 0.5)); locations.put("Copy", region.getBuildArea().getCopyPoint().toLocation(p).add(0.5, 0, 0.5));
} }
if (!region.getTestblockArea().isEmpty()) { if (!region.getTestblockArea().isEmpty()) {
locations.put("TestBlock", region.getTestblockArea().getCopyPoint(false).toLocation(p).add(0.5, 0, 0.5)); locations.put("TestBlock", region.getTestblockArea().getCopyPoint().toLocation(p).add(0.5, 0, 0.5));
} }
} }
@@ -28,6 +28,7 @@ import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaumemberUpdatePacket; import de.steamwar.network.packets.server.BaumemberUpdatePacket;
import de.steamwar.sql.BauweltMember;
import io.papermc.paper.event.player.PlayerPickBlockEvent; import io.papermc.paper.event.player.PlayerPickBlockEvent;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor; import org.bukkit.boss.BarColor;
@@ -57,6 +58,8 @@ public class BauMemberUpdate extends PacketHandler implements Listener {
} }
public static void baumemberUpdate() { public static void baumemberUpdate() {
// Packet handlers run in no guaranteed order, so don't rely on CoreNetworkHandler having cleared the cache yet.
BauweltMember.clear();
Set<Player> newSpectator = new HashSet<>(); Set<Player> newSpectator = new HashSet<>();
Set<Player> newBuilder = new HashSet<>(); Set<Player> newBuilder = new HashSet<>();
Bukkit.getOnlinePlayers().forEach(player -> { Bukkit.getOnlinePlayers().forEach(player -> {
@@ -119,8 +119,11 @@ public class BauScoreboard implements Listener {
@Override @Override
public String getTitle() { public String getTitle() {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
if (region.getRegionData().has(Flag.COLOR).notVisibleInScoreboard()) return "§eSteam§8War"; if (region.getType().isGlobal()) return "§eSteam§8War";
String colorCode = "§" + region.getRegionData().get(Flag.COLOR).getWithDefault().getColorCode(); String colorCode = "§e";
if (region.getRegionData().has(Flag.COLOR).isReadable()) {
colorCode = "§" + region.getRegionData().get(Flag.COLOR).orElse(ColorMode.PINK).getColorCode();
}
return colorCode + "■ §eSteam§8War " + colorCode + "■"; // ■ return colorCode + "■ §eSteam§8War " + colorCode + "■"; // ■
} }
}); });
@@ -128,6 +131,7 @@ public class BauScoreboard implements Listener {
@EventHandler @EventHandler
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
SWScoreboard.impl.removeScoreboard(event.getPlayer());
ADDITIONAL_SCOREBOARD_LINES.remove(event.getPlayer()); ADDITIONAL_SCOREBOARD_LINES.remove(event.getPlayer());
} }
} }
@@ -28,7 +28,6 @@ import org.bukkit.scheduler.BukkitRunnable;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@Linked @Linked
public class BackupScheduler implements Enable { public class BackupScheduler implements Enable {
@@ -44,7 +43,6 @@ public class BackupScheduler implements Enable {
Iterator<Region> regionsToBackup = RegionSystem.INSTANCE.getRegions() Iterator<Region> regionsToBackup = RegionSystem.INSTANCE.getRegions()
.filter(region -> region.getRegionData().has(Flag.CHANGED).isReadable()) .filter(region -> region.getRegionData().has(Flag.CHANGED).isReadable())
.filter(region -> region.getRegionData().get(Flag.CHANGED).isWithDefault(ChangedMode.HAS_CHANGE)) .filter(region -> region.getRegionData().get(Flag.CHANGED).isWithDefault(ChangedMode.HAS_CHANGE))
.collect(Collectors.toList())
.iterator(); .iterator();
if (!regionsToBackup.hasNext()) return; if (!regionsToBackup.hasNext()) return;
doBackup(regionsToBackup); doBackup(regionsToBackup);
@@ -48,18 +48,10 @@ public class Point {
return new Point(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()); return new Point(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ());
} }
public Point add(Point point) {
return add(point.x, point.y, point.z);
}
public Point add(int x, int y, int z) { public Point add(int x, int y, int z) {
return new Point(this.x + x, this.y + y, this.z + z); return new Point(this.x + x, this.y + y, this.z + z);
} }
public Point subtract(Point point) {
return subtract(point.x, point.y, point.z);
}
public Point subtract(int x, int y, int z) { public Point subtract(int x, int y, int z) {
return new Point(this.x - x, this.y - y, this.z - z); return new Point(this.x - x, this.y - y, this.z - z);
} }
@@ -68,18 +60,6 @@ public class Point {
return new Point(x / factor, y / factor, z / factor); return new Point(x / factor, y / factor, z / factor);
} }
public Point setX(int x) {
return new Point(x, this.y, this.z);
}
public Point setY(int y) {
return new Point(this.x, y, this.z);
}
public Point setZ(int z) {
return new Point(this.x, this.y, z);
}
public Location toLocation(World world) { public Location toLocation(World world) {
return new Location(world, x, y, z); return new Location(world, x, y, z);
} }
@@ -24,16 +24,16 @@ import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.sql.GameModeConfig; import de.steamwar.sql.GameModeConfig;
import lombok.NonNull; import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface Region extends RegionDataStore { public interface Region {
static Stream<Region> getRegions() { static Stream<Region> getRegions() {
return RegionSystem.INSTANCE.getRegions(); return RegionSystem.INSTANCE.getRegions();
@@ -74,14 +74,11 @@ public interface Region extends RegionDataStore {
@NonNull @NonNull
RegionBackups getBackups(); RegionBackups getBackups();
abstract class Area { interface Area {
public static final int WORLD_MIN_Y = 0; Area EMPTY = new Area() {
public static final int WORLD_MAX_Y = Bukkit.getWorlds().get(0).getMaxHeight();
public static final Area EMPTY = new Area() {
@Override @Override
protected boolean isEmptyInternal() { public boolean isEmpty() {
return true; return true;
} }
@@ -96,7 +93,7 @@ public interface Region extends RegionDataStore {
} }
@Override @Override
public Point getCopyPoint(boolean extension) { public Point getCopyPoint() {
return Point.ZERO; return Point.ZERO;
} }
@@ -105,18 +102,18 @@ public interface Region extends RegionDataStore {
return false; return false;
} }
@Override
public boolean inRegion(int x, int z, boolean extension) {
return false;
}
@Override @Override
public Clipboard copy(boolean extension) { public Clipboard copy(boolean extension) {
return null; return null;
} }
@Override @Override
public void place(PasteBuilder pasteBuilder, boolean extension) { public File getResetFile() {
return null;
}
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
} }
@Override @Override
@@ -129,24 +126,20 @@ public interface Region extends RegionDataStore {
} }
}; };
public final boolean isEmpty() { default boolean isEmpty() {
return isEmptyInternal();
}
boolean isEmptyInternal() {
return false; return false;
} }
@NonNull @NonNull
public abstract Point getMinPoint(boolean extension); Point getMinPoint(boolean extension);
@NonNull @NonNull
public abstract Point getMaxPoint(boolean extension); Point getMaxPoint(boolean extension);
@NonNull @NonNull
public abstract Point getCopyPoint(boolean extension); Point getCopyPoint();
public boolean inRegion(Location location, boolean extension) { default boolean inRegion(Location location, boolean extension) {
Point minPoint = getMinPoint(extension); Point minPoint = getMinPoint(extension);
Point maxPoint = getMaxPoint(extension); Point maxPoint = getMaxPoint(extension);
if (location.getBlockX() < minPoint.getX() || location.getBlockX() > maxPoint.getX()) return false; if (location.getBlockX() < minPoint.getX() || location.getBlockX() > maxPoint.getX()) return false;
@@ -155,26 +148,17 @@ public interface Region extends RegionDataStore {
return true; return true;
} }
public boolean inRegion(int x, int z, boolean extension) { @Nullable
Point minPoint = getMinPoint(extension); default Clipboard copy(boolean extension) {
Point maxPoint = getMaxPoint(extension); return FlatteningWrapper.impl.copy(getMinPoint(extension), getMaxPoint(extension), getCopyPoint());
if (x < minPoint.getX() || x > maxPoint.getX()) return false;
if (z < minPoint.getZ() || z > maxPoint.getZ()) return false;
return true;
} }
@Nullable @Nullable
public Clipboard copy(boolean extension) { File getResetFile();
return FlatteningWrapper.impl.copy(getMinPoint(extension), getMaxPoint(extension), getCopyPoint(extension));
}
public void reset(PasteBuilder pasteBuilder, boolean extension) { void reset(PasteBuilder pasteBuilder, boolean extension);
place(pasteBuilder, extension);
}
public abstract void place(PasteBuilder pasteBuilder, boolean extension); default void forEachChunk(BiConsumer<Integer, Integer> executor) {
public void forEachChunk(BiConsumer<Integer, Integer> executor) {
Point minPoint = getMinPoint(false); Point minPoint = getMinPoint(false);
Point maxPoint = getMaxPoint(false); Point maxPoint = getMaxPoint(false);
for (int x = (int) Math.floor(minPoint.getX() / 16.0); x <= (int) Math.ceil(maxPoint.getX() / 16.0); x++) { for (int x = (int) Math.floor(minPoint.getX() / 16.0); x <= (int) Math.ceil(maxPoint.getX() / 16.0); x++) {
@@ -184,7 +168,7 @@ public interface Region extends RegionDataStore {
} }
} }
public boolean isChunkOutside(int chunkX, int chunkZ) { default boolean isChunkOutside(int chunkX, int chunkZ) {
Point minPoint = getMinPoint(true); Point minPoint = getMinPoint(true);
Point maxPoint = getMaxPoint(true); Point maxPoint = getMaxPoint(true);
return Math.floor(minPoint.getX() / 16.0) > chunkX || chunkX >= Math.ceil(maxPoint.getX() / 16.0) || return Math.floor(minPoint.getX() / 16.0) > chunkX || chunkX >= Math.ceil(maxPoint.getX() / 16.0) ||
@@ -25,15 +25,11 @@ import lombok.RequiredArgsConstructor;
import javax.annotation.CheckReturnValue; import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
public interface RegionBackups { public interface RegionBackups {
DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd' 'HH:mm:ss");
@RequiredArgsConstructor @RequiredArgsConstructor
enum BackupType { enum BackupType {
MANUAL(5), MANUAL(5),
@@ -43,43 +39,21 @@ public interface RegionBackups {
public final int maxBackups; public final int maxBackups;
} }
@RequiredArgsConstructor
@Getter @Getter
abstract class Backup implements RegionDataStore, Comparable<Backup> { abstract class Backup {
@NonNull @NonNull
protected final BackupType type; private final BackupType type;
@NonNull @NonNull
protected final String name; private final String name;
@NonNull @NonNull
protected final RegionData regionData; private final RegionData data;
protected Backup(@NonNull BackupType type, @NonNull String name, @NonNull Function<Backup, RegionData> regionDataConstructor) {
this.type = type;
this.name = name;
regionData = regionDataConstructor.apply(this);
}
@CheckReturnValue @CheckReturnValue
public abstract boolean load(); public abstract boolean load();
@Override
public final void save() {
}
public abstract long getCreationTime();
@Override
public int compareTo(Backup o) {
return Long.compare(getCreationTime(), o.getCreationTime());
}
@Override
public final void load(RegionData regionData) {
}
@SuppressWarnings("java:S3038") // This forces everybody to implement 'deleteRegion' for Backups!
@Override
public abstract void delete(); public abstract void delete();
} }
@@ -90,7 +64,7 @@ public interface RegionBackups {
List<Backup> list(); List<Backup> list();
@Nullable @Nullable
Backup get(@NonNull String name); Backup get(String name);
RegionBackups EMPTY = new RegionBackups() { RegionBackups EMPTY = new RegionBackups() {
@Override @Override
@@ -105,7 +79,7 @@ public interface RegionBackups {
@Nullable @Nullable
@Override @Override
public Backup get(@NonNull String name) { public Backup get(String name) {
return null; return null;
} }
}; };
@@ -20,44 +20,44 @@
package de.steamwar.bausystem.region; package de.steamwar.bausystem.region;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import lombok.NonNull; import lombok.NonNull;
import java.util.*; import java.util.HashMap;
import java.util.function.Function; import java.util.Map;
import java.util.stream.Stream;
public abstract class RegionData { public abstract class RegionData {
protected RegionDataStore store; protected final WorldData.RegionEntry data;
protected final Map<String, String> flagData;
private final List<Property<?, ?>> properties = new ArrayList<>(); protected final Runnable onChange;
protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>(); protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>();
// TODO: These should be turned into an ECS like System because not every Region has them or should have them!
protected final Property<SchematicNode, Integer> testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId);
protected RegionData(RegionDataStore store) { private SchematicNode testblockSchematic;
this.store = store;
protected RegionData(WorldData.RegionEntry data, Runnable onChange) {
this.data = data;
this.flagData = data.getFlags();
this.onChange = onChange;
initialize(); initialize();
store.load(this); for (final Flag flag : Flag.getFlags()) {
} if (!has(flag).isWritable()) continue;
try {
public final void setStore(RegionDataStore store) { String s = flagData.get(flag.name());
this.store = store; flagMap.put(flag, flag.valueOfValue(s));
store.save(); } catch (Exception e) {
flagMap.put(flag, (Flag.Value<?>) flag.getDefaultValue());
}
}
if (data.getTestblockSchematic() != null) {
testblockSchematic = SchematicNode.byId(data.getTestblockSchematic());
}
} }
protected void initialize() { protected void initialize() {
} }
/**
* All connected Regions are required to have the same type as their RegionData.
*/
protected Stream<Region> connectedRegions() {
return Stream.empty();
}
@NonNull @NonNull
public abstract <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag); public abstract <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag);
@@ -66,15 +66,11 @@ public abstract class RegionData {
*/ */
public final <T extends Enum<T> & Flag.Value<T>> boolean set(@NonNull Flag<T> flag, @NonNull T value) { public final <T extends Enum<T> & Flag.Value<T>> boolean set(@NonNull Flag<T> flag, @NonNull T value) {
if (has(flag).isWritable()) { if (has(flag).isWritable()) {
boolean needsSave = flagMap.put(flag, value) != value; if (flagMap.put(flag, value) != value) {
if (needsSave) store.save(); flagData.put(flag.name(), value.name());
onChange.run();
connectedRegions().forEach(region -> { return true;
if (region.getRegionData().flagMap.put(flag, value) != value) { }
region.save();
}
});
return needsSave;
} }
return false; return false;
} }
@@ -85,94 +81,35 @@ public abstract class RegionData {
} }
public final void clear() { public final void clear() {
Set<Flag> remove = new HashSet<>();
for (Flag flag : Flag.getFlags()) { for (Flag flag : Flag.getFlags()) {
if (has(flag).isWritable()) { if (has(flag).isWritable()) {
flagMap.remove(flag); flagMap.remove(flag);
remove.add(flag); flagData.remove(flag.name());
} }
} }
initialize(); testblockSchematic = null;
properties.forEach(property -> property.set(null)); data.setTestblockSchematic(null);
store.save(); onChange.run();
connectedRegions().forEach(region -> {
region.getRegionData().flagMap.keySet().removeAll(remove);
region.getRegionData().initialize();
region.getRegionData().properties.forEach(property -> property.set(null));
region.save();
});
}
/**
* This method only copies all flags and properties from this into other without saving other afterward.
* TODO: If {@link #connectedRegions()} is overridden this method will not work correctly!
*/
public final void copyInto(RegionData other) {
if (this == other) return;
other.flagMap.clear();
other.flagMap.putAll(flagMap);
// TODO: This might not be correct, needs to be investigated
other.properties.clear();
other.properties.addAll(properties);
} }
public final Map<Flag<?>, Flag.Value<?>> getBackedMap() { public final Map<Flag<?>, Flag.Value<?>> getBackedMap() {
return flagMap; return flagMap;
} }
public final List<Property<Object, Object>> getBackedProperties() { public SchematicNode getTestblockSchematic() {
return (List) properties; return testblockSchematic;
} }
public final SchematicNode getTestblockSchematic() { public void setTestblockSchematic(SchematicNode schematic) {
return testblockSchematic.get(); testblockSchematic = schematic;
} data.setTestblockSchematic(schematic == null ? null : schematic.getId());
onChange.run();
public final void setTestblockSchematic(SchematicNode schematic) {
testblockSchematic.set(schematic);
store.save();
} }
@Override @Override
public final String toString() { public final String toString() {
StringBuilder st = new StringBuilder(); return getClass().getSimpleName() + "{" +
st.append(getClass().getSimpleName()).append("{"); "flagMap=" + flagMap +
st.append("flagMap=").append(flagMap); '}';
for (Property<?, ?> p : properties) {
st.append(p);
}
st.append("}");
return st.toString();
}
public final class Property<T, K> {
public final String field;
public final Function<K, T> loader;
public final Function<T, K> writer;
private T value;
private Property(String field, Function<K, T> loader, Function<T, K> writer) {
this.field = field;
this.loader = loader;
this.writer = writer;
properties.add(this);
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
}
@Override
public String toString() {
Object value = this.value;
if (value != null) value = writer.apply((T) value);
return ", " + field + "=" + value;
}
} }
} }
@@ -35,8 +35,4 @@ public enum RegionFlagPolicy {
public boolean isApplicable() { public boolean isApplicable() {
return readable || writable; return readable || writable;
} }
public boolean notVisibleInScoreboard() {
return !writable;
}
} }
@@ -19,8 +19,6 @@
package de.steamwar.bausystem.region; package de.steamwar.bausystem.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.core.Core;
import lombok.NonNull; import lombok.NonNull;
import org.bukkit.Location; import org.bukkit.Location;
@@ -28,15 +26,10 @@ import javax.annotation.CheckReturnValue;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Logger;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface RegionSystem { public interface RegionSystem {
Logger LOGGER = BauSystem.getInstance().getLogger();
UUID GLOBAL_REGION_ID = new UUID(0, 0);
RegionSystem INSTANCE = init(); RegionSystem INSTANCE = init();
/** /**
@@ -44,6 +37,11 @@ public interface RegionSystem {
*/ */
void load(); void load();
/**
* Saves anything that should be written to file on either CRIUSleepEvent or plugin disable.
*/
void save();
/** /**
* Returns the Location to teleport players to when they first join or Warp to "WorldSpawn" * Returns the Location to teleport players to when they first join or Warp to "WorldSpawn"
*/ */
@@ -76,63 +74,47 @@ public interface RegionSystem {
@NonNull @NonNull
Stream<Region> getRegions(); Stream<Region> getRegions();
/**
* Only contains Regions of the same Type as the one you inputted.
*/
@NonNull
Stream<Region> getConnectedRegions(@NonNull Region region);
private static RegionSystem init() { private static RegionSystem init() {
if (Core.getVersion() >= 21) {
// TODO: Add some kind of detection if the DynamicRegionSystem should be used!
try {
return (RegionSystem) Class.forName("de.steamwar.bausystem.region.DynamicRegionSystem").getConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
// Ignore
}
}
try { try {
return (RegionSystem) Class.forName("de.steamwar.bausystem.region.FixedRegionSystem").getConstructor().newInstance(); return (RegionSystem) Class.forName("de.steamwar.bausystem.region.FixedRegionSystem").getConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) { InvocationTargetException e) {
// Ignore return new RegionSystem() {
@Override
public void load() {
throw new UnsupportedOperationException();
}
@Override
public void save() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Location getWorldSpawn() {
throw new UnsupportedOperationException();
}
@Override
public Region getGlobalRegion() {
throw new UnsupportedOperationException();
}
@Override
public Region get(Location location) {
throw new UnsupportedOperationException();
}
@Override
public Optional<Region> getRegion(UUID id) {
throw new UnsupportedOperationException();
}
@Override
public Stream<Region> getRegions() {
throw new UnsupportedOperationException();
}
};
} }
return new RegionSystem() {
@Override
public void load() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Location getWorldSpawn() {
throw new UnsupportedOperationException();
}
@Override
public Region getGlobalRegion() {
throw new UnsupportedOperationException();
}
@Override
public Region get(Location location) {
throw new UnsupportedOperationException();
}
@Override
public Optional<Region> getRegion(UUID id) {
throw new UnsupportedOperationException();
}
@Override
public Stream<Region> getRegions() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Stream<Region> getConnectedRegions(@NonNull Region region) {
throw new UnsupportedOperationException();
}
};
} }
} }
@@ -26,44 +26,9 @@ import lombok.RequiredArgsConstructor;
@Getter @Getter
public enum RegionType { public enum RegionType {
GLOBAL(ConnectionType.Water), GLOBAL(true),
/** NORMAL(false),
* This should not be used by the DynamicRegionSystem
*/
NORMAL(ConnectionType.Plot),
SPAWN(ConnectionType.Water),
SPAWN_EXTENSION(ConnectionType.Water),
PATH(ConnectionType.Path),
DRY(ConnectionType.Water),
DRY_SPECIAL(ConnectionType.Plot),
WET(ConnectionType.Water),
WET_SPECIAL(ConnectionType.Water),
; ;
private final ConnectionType connectionType; private final boolean global;
public boolean isGlobal() {
return this == GLOBAL;
}
public boolean isDeletable() {
return this == NORMAL || this == SPAWN_EXTENSION || this == PATH || this == DRY || this == DRY_SPECIAL || this == WET || this == WET_SPECIAL;
}
public boolean isSpecial() {
return this == DRY_SPECIAL || this == WET_SPECIAL;
}
public boolean isSpawn() {
return this == SPAWN || this == SPAWN_EXTENSION;
}
public enum ConnectionType {
Plot,
Path,
Water,
Garden,
}
} }
@@ -28,7 +28,6 @@ import org.bukkit.entity.Player;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream;
@UtilityClass @UtilityClass
public class RegionUtils { public class RegionUtils {
@@ -59,12 +58,10 @@ public class RegionUtils {
} }
public void forEachInRegion(Region region, Consumer<Player> consumer) { public void forEachInRegion(Region region, Consumer<Player> consumer) {
Stream<? extends Player> players = Bukkit.getOnlinePlayers().stream(); Bukkit.getOnlinePlayers()
if (region.getType().isGlobal()) { .stream()
players = players.filter(player -> Region.getRegion(player.getLocation()).getType().isGlobal()); .filter(player -> region.getArea().inRegion(player.getLocation(), false))
} else { .filter(player -> !region.getType().isGlobal() || Region.getRegion(player.getLocation()).getType().isGlobal())
players = players.filter(player -> region.getArea().inRegion(player.getLocation(), false)); .forEach(consumer);
}
players.forEach(consumer);
} }
} }
@@ -42,7 +42,7 @@ import java.util.function.BiPredicate;
@Getter @Getter
public class PasteBuilder { public class PasteBuilder {
private ClipboardProvider clipboardProvider; private final ClipboardProvider clipboardProvider;
private Point pastPoint; private Point pastPoint;
private boolean rotate; private boolean rotate;
private boolean ignoreAir; private boolean ignoreAir;
@@ -53,20 +53,10 @@ public class PasteBuilder {
private List<BiPredicate<BaseBlock, String>> predicates = new ArrayList<>(); private List<BiPredicate<BaseBlock, String>> predicates = new ArrayList<>();
private List<BiConsumer<Clipboard, BlockVector3>> mappers = new ArrayList<>(); private List<BiConsumer<Clipboard, BlockVector3>> mappers = new ArrayList<>();
public PasteBuilder() {
clipboardProvider = ClipboardProvider.EMPTY;
}
public PasteBuilder(@NonNull ClipboardProvider clipboardProvider) { public PasteBuilder(@NonNull ClipboardProvider clipboardProvider) {
this.clipboardProvider = clipboardProvider; this.clipboardProvider = clipboardProvider;
} }
public PasteBuilder with(@NonNull ClipboardProvider clipboardProvider) {
if (this.clipboardProvider != ClipboardProvider.EMPTY) return this;
this.clipboardProvider = clipboardProvider;
return this;
}
public PasteBuilder pastePoint(Point point) { public PasteBuilder pastePoint(Point point) {
this.pastPoint = point; this.pastPoint = point;
return this; return this;
@@ -102,16 +92,6 @@ public class PasteBuilder {
return this; return this;
} }
private PasteBuilder predicates(List<BiPredicate<BaseBlock, String>> predicates) {
this.predicates = predicates;
return this;
}
public PasteBuilder mappers(List<BiConsumer<Clipboard, BlockVector3>> mappers) {
this.mappers = mappers;
return this;
}
public PasteBuilder only(BiPredicate<BaseBlock, String> predicate) { public PasteBuilder only(BiPredicate<BaseBlock, String> predicate) {
predicates.add(predicate); predicates.add(predicate);
return this; return this;
@@ -202,27 +182,13 @@ public class PasteBuilder {
} }
public EditSession run() { public EditSession run() {
if (pastPoint != null || minPoint != null) { if (pastPoint == null) {
return FlatteningWrapper.impl.paste(this); throw new IllegalStateException("pastePoint is null");
} }
throw new IllegalStateException("pastePoint is null"); return FlatteningWrapper.impl.paste(this);
} }
public interface ClipboardProvider { public interface ClipboardProvider {
ClipboardProvider EMPTY = new EmptyProvider();
static ClipboardProvider file(File file) {
return new FileProvider(file);
}
static ClipboardProvider schematic(SchematicNode schematic) {
return new SchematicProvider(schematic);
}
static ClipboardProvider clipboard(Clipboard clipboard) {
return new ClipboardProviderImpl(clipboard);
}
Clipboard getClipboard(); Clipboard getClipboard();
default <T extends ClipboardProvider> boolean is(Class<T> clazz) { default <T extends ClipboardProvider> boolean is(Class<T> clazz) {
@@ -234,22 +200,12 @@ public class PasteBuilder {
} }
} }
private static class EmptyProvider implements ClipboardProvider {
private EmptyProvider() {
}
@Override
public Clipboard getClipboard() {
return null;
}
}
@Getter @Getter
public static class FileProvider implements ClipboardProvider { public static class FileProvider implements ClipboardProvider {
private final File file; private final File file;
private final Clipboard clipboard; private final Clipboard clipboard;
private FileProvider(File file) { public FileProvider(File file) {
this.file = file; this.file = file;
this.clipboard = FlatteningWrapper.impl.loadSchematic(file); this.clipboard = FlatteningWrapper.impl.loadSchematic(file);
} }
@@ -265,7 +221,7 @@ public class PasteBuilder {
private final SchematicNode schematic; private final SchematicNode schematic;
private final Clipboard clipboard; private final Clipboard clipboard;
private SchematicProvider(SchematicNode schematic) { public SchematicProvider(SchematicNode schematic) {
this.schematic = schematic; this.schematic = schematic;
try { try {
this.clipboard = new SchematicData(schematic).load(); this.clipboard = new SchematicData(schematic).load();
@@ -284,7 +240,7 @@ public class PasteBuilder {
public static class ClipboardProviderImpl implements ClipboardProvider { public static class ClipboardProviderImpl implements ClipboardProvider {
private final Clipboard clipboard; private final Clipboard clipboard;
private ClipboardProviderImpl(Clipboard clipboard) { public ClipboardProviderImpl(Clipboard clipboard) {
this.clipboard = clipboard; this.clipboard = clipboard;
} }
@@ -1,101 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.worlddata;
import de.steamwar.sql.SteamwarUser;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import yapion.hierarchy.output.FileOutput;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.InputStreamCharsets;
import yapion.parser.YAPIONParser;
import yapion.parser.options.FileOptions;
import java.io.File;
import java.util.List;
@UtilityClass
public class SimulatorData {
private final File simulatorsDir = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "simulators");
static {
simulatorsDir.mkdirs();
}
@SneakyThrows
public YAPIONObject getSimulator(Player player) {
File file = getFile(player);
if (!file.exists()) {
return new YAPIONObject();
}
return YAPIONParser.parse(file, new FileOptions().charset(InputStreamCharsets.UTF_8));
}
@SneakyThrows
private void internalSaveSimulator(Player player, YAPIONObject yapionObject) {
File file = getFile(player);
yapionObject.toYAPION(new FileOutput(file)).close();
}
@SneakyThrows
void saveSimulator(SteamwarUser steamwarUser, YAPIONObject yapionObject) {
File file = new File(simulatorsDir, steamwarUser.getId() + ".yapion");
yapionObject.toYAPION(new FileOutput(file)).close();
}
public List<String> listSimulator(Player player) {
List<String> strings = getSimulator(player).getKeys();
strings.remove("");
return strings;
}
public void saveSimulator(Player player, String name) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.put(name, yapionObject.getArray("").copy());
internalSaveSimulator(player, yapionObject);
}
public void saveSimulator(Player player, YAPIONArray simulator) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.put("", simulator);
internalSaveSimulator(player, yapionObject);
}
public void loadSimulator(Player player, String name) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.put("", yapionObject.getArray(name).copy());
internalSaveSimulator(player, yapionObject);
}
public void removeSimulator(Player player, String name) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.remove(name);
internalSaveSimulator(player, yapionObject);
}
private File getFile(Player player) {
SteamwarUser steamwarUser = SteamwarUser.get(player.getUniqueId());
return new File(simulatorsDir, steamwarUser.getId() + ".yapion");
}
}
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2025 SteamWar.de-Serverteam * Copyright (C) 2026 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@@ -19,58 +19,83 @@
package de.steamwar.bausystem.worlddata; package de.steamwar.bausystem.worlddata;
import lombok.experimental.UtilityClass; import com.google.gson.GsonBuilder;
import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.worlddata.converter.WorldDataV1ToV2Converter;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import yapion.hierarchy.output.FileOutput;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.InputStreamCharsets;
import yapion.parser.YAPIONParser;
import yapion.parser.options.StreamOptions;
import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.util.HashMap;
import java.io.IOException; import java.util.Map;
@UtilityClass /**
public class WorldData { * The current world data. It is stored as JSON, fields missing in the
* stored JSON keep the default value of their initializer, so adding a new
* field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to WorldDataV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class WorldData implements VersionedConfig {
private final File optionsFile = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"); public static final int VERSION = 2;
private YAPIONObject worldData;
public YAPIONObject getWorldData() { public static final ConfigStore<WorldData> STORE = new ConfigStore<>(WorldData.class, VERSION, WorldData::new)
if (worldData == null) { .gson(new GsonBuilder().setPrettyPrinting().create())
read(); .converter(new WorldDataV1ToV2Converter())
} .file(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.json"))
return worldData; .legacyFile(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"), WorldDataV1::parse);
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* Region name to the data of the region
*/
protected Map<String, RegionEntry> regions = new HashMap<>();
protected RegionEntry globalRegion = new RegionEntry();
/**
* Warp name to warp
*/
protected Map<String, WarpEntry> warps = new HashMap<>();
@Getter
@Setter
public static class RegionEntry {
/**
* Overrides the prototype of the region config, null if not overridden
*/
protected String prototype = null;
/**
* Flag name to flag value name
*/
protected Map<String, String> flags = new HashMap<>();
/**
* Id of the testblock SchematicNode, null if the default testblock is used
*/
protected Integer testblockSchematic = null;
} }
public YAPIONObject getRegionsData() { @Getter
return getWorldData().getYAPIONObjectOrSetDefault("regions", new YAPIONObject()); @Setter
} public static class WarpEntry {
public YAPIONObject getWarpData() { protected double x;
return getWorldData().getYAPIONObjectOrSetDefault("warps", new YAPIONObject()); protected double y;
} protected double z;
protected float yaw;
private void read() { protected float pitch;
worldData = new YAPIONObject(); protected String material;
if (optionsFile.length() != 0) {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(optionsFile))) {
worldData = YAPIONParser.parse(bufferedInputStream, new StreamOptions().charset(InputStreamCharsets.UTF_8));
} catch (IOException e) {
// Ignored
}
} else {
write();
}
}
public void write() {
try {
worldData.toYAPION(new FileOutput(optionsFile)).close();
} catch (IOException e) {
e.printStackTrace();
}
} }
} }
@@ -0,0 +1,111 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.worlddata;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.legacy.YapionReader;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
/**
* The legacy world data that was stored in the YAPION format in options.yapion.
*/
@Getter
@Setter
@Deprecated
public class WorldDataV1 implements VersionedConfig {
public static final int VERSION = 1;
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* Region name to the data of the region, the global region is stored with the name "global"
*/
protected Map<String, RegionEntry> regions = new HashMap<>();
/**
* Warp name to warp
*/
protected Map<String, WarpEntry> warps = new HashMap<>();
@Getter
@Setter
public static class RegionEntry {
protected String prototype = null;
/**
* Flag name to flag value name and "testblockSchematic" to the SchematicNode id
*/
protected Map<String, Object> flagStorage = new HashMap<>();
}
@Getter
@Setter
public static class WarpEntry {
protected double x;
protected double y;
protected double z;
protected float yaw;
protected float pitch;
protected String material;
}
public static WorldDataV1 parse(String yapion) {
Map<String, Object> object = YapionReader.parse(yapion);
WorldDataV1 worldData = new WorldDataV1();
if (object.get("regions") instanceof Map<?, ?> regions) {
regions.forEach((name, value) -> {
if (!(value instanceof Map<?, ?> region)) return;
RegionEntry entry = new RegionEntry();
if (region.get("prototype") instanceof String prototype) {
entry.prototype = prototype;
}
if (region.get("flagStorage") instanceof Map<?, ?> flagStorage) {
flagStorage.forEach((key, flag) -> entry.flagStorage.put((String) key, flag));
}
worldData.regions.put((String) name, entry);
});
}
if (object.get("warps") instanceof Map<?, ?> warps) {
warps.forEach((name, value) -> {
if (!(value instanceof Map<?, ?> warp)) return;
WarpEntry entry = new WarpEntry();
entry.x = ((Number) warp.get("x")).doubleValue();
entry.y = ((Number) warp.get("y")).doubleValue();
entry.z = ((Number) warp.get("z")).doubleValue();
entry.yaw = ((Number) warp.get("yaw")).floatValue();
entry.pitch = ((Number) warp.get("pitch")).floatValue();
entry.material = (String) warp.get("material");
worldData.warps.put((String) name, entry);
});
}
return worldData;
}
}
@@ -0,0 +1,75 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.worlddata.converter;
import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.bausystem.worlddata.WorldDataV1;
@Deprecated
public class WorldDataV1ToV2Converter implements ConfigConverter<WorldDataV1, WorldData> {
private static final String GLOBAL_REGION = "global";
private static final String TESTBLOCK_SCHEMATIC = "testblockSchematic";
@Override
public int version() {
return WorldDataV1.VERSION;
}
@Override
public WorldData convert(WorldDataV1 old) {
WorldData worldData = new WorldData();
old.getRegions().forEach((name, region) -> {
if (name.equals(GLOBAL_REGION)) {
worldData.setGlobalRegion(convert(region));
} else {
worldData.getRegions().put(name, convert(region));
}
});
old.getWarps().forEach((name, warp) -> worldData.getWarps().put(name, convert(warp)));
return worldData;
}
private WorldData.RegionEntry convert(WorldDataV1.RegionEntry old) {
WorldData.RegionEntry region = new WorldData.RegionEntry();
region.setPrototype(old.getPrototype());
old.getFlagStorage().forEach((key, value) -> {
if (value == null) return;
if (key.equals(TESTBLOCK_SCHEMATIC)) {
region.setTestblockSchematic(((Number) value).intValue());
} else {
region.getFlags().put(key, value.toString());
}
});
return region;
}
private WorldData.WarpEntry convert(WorldDataV1.WarpEntry old) {
WorldData.WarpEntry warp = new WorldData.WarpEntry();
warp.setX(old.getX());
warp.setY(old.getY());
warp.setZ(old.getZ());
warp.setYaw(old.getYaw());
warp.setPitch(old.getPitch());
warp.setMaterial(old.getMaterial());
return warp;
}
}
@@ -1,44 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
steamwar.java
}
tasks.compileJava {
options.isWarnings = false
}
dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":BauSystem:BauSystem_Main", "default"))
compileOnly(project(":SpigotCore", "default"))
compileOnly(libs.paperapi)
compileOnly(libs.axiom)
compileOnly(libs.authlib)
compileOnly(libs.viaapi)
compileOnly(libs.nms)
compileOnly(libs.fawe)
implementation(libs.luaj)
}
@@ -1,77 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.SWCommand;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.logging.Level;
public class WireframeCommand extends SWCommand {
public WireframeCommand() {
super("wireframe");
}
@Register(description = "REGION_WIREFRAME_HELP_RESET")
public void wireframeCommand(@Validator Player p) {
Region region = regionCheck(p);
if (region == null) return;
try {
PasteBuilder pasteBuilder = new PasteBuilder()
.ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getBuildArea().reset(pasteBuilder, false);
RegionUtils.message(region, "REGION_WIREFRAME_DONE");
} catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_WIREFRAME_ERROR", p);
Bukkit.getLogger().log(Level.WARNING, "Failed wireframe", e);
}
}
private Region regionCheck(Player player) {
Region region = Region.getRegion(player.getLocation());
if (region.getRegionData().has(Flag.TESTBLOCK).isWritable() && region.getRegionData().get(Flag.TESTBLOCK).isWithDefault(TestblockMode.NO_VALUE)) {
Point minPoint = region.getArea().getMinPoint(false);
Point maxPoint = region.getArea().getMaxPoint(false);
// TODO: Check if empty!
int half = minPoint.getZ() + (maxPoint.getZ() - minPoint.getZ()) / 2;
if (player.getLocation().getBlockZ() <= half) {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.SOUTH);
} else {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.NORTH);
}
}
if (region.getTestblockArea().isEmpty()) {
BauSystem.MESSAGE.send("REGION_WIREFRAME_NO_REGION", player);
return null;
}
return region;
}
}
@@ -1,9 +0,0 @@
{
"flags": {
"TNT": "DENY",
"FREEZE": "INACTIVE"
},
"properties": {
"testblockSchematic": 0
}
}
@@ -1,9 +0,0 @@
{
"region_identifier": "SpawnRegion",
"tiles": [
{
"tile_x": 0,
"tile_z": 0
}
]
}
@@ -1,59 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.features.region.RegionCommand;
import de.steamwar.command.AbstractSWCommand;
import de.steamwar.command.SWCommand;
import de.steamwar.core.SWPlayer;
import org.bukkit.entity.Player;
@AbstractSWCommand.PartOf(RegionCommand.class)
public class DynamicRegionCommand extends SWCommand {
public DynamicRegionCommand() {
super("");
}
@Register({"dynamic"})
public void visualizeRegions(Player player) {
SWPlayer swPlayer = SWPlayer.of(player);
if (swPlayer.hasComponent(DynamicRegionVisualizer2.class)) {
swPlayer.removeComponent(DynamicRegionVisualizer2.class);
swPlayer.removeComponent(DynamicRegionEditor2.class);
} else {
swPlayer.setComponent(DynamicRegionVisualizer2.INSTANCE);
if (Permission.SUPERVISOR.hasPermission(player)) {
swPlayer.setComponent(new DynamicRegionEditor2());
}
}
if (true) return;
if (swPlayer.hasComponent(DynamicRegionVisualizer.class)) {
swPlayer.removeComponent(DynamicRegionVisualizer.class);
swPlayer.removeComponent(DynamicRegionEditor.class);
} else {
swPlayer.setComponent(DynamicRegionVisualizer.INSTANCE);
if (Permission.SUPERVISOR.hasPermission(player)) {
swPlayer.setComponent(new DynamicRegionEditor(player.getPlayer()));
}
}
}
}
@@ -1,525 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository;
import de.steamwar.bausystem.region.dynamic.RegionConstructorData;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.bausystem.region.dynamic.spawn.SpawnRegion;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.CArea;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import de.steamwar.inventory.SWListInv;
import lombok.NonNull;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import org.joml.RayAabIntersection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
public class DynamicRegionEditor implements SWPlayer.Component, Listener {
static {
BiFunction<Player, Object, Object> function = (player, object) -> {
SWPlayer.of(player).getComponent(DynamicRegionEditor.class).ifPresent(DynamicRegionEditor::calcCursor);
return object;
};
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, function);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, function);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, function);
}
private final REntityServer entityServer = new REntityServer();
private final CArea area = new CArea(entityServer);
private static final BlockData DELETE = Material.RED_CONCRETE.createBlockData();
private static final BlockData INVALID = Material.YELLOW_CONCRETE.createBlockData();
private static final BlockData PLACE = Material.LIME_CONCRETE.createBlockData();
{
area.hide(true);
}
private final Player player;
private final BukkitTask task;
private Type type = new Empty();
public DynamicRegionEditor(Player player) {
this.player = player;
entityServer.addPlayer(player);
Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
for (int i = 0; i < 121; i++) {
new RInteraction(entityServer, player.getLocation());
}
moveInteractionEntities(Point.fromLocation(player.getLocation()));
CArea area = new CArea(entityServer);
area.setPos1And2(new Location(null, -Tile.maxTile, 51, -Tile.maxTile), new Location(null, Tile.maxTile, 51, Tile.maxTile));
area.setBlock(Material.BEDROCK.createBlockData());
task = Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), this::showHotbar, 0, 20);
}
private void showHotbar() {
entityServer.tick();
StringBuilder st = new StringBuilder();
st.append(type.title());
st.append(" §8(§7Change with §eSwap Hands§8)");
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(st.toString()));
}
@Override
public void onUnmount(SWPlayer player) {
entityServer.close();
task.cancel();
PlayerMoveEvent.getHandlerList().unregister(this);
PlayerSwapHandItemsEvent.getHandlerList().unregister(this);
PlayerInteractEvent.getHandlerList().unregister(this);
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (event.getPlayer() != player) return;
if (!Permission.SUPERVISOR.hasPermission(player)) {
SWPlayer.of(player).removeComponent(DynamicRegionEditor.class);
return;
}
if (event.getTo() == null) return;
if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) {
return;
}
moveInteractionEntities(Point.fromLocation(event.getTo()));
}
private void moveInteractionEntities(Point position) {
List<RInteraction> interactionList = entityServer.getEntitiesByType(RInteraction.class);
for (int x = -5; x <= 5; x++) {
for (int z = -5; z <= 5; z++) {
interactionList.removeFirst().move(position.getX() + x + 0.5, 50, position.getZ() + z + 0.5, 0, 0, (byte) 0);
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerSwapHandItems(PlayerSwapHandItemsEvent event) {
if (event.getPlayer() != player) return;
event.setCancelled(true);
List<SWListInv.SWListEntry<Type>> list = new ArrayList<>();
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.STRUCTURE_VOID, "§6Deselect"), new Empty()));
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.BARRIER, "§cDelete"), new Delete(false)));
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.COMMAND_BLOCK, "§4§lForce Delete", List.of("§7Force deletes enables §cdeleting", "§7of §6Special§7 regions without", "§7confirmation§8."), false, null), new Delete(true)));
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.LODESTONE, "§fBig Spawn"), new Spawn()));
DynamicRegionSystem.constructorDataMap.values()
.stream()
.filter(RegionConstructorData::placeable)
.sorted(Comparator.comparing(RegionConstructorData::name))
.forEach(data -> {
list.add(new SWListInv.SWListEntry<>(new SWItem(data.material(), "§f" + data.name()), new Place(data)));
});
SWListInv<Type> inventory = new SWListInv<>(player, "Select Region to place", list, (click, type) -> {
if (type instanceof Delete delete) {
delete.confirmForceDelete(player, () -> {
player.closeInventory();
this.type = delete;
area.hide(true);
showHotbar();
});
} else {
player.closeInventory();
this.type = type;
area.hide(true);
showHotbar();
}
});
inventory.open();
}
private void calcCursor() {
Location startPos = player.getLocation().clone().add(0.0, player.getEyeHeight(), 0.0);
Vector direction = player.getLocation().getDirection();
RayAabIntersection intersection = new RayAabIntersection((float) startPos.getX(), (float) startPos.getY(), (float) startPos.getZ(), (float) direction.getX(), (float) direction.getY(), (float) direction.getZ());
for (RInteraction interaction : entityServer.getEntitiesByType(RInteraction.class)) {
float x = (float) (interaction.getX() - 0.5);
float z = (float) (interaction.getZ() - 0.5);
if (intersection.test(x, (float) 50.95, z, x + 1, (float) 51.05, z + 1)) {
int tileX = (int) x;
int tileZ = (int) z;
if (type.visualize(area, tileX, tileZ)) {
area.hide(false);
return;
} else {
break;
}
}
}
area.hide(true);
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getPlayer() != player) return;
Location startPos = player.getLocation().clone().add(0.0, player.getEyeHeight(), 0.0);
Vector direction = player.getLocation().getDirection();
if (direction.getY() > 0) return;
RayAabIntersection intersection = new RayAabIntersection((float) startPos.getX(), (float) startPos.getY(), (float) startPos.getZ(), (float) direction.getX(), (float) direction.getY(), (float) direction.getZ());
for (RInteraction interaction : entityServer.getEntitiesByType(RInteraction.class)) {
float x = (float) (interaction.getX() - 0.5);
float z = (float) (interaction.getZ() - 0.5);
if (intersection.test(x, (float) 50.95, z, x + 1, (float) 51.05, z + 1)) {
int tileX = (int) x;
int tileZ = (int) z;
type.run(event.getPlayer(), tileX, tileZ);
break;
}
}
}
private interface Type {
String title();
boolean visualize(CArea area, int tileX, int tileZ);
void run(Player player, int tileX, int tileZ);
}
private static class Empty implements Type {
private Empty() {
}
@Override
public String title() {
return "§6---";
}
@Override
public boolean visualize(CArea area, int tileX, int tileZ) {
return false;
}
@Override
public void run(Player player, int tileX, int tileZ) {
}
}
private static class Delete implements Type {
protected static final String CONFIRM_DELETE = "Confirm delete: ";
protected static final String ACCEPT_DELETE = "§cDelete: ";
private final boolean force;
private boolean valid = false;
private Delete(boolean force) {
this.force = force;
}
public void confirmForceDelete(Player player, Runnable callback) {
if (force) {
confirmInventory(player, "Enable Force Delete", "§cEnable", "", callback);
} else {
callback.run();
}
}
@Override
public String title() {
if (force) {
return "§4§lForce Delete";
} else {
return "§cDelete";
}
}
private boolean isBigSpawn() {
if (!DynamicRegionSystem.INSTANCE.get(Tile.ZERO).getType().isSpawn()) return false;
return Tile.ZERO.neighboursRing()
.map(DynamicRegionSystem.INSTANCE::get)
.map(Region::getType)
.allMatch(RegionType::isSpawn);
}
@Override
public boolean visualize(CArea area, int tileX, int tileZ) {
// TODO: Special case for big Spawn!
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) {
valid = false;
return false;
}
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region.getType().isGlobal()) {
valid = false;
return false;
}
if (region.getType().isSpawn()) {
if (!isBigSpawn()) {
valid = false;
return false;
}
return selectSpawn(area);
} else {
return selectRegion(area, region);
}
}
private boolean selectSpawn(CArea area) {
Location minLoc = new Location(null, -1, 51, -1);
Location maxLoc = new Location(null, 1, 51, 1);
area.setBlock(DELETE);
area.setPos1And2(minLoc, maxLoc);
valid = true;
return true;
}
private boolean selectRegion(CArea area, Region region) {
Tile minTile = Tile.fromPoint(region.getArea().getMinPoint(false)).orElse(null);
Tile maxTile = Tile.fromPoint(region.getArea().getMaxPoint(false)).orElse(null);
if (minTile == null || maxTile == null) {
valid = false;
return false;
}
Location minLoc = new Location(null, minTile.getTileX(), 51, minTile.getTileZ());
Location maxLoc = new Location(null, maxTile.getTileX(), 51, maxTile.getTileZ());
area.setBlock(DELETE);
area.setPos1And2(minLoc, maxLoc);
valid = true;
return true;
}
@Override
public void run(Player player, int tileX, int tileZ) {
if (!valid) return;
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) return;
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region.getType().isGlobal()) return;
if (region.getType().isSpawn()) {
if (isBigSpawn()) deleteSpawn(player);
return;
}
if (region.getType() == RegionType.PATH || (force && region.getType().isSpecial())) {
region.delete();
} else {
deleteRegion(player, tile, region);
}
}
private void deleteSpawn(Player player) {
confirmInventory(player, CONFIRM_DELETE, ACCEPT_DELETE, "Big Spawn", () -> {
checkSpawnRegionAndDelete(Tile.TILE_NN);
checkSpawnRegionAndDelete(Tile.TILE_NP);
checkSpawnRegionAndDelete(Tile.TILE_PN);
checkSpawnRegionAndDelete(Tile.TILE_PP);
DynamicRegionSystem.INSTANCE.get(Tile.ZERO).getArea().place(new PasteBuilder(), false);
DynamicRegionSystem.INSTANCE.get(Tile.TILE_NZ).getArea().place(new PasteBuilder(), false);
DynamicRegionSystem.INSTANCE.get(Tile.TILE_PZ).getArea().place(new PasteBuilder(), false);
DynamicRegionSystem.INSTANCE.get(Tile.TILE_ZN).getArea().place(new PasteBuilder(), false);
DynamicRegionSystem.INSTANCE.get(Tile.TILE_ZP).getArea().place(new PasteBuilder(), false);
});
}
private void checkSpawnRegionAndDelete(Tile tile) {
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region.getType().isSpawn()) region.delete();
}
private void deleteRegion(Player player, Tile tile, Region region) {
RegionConstructorData data = DynamicRegionSystem.constructorDataMap.get(region.getClass());
confirmInventory(player, CONFIRM_DELETE, ACCEPT_DELETE, data.name(), () -> {
Region checkRegion = DynamicRegionSystem.INSTANCE.get(tile);
if (region == checkRegion) region.delete();
});
}
private void confirmInventory(Player player, String title, String accept, String name, Runnable deleteCallback) {
SWInventory inventory = new SWInventory(player, 9, title + name);
inventory.setItem(0, new SWItem(Material.GRAY_CONCRETE, "§7Cancel", click -> {
player.closeInventory();
}));
inventory.setItem(8, new SWItem(Material.RED_CONCRETE, accept + name, click -> {
player.closeInventory();
deleteCallback.run();
}));
inventory.open();
}
}
private static class Spawn extends Place {
private Spawn() {
super(3, 3);
}
@Override
public String title() {
return "§ePlace§8: §aBig Spawn";
}
@Override
public boolean validPlacement(int tileX, int tileZ) {
if (tileX != -1 || tileZ != -1) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_NN).getType().isGlobal()) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_NP).getType().isGlobal()) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_PN).getType().isGlobal()) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_PP).getType().isGlobal()) return false;
return true;
}
@Override
public void run(Player player, int tileX, int tileZ) {
if (!valid) return;
// Center Tile placement!
tileX -= widthX / 2;
tileZ -= widthZ / 2;
if (!validPlacement(tileX, tileZ)) return;
constructSpawnRegion(Tile.TILE_NN);
constructSpawnRegion(Tile.TILE_NP);
constructSpawnRegion(Tile.TILE_PN);
DynamicRegion region = constructSpawnRegion(Tile.TILE_PP);
if (region != null) region.getArea().reset(new PasteBuilder(), false);
}
private DynamicRegion constructSpawnRegion(Tile tile) {
DynamicRegion region = DynamicRegionRepository.constructRegion(SpawnRegion.class, tile);
if (region == null) return null;
region.updateNeighbours();
return region;
}
}
private static class Place implements Type {
private final RegionConstructorData data;
protected final int widthX;
protected final int widthZ;
protected boolean valid = false;
private Place(@NonNull RegionConstructorData data) {
this(data, data.widthX(), data.widthZ());
}
private Place(int widthX, int widthZ) {
this(null, widthX, widthZ);
}
private Place(RegionConstructorData data, int widthX, int widthZ) {
this.data = data;
this.widthX = widthX - 1;
this.widthZ = widthZ - 1;
}
@Override
public String title() {
return "§ePlace§8: §a" + data.name();
}
public boolean validPlacement(int tileX, int tileZ) {
for (int x = 0; x <= widthX; x++) {
for (int z = 0; z <= widthZ; z++) {
Tile tile = Tile.fromTile(tileX + x, tileZ + z).orElse(null);
if (tile == null) return false;
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (!region.getType().isGlobal()) return false;
}
}
return true;
}
@Override
public boolean visualize(CArea area, int tileX, int tileZ) {
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) {
valid = false;
return false;
}
// Center Tile placement!
tileX -= widthX / 2;
tileZ -= widthZ / 2;
// Validate placements!
if (validPlacement(tileX, tileZ)) {
area.setBlock(PLACE);
valid = true;
} else {
area.setBlock(INVALID);
valid = false;
}
area.setPos1And2(new Location(null, tileX, 51, tileZ), new Location(null, tileX + widthX, 51, tileZ + widthZ));
return true;
}
@Override
public void run(Player player, int tileX, int tileZ) {
if (!valid) return;
// Center Tile placement!
tileX -= widthX / 2;
tileZ -= widthZ / 2;
if (!validPlacement(tileX, tileZ)) return;
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) return;
DynamicRegion region = DynamicRegionRepository.constructRegion(DynamicRegionSystem.identifierDataMap.get(data.identifier()), tile);
if (region == null) return;
region.getArea().place(new PasteBuilder(), false);
region.updateNeighbours();
}
}
}
@@ -1,250 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.region.WireframeCommand;
import de.steamwar.bausystem.region.dynamic.*;
import de.steamwar.bausystem.region.dynamic.global.GlobalRegion;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DynamicRegionSystem implements RegionSystem {
public static DynamicRegionSystem INSTANCE;
private static final Map<Long, Region> regionCache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Long, Region> eldest) {
return size() > 8192; // Tweak this number if needed!
}
}; // Will be cleared on region add/delete/remove!
private static final Map<UUID, Region> regionMap = new HashMap<>();
private static final Map<RegionType, Set<Region>> regionTypeMap = new EnumMap<>(RegionType.class);
public void add(DynamicRegion region) {
regionCache.clear();
regionMap.put(region.getID(), region);
regionTypeMap.computeIfAbsent(region.getType(), __ -> new HashSet<>()).add(region);
DynamicRegionVisualizer.INSTANCE.addRegion(region);
}
public void remove(DynamicRegion region) {
regionCache.clear();
regionMap.remove(region.getID());
regionTypeMap.getOrDefault(region.getType(), Collections.emptySet()).remove(region);
DynamicRegionVisualizer.INSTANCE.removeRegion(region);
}
public static Map<Class<? extends DynamicRegion>, RegionConstructorData> constructorDataMap = new HashMap<>();
public static Map<String, Class<? extends DynamicRegion>> identifierDataMap = new HashMap<>();
@Override
public void load() {
INSTANCE = this;
// Loading all Region Constructor Data that are defined inside the Code
constructorDataMap = new BufferedReader(new InputStreamReader(BauSystem.getInstance().getClass().getResourceAsStream("/META-INF/annotations/de.steamwar.bausystem.region.dynamic.RegionConstructorData")))
.lines()
.map(s -> {
try {
return Class.forName(s, false, BauSystem.getInstance().getClass().getClassLoader());
} catch (ClassNotFoundException | NoClassDefFoundError e) {
throw new SecurityException(e.getMessage(), e);
}
})
.filter(DynamicRegion.class::isAssignableFrom)
.map(clazz -> (Class<? extends DynamicRegion>) clazz)
.collect(Collectors.toUnmodifiableMap(Function.identity(), clazz -> clazz.getAnnotation(RegionConstructorData.class)));
identifierDataMap = constructorDataMap.entrySet()
.stream()
.collect(Collectors.toUnmodifiableMap(entry -> entry.getValue().identifier(), Map.Entry::getKey));
DynamicRegionRepository.loadRegions();
new DynamicRegionCommand();
new WireframeCommand();
}
@Override
public @NonNull Location getWorldSpawn() {
return Bukkit.getWorlds().get(0).getSpawnLocation().clone().add(0.5, 0, 0.5); // TODO: Temporary
}
@Override
public @NonNull Region getGlobalRegion() {
return GlobalRegion.INSTANCE;
}
public Set<Tile> getTilesOfRegion(@NonNull Region region) {
Point minPoint = region.getArea().getMinPoint(false);
Point maxPoint = region.getArea().getMaxPoint(false);
Set<Tile> tiles = new HashSet<>();
for (int x = minPoint.getX(); x < maxPoint.getX(); x += Tile.tileSize) {
for (int z = minPoint.getZ(); z < maxPoint.getZ(); z += Tile.tileSize) {
tiles.add(Tile.fromXZ(x, z).orElse(null));
}
}
tiles.remove(null);
return Collections.unmodifiableSet(tiles);
}
public @NonNull Region get(@Nullable Tile tile) {
return get(tile, true, regionMap.values());
}
private Region get(@Nullable Tile tile, boolean fastCache, Collection<Region> regions) {
if (tile == null) return getGlobalRegion();
if (regionCache.containsKey(tile.getId())) {
Region region = regionCache.get(tile.getId());
if (fastCache || regions.contains(region)) return region;
}
Location location = tile.getCenterLocation();
Region region = regions.stream()
.filter(rg -> rg.getArea().inRegion(location, false))
.findFirst()
.orElseGet(this::getGlobalRegion);
if (fastCache || regions.contains(region)) {
regionCache.put(tile.getId(), region);
}
return region;
}
@Override
public @NonNull Region get(@NonNull Location location) {
return get(Tile.fromLocation(location).orElse(null), true, regionMap.values());
}
@Override
public Optional<Region> getRegion(@NonNull UUID id) {
return Optional.ofNullable(regionMap.get(id));
}
@Override
public @NonNull Stream<Region> getRegions() {
return regionMap.values().stream();
}
public @NonNull Stream<Region> getRegionsByType(RegionType type) {
return regionTypeMap.getOrDefault(type, Collections.emptySet()).stream();
}
public record Neighbour<T extends Region>(T region, Tile tile, NeighbourDirection direction) {
public <O extends Region> Neighbour<O> as(Class<O> clazz) {
if (!clazz.isInstance(region)) {
return null;
} else {
return (Neighbour<O>) this;
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Neighbour<?> neighbour)) return false;
return Objects.equals(tile, neighbour.tile) && direction == neighbour.direction;
}
@Override
public int hashCode() {
return Objects.hash(tile, direction);
}
}
private Stream<Neighbour<Region>> getNeighbours(Region region, boolean noCorners, boolean fastCache, Collection<Region> regions) {
Tile minTile = Tile.fromPoint(region.getArea().getMinPoint(false)).orElse(null);
Tile maxTile = Tile.fromPoint(region.getArea().getMaxPoint(false)).orElse(null);
if (minTile == null || maxTile == null) return Stream.empty();
Set<Neighbour<Region>> neighbours = new HashSet<>();
if (!noCorners) {
neighbours.add(new Neighbour<>(get(minTile.add(-1, -1).orElse(null), fastCache, regions), minTile, NeighbourDirection.NorthWest));
Tile.fromTile(minTile.getTileX(), maxTile.getTileZ()).ifPresent(cornerMinMaxSelf -> {
neighbours.add(new Neighbour<>(get(cornerMinMaxSelf.add(-1, 1).orElse(null), fastCache, regions), cornerMinMaxSelf, NeighbourDirection.SouthWest));
});
Tile.fromTile(maxTile.getTileX(), minTile.getTileZ()).ifPresent(cornerMaxMinSelf -> {
neighbours.add(new Neighbour<>(get(cornerMaxMinSelf.add(1, -1).orElse(null), fastCache, regions), cornerMaxMinSelf, NeighbourDirection.NorthEast));
});
neighbours.add(new Neighbour<>(get(maxTile.add(1, 1).orElse(null), fastCache, regions), maxTile, NeighbourDirection.SouthEast));
}
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
Tile.fromTile(x, minTile.getTileZ()).ifPresent(tileMinZSelf -> {
neighbours.add(new Neighbour<>(get(tileMinZSelf.add(0, -1).orElse(null), fastCache, regions), tileMinZSelf, NeighbourDirection.North));
});
Tile.fromTile(x, maxTile.getTileZ()).ifPresent(tileMaxZSelf -> {
neighbours.add(new Neighbour<>(get(tileMaxZSelf.add(0, 1).orElse(null), fastCache, regions), tileMaxZSelf, NeighbourDirection.South));
});
}
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
Tile.fromTile(minTile.getTileX(), z).ifPresent(tileMinXSelf -> {
neighbours.add(new Neighbour<>(get(tileMinXSelf.add(-1, 0).orElse(null), fastCache, regions), tileMinXSelf, NeighbourDirection.West));
});
Tile.fromTile(maxTile.getTileX(), z).ifPresent(tileMaxXSelf -> {
neighbours.add(new Neighbour<>(get(tileMaxXSelf.add(1, 0).orElse(null), fastCache, regions), tileMaxXSelf, NeighbourDirection.East));
});
}
neighbours.removeIf(neighbour -> neighbour.region.getType().isGlobal());
return neighbours.stream();
}
public Stream<Neighbour<DynamicRegion>> getNeighbours(Region region) {
return getNeighbours(region, false, true, regionMap.values())
.map(neighbour -> neighbour.as(DynamicRegion.class))
.filter(Objects::nonNull);
}
@Override
@NotNull
public Stream<Region> getConnectedRegions(@NonNull Region region) {
Set<Region> regions = regionTypeMap.get(region.getType());
Set<Region> connected = new HashSet<>();
LinkedHashSet<Region> current = new LinkedHashSet<>();
current.add(region);
while (!current.isEmpty()) {
Region r = current.removeFirst();
if (!connected.add(r)) continue;
getNeighbours(r, true, false, regions)
.map(neighbour -> neighbour.region)
.forEach(current::add);
}
return connected.stream();
}
}
@@ -1,114 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.dynamic.RegionConstructorData;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.*;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Display;
import org.bukkit.util.Transformation;
import org.joml.Quaternionf;
import org.joml.Vector3f;
public class DynamicRegionVisualizer implements SWPlayer.Component {
public static final DynamicRegionVisualizer INSTANCE = new DynamicRegionVisualizer();
private final REntityServer entityServer = new REntityServer();
private DynamicRegionVisualizer() {
RTextDisplay text = new RTextDisplay(entityServer, new Location(null, 0.5, 51.1, 0.5));
text.setText("Spawn");
text.setBillboard(Display.Billboard.VERTICAL);
text.setBackgroundColor(0);
text.setShadowed(false);
text.setTransform(new Transformation(new Vector3f(0, 0, 0), new Quaternionf().rotationX((float) Math.toRadians(270)), new Vector3f(1, 1, 1), new Quaternionf()));
}
public void addRegion(DynamicRegion region) {
new CRegion(entityServer, region);
}
public void removeRegion(DynamicRegion region) {
entityServer.getEntitiesByType(CRegion.class)
.stream()
.filter(cRegion -> cRegion.region == region)
.forEach(CRegion::die);
}
@Override
public void onMount(SWPlayer player) {
entityServer.addPlayer(player.getPlayer());
}
@Override
public void onUnmount(SWPlayer player) {
entityServer.removePlayer(player.getPlayer());
}
private static final Vector3f VEC_ZERO = new Vector3f(0, 0, 0);
private static final Quaternionf QUT_ZERO = new Quaternionf(0, 0, 0, 1);
public static Point toVisualization(Location worldLocation) {
Tile tile = Tile.fromLocation(worldLocation).orElseThrow();
return new Point(tile.getTileX(), 50, tile.getTileZ());
}
private static class CRegion extends CEntity {
private final DynamicRegion region;
private CRegion(REntityServer server, DynamicRegion region) {
super(server);
this.region = region;
RegionConstructorData data = DynamicRegionSystem.constructorDataMap.get(region.getClass());
int widthX = data.widthX();
int widthZ = data.widthZ();
Point point = toVisualization(region.getArea().getMinPoint(false).toLocation((World) null));
if (widthX != 1 || widthZ != 1) {
CArea area = new CArea(server);
area.setBlock(Material.WHITE_CONCRETE.createBlockData());
area.setPos1And2(point.toLocation((World) null).add(0, 1 - CArea.DEFAULT_WIDTH, 0), point.toLocation((World) null).add(widthX - 1, 1 - CArea.DEFAULT_WIDTH, widthZ - 1));
entities.add(area);
RTextDisplay text = new RTextDisplay(server, point.toLocation((World) null).add(widthX / 2.0, 51.1, widthZ / 2.0));
text.setText(data.name());
text.setBillboard(Display.Billboard.VERTICAL);
text.setBackgroundColor(0);
text.setShadowed(false);
text.setTransform(new Transformation(new Vector3f(0, 0, 0), new Quaternionf().rotationX((float) Math.toRadians(270)), new Vector3f(1, 1, 1), new Quaternionf()));
entities.add(text);
}
RBlockDisplay display = new RBlockDisplay(server, point.toLocation((World) null));
display.setTransform(new Transformation(VEC_ZERO, QUT_ZERO, new Vector3f(widthX, 1, widthZ), QUT_ZERO));
display.setBlock(data.material().createBlockData());
entities.add(display);
}
}
}
@@ -1,80 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import de.steamwar.bausystem.region.dynamic.RegionConstructorData;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.bausystem.region.dynamic.path.PathRegion;
import de.steamwar.core.SWPlayer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
public class DynamicRegionVisualizer2 implements SWPlayer.Component {
public static final DynamicRegionVisualizer2 INSTANCE = new DynamicRegionVisualizer2();
private static final World WORLD = Bukkit.getWorlds().get(0);
private static final int ORIGIN_X = 10000;
private static final int ORIGIN_Z = 10000;
public DynamicRegionVisualizer2() {
if (true) return;
// TODO: This is only needed once on startup with a clear beforehand to make the region visualization up to date with the Region Data
DynamicRegionSystem.INSTANCE.getRegions().forEach(region -> {
RegionConstructorData data = DynamicRegionSystem.constructorDataMap.get(region.getClass());
if (data == null) return;
int widthX = data.widthX();
int widthZ = data.widthZ();
Material material = data.material();
if (region instanceof PathRegion pathRegion && pathRegion.isGarden()) {
material = Material.GRASS_BLOCK;
}
Tile tile = Tile.fromPoint(region.getArea().getMinPoint(false)).orElseThrow();
for (int x = 0; x < widthX; x++) {
for (int z = 0; z < widthZ; z++) {
int cx = x + tile.getTileX() + ORIGIN_X;
int cz = z + tile.getTileZ() + ORIGIN_Z;
WORLD.getBlockAt(cx, 1, cz).setType(material, false);
}
}
});
}
@Override
public void onMount(SWPlayer player) {
Location location = player.getPlayer().getLocation().clone().multiply(1.0 / Tile.tileSize).add(ORIGIN_X + 0.5, 0, ORIGIN_Z + 0.5);
location.setY(10);
player.getPlayer().teleport(location);
// TODO: Add anything that is needed for setup
}
@Override
public void onUnmount(SWPlayer player) {
Location location = player.getPlayer().getLocation().clone().subtract(ORIGIN_X + 0.5, 0, ORIGIN_Z + 0.5).multiply(Tile.tileSize);
location.setY(50);
player.getPlayer().teleport(location.add(0.5, 0, 0.5));
// TODO: Add anything that is needed for shutdown
}
}
@@ -1,177 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
import de.steamwar.bausystem.region.DynamicRegionSystem;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.dynamic.path.PathRegion;
import de.steamwar.bausystem.region.dynamic.path.PathSide;
import de.steamwar.bausystem.utils.PasteBuilder;
import lombok.Getter;
import lombok.NonNull;
import java.io.IOException;
import java.util.*;
public abstract class DynamicRegion implements Region {
protected final UUID id;
@Getter
protected RegionData regionData = null;
/**
* This Constructor should be used if a Region is placed newly onto the world!
*
* @param tile this parameter is never used but forces the implementor to have it as a parameter
*/
protected DynamicRegion(Tile tile) {
this.id = UUID.randomUUID();
}
/**
* This constructor is used for loading the Region from a file
*
* @param id
* @param tileData this parameter is never used but forces the implementor to have it as a parameter
*/
protected DynamicRegion(UUID id, JsonArray tileData) {
this.id = id;
}
/**
* This method should be called when a Region is created and needs to be saved afterward
*/
protected final void finishCreate() {
finishLoad();
save();
}
/**
* This method should be called when a Region is loaded from file!
*/
protected final void finishLoad() {
DynamicRegionSystem.INSTANCE.add(this);
}
public abstract void writeTileData(JsonWriter writer) throws IOException;
public final void updateNeighbours() {
List<DynamicRegionSystem.Neighbour<PathRegion>> list = DynamicRegionSystem.INSTANCE.getNeighbours(this)
.map(neighbour -> neighbour.as(PathRegion.class))
.filter(Objects::nonNull)
.toList();
// Calculate Garden State for all neighbouring PathRegions
Set<UUID> needsFullReset = new HashSet<>();
list.forEach(data -> {
boolean previousGardenState = data.region().isGarden();
PathSide previousToriState = data.region().getToriiSide();
data.region().calculateGardenState();
data.region().calculateToriiState();
if (data.region().isGarden() != previousGardenState) {
needsFullReset.add(data.region().getID());
} else if (data.region().getToriiSide() != previousToriState) {
needsFullReset.add(data.region().getID());
}
});
// Updating world state for all neighbouring PathRegions
list.forEach(data -> {
if (needsFullReset.contains(data.region().getID())) {
data.region().getArea().reset(new PasteBuilder(), false);
} else {
data.region().update(this, data.direction().opposite());
}
});
// All full reset regions need to update their neighbours!
needsFullReset.forEach(uuid -> {
Region region = DynamicRegionSystem.INSTANCE.getRegion(uuid).orElse(null);
if (!(region instanceof DynamicRegion dynamicRegion)) return;
DynamicRegionSystem.INSTANCE.getNeighbours(dynamicRegion)
.map(neighbour -> neighbour.as(PathRegion.class))
.filter(Objects::nonNull)
.forEach(data -> {
if (data.region().isGarden()) return;
data.region().update(dynamicRegion, data.direction().opposite());
});
});
}
public void setRegionData(@NonNull RegionData regionData) {
this.regionData = regionData;
regionData.setStore(this);
}
@NonNull
public PathConnectResult shouldPathConnect(Tile source, PathSide side, boolean diagonal) {
return PathConnectResult.BY_TYPE;
}
public enum PathConnectResult {
ALWAYS,
NEVER,
BY_TYPE,
;
public static PathConnectResult of(Boolean result) {
return of(result, NEVER);
}
public static PathConnectResult of(Boolean result, PathConnectResult falseResult) {
if (result == null) return BY_TYPE;
if (result) {
return ALWAYS;
} else {
return falseResult;
}
}
}
@Override
public void delete() {
if (!getType().isDeletable()) return;
DynamicRegionSystem.INSTANCE.remove(this);
DynamicRegionRepository.deleteRegion(this);
Point minPoint = getArea().getMinPoint(false);
Point maxPoint = getArea().getMaxPoint(false);
PasteUtils.reset(minPoint, maxPoint);
this.updateNeighbours();
}
@Override
public final @NonNull UUID getID() {
return id;
}
@Override
public final void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public final void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -1,362 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import com.google.gson.*;
import com.google.gson.stream.JsonWriter;
import de.steamwar.bausystem.region.*;
import de.steamwar.bausystem.region.dynamic.path.PathRegion;
import de.steamwar.bausystem.region.dynamic.path.PathRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.UUID;
import java.util.logging.Level;
@UtilityClass
public class DynamicRegionRepository {
// Example regions:
// steamwar_regions/
// \- 00000000-0000-0000-0000-000000000000/
// | \- flags.json
// \- 9494bbf6-b22c-4050-b62b-4be0594ed8ba/
// \- meta.json
// \- flags.json
// \- backups/
// \- MANUAL/
// | \- 2026.03.01 14:40:00/
// | \- flags.json
// | \- backup.schem
// \- AUTOMATIC/
// \- 2026.02.30 12:00:00/
// | \- flags.json
// | \- backup.schem
// \- 2026.02.28 19:39:00/
// \- flags.json
// \- backup.schem
public static final File REGION_DATA_FOLDER = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "steamwar_regions");
public static final String META_FILE_NAME = "meta.json";
public static final String META_FILE_REGION_IDENTIFIER = "region_identifier";
@Deprecated
private static final String META_FILE_TILE_X = "tile_x";
@Deprecated
private static final String META_FILE_TILE_Z = "tile_z";
public static final String META_FILES_TILES = "tiles";
public static final String FLAG_FILE_NAME = "flags.json";
public static final String BACKUPS_DIR_NAME = "backups";
public static final String BACKUP_FILE_NAME = "backup.schem";
public static final String FLAGS_KEY = "flags";
public static final String PROPERTIES_KEY = "properties";
static {
REGION_DATA_FOLDER.mkdirs();
}
public static void loadRegions() {
// Loading all saved regions from the files
File[] regions = REGION_DATA_FOLDER.listFiles();
for (File region : regions) {
UUID regionUUID;
try {
regionUUID = UUID.fromString(region.getName());
} catch (IllegalArgumentException e) {
RegionSystem.LOGGER.log(Level.WARNING, "Failed to resolve region id: " + region.getName());
continue;
}
if (regionUUID.equals(RegionSystem.GLOBAL_REGION_ID) || regionUUID.equals(PathRegionData.PATH_REGION_ID)) {
continue;
}
File metaFile = new File(region, META_FILE_NAME);
if (!metaFile.exists() || !metaFile.isFile()) {
RegionSystem.LOGGER.log(Level.WARNING, "Failed to resolve " + region.getName());
continue;
}
JsonObject metaData;
try {
metaData = JsonParser.parseReader(new FileReader(metaFile)).getAsJsonObject();
} catch (JsonIOException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (unknown)");
continue;
} catch (JsonSyntaxException | IllegalStateException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)");
continue;
} catch (FileNotFoundException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (not found)");
continue;
}
String identifier;
try {
identifier = metaData.getAsJsonPrimitive(META_FILE_REGION_IDENTIFIER).getAsString();
} catch (ClassCastException | NumberFormatException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)");
continue;
}
// TODO: Maybe add static method to DynamicRegionSystem
Class<? extends DynamicRegion> regionClass = DynamicRegionSystem.identifierDataMap.get(identifier);
if (regionClass == null) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region no longer exists)");
continue;
}
JsonArray tileData = metaData.getAsJsonArray(META_FILES_TILES);
if (tileData == null) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (tile is no longer in bounds)");
continue;
}
constructRegion(regionClass, regionUUID, tileData);
}
// Calculate Garden State for all PathRegions
DynamicRegionSystem.INSTANCE.getRegionsByType(RegionType.PATH)
.forEach(region -> {
((PathRegion) region).calculateGardenState();
((PathRegion) region).calculateToriiState();
});
}
public static DynamicRegion constructRegion(Class<? extends DynamicRegion> clazz, Tile tile) {
Constructor<? extends DynamicRegion> regionConstructor;
try {
regionConstructor = clazz.getConstructor(Tile.class);
} catch (NoSuchMethodException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to create region (region constructor not found)");
return null;
}
try {
return regionConstructor.newInstance(tile);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to create region (invalid data)");
return null;
}
}
public static DynamicRegion constructRegion(Class<? extends DynamicRegion> clazz, UUID uuid, JsonArray tileData) {
Constructor<? extends DynamicRegion> regionConstructor;
try {
regionConstructor = clazz.getConstructor(UUID.class, JsonArray.class);
} catch (NoSuchMethodException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region constructor not found)");
return null;
}
try {
return regionConstructor.newInstance(uuid, tileData);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
e.printStackTrace();
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid data)");
return null;
}
}
private static File getRegionDirectory(UUID uuid) {
return new File(REGION_DATA_FOLDER, uuid.toString());
}
public static File getRegionDirectory(Region region) {
return getRegionDirectory(region.getID());
}
public static File getBackupsTypeDirectory(Region region, RegionBackups.BackupType backupType) {
File regionDirectory = getRegionDirectory(region);
File backupsDirectory = new File(regionDirectory, BACKUPS_DIR_NAME);
return new File(backupsDirectory, backupType.name());
}
public static File getBackupDirectory(Region region, RegionBackups.Backup backup) {
return new File(getBackupsTypeDirectory(region, backup.getType()), backup.getName());
}
public static void loadRegionData(UUID uuid, RegionData regionData) {
File regionDirectory = getRegionDirectory(uuid);
if (!regionDirectory.exists()) return;
loadRegionData(regionDirectory, regionData);
}
public static void loadRegionData(Region region, RegionData regionData) {
if (region.getRegionData() instanceof PathRegionData) {
return;
}
File regionDirectory = getRegionDirectory(region);
if (!regionDirectory.exists()) return;
loadRegionData(regionDirectory, regionData);
}
public static void loadRegionData(Region region, RegionBackups.Backup backup, RegionData regionData) {
File backupDirectory = getBackupDirectory(region, backup);
if (!backupDirectory.exists()) return;
loadRegionData(backupDirectory, regionData);
}
private static void loadRegionData(File regionDirectory, RegionData regionData) {
JsonObject flagsData;
try {
flagsData = JsonParser.parseReader(new FileReader(new File(regionDirectory, FLAG_FILE_NAME))).getAsJsonObject();
} catch (JsonIOException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (unknown)");
return;
} catch (JsonSyntaxException | IllegalStateException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)");
return;
} catch (FileNotFoundException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (not found)");
return;
}
JsonObject flags = flagsData.getAsJsonObject(FLAGS_KEY);
for (String key : flags.keySet()) {
Flag flag;
try {
flag = Flag.valueOf(key);
} catch (IllegalArgumentException e) {
continue;
}
String value = flags.getAsJsonPrimitive(key).getAsString();
Flag.Value<?> flagValue;
try {
flagValue = flag.valueOfValue(value);
} catch (IllegalArgumentException e) {
continue;
}
regionData.getBackedMap().put(flag, flagValue);
}
JsonObject properties = flagsData.getAsJsonObject(PROPERTIES_KEY);
// TODO: Implement!
}
public static void saveRegion(Region region) {
if (!(region.getType().isGlobal() || region instanceof DynamicRegion)) {
throw new IllegalArgumentException();
}
File regionDirectory = getRegionDirectory(region);
if (!regionDirectory.exists()) regionDirectory.mkdir();
if (region instanceof DynamicRegion dynamicRegion) {
RegionConstructorData constructorData = DynamicRegionSystem.constructorDataMap.get(region.getClass());
writeMetaFile(regionDirectory, constructorData, dynamicRegion);
}
if (region.getRegionData() instanceof PathRegionData) {
return;
}
writeFlagsFile(regionDirectory, region.getRegionData());
}
public static void saveBackup(Region region, RegionBackups.Backup backup) {
File backupDirectory = getBackupDirectory(region, backup);
if (!backupDirectory.exists()) {
backupDirectory.mkdirs();
}
writeFlagsFile(backupDirectory, backup.getRegionData());
}
public static void saveData(UUID uuid, RegionData regionData) {
File regionDirectory = getRegionDirectory(uuid);
writeFlagsFile(regionDirectory, regionData);
}
@SneakyThrows
private static void writeMetaFile(File regionDirectory, RegionConstructorData constructorData, DynamicRegion dynamicRegion) {
@Cleanup
JsonWriter jsonWriter = new JsonWriter(new FileWriter(new File(regionDirectory, META_FILE_NAME)));
jsonWriter.setIndent(" ");
jsonWriter.beginObject();
jsonWriter.name(META_FILE_REGION_IDENTIFIER);
jsonWriter.value(constructorData.identifier());
jsonWriter.name(META_FILES_TILES);
jsonWriter.beginArray();
dynamicRegion.writeTileData(jsonWriter);
jsonWriter.endArray();
jsonWriter.endObject();
}
@SneakyThrows
private static void writeFlagsFile(File directory, RegionData regionData) {
JsonWriter jsonWriter = new JsonWriter(new FileWriter(new File(directory, FLAG_FILE_NAME)));
jsonWriter.setIndent(" ");
jsonWriter.beginObject();
jsonWriter.name(FLAGS_KEY);
jsonWriter.beginObject();
for (Flag<?> flag : Flag.getFlags()) {
if (!regionData.has(flag).isApplicable()) continue;
jsonWriter.name(flag.name());
jsonWriter.value(regionData.get(flag).nameWithDefault());
}
jsonWriter.endObject();
jsonWriter.name(PROPERTIES_KEY);
jsonWriter.beginObject();
// TODO: Write out needed properties!
jsonWriter.endObject();
jsonWriter.endObject();
jsonWriter.close();
}
public static void deleteRegion(Region region) {
deleteDir(getRegionDirectory(region));
}
public static void deleteBackup(Region region, RegionBackups.Backup backup) {
deleteDir(getBackupDirectory(region, backup));
}
@SneakyThrows
private static void deleteDir(File file) {
Files.walkFileTree(file.toPath(), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}
@@ -1,59 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import de.steamwar.bausystem.region.dynamic.path.PathCorner;
import de.steamwar.bausystem.region.dynamic.path.PathSide;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Set;
@AllArgsConstructor
@Getter
public enum NeighbourDirection {
North(0, -1, Set.of(PathSide.North), Set.of(PathCorner.NorthEast, PathCorner.NorthWest)),
South(0, 1, Set.of(PathSide.South), Set.of(PathCorner.SouthEast, PathCorner.SouthWest)),
West(-1, 0, Set.of(PathSide.West), Set.of(PathCorner.NorthWest, PathCorner.SouthWest)),
East(1, 0, Set.of(PathSide.East), Set.of(PathCorner.NorthEast, PathCorner.SouthEast)),
NorthWest(-1, -1, Set.of(), Set.of(PathCorner.NorthWest)),
NorthEast(1, -1, Set.of(), Set.of(PathCorner.NorthEast)),
SouthWest(-1, 1, Set.of(), Set.of(PathCorner.SouthWest)),
SouthEast(1, 1, Set.of(), Set.of(PathCorner.SouthEast)),
;
private final int tileOffsetX;
private final int tileOffsetZ;
private final Set<PathSide> sideUpdates;
private final Set<PathCorner> cornerUpdates;
public NeighbourDirection opposite() {
return switch (this) {
case North -> South;
case South -> North;
case East -> West;
case West -> East;
case NorthWest -> SouthEast;
case NorthEast -> SouthWest;
case SouthWest -> NorthEast;
case SouthEast -> NorthWest;
};
}
}
@@ -1,94 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.sql.SchematicData;
import de.steamwar.sql.SchematicNode;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.IOException;
@UtilityClass
public class PasteUtils {
// TODO: Create special tiles for global water reset!
private static final VariantSelector Reset = VariantSelector.Folder("Wet");
public static void reset(Point minPoint, Point maxPoint) {
if (true) {
// TODO: Update to set Water Tile since no longer void world!
Tile minTile = Tile.fromPoint(minPoint).orElseThrow();
Tile maxTile = Tile.fromPoint(maxPoint).orElseThrow();
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
SchematicNode node = Reset.select().orElseThrow();
paste(node, Tile.fromTile(x, z).orElseThrow().getMinPoint().setY(Region.Area.WORLD_MIN_Y), 0);
}
}
return;
}
EditSession editSession = WorldEdit.getInstance()
.newEditSessionBuilder()
.world(BukkitAdapter.adapt(Bukkit.getWorlds().get(0)))
.checkMemory(false)
.allowedRegionsEverywhere()
.limitUnlimited()
.changeSetNull()
.build();
editSession.setBlocks((com.sk89q.worldedit.regions.Region) new CuboidRegion(minPoint.toBlockVector3(), maxPoint.toBlockVector3()), BlockTypes.AIR.getDefaultState());
editSession.close();
}
public static EditSession paste(File file, Point minPoint, int rotate) {
try (Clipboard clipboard = FlatteningWrapper.impl.loadSchematic(file)) {
return paste(clipboard, minPoint, rotate);
} catch (SecurityException exception) {
return null;
}
}
public static EditSession paste(SchematicNode schematic, Point minPoint, int rotate) {
try (Clipboard clipboard = new SchematicData(schematic).load()) {
return paste(clipboard, minPoint, rotate);
} catch (SecurityException | IOException exception) {
return null;
}
}
private static EditSession paste(Clipboard clipboard, Point minPoint, int rotate) {
BlockVector3 offset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
BlockVector3 to = minPoint.toBlockVector3().subtract(offset);
return clipboard.paste(BukkitAdapter.adapt(Bukkit.getWorlds().get(0)), to, false, true, new AffineTransform().rotateY(rotate));
}
}
@@ -1,40 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import org.atteo.classindex.IndexAnnotated;
import org.bukkit.Material;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@IndexAnnotated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RegionConstructorData {
String identifier();
String name();
Material material();
int widthX();
int widthZ();
boolean placeable() default true;
}
@@ -1,196 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import de.steamwar.bausystem.region.Point;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.bukkit.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@Getter
@EqualsAndHashCode
public class Tile {
public static final Tile ZERO = new Tile(0, 0);
public static final Tile TILE_PZ = new Tile(1, 0);
public static final Tile TILE_NZ = new Tile(-1, 0);
public static final Tile TILE_ZP = new Tile(0, 1);
public static final Tile TILE_ZN = new Tile(0, -1);
public static final Tile TILE_PP = new Tile(1, 1);
public static final Tile TILE_PN = new Tile(1, -1);
public static final Tile TILE_NP = new Tile(-1, 1);
public static final Tile TILE_NN = new Tile(-1, -1);
public static final int tileSize = 21;
public static final int tileOffset = tileSize / 2;
public static final int maxTile = 127;
public static final int minTile = -maxTile;
public static final int tilesPerAxis = maxTile * 2 + 1;
private final int tileX;
private final int tileZ;
private Tile(int tileX, int tileZ) {
this.tileX = tileX;
this.tileZ = tileZ;
}
public static Optional<Tile> fromTile(int tileX, int tileZ) {
if (tileX < minTile || tileZ < minTile) return Optional.empty();
if (tileX > maxTile || tileZ > maxTile) return Optional.empty();
return Optional.of(new Tile(tileX, tileZ));
}
public static Optional<Tile> fromLocation(Location location) {
return fromXZ(location.getBlockX(), location.getBlockZ());
}
public static Optional<Tile> fromPoint(Point point) {
return fromXZ(point.getX(), point.getZ());
}
public static Optional<Tile> fromXZ(int x, int z) {
x = (int) Math.floor((x + tileOffset) / (double) tileSize);
z = (int) Math.floor((z + tileOffset) / (double) tileSize);
return fromTile(x, z);
}
public static int getMinX(int tileX) {
return tileX * tileSize - tileOffset;
}
public int getMinX() {
return getMinX(tileX);
}
public static int getMinZ(int tileZ) {
return tileZ * tileSize - tileOffset;
}
public int getMinZ() {
return getMinZ(tileZ);
}
public static int getMaxX(int tileX) {
return tileX * tileSize + tileOffset;
}
public int getMaxX() {
return getMaxX(tileX);
}
public static int getMaxZ(int tileZ) {
return tileZ * tileSize + tileOffset;
}
public int getMaxZ() {
return getMaxZ(tileZ);
}
public static Location getMinLocation(int tileX, int tileZ) {
return new Location(null, getMinX(tileX), 0, getMinZ(tileZ));
}
public Point getMinPoint() {
return new Point(getMinX(tileX), 0, getMinZ(tileZ));
}
public Location getMinLocation() {
return getMinLocation(tileX, tileZ);
}
public static Location getMaxLocation(int tileX, int tileZ) {
return new Location(null, getMaxX(tileX), 0, getMaxZ(tileZ));
}
public Location getMaxLocation() {
return getMaxLocation(tileX, tileZ);
}
public static Location getCenterLocation(int tileX, int tileZ) {
return new Location(null, tileX * tileSize, 0, tileZ * tileSize);
}
public Location getCenterLocation() {
return getCenterLocation(tileX, tileZ);
}
public Optional<Tile> add(int offsetX, int offsetZ) {
return fromTile(tileX + offsetX, tileZ + offsetZ);
}
public static long getId(int tileX, int tileZ) {
return (tileX + maxTile) * tilesPerAxis + tileZ + maxTile;
}
public long getId() {
return getId(tileX, tileZ);
}
public Stream<Tile> neighboursPlus() {
List<Tile> tiles = new ArrayList<>();
add(-1, 0).ifPresent(tiles::add);
add(1, 0).ifPresent(tiles::add);
add(0, -1).ifPresent(tiles::add);
add(0, 1).ifPresent(tiles::add);
return tiles.stream();
}
public Stream<Tile> neighboursRing() {
List<Tile> tiles = new ArrayList<>();
add(-1, -1).ifPresent(tiles::add);
add(-1, 0).ifPresent(tiles::add);
add(-1, 1).ifPresent(tiles::add);
add(0, -1).ifPresent(tiles::add);
add(0, 1).ifPresent(tiles::add);
add(1, -1).ifPresent(tiles::add);
add(1, 0).ifPresent(tiles::add);
add(1, 1).ifPresent(tiles::add);
return tiles.stream();
}
@Override
public String toString() {
return tileX + ":" + tileZ;
}
public String display() {
StringBuilder st = new StringBuilder();
if (tileX >= 0) {
st.append("+");
}
st.append(tileX);
st.append(" / ");
if (tileZ >= 0) {
st.append("+");
}
st.append(tileZ);
return st.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More