Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
90 changes: 90 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,93 @@ runs/
macos/*.dmg

**/.intentionally-empty-file.o
# AI agents and assistants
#
# Some common agent instruction and project configuration files are listed
# below as commented-out examples. They are often intentionally committed and
# shared with a team, so only uncomment them if they are local-only in your
# project.

# GEMINI.md
# WARP.md
# CRUSH.md
# QWEN.md

# OpenAI Codex
AGENTS.md
.codex/

# Aider
.aider.input.history
.aider.chat.history.md
.aider.llm.history
.aider.tags.cache.v*
# .aiderignore

# Claude Code
.claude/*.local.json
.claude/**/*.log
CLAUDE.local.md
# .claude/

# Gemini CLI
gemini-debug.log
.gemini-clipboard/
# .gemini/

# Cursor AI
# .cursorrules
# .cursor/
# .cursor.json
# .cursor-settings.yaml

# Continue
# .continue/
# .continuerc.json

# Cline
# .cline/
# .clinerules
# cline.json

# Other agent/editor project config
# .warp/
# .crush/
# .codeium/
# .deepseek/
# .amazon-codewhisperer/
# .tabnineignore
# .tabnine/

# GitHub Copilot
# .github/copilot-instructions.md

# Windsurf Editor
# .windsurfrules
# .windsurf/

# Replit AI Development
# .replit
# replit.nix

# Vim and Neovim
# Swap
[._]*.s[a-v][a-z]
# comment out the next line if you don't need vector files
!*.svg
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]

# Session
Session.vim
Sessionx.vim

# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
5 changes: 5 additions & 0 deletions CONTRIBUTING.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 贡献

当你向 Sable 提交拉取请求(PR)时,即表示你授予 [RyanHCode](https://github.com/ryanhcode) 依据 [Polyform Shield License (Version 1.0.0)](LICENSE.md) 许可你的贡献的权利,并且你不可撤销地允许 Sable 未来可能进行的、包含你贡献内容的再许可,许可方式为 [MIT 许可证](https://opensource.org/license/mit)

你提交的代码必须是你的原创作品,并且你拥有对其进行贡献的权利。
49 changes: 49 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<p align="center"><img src="./.idea/icon.png" alt="Logo" width="200"></p>
<h1 align="center">Sable<br>
<div align="center">
<a href="https://discord.gg/createaeronautics">
<img alt="Discord" src="https://img.shields.io/discord/937435293294919690?style=flat&logo=discord&label=Discord&color=5865F2">
</a>
<a href="https://modrinth.com/mod/sable">
<img src="https://img.shields.io/modrinth/dt/sable?logo=modrinth&amp;label=&amp;suffix=%20&amp;style=flat&amp;color=242629&amp;labelColor=5CA424&amp;logoColor=1C1C1C" alt="Modrinth Download"/>
</a>
<a href="https://www.curseforge.com/minecraft/mc-mods/sable">
<img src="https://img.shields.io/curseforge/dt/1312371?logo=curseforge&amp;label=&amp;suffix=%20&amp;style=flat&amp;color=242629&amp;labelColor=F16436&amp;logoColor=1C1C1C" alt="CurseForge Download"/>
</a>
</div>
</h1>

<p>Sable 是一个面向 Minecraft 的侵入式库 Mod,实现了我所设想的交互式移动方块结构,称为"子层级"(sub-levels)。子层级内包含普通的 Minecraft 区块、实体和方块实体,但存在于 Minecraft 世界中一个独立动态位置和朝向上。我的目标是尽可能最大化与子层级交互时的兼容性、性能与沉浸感,并且尽量简单。</p>

### 兼容性警告

Sable 是一个侵入性极强的 Mod。它大量使用 mixin,极易与其他 Mod 产生兼容性问题。

### 开发者

如需添加可选且简单的兼容性,使某个 Mod 能与 Sable 协同工作,请查看 [Sable Companion](https://github.com/ryanhcode/sable-companion)。

查看 [Sable 开发者 Wiki](https://github.com/ryanhcode/sable/wiki) 获取文档和指南。

加入 sable zone 进行开发讨论:https://discord.gg/pnkzu2dtVA

# 构建 Rust Natives

1. 从 https://www.docker.com/get-started/ 或你的软件包管理器安装 Docker
2. 运行 `gradlew common:buildImages`(只需执行一次)
3. 运行 `gradlew common:buildRustNatives`

### 致谢

- Dimforge 的维护者与贡献者,感谢他们在默认物理管线中所包含的出色的 Rapier 物理引擎
- Eriksonn,感谢他的子层级拆分区域算法、悬浮方块,以及惊人的数学魔法
- Ocelot,感谢他出色的子层级渲染器,以及大量优化和 API 帮助
- Cyvack,感谢他为 Create 兼容性所做的许多修复和功能、装配帮助,以及整体开发
- BeeIsYou,感谢他的升力数学、兼容性修复、Bug 修复和大量整体开发
- KyanBirb,感谢他的资源、兼容性修复、Bug 修复和大量整体开发
- Cake,感谢他的 Bug 修复和整体开发帮助
- Rhyguy1,感谢他带来的士气

### 许可

除非另有说明,本仓库中的全部内容均受 RyanHCode 的 [Polyform Shield License 1.0.0](LICENSE.md) 许可。
2 changes: 2 additions & 0 deletions changelog.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- 修复子层级拆分(sub-level splitting)时的崩溃
- 修复并增加了针对子层级因边界被设置为 [0,0,0,0,0,0] 而无法加载的问题的恢复机制
4 changes: 4 additions & 0 deletions common/src/main/java/dev/ryanhcode/sable/Sable.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import java.time.LocalDate;
import java.util.List;

/**
* Common entry point for the Sable mod: mod constants, shared init, crash headers, and the default sub-level container
* initializer that wires up physics, tracking, and ticket-loading systems.
*/
public final class Sable {

public static final String MOD_NAME = "Sable";
Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/dev/ryanhcode/sable/SableClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
import foundry.veil.platform.VeilEventPlatform;
import net.minecraft.client.Minecraft;

/**
* Client-side entry point for the Sable mod: registers shader pre-processors, the sky-light shadow pass, gizmo
* handling, and the ImGui sub-level container inspector.
*/
public class SableClient {

public static final SableClientGizmoHandler GIZMO_HANDLER = new SableClientGizmoHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

import java.util.Arrays;

/**
* Client-side configuration options for Sable's rendering and networking, defined as a NeoForge
* {@link ModConfigSpec}. Changes are applied by {@link #onUpdate(boolean)}.
*/
public final class SableClientConfig {

public static final ModConfigSpec SPEC;
Expand Down
3 changes: 3 additions & 0 deletions common/src/main/java/dev/ryanhcode/sable/SableConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import net.neoforged.neoforge.common.ModConfigSpec;

/**
* Common (server-agnostic) configuration options for Sable, defined as a NeoForge {@link ModConfigSpec}.
*/
public final class SableConfig {

public static final ModConfigSpec SPEC;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import net.neoforged.neoforge.common.ModConfigSpec;

/**
* Server-side configuration options for Sable's physics simulation, defined as a NeoForge {@link ModConfigSpec}.
*/
public final class SableServerConfig {

public static final ModConfigSpec SPEC;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import net.minecraft.world.phys.Vec3;
import org.joml.Vector3d;

/**
* An interface for {@link net.minecraft.world.level.block.entity.BlockEntity} sub-classes that apply thrust to a
* sub-level via a {@link BlockEntityPropeller}. Forces are applied on each physics tick while the propeller is active.
*/
public interface BlockEntitySubLevelPropellerActor extends BlockEntitySubLevelActor {

Vector3d THRUST_VECTOR = new Vector3d();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

/**
* A Brigadier argument type that parses a {@link SubLevelTarget} from a command string. Supports {@code @} selectors,
* UUIDs, and an optional {@code static_world} target, with configurable single/multiple result and static-level
* permissions.
*/
public class SubLevelArgumentType implements ArgumentType<SubLevelTarget> {

public static final Function<SuggestionsBuilder, SuggestionsBuilder> NO_SUGGESTIONS = b -> b;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

import java.util.Map;

/**
* A kinematic (position and orientation driven) moving structure that can be hosted on a sub-level. Provides access to
* the local bounds, block state, mass, pose, lift providers, and floating clusters of the contraption.
*/
public interface KinematicContraption {

void sable$getLocalBounds(final BoundingBox3i bounds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import java.util.UUID;

/**
* A request to keep a specific sub-level loaded, scoped to a {@link SubLevelLoadingTicketType}. Multiple tickets can
* reference the same sub-level with different keys.
*/
public record SubLevelLoadingTicket<T>(SubLevelLoadingTicketType<T> type, UUID subLevelId, T key) {

public String toCompactString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import java.util.HashMap;
import java.util.Map;

/**
* A registry entry describing the kind of loading ticket a sub-level can be held by, along with the codec used to
* serialize its key. Types are created and looked up by {@link ResourceLocation}.
*/
public record SubLevelLoadingTicketType<T>(ResourceLocation name, Codec<T> codec) {
private static final Map<ResourceLocation, SubLevelLoadingTicketType<?>> REGISTRY = new HashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

/**
* Internal holder of the loading tickets and (optional) saved-world pointer associated with a single sub-level.
*/
@ApiStatus.Internal
public class SubLevelTicketInfo {
private final ObjectSet<SubLevelLoadingTicket<?>> tickets = new ObjectArraySet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
import java.util.List;
import java.util.Set;

/**
* Base {@link IMixinConfigPlugin} used by all Sable mixin configs. Conditionally applies renderer mixins (Sodium vs
* vanilla) and compatibility mixins, honoring {@link MixinModVersionConstraint} version ranges on target mods.
*/
public abstract class AbstractSableMixinPlugin implements IMixinConfigPlugin {
public static final Logger LOGGER = LogUtils.getLogger();
private final Object2BooleanMap<String> modLoadedCache = new Object2BooleanOpenHashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

/**
* Mixin into {@link net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity} to also clear the recipes used map when the container is cleared.
*/
@Mixin(AbstractFurnaceBlockEntity.class)
public abstract class AbstractFurnaceBlockEntityMixin extends BaseContainerBlockEntity implements Clearable {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;

/**
* Make the compass target angle calculation take sub-level transforms into account.
*/
@Mixin(CompassItemPropertyFunction.class)
public abstract class CompassItemPropertyFunctionMixin {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

import java.util.function.Function;

/**
* Apply the sub-level orientation to the entity's view vector calculation.
*/
@Mixin(Entity.class)
public abstract class EntityMixin {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@

import java.util.Collection;

/**
* Implement camera zoom in sub-level view, smoothly interpolating and clamping the maximum zoom distance to the sub-level bounds.
*/
@Mixin(Camera.class)
public abstract class CameraMixin implements CameraZoomExtension {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;

/**
* Make the scroll wheel control camera zoom in sub-level view instead of cycling the hotbar.
*/
@Mixin(MouseHandler.class)
public class MouseHandlerMixin {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* Fall back to first-person view when rendering the level if the sub-level view is invalid.
*/
@Mixin(GameRenderer.class)
public class GameRendererMixin {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* Handle the enter and exit logic of the sub-level view when cycling camera types.
*/
@Mixin(Minecraft.class)
public class MinecraftMixin {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import net.minecraft.world.level.chunk.LevelChunkSection;
import org.spongepowered.asm.mixin.Mixin;

/**
* Mixin into {@link net.minecraft.world.level.chunk.LevelChunkSection} to support replacing chunk sections inside sub-level plots.
*/
@Mixin(LevelChunkSection.class)
public class LevelChunkSectionMixin {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

import java.util.function.Predicate;

/**
* Mixin into {@link net.minecraft.world.level.ClipContext} to add fields allowing raytraces to ignore or restrict sub-levels, implementing {@link dev.ryanhcode.sable.mixinterface.clip_overwrite.ClipContextExtension}.
*/
@Mixin(ClipContext.class)
public class ClipContextMixin implements ClipContextExtension {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

/**
* Mixin into {@link net.minecraft.world.entity.Entity} to use an eye position that accounts for sub-level transforms during {@code pick()}.
*/
@Mixin(Entity.class)
public class EntityMixin {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

/**
* Mixin into {@link net.minecraft.commands.synchronization.ArgumentTypeInfos} to register Sable's custom command argument types.
*/
@Mixin(ArgumentTypeInfos.class)
public abstract class ArgumentTypeInfosMixin {

Expand Down
Loading