Skip to content

Commit 53920e1

Browse files
committed
Add Bounce Blocks mechanic
1 parent 5bc32ed commit 53920e1

File tree

12 files changed

+268
-222
lines changed

12 files changed

+268
-222
lines changed

craftbook-bukkit/doctools/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ plugins {
66
application.mainClass.set("org.enginehub.craftbook.internal.util.DocumentationPrinter")
77
tasks.named<JavaExec>("run") {
88
workingDir = rootProject.projectDir
9+
10+
// Add this env var to bypass systems that break docgen.
11+
environment("CRAFTBOOK_DOCGEN", "true");
912
}
1013

1114
repositories {

craftbook-bukkit/doctools/src/main/java/org/enginehub/craftbook/internal/util/DocumentationPrinter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,6 @@ public static void main(String[] args) {
8484
MechanicConfigurationGenerator.generateMechanicConfiguration();
8585

8686
worldEdit.onStopped();
87+
System.exit(0);
8788
}
8889
}

craftbook-bukkit/doctools/src/main/java/org/enginehub/craftbook/internal/util/MechanicTypesGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static void generateMechanicTypes() {
3131
MechanicType.REGISTRY.keySet().stream().sorted().forEach(mech -> {
3232
MechanicType<?> type = MechanicType.REGISTRY.get(mech);
3333
try {
34-
writer.write("public static final @Nullable MechanicType<" + type.getMechanicClass().getSimpleName() + "> " + mech.toUpperCase(Locale.ENGLISH) + " = get(\"" + mech + "\");\n");
34+
writer.write("public static final @Nullable Supplier<MechanicType<" + type.getMechanicClass().getSimpleName() + ">> " + mech.toUpperCase(Locale.ENGLISH) + " = get(\"" + mech + "\");\n");
3535
} catch (Throwable e) {
3636
System.err.println("Failed to generate mechanic type for " + mech);
3737
e.printStackTrace();

craftbook-bukkit/src/main/java/org/enginehub/craftbook/bukkit/mechanic/BukkitMechanicManager.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ public void setup() {
3838
// registerMechanic("Cauldron", org.enginehub.craftbook.mechanics.cauldron.ImprovedCauldron.class, MechanicCategory.CUSTOMISATION);
3939
// registerMechanic("Pay", org.enginehub.craftbook.mechanics.Payment.class, MechanicCategory.CIRCUIT);
4040
// registerMechanic("Pipes", org.enginehub.craftbook.mechanics.pipe.Pipes.class, MechanicCategory.CIRCUIT);
41-
// registerMechanic("BounceBlocks", org.enginehub.craftbook.mechanics.BounceBlocks.class, MechanicCategory.GENERAL);
4241
// registerMechanic("IntegratedCircuits", org.enginehub.craftbook.mechanics.ic.ICMechanic.class, MechanicCategory.CIRCUIT);
4342
// registerMechanic("MinecartSorter", org.enginehub.craftbook.mechanics.minecart.blocks.CartSorter.class, MechanicCategory.MINECART);
4443
// registerMechanic("MinecartDeposit", org.enginehub.craftbook.mechanics.minecart.blocks.CartDeposit.class, MechanicCategory.MINECART);
@@ -551,6 +550,15 @@ public void setup() {
551550
.className("org.enginehub.craftbook.bukkit.mechanics.BukkitHiddenSwitch")
552551
.buildAndRegister();
553552

553+
MechanicType.Builder
554+
.create()
555+
.id("bounce_blocks")
556+
.name("BounceBlocks")
557+
.description(TranslatableComponent.of("craftbook.bounceblocks.description"))
558+
.category(MechanicCategory.GENERAL)
559+
.className("org.enginehub.craftbook.bukkit.mechanics.BukkitBounceBlocks")
560+
.buildAndRegister();
561+
554562
// TODO CommandItems needs to load early (after variables).
555563
}
556564

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* CraftBook Copyright (C) EngineHub and Contributors <https://enginehub.org/>
3+
*
4+
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5+
* License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9+
* warranty of MERCHANTABILITY or
10+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along with this program. If not,
13+
* see <http://www.gnu.org/licenses/>.
14+
*/
15+
16+
package org.enginehub.craftbook.bukkit.mechanics;
17+
18+
import com.sk89q.worldedit.blocks.Blocks;
19+
import com.sk89q.worldedit.bukkit.BukkitAdapter;
20+
import com.sk89q.worldedit.util.formatting.text.TextComponent;
21+
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
22+
import net.kyori.adventure.text.Component;
23+
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
24+
import org.bukkit.Particle;
25+
import org.bukkit.Sound;
26+
import org.bukkit.block.Block;
27+
import org.bukkit.block.BlockFace;
28+
import org.bukkit.block.sign.Side;
29+
import org.bukkit.event.EventHandler;
30+
import org.bukkit.event.EventPriority;
31+
import org.bukkit.event.Listener;
32+
import org.bukkit.event.block.SignChangeEvent;
33+
import org.bukkit.event.player.PlayerMoveEvent;
34+
import org.bukkit.util.Vector;
35+
import org.enginehub.craftbook.ChangedSign;
36+
import org.enginehub.craftbook.CraftBook;
37+
import org.enginehub.craftbook.CraftBookPlayer;
38+
import org.enginehub.craftbook.bukkit.CraftBookPlugin;
39+
import org.enginehub.craftbook.mechanic.CraftBookMechanic;
40+
import org.enginehub.craftbook.mechanic.MechanicType;
41+
import org.enginehub.craftbook.mechanics.BounceBlocks;
42+
import org.enginehub.craftbook.util.EventUtil;
43+
import org.enginehub.craftbook.util.RegexUtil;
44+
import org.enginehub.craftbook.util.SignUtil;
45+
46+
public class BukkitBounceBlocks extends BounceBlocks implements Listener {
47+
48+
public BukkitBounceBlocks(MechanicType<? extends CraftBookMechanic> mechanicType) {
49+
super(mechanicType);
50+
}
51+
52+
@EventHandler(ignoreCancelled = true)
53+
public void onPlayerMove(final PlayerMoveEvent event) {
54+
if (event.getFrom().distanceSquared(event.getTo()) <= 0.0001) {
55+
// Ignore tiny movements for performance reasons.
56+
return;
57+
}
58+
59+
Block block = event.getFrom().getBlock().getRelative(BlockFace.DOWN);
60+
if (!Blocks.containsFuzzy(allowedBlocks, BukkitAdapter.adapt(block.getBlockData()))) {
61+
return;
62+
}
63+
64+
if (!event.getPlayer().hasPermission("craftbook.bounceblocks.use")) {
65+
// Check permissions after the simple calculations, permission lookup is slower.
66+
// Given this is movement based, we also want to avoid providing feedback to the player.
67+
return;
68+
}
69+
70+
CraftBookPlugin.logDebugMessage("Player jumped on a block that is a bounce block!", "bounce-blocks");
71+
72+
Block sign = block.getRelative(BlockFace.DOWN);
73+
74+
if (SignUtil.isSign(sign)) {
75+
for (Side side : Side.values()) {
76+
final ChangedSign s = ChangedSign.create(sign, side);
77+
78+
String signLine1 = PlainTextComponentSerializer.plainText().serialize(s.getLine(1));
79+
if (signLine1.equals("[Jump]") || signLine1.equals("[Launch]")) {
80+
boolean requiresManualJump = signLine1.equals("[Jump]");
81+
82+
if (requiresManualJump) {
83+
// Sensitivity setting for the jumping, may need tweaking
84+
if (!(Math.abs(event.getTo().getY() - event.getFrom().getY()) > sensitivity) || !(event.getFrom().getY() - event.getFrom().getBlockY() < 0.25)) {
85+
// Jump blocks require a more significant Y movement to trigger
86+
return;
87+
}
88+
}
89+
90+
String signLine2 = PlainTextComponentSerializer.plainText().serialize(s.getLine(2, CraftBookPlugin.inst().wrapPlayer(event.getPlayer())));
91+
92+
CraftBookPlugin.logDebugMessage("Jump sign found where player jumped!", "bounce-blocks");
93+
94+
double x = 0;
95+
double y = 0;
96+
double z = 0;
97+
boolean straight = signLine2.startsWith("!");
98+
99+
String[] bits = RegexUtil.COMMA_PATTERN.split(signLine2.replace("!", ""));
100+
if (bits.length == 0) {
101+
y = 0.5;
102+
} else if (bits.length == 1) {
103+
try {
104+
y = Double.parseDouble(bits[0]);
105+
} catch (NumberFormatException e) {
106+
y = 0.5;
107+
}
108+
} else {
109+
x = Double.parseDouble(bits[0]);
110+
y = Double.parseDouble(bits[1]);
111+
z = Double.parseDouble(bits[2]);
112+
}
113+
114+
if (!straight) {
115+
// Attempt to approximate velocity as basic player movement is client side and not replicated to the server.
116+
Vector facingBasis = event.getTo().toVector().subtract(event.getFrom().toVector()).setY(0);
117+
118+
if (facingBasis.lengthSquared() <= 0.0001) {
119+
// If the player isn't moving, default to their look direction to avoid NaN
120+
facingBasis = event.getTo().getDirection().clone().setY(0);
121+
}
122+
123+
Vector forward = facingBasis.normalize();
124+
125+
// Compute the right vector by crossing forward with up
126+
Vector up = new Vector(0, 1, 0);
127+
Vector right = forward.clone().crossProduct(up).normalize();
128+
129+
up = right.clone().crossProduct(forward).normalize();
130+
131+
// Convert velocity space to world coordinates
132+
Vector worldVel = forward.multiply(x).add(up.multiply(y)).add(right.multiply(z));
133+
134+
x = worldVel.getX();
135+
z = worldVel.getZ();
136+
}
137+
138+
event.getPlayer().setVelocity(new Vector(x, y, z));
139+
// We can set this to a large negative value to prevent fall damage, it'll reset to 0 when they
140+
// next touch a surface. Not really a better way to do this.
141+
event.getPlayer().setFallDistance(-1000f);
142+
143+
// Some fancy visuals/sounds to go along with the jump
144+
event.getPlayer().playSound(block.getLocation(), Sound.ENTITY_WIND_CHARGE_WIND_BURST, 1.0f, 1.0f);
145+
event.getPlayer().spawnParticle(Particle.GUST, block.getLocation().add(0.5, 1.5, 0.5), 1, 0, 0, 0, 0.1);
146+
}
147+
return;
148+
}
149+
}
150+
}
151+
152+
@EventHandler(priority = EventPriority.HIGH)
153+
public void onSignChange(SignChangeEvent event) {
154+
if (!EventUtil.passesFilter(event)) {
155+
return;
156+
}
157+
158+
String signLine1 = PlainTextComponentSerializer.plainText().serialize(event.line(1));
159+
if (!signLine1.equalsIgnoreCase("[Jump]") && !signLine1.equalsIgnoreCase("[Launch]")) {
160+
return;
161+
}
162+
163+
CraftBookPlayer player = CraftBookPlugin.inst().wrapPlayer(event.getPlayer());
164+
165+
if (!player.hasPermission("craftbook.bounceblocks.create")) {
166+
if (CraftBook.getInstance().getPlatform().getConfiguration().showPermissionMessages) {
167+
player.printError(TranslatableComponent.of(
168+
"craftbook.mechanisms.create-permission",
169+
TextComponent.of(getMechanicType().getName())
170+
));
171+
}
172+
SignUtil.cancelSignChange(event);
173+
return;
174+
}
175+
176+
try {
177+
String signLine2 = PlainTextComponentSerializer.plainText().serialize(event.line(2));
178+
String[] bits = RegexUtil.COMMA_PATTERN.split(signLine2.replace("!", ""));
179+
if (bits.length == 1) {
180+
Double.parseDouble(bits[0]);
181+
} else if (bits.length == 3) {
182+
Double.parseDouble(bits[0]);
183+
Double.parseDouble(bits[1]);
184+
Double.parseDouble(bits[2]);
185+
} else if (bits.length != 0) {
186+
throw new Exception("Invalid number of velocity components");
187+
}
188+
} catch (Exception e) {
189+
player.printError(TranslatableComponent.of("craftbook.bounceblocks.invalid-velocity"));
190+
SignUtil.cancelSignChange(event);
191+
return;
192+
}
193+
194+
if (signLine1.equalsIgnoreCase("[Jump]")) {
195+
event.line(1, Component.text("[Jump]"));
196+
} else if (signLine1.equalsIgnoreCase("[Launch]")) {
197+
event.line(1, Component.text("[Launch]"));
198+
}
199+
player.printInfo(TranslatableComponent.of("craftbook.bounceblocks.create"));
200+
}
201+
}

0 commit comments

Comments
 (0)