This commit is contained in:
2025-04-30 14:51:50 +09:00
commit 24cc7a10a7
14 changed files with 650 additions and 0 deletions

119
.gitignore vendored Normal file
View File

@@ -0,0 +1,119 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Cache of project
.gradletasknamecache
**/build/
# Common working directory
run/
runs/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

2
LICENSE.txt Normal file
View File

@@ -0,0 +1,2 @@
Copyright (c) 2025
All rights reserved.

95
build.gradle Normal file
View File

@@ -0,0 +1,95 @@
plugins {
id 'fabric-loom' version '1.10-SNAPSHOT'
id 'maven-publish'
}
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
}
fabricApi {
configureDataGeneration {
client = true
}
}
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
}
processResources {
inputs.property "version", project.version
inputs.property "minecraft_version", project.minecraft_version
inputs.property "loader_version", project.loader_version
filteringCharset "UTF-8"
filesMatching("fabric.mod.json") {
expand "version": project.version,
"minecraft_version": project.minecraft_version,
"loader_version": project.loader_version
}
}
def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release.set(targetJavaVersion)
}
}
java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
}
jar {
from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" }
}
}
// configure the maven publication
publishing {
publications {
create("mavenJava", MavenPublication) {
artifactId = project.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}

14
gradle.properties Normal file
View File

@@ -0,0 +1,14 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8
loader_version=0.16.14
# Mod Properties
mod_version=1.0
maven_group=org.walruslab
archives_base_name=kepler
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.119.2+1.21.4

View File

@@ -0,0 +1 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip

9
settings.gradle Normal file
View File

@@ -0,0 +1,9 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}

View File

@@ -0,0 +1,10 @@
package org.walruslab.kepler;
import net.fabricmc.api.ModInitializer;
public class Kepler implements ModInitializer {
@Override
public void onInitialize() {
}
}

View File

@@ -0,0 +1,17 @@
package org.walruslab.kepler.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.entity.player.PlayerEntity;
import org.walruslab.kepler.render.TrajectoryRenderer;
public class KeplerClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientEntityEvents.ENTITY_LOAD.register((entity, _a) -> {
if (entity instanceof PlayerEntity) TrajectoryRenderer.setupRenderLine((PlayerEntity) entity);
});
}
}

View File

@@ -0,0 +1,12 @@
package org.walruslab.kepler.client;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
public class KeplerDataGenerator implements DataGeneratorEntrypoint {
@Override
public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
FabricDataGenerator.Pack pack = fabricDataGenerator.createPack();
}
}

View File

@@ -0,0 +1,176 @@
package org.walruslab.kepler.render;
import com.mojang.blaze3d.systems.RenderSystem;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.jpountz.util.Utils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.ShaderProgramKeys;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.ArrowEntity;
import net.minecraft.item.BowItem;
import net.minecraft.item.Items;
import net.minecraft.predicate.entity.EntityPredicate;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.text.Text;
import net.minecraft.util.TypeFilter;
import net.minecraft.util.Util;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import org.walruslab.kepler.trajectory.Trajectory;
import java.util.List;
import java.util.function.Predicate;
public class TrajectoryRenderer {
public static void setupRenderLine(PlayerEntity player) {
player.sendMessage(Text.of("Kepler: TrajectoryRenderer setupRenderLine"), false);
WorldRenderEvents.END.register((drawContext) -> {
// get all error entity in player world
var world = player.getWorld();
var arrows = world.getEntitiesByType(TypeFilter.instanceOf(ArrowEntity.class), new Box(player.getPos().subtract(Vec3d.of(new Vec3i(100, 100, 100))), player.getPos().add(Vec3d.of(new Vec3i(100, 100, 100)))), EntityPredicates.VALID_ENTITY);
for (ArrowEntity arrow : arrows) {
if (arrow.groundCollision) return;
var pos = arrow.getPos();
var velocity = arrow.getVelocity();
var tr = new Trajectory(pos.toVector3f(), velocity.toVector3f());
var hit = tr.getTrajectoryPoints(world, 0.1f, player);
var points = hit.points();
if (points.size() < 2) {
continue; // Skip if not enough points
}
// Render the trajectory line
if (hit.hitEntity()) {
renderLine(drawContext, points, 0.0f, 1.0f, 0.0f); // Red for hit
} else {
renderLine(drawContext, points, 1.0f, 0.0f, 1.0f); // Green for no hit
}
}
var livingEntities = world.getOtherEntities(player, new Box(player.getPos().subtract(Vec3d.of(new Vec3i(100, 100, 100))), player.getPos().add(Vec3d.of(new Vec3i(100, 100, 100)))), EntityPredicates.VALID_ENTITY);
for (var entity : livingEntities) {
if (entity instanceof ArrowEntity) continue;
var pos = entity.getPos();
var velocity = entity.getVelocity();
if (velocity.length() < 0.1) continue; // Skip if velocity is too low
var predictedPos = new Vector3f(
(float) (entity.getX() + velocity.x * 20),
(float) (entity.getY() + velocity.y * 20),
(float) (entity.getZ() + velocity.z * 20)
);
var points = List.of(
new Vector3f((float) pos.x, (float) pos.y, (float) pos.z),
predictedPos
);
// Render the trajectory line
renderLine(drawContext, points, 0.0f, 0.0f, 1.0f); // Blue for entity
}
// check player is shooting bow
var activeItem = player.getMainHandStack();
if (activeItem != null && (activeItem.isOf(Items.BOW) || activeItem.isOf(Items.CROSSBOW))) {
float speed;
if (activeItem.isOf(Items.BOW)) {
var item = (BowItem) activeItem.getItem();
var f = BowItem.getPullProgress(player.getItemUseTime());
speed = f * 3.0f;
} else {
speed = 3.15f;
}
var dirDouble = player.getRotationVec(1.0f);
var dir = new Vector3f(
(float) dirDouble.x,
(float) dirDouble.y,
(float) dirDouble.z
).normalize();
var pos = player.getEyePos();
var playerVelocity = player.getVelocity();
var velocity = new Vector3f(
(float) (dir.x * speed + playerVelocity.x),
(float) (dir.y * speed + playerVelocity.y),
(float) (dir.z * speed + playerVelocity.z)
);
var tr = new Trajectory(pos.toVector3f(), velocity);
var hit = tr.getTrajectoryPoints(world, 0.1f, player);
var points = hit.points();
if (points.size() >= 2) {
var playerRight = dir.cross(new Vector3f(0, 1, 0)).normalize();
// move points to the right, closest point -> move 1, farthest point -> move 0, linear
for (int i = 0; i < points.size(); i++) {
var point = points.get(i);
var rightOffset = 0.05;
var dx = (float) (point.x + playerRight.x * rightOffset * (1 - ((float) i / points.size())));
var dy = (float) (point.y + playerRight.y * rightOffset * (1 - ((float) i / points.size())));
var dz = (float) (point.z + playerRight.z * rightOffset * (1 - ((float) i / points.size())));
points.set(i, new Vector3f(dx, dy, dz));
}
// Render the trajectory line
if (hit.hitEntity()) {
renderLine(drawContext, points, 0.0f, 1.0f, 0.0f); // Red for hit
} else {
renderLine(drawContext, points, 1.0f, 0.0f, 1.0f); // Green for no hit
}
}
}
});
}
private static void renderLine(WorldRenderContext drawContext, List<Vector3f> points, float r, float g, float b) {
// enable z-buffer
RenderSystem.enableDepthTest();
RenderSystem.depthFunc(GL11.GL_LEQUAL);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShaderColor(r, g, b, 1.0f);
var tesselator = Tessellator.getInstance();
var buf = tesselator.begin(VertexFormat.DrawMode.DEBUG_LINE_STRIP, VertexFormats.POSITION_COLOR);
var mat = drawContext.positionMatrix();
var playerPos = MinecraftClient.getInstance().getCameraEntity().getCameraPosVec(0);
for (var point: points) {
float dx = (float) (point.x - playerPos.x);
float dy = (float) (point.y - playerPos.y);
float dz = (float) (point.z - playerPos.z);
buf.vertex(mat, dx, dy, dz);
buf.color(r, g, b, 1.0f);
}
RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
BufferRenderer.drawWithGlobalProgram(buf.end());
}
}

View File

@@ -0,0 +1,142 @@
package org.walruslab.kepler.trajectory;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.decoration.EndCrystalEntity;
import net.minecraft.util.Pair;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.World;
import org.joml.Vector3f;
import java.util.List;
import java.util.ArrayList;
public class Trajectory {
public final Vector3f position;
public final Vector3f velocity;
public final float speedMultiplier;
public final float gravity;
public Trajectory(Vector3f position, Vector3f velocity) {
this.position = position;
this.velocity = velocity;
this.speedMultiplier = 0.99f;
this.gravity = 1.0f / 20;
}
public Trajectory(Vector3f position, Vector3f velocity, float speedMultiplier, float gravity) {
this.position = position;
this.velocity = velocity;
this.speedMultiplier = speedMultiplier;
this.gravity = gravity;
}
public TrajectoryHit getTrajectoryPoints(World world, float density, Entity exclusionEntity) {
List<Vector3f> points = new ArrayList<>();
Vector3f currentPosition = new Vector3f(position);
Vector3f currentVelocity = new Vector3f(velocity);
int currentTick = 0;
while (true) {
Vec3d start = new Vec3d(currentPosition.x, currentPosition.y, currentPosition.z);
Vec3d end = new Vec3d(currentPosition.x + currentVelocity.x, currentPosition.y + currentVelocity.y, currentPosition.z + currentVelocity.z);
// get all entities in range of 10 blocks
var entities = world.getOtherEntities(null, new Box(start.subtract(100, 100, 100), end.add(100, 100, 100)), entity -> entity instanceof LivingEntity || entity instanceof EndCrystalEntity);
for (Entity entity : entities) {
if (entity.equals(exclusionEntity)) continue;
// check start-end line collides with entity bounding box
var bb = entity.getBoundingBox();
var entityVelocity = entity.getVelocity();
// predict entity position by adding its velocity * seconds(by tick)
var curEntityPos = new Vector3f(
(float) (entity.getX()),
(float) (entity.getY()),
(float) (entity.getZ())
);
var predictEntityPos = new Vector3f(
(float) (entity.getX() + entityVelocity.x * currentTick),
(float) (entity.getY() + entityVelocity.y * currentTick),
(float) (entity.getZ() + entityVelocity.z * currentTick)
);
var entityStart = new Vec3d(bb.minX, bb.minY, bb.minZ).add(entityVelocity.x * currentTick, entityVelocity.y * currentTick, entityVelocity.z * currentTick);
var entityEnd = new Vec3d(bb.maxX, bb.maxY, bb.maxZ).add(entityVelocity.x * currentTick, entityVelocity.y * currentTick, entityVelocity.z * currentTick);
var entityBox = new Box(entityStart, entityEnd);
if (this.boxIntersectsLine(start, end, entityBox)) {
return new TrajectoryHit(points, true); // Hit an entity
}
}
var rContext = new RaycastContext(
start,
end,
RaycastContext.ShapeType.COLLIDER,
RaycastContext.FluidHandling.NONE,
ShapeContext.absent()
);
var hitResult = world.raycast(rContext);
if (hitResult.getType() != HitResult.Type.MISS) {
// Check if the hit result is a block
if (hitResult.getType() == HitResult.Type.BLOCK) {
return new TrajectoryHit(points, false); // Hit a block
}
}
points.add(new Vector3f(currentPosition));
// Update position and velocity
currentPosition.add(currentVelocity);
currentVelocity.y -= gravity; // Apply gravity
currentVelocity.mul(speedMultiplier); // Apply speed multiplier
currentTick++;
if (currentTick > 5000 / density) {
break; // Prevent infinite loop
}
}
return new TrajectoryHit(points, false); // No hit
}
private boolean boxIntersectsLine(Vec3d start, Vec3d end, Box box) {
double tMin = 0.0;
double tMax = 1.0;
double[] startArr = {start.x, start.y, start.z};
double[] endArr = {end.x, end.y, end.z};
double[] boxMin = {box.minX, box.minY, box.minZ};
double[] boxMax = {box.maxX, box.maxY, box.maxZ};
for (int i = 0; i < 3; i++) {
double d = endArr[i] - startArr[i];
if (Math.abs(d) < 1e-8) {
if (startArr[i] < boxMin[i] || startArr[i] > boxMax[i]) {
return false;
}
} else {
double t1 = (boxMin[i] - startArr[i]) / d;
double t2 = (boxMax[i] - startArr[i]) / d;
if (t1 > t2) {
double temp = t1;
t1 = t2;
t2 = temp;
}
tMin = Math.max(tMin, t1);
tMax = Math.min(tMax, t2);
if (tMin > tMax) {
return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,9 @@
package org.walruslab.kepler.trajectory;
import net.minecraft.util.Pair;
import org.joml.Vector3f;
import java.util.List;
public record TrajectoryHit(List<Vector3f> points, boolean hitEntity) {
}

View File

@@ -0,0 +1,31 @@
{
"schemaVersion": 1,
"id": "kepler",
"version": "${version}",
"name": "Kepler",
"description": "",
"authors": [],
"contact": {},
"license": "All-Rights-Reserved",
"icon": "assets/kepler/icon.png",
"environment": "client",
"entrypoints": {
"fabric-datagen": [
"org.walruslab.kepler.client.KeplerDataGenerator"
],
"client": [
"org.walruslab.kepler.client.KeplerClient"
],
"main": [
"org.walruslab.kepler.Kepler"
]
},
"mixins": [
"kepler.mixins.json"
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "${minecraft_version}"
}
}

View File

@@ -0,0 +1,13 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.walruslab.kepler.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}