更新版本为1.0.7

1. 修复 #5 #9 的问题:
服务器端崩溃(因为依赖加载了客户端专属类,感谢 璃之雨 和 AtinyFurina 的指正)

2.添加了Datagen

3.引入自写的库[Lib39](https://github.com/3944Realms/Lib39/tree/Forge_1_20_1)来辅助Datagen,理论上是不需要安装Lib39的

4.更新了Readme
This commit is contained in:
叁玖领域 2025-11-22 19:43:59 +08:00
parent bf5e9f98b9
commit 9511f0074c
53 changed files with 674 additions and 137 deletions

View File

@ -15,8 +15,8 @@ All you need is a cannon and enough gunpowder, and you can experience a journey
| Mod Update Plan | Status |
|----------------|--------|
| Migration to Forge 1.20.1 | ✅ Completed |
| Migration to higher versions of NeoForge | Not started, planned for July/August |
| Open API for cross-mod compatibility | Not started, planned for July/August |
| Migration to higher versions of NeoForge | Not started |
| Open API for cross-mod compatibility | Not started, |
| Improve and refactor the original mod's operations | Not started |
## Mod Development Statement
@ -26,33 +26,30 @@ This mod is a migration and refactor of **Blast Travel** (the original mod only
- The **1.20.1 Quilt version** is based on **Abbie5's fork**, with the MODID remaining as `blasttravel`.
- The **1.20.1 Forge version** is a partial logic refactor by the author, with the MODID changed to `blasttravelreborn`. Please note the difference.
## Q&A
**Q1: Will there be Fabric support for future higher versions?**
**A1:** Probably not. The author is not a fan of Fabric's development workflow. However, anyone is welcome to fork the project and develop a Fabric version. That said, the author might consider using the **Architectury** framework for multi-loader support (though it would be quite a hassle).
**Q2: Can my mod integrate with this mod?**
**A2:** Yes! The author welcomes mod integrations. If you're a mod developer interested in compatibility, feel free to submit an **ISSUE** in this repository. The author is happy to provide code-level support.
**Q3: Will you support versions below 1.19.2?**
**A3:** No. The author will **never** backport to lower versions. However, there is a mod with similar functionality—**Cannon (Teacon2022 Example Mod)**, which supports **1.181.18.2** and can serve as an alternative.
# Adding to Your Project
1. Download mod jar fileand put it under your project's 'libs/' dir, and rename it into "{modid}-{minecraft-version}-{mod_version}.jar"(for example, "blasttravelreborn-1.20.1-1.0.6-forge-all.jar")
2. Ensure your `build.gradle` has enable 'libs' flatDir repositories
```groovy
repositories {
flatDir {
dir 'libs'
}
# For Developer
You can integrate and automatically download JsonEM for your mod project using Gradle.
Just add the following to your build script (`build.gradle`):
## Repositories
```gradle
repositories {
maven {
name = "LTD Maven"
url = "https://nexus.bot.leisuretimedock.top/repository/maven-public/"
}
````
3. Add it to dependencies
```groovy
dependencies {
implementation fg.deobf("blank:blasttravelreborn-1.20.1:1.0.6-forge-all")//for example
}
````
}
```
### Forge
```gradle
dependencies {
implementation fg.deobf("com.leisuretimedock.blastravelreborn:${minecraft_version}-${blasttravelreborn_version})"
}
```
### Choose a Version
`${mc_version}` gets replaced by the current Minecraft version. (i.e. `1.20.1`)
`${blasttravelreborn_version}` gets replaced by the version of JEI you want to use (i.e `1.0.7`)
Cannon Model please install [BlockBench Plugin](https://github.com/LeisureTimeDock/JsonEM_Neo_Forge/blob/1.21/jsonem_models.js) in your BlockBench.
And import 'entity/cannon/main.json' with this jsonem plugin. Then you can view the cannon model correctly and create its texture.

View File

@ -8,6 +8,7 @@ buildscript {
plugins {
id 'eclipse'
id 'maven-publish'
id 'idea'
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
id 'org.parchmentmc.librarian.forgegradle' version '1.+'
@ -152,7 +153,11 @@ repositories {
}
// If you have mod jar dependencies in ./libs, you can declare them as a repository like so.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver
flatDir {
maven {
name = "LTD Maven"
url = "https://nexus.bot.leisuretimedock.top/repository/maven-public/"
}
flatDir {
dir 'libs'
}
@ -167,9 +172,12 @@ dependencies {
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
implementation 'org.spongepowered:mixin:0.8.5'
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
implementation jarJar(fg.deobf("blank:jsonem-${minecraft_version}:${jsonem_version}")) {
jarJar.ranged(it, "[0.0,)")
implementation "top.r3944realms.lib39:lib39:${lib39_version}"
implementation jarJar(fg.deobf("com.leisuretimedock:jsonem-forge-${minecraft_version}:${jsonem_version}")) {
jarJar.ranged(it, "[${jsonem_version},)")
}
// Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}")
@ -226,3 +234,43 @@ tasks.named('jar', Jar).configure {
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
publishing {
publications {
mavenJava(MavenPublication) {
artifactId = base.archivesName.get()
from components.java
artifactId = "${mod_id}-${project.name}-${minecraft_version}"
pom {
name = rootProject.mod_id
description = rootProject.mod_description
url = rootProject.mod_source
licenses {
license {
name = rootProject.mod_license
}
}
developers {
developer {
id = rootProject.mod_id
name = rootProject.mod_authors
}
}
}
}
}
repositories {
maven {
url "file://${project.projectDir}/mcmodsrepo"
}
maven {
name = 'LTDNexus'
url = 'https://nexus.bot.leisuretimedock.top/repository/maven-releases/'
credentials {
username = System.getenv('LTDNexusUsername') ?: ''
password = System.getenv('LTDNexusPassword') ?: ''
}
}
}
}

View File

@ -38,8 +38,10 @@ mod_name=BlastTravel-Reborn
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT
# The mod version. See https://semver.org/
mod_version=1.0.6+1.20.1-forge
jsonem_version=0.2.2+1.20-fabrge
mod_version=1.0.7
jsonem_version=0.2.4
lib39_version=1.20.1-0.0.17
mod_source=https://github.com/LeisureTimeDock/BlastTravel_Neo_Forge
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
@ -47,4 +49,4 @@ mod_group_id=com.leisuretimedock.blasttravelreborn
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=FoundationGames(Original Author), Abbie5(Migrate into Quilt 1.20.1), R3944Realms
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description=Migrate from Quilt 1.20.1,1.19.2
mod_description=Migrate from Quilt 1.20.1, 1.19.2

View File

@ -0,0 +1 @@
0195af1841c2087eb2a10cbc75e0b9fc1d30f62f2a752a6797acbb3d503b8af3

View File

@ -0,0 +1 @@
ac8caf27e4c5d9e7d04fb6b4eff0bc5aa27e8e0440be346cbf2161c775a5a23c5326c201a83d93fbaea95ee40c8a6fb0a649b5db0d3cc0b14f11dfb8568c3c0f

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.leisuretimedock.blasttravelreborn</groupId>
<artifactId>blasttravel-reborn</artifactId>
<version>1.0.6+1.20.1-forge</version>
</project>

View File

@ -0,0 +1 @@
48136ed719d64d3e436b1782f494892fdb7abd6f08b9ff7750c07201bc9ff8f8

View File

@ -0,0 +1 @@
30938abad6f11d4d26e73abe9cee3cb8321def9421f530574e68a89010f79e56e3bd860ec491e25a5a841a4787ea93a094675ecfc114b193137f808a558ab16c

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>com.leisuretimedock.blasttravelreborn</groupId>
<artifactId>blasttravel-reborn</artifactId>
<versioning>
<latest>1.0.6+1.20.1-forge</latest>
<release>1.0.6+1.20.1-forge</release>
<versions>
<version>1.0.6+1.20.1-forge</version>
</versions>
<lastUpdated>20250612155714</lastUpdated>
</versioning>
</metadata>

View File

@ -0,0 +1 @@
7be97661cf89db9c24d0e144216f7911

View File

@ -0,0 +1 @@
bcbda3d6ce62aa2d5daa5cd991285b008b51ea81

View File

@ -0,0 +1 @@
e56c09fc1d1fa062ea5b77ea5673c19c4a56ee4b91450646d2aed786832ce267

View File

@ -0,0 +1 @@
3bdb8ae4325fd516bfaafeb33b0221f51f88eadd8388e3c49ea7f160eca95d34f5e04809a389afd6af913e21bf4985dd3a6d99cb4b491e12961b458293110f62

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-22T19:25:06.9619789 Registries
e615490187adee92d48183e84dd6953d340753c2 data/blasttravelreborn/damage_type/cannon.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-22T19:25:06.9619789 Languages: zh_tw
094e7efc852fc45d55f97c0976bf2453de88e219 assets/blasttravelreborn/lang/zh_tw.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-22T19:25:06.9619789 Languages: zh_cn
03cccc87a6370bef5c6b95b1d76cd9d22547b86c assets/blasttravelreborn/lang/zh_cn.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-22T19:25:06.9619789 Item Models: blasttravelreborn
3efedd95103ec88f295ab752f9591da6f20e9cb2 assets/blasttravelreborn/models/item/cannon.json

View File

@ -0,0 +1,3 @@
// 1.20.1 2025-11-22T19:25:06.9619789 Recipes
d38155e7a052e0f1143b0b82dc1a595026c4d98c data/blasttravelreborn/advancements/recipes/misc/cannon.json
7fd4c6df73dc342481e69f6d24914ca8e767b578 data/blasttravelreborn/recipes/cannon.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-22T19:28:42.6814983 Particle Descriptions
1aacb18bd6bc829ecf1ac93ae1e3e4a9c30459b9 assets/blasttravelreborn/particles/cannon_blast.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-22T19:25:06.9619789 Languages: en_us
c3f0d412d3d39f47d1dd4065782d0984a17ed4e7 assets/blasttravelreborn/lang/en_us.json

View File

@ -0,0 +1,9 @@
{
"container.blasttravelreborn.cannon_container_menu": "Cannon",
"death.attack.blasttravelreborn.cannon": "%s was knocked out by a flying %s",
"dialog.blasttravelreborn.full_cannon": "Cannon is full!",
"dialog.blasttravelreborn.no_gunpowder": "Cannon has no gunpowder!",
"entity.blasttravelreborn.cannon": "Cannon",
"item.blasttravelreborn.cannon": "Cannon",
"mount.blasttravelreborn.cannon.onboard": "Press %s to Exit, or %s to Fire"
}

View File

@ -1,10 +1,9 @@
{
"container.blasttravelreborn.cannon_container_menu": "大炮",
"item.blasttravelreborn.cannon": "大炮",
"entity.blasttravelreborn.cannon": "大炮实体",
"container.blasttravelreborn.cannon_container_menu": "大炮实体",
"death.attack.blasttravelreborn.cannon": "%s被飞行的%s击倒了",
"dialog.blasttravelreborn.full_cannon": "大炮已装满!",
"dialog.blasttravelreborn.no_gunpowder": "大炮没有火药了!",
"entity.blasttravelreborn.cannon": "大炮",
"item.blasttravelreborn.cannon": "大炮",
"mount.blasttravelreborn.cannon.onboard": "按%s离开或按%s发射"
}
}

View File

@ -0,0 +1,9 @@
{
"container.blasttravelreborn.cannon_container_menu": "大砲實體",
"death.attack.blasttravelreborn.cannon": "%s被飛行的%s擊倒了",
"dialog.blasttravelreborn.full_cannon": "大砲已裝滿!",
"dialog.blasttravelreborn.no_gunpowder": "大砲沒有火藥了!",
"entity.blasttravelreborn.cannon": "大砲",
"item.blasttravelreborn.cannon": "大砲",
"mount.blasttravelreborn.cannon.onboard": "按%s離開或按%s發射"
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "blasttravelreborn:item/cannon"
}
}

View File

@ -0,0 +1,12 @@
{
"textures": [
"blasttravelreborn:blast_smoke_0",
"blasttravelreborn:blast_smoke_1",
"blasttravelreborn:blast_smoke_2",
"blasttravelreborn:blast_smoke_3",
"blasttravelreborn:blast_smoke_4",
"blasttravelreborn:blast_smoke_5",
"blasttravelreborn:blast_smoke_6",
"blasttravelreborn:blast_smoke_7"
]
}

View File

@ -0,0 +1,35 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_iron_block": {
"conditions": {
"items": [
{
"items": [
"minecraft:iron_block"
]
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "blasttravelreborn:cannon"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_iron_block",
"has_the_recipe"
]
],
"rewards": {
"recipes": [
"blasttravelreborn:cannon"
]
},
"sends_telemetry_event": false
}

View File

@ -1,5 +1,5 @@
{
"exhaustion": 0.0,
"message_id": "cannon",
"exhaustion": 0,
"scaling": "when_caused_by_living_non_player"
}
}

View File

@ -0,0 +1,30 @@
{
"type": "minecraft:crafting_shaped",
"category": "misc",
"key": {
"A": {
"item": "minecraft:iron_block"
},
"B": {
"item": "minecraft:string"
},
"C": {
"tag": "minecraft:stone_crafting_materials"
},
"D": {
"item": "minecraft:stick"
},
"E": {
"tag": "minecraft:logs"
}
},
"pattern": [
" A",
"BA ",
"CDE"
],
"result": {
"item": "blasttravelreborn:cannon"
},
"show_notification": true
}

View File

@ -9,12 +9,10 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod(value = BlastTravelReborn.MOD_ID)
public class BlastTravelReborn {
public static final String MOD_ID = "blasttravelreborn";
public static final Logger LOG = LoggerFactory.getLogger("Blast Travel Reborn");
public BlastTravelReborn() {
FMLJavaModLoadingContext fmlJavaModLoadingContext = FMLJavaModLoadingContext.get();
IEventBus modEventBus = fmlJavaModLoadingContext.getModEventBus();

View File

@ -0,0 +1,12 @@
package com.leisuretimedock.blasttravelreborn.api.events;
import com.leisuretimedock.blasttravelreborn.content.entity.cannon.CannonBehavior;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import org.jetbrains.annotations.NotNull;
public class RegisterCannonBehaviorEvent extends Event implements IModBusEvent {
public void registerCannonBehavior(@NotNull CannonBehavior behavior) {
behavior.register();
}
}

View File

@ -17,6 +17,7 @@ import net.minecraft.util.Mth;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.NotNull;
@OnlyIn(Dist.CLIENT)
public class CannonEntityRenderer extends EntityRenderer<CannonEntity> {
public static final ModelLayerLocation MODEL = new ModelLayerLocation(BlastTravelReborn.id("cannon"), "main");

View File

@ -0,0 +1,23 @@
package com.leisuretimedock.blasttravelreborn.content;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.BootstapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.damagesource.DamageScaling;
import net.minecraft.world.damagesource.DamageType;
public class BTRDamageTypes {
public static final ResourceKey<DamageType> CANNON = ResourceKey.create(Registries.DAMAGE_TYPE,
BlastTravelReborn.id("cannon"));
public static void bootstrap(BootstapContext<DamageType> context) {
context.register(CANNON, new DamageType(
"cannon",
DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER,
0.0F
));
}
}

View File

@ -17,7 +17,6 @@ import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
@ -163,6 +162,11 @@ public class CannonEntity extends Entity {
@Override
public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) {
ItemStack handItem = player.getItemInHand(hand);
// 如果手持火药填充到大炮中
if(!player.isShiftKeyDown()) {
if (testAndInteract(player, handItem)) return InteractionResult.sidedSuccess(level().isClientSide());
}
if (player == this.getFirstPassenger()) {
return super.interact(player, hand);
}
@ -196,6 +200,90 @@ public class CannonEntity extends Entity {
return super.interact(player, hand);
}
private boolean testAndInteract(Player player, @NotNull ItemStack stack) {
if (stack.is(Items.GUNPOWDER)) {
handlerGunPower(player, stack);
return true;
} else if (stack.is(Items.CHAIN)) {
handlerChain(player, stack);
return true;
} else if (CannonBehavior.isValidBehaviorStack(stack)) {
handlerBehaviorItem(player, stack);
return true;
}
return false;
}
private void handlerGunPower(Player player, ItemStack stack) {
if (!level().isClientSide()) {
ItemStack currentGunpowder = inventory.getItem(0);
// 如果炮内已有火药先还给玩家
if (currentGunpowder.getCount() >= currentGunpowder.getMaxStackSize()) {
if (!player.isCreative() && !player.getInventory().add(currentGunpowder)) {
// 如果背包满了掉落在地上
player.drop(currentGunpowder, false);
}
}
// 设置新的火药到炮中
ItemStack newGunpowder = stack.copy();
newGunpowder.setCount(currentGunpowder.getCount() + 1); // 只放一个火药
((ServerPlayer) player).sendSystemMessage(Component.literal(String.format("%d / %d", currentGunpowder.getCount(), currentGunpowder.getMaxStackSize())), true);
inventory.setItem(0, newGunpowder);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), SoundEvents.SAND_PLACE, SoundSource.BLOCKS, 1.0f, 1.0f);
}
}
private void handlerChain(Player player, ItemStack stack) {
if (!level().isClientSide()) {
ItemStack currentChain = inventory.getItem(2);
if (currentChain.isEmpty()) { // 添加锁链
ItemStack item = stack.copy();
item.setCount(1);
inventory.setItem(2, item);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), SoundEvents.CHAIN_PLACE, SoundSource.BLOCKS, 1.0f, 1.0f);
} else {
inventory.setItem(2, ItemStack.EMPTY);
if (!player.isCreative()) { // 移除锁链
if (!player.getInventory().add(currentChain)) {
player.drop(currentChain, false);
}
}
level().playSound(null, this.blockPosition(), SoundEvents.CHAIN_FALL, SoundSource.BLOCKS, 1.0f, 1.0f);
}
}
}
private void handlerBehaviorItem(Player player, ItemStack stack) {
if (!level().isClientSide()) {
ItemStack currentBehaviorItem = inventory.getItem(2);
if (currentBehaviorItem.isEmpty()) { // 添加物品
ItemStack item = stack.copy();
item.setCount(1);
inventory.setItem(2, item);
if (!player.isCreative()) {
stack.shrink(1);
}
level().playSound(null, this.blockPosition(), getBehavior().getPlaceInSound(), SoundSource.BLOCKS, 1.0f, 1.0f);
} else {
if (stack.is(currentBehaviorItem.getItem())) { //同类物品 +1
} else { //不同类替换
}
}
}
}
@Override
public boolean skipAttackInteraction(@NotNull Entity attacker) {
if (attacker instanceof Player player && player != this.getFirstPassenger()) {
@ -299,12 +387,17 @@ public class CannonEntity extends Entity {
System.out.println(" - [B]Server: Bounding Box = " + player.getBoundingBox());
System.out.println(" - [B]Server: Delta Movement = " + player.getDeltaMovement());
}
player.stopRiding();
((ServerPlayer) player).connection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
((ServerPlayer) player).connection.send(new ClientboundSetEntityMotionPacket(player));
// 关键修改先设置速度再停止乘坐
player.setDeltaMovement(vel);
player.hurtMarked = true;
player.hurtMarked = true; // 强制同步运动状态
// 然后停止乘坐
player.stopRiding();
// 设置飞行状态
((PlayerEntityDuck)player).blasttravel$setCannonFlight(true);
if (!FMLEnvironment.production) {
System.out.println(" - [A]Server: Is Passenger = " + player.isPassenger());
System.out.println(" - [A]Server: On Ground = " + player.onGround());
@ -315,7 +408,6 @@ public class CannonEntity extends Entity {
System.out.println(" - [A]Server: Delta Movement = " + player.getDeltaMovement());
}
firedPlayer = player;
}
this.level().playSound(null, this.blockPosition(), SoundEvents.GENERIC_EXPLODE, SoundSource.BLOCKS, 1, 1);

View File

@ -5,6 +5,8 @@ import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
@ -16,13 +18,15 @@ import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
public class CannonBehavior {
protected static final Vector3f WHITE = new Vector3f(1, 1, 1);
private static final List<CannonBehavior> ID_TO_BEHAVIOR = new ArrayList<>();
private static final Object2IntMap<Predicate<ItemStack>> FILTER_TO_BEHAVIOR_ID = new Object2IntOpenHashMap<>();
private final AtomicBoolean registered = new AtomicBoolean(false);
private SoundEvent placeInSound = SoundEvents.SAND_PLACE, takeOutSound = SoundEvents.EMPTY;
public final Item icon;
public final Predicate<ItemStack> filter;
private final ResourceLocation texture;
@ -41,10 +45,28 @@ public class CannonBehavior {
this.texture = texture;
}
public CannonBehavior register() {
FILTER_TO_BEHAVIOR_ID.put(this.filter, ID_TO_BEHAVIOR.size());
ID_TO_BEHAVIOR.add(this);
public void setPlaceInSound(SoundEvent placeInSound) {
this.placeInSound = placeInSound;
}
public SoundEvent getPlaceInSound() {
return placeInSound;
}
public SoundEvent getTakeOutSound() {
return takeOutSound;
}
public void setTakeOutSound(SoundEvent takeOutSound) {
this.takeOutSound = takeOutSound;
}
public CannonBehavior register() {
if (!registered.get()) {
FILTER_TO_BEHAVIOR_ID.put(this.filter, ID_TO_BEHAVIOR.size());
ID_TO_BEHAVIOR.add(this);
registered.set(true);
}
return this;
}
@ -86,6 +108,7 @@ public class CannonBehavior {
return texture;
}
@OnlyIn(Dist.CLIENT)
public ResourceLocation headTexture(CannonEntity entity) {
var player = entity.getClientPlayer();
@ -105,4 +128,5 @@ public class CannonBehavior {
public @Nullable Vector3f fireColor(CannonEntity entity) {
return null;
}
}

View File

@ -0,0 +1,11 @@
package com.leisuretimedock.blasttravelreborn.datagen;
import com.leisuretimedock.blasttravelreborn.content.BTRDamageTypes;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.core.registries.Registries;
public class BTRRegistries {
public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder()
.add(Registries.DAMAGE_TYPE, BTRDamageTypes::bootstrap)
;
}

View File

@ -0,0 +1,62 @@
package com.leisuretimedock.blasttravelreborn.datagen;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import com.leisuretimedock.blasttravelreborn.datagen.provider.BTRItemModelProvider;
import com.leisuretimedock.blasttravelreborn.datagen.provider.BTRParticleProvider;
import com.leisuretimedock.blasttravelreborn.datagen.provider.BTRRecipeProvider;
import com.leisuretimedock.blasttravelreborn.datagen.value.BTRLangKeys;
import net.minecraft.data.DataProvider;
import net.minecraftforge.common.data.DatapackBuiltinEntriesProvider;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.datagen.provider.SimpleLanguageProvider;
import top.r3944realms.lib39.datagen.value.McLocale;
import java.util.Set;
@Mod.EventBusSubscriber(modid = BlastTravelReborn.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class DataGeneratorHandler {
@SubscribeEvent
public static void generatorDataEvent(GatherDataEvent event) {
LanguageGenerate(event, McLocale.EN_US);
LanguageGenerate(event, McLocale.ZH_CN);
LanguageGenerate(event, McLocale.ZH_TW);
ModelDataGenerate(event);
ParticleGenerate(event);
RecipeGenerate(event);
DatapackBuiltinEntriesGenerate(event);
}
private static void LanguageGenerate(@NotNull GatherDataEvent event, McLocale language) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<SimpleLanguageProvider>) pOutput -> new SimpleLanguageProvider(pOutput, BlastTravelReborn.MOD_ID ,language , BTRLangKeys.INSTANCE)
);
}
private static void ModelDataGenerate(GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<BTRItemModelProvider>) pOutput -> new BTRItemModelProvider(pOutput, event.getExistingFileHelper())
);
}
private static void RecipeGenerate(GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeServer(),
(DataProvider.Factory<BTRRecipeProvider>) BTRRecipeProvider::new
);
}
private static void ParticleGenerate(GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<BTRParticleProvider>) pOutput -> new BTRParticleProvider(pOutput, event.getExistingFileHelper())
);
}
private static void DatapackBuiltinEntriesGenerate(GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeServer(),
(DataProvider.Factory<DatapackBuiltinEntriesProvider>) pOutput -> new DatapackBuiltinEntriesProvider(pOutput, event.getLookupProvider(), BTRRegistries.BUILDER, Set.of(BlastTravelReborn.MOD_ID))
);
}
}

View File

@ -0,0 +1,35 @@
package com.leisuretimedock.blasttravelreborn.datagen.provider;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import com.leisuretimedock.blasttravelreborn.content.item.BTRItems;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.client.model.generators.ItemModelProvider;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.Objects;
public class BTRItemModelProvider extends ItemModelProvider {
public static final String GENERATED = "item/generated";
public BTRItemModelProvider(PackOutput packOutput, ExistingFileHelper existingFileHelper) {
super(packOutput, BlastTravelReborn.MOD_ID, existingFileHelper);
}
@Override
protected void registerModels() {
itemGeneratedModel(BTRItems.CANNON_ITEM.get(), resourceItem("cannon"));
}
public void itemGeneratedModel(Item item, ResourceLocation texture) {
withExistingParent(itemName(item), GENERATED).texture("layer0", texture);
}
private String itemName(Item item) {
return Objects.requireNonNull(ForgeRegistries.ITEMS.getKey(item)).getPath();
}
public ResourceLocation resourceItem(String path) {
return BlastTravelReborn.id("item/" + path);
}
}

View File

@ -0,0 +1,27 @@
package com.leisuretimedock.blasttravelreborn.datagen.provider;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import com.leisuretimedock.blasttravelreborn.content.BTRParticleTypes;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.common.data.ParticleDescriptionProvider;
import java.util.ArrayList;
import java.util.List;
public class BTRParticleProvider extends ParticleDescriptionProvider {
public BTRParticleProvider(PackOutput output, ExistingFileHelper fileHelper) {
super(output, fileHelper);
}
@Override
protected void addDescriptions() {
List<ResourceLocation> cannonBlastTextures = new ArrayList<>();
for (int i = 0; i <= 7; i++){
cannonBlastTextures.add(BlastTravelReborn.id("blast_smoke_" + i));
}
spriteSet(BTRParticleTypes.CANNON_BLAST.get(), cannonBlastTextures);
}
}

View File

@ -0,0 +1,35 @@
package com.leisuretimedock.blasttravelreborn.datagen.provider;
import com.leisuretimedock.blasttravelreborn.content.item.BTRItems;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
public class BTRRecipeProvider extends RecipeProvider {
public BTRRecipeProvider(PackOutput pOutput) {
super(pOutput);
}
@Override
protected void buildRecipes(@NotNull Consumer<FinishedRecipe> pWriter) {
// 第二个配方您提供的JSON转换而来
ShapedRecipeBuilder.shaped(RecipeCategory.MISC, BTRItems.CANNON_ITEM.get(), 1)
.define('A', Items.IRON_BLOCK)
.define('B', Items.STRING)
.define('C', ItemTags.STONE_CRAFTING_MATERIALS)
.define('D', Items.STICK)
.define('E', ItemTags.LOGS)
.pattern(" A")
.pattern("BA ")
.pattern("CDE")
.unlockedBy("has_iron_block", has(Items.IRON_BLOCK))
.save(pWriter);
}
}

View File

@ -0,0 +1,81 @@
package com.leisuretimedock.blasttravelreborn.datagen.value;
import com.leisuretimedock.blasttravelreborn.BlastTravelReborn;
import com.leisuretimedock.blasttravelreborn.content.item.BTRItems;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import top.r3944realms.lib39.datagen.value.ILangKeyValueCollection;
import top.r3944realms.lib39.datagen.value.LangKeyValue;
import top.r3944realms.lib39.datagen.value.ModPartEnum;
import java.util.ArrayList;
import java.util.List;
public enum BTRLangKeys implements ILangKeyValueCollection {
INSTANCE;
BTRLangKeys() {
initLangKeyValues();
}
final List<LangKeyValue> langKeyValues = new ArrayList<>();
public void initLangKeyValues() {
addLang(LangKeyValue.ofSupplier(
BTRItems.CANNON_ITEM, ModPartEnum.ITEM,
"Cannon",
"大炮",
"大砲",
true
));
addLang(LangKeyValue.ofKey(
ModPartEnum.ENTITY.getFullKey(BlastTravelReborn.MOD_ID, "cannon"), ModPartEnum.ENTITY,
"Cannon",
"大炮",
"大砲"
));
addLang(LangKeyValue.ofKey(
"container.blasttravelreborn.cannon_container_menu", ModPartEnum.GUI,
"Cannon",
"大炮实体",
"大砲實體"
));
addLang(LangKeyValue.ofKey(
"death.attack.blasttravelreborn.cannon", ModPartEnum.MESSAGE,
"%s was knocked out by a flying %s",
"%s被飞行的%s击倒了",
"%s被飛行的%s擊倒了"
));
addLang(LangKeyValue.ofKey(
"dialog.blasttravelreborn.full_cannon", ModPartEnum.MESSAGE,
"Cannon is full!",
"大炮已装满!",
"大砲已裝滿!"
));
addLang(LangKeyValue.ofKey(
"dialog.blasttravelreborn.no_gunpowder", ModPartEnum.MESSAGE,
"Cannon has no gunpowder!",
"大炮没有火药了!",
"大砲沒有火藥了!"
));
addLang(LangKeyValue.ofKey(
"mount.blasttravelreborn.cannon.onboard", ModPartEnum.MESSAGE,
"Press %s to Exit, or %s to Fire",
"按%s离开或按%s发射",
"按%s離開或按%s發射"
));
}
public void addLang(LangKeyValue keyValue) {
langKeyValues.add(keyValue);
}
public void clear() {
langKeyValues.clear();
}
@Contract(pure = true)
@Override
public @Unmodifiable List<LangKeyValue> getValues() {
return List.copyOf(langKeyValues);
}
}

View File

@ -2,7 +2,6 @@ package com.leisuretimedock.blasttravelreborn.network.toClient;
import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity;
import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
@ -40,45 +39,35 @@ public record FireCannonPayload(int cannonId, Optional<Integer> launchedId, doub
}
public void handler(Supplier<NetworkEvent.Context> ctx) {
NetworkEvent.Context context = ctx.get();
if(context.getNetworkManager().getPacketListener() instanceof ClientPacketListener clientPacketListener) {
if (context.getNetworkManager().getPacketListener() instanceof ClientPacketListener clientPacketListener) {
context.enqueueWork(() -> {
if(hasPlayer()) {
//noinspection OptionalGetWithoutIsPresent
if (hasPlayer()) {
if (clientPacketListener.getLevel().getEntity(launchedId.get()) instanceof Player launchedPlayer) {
// 记录发射前状态
if (!FMLEnvironment.production) {
System.out.println(" - [B]Client: Is Passenger = " + launchedPlayer.isPassenger());
System.out.println(" - [B]Client: On Ground = " + launchedPlayer.onGround());
System.out.println(" - [B]Client: noPhysics =" + launchedPlayer.noPhysics);
System.out.println(" - [B]Client: Pose = " + launchedPlayer.getPose());
System.out.println(" - [B]Client: riding entity = " + launchedPlayer.getVehicle());
System.out.println(" - [B]Client: Bounding Box = " + launchedPlayer.getBoundingBox());
System.out.println(" - [B]Client: Delta Movement = " + launchedPlayer.getDeltaMovement());
}
// 立即停止乘坐并设置速度
if (launchedPlayer.isPassenger()) {
launchedPlayer.stopRiding();
}
// 延迟一个 tick 执行飞行初始化逻辑
Minecraft.getInstance().tell(() -> {
// 此时已脱离炮台设置速度
launchedPlayer.getAbilities().flying = false;
launchedPlayer.setPos(launchedPlayer.getX(), launchedPlayer.getY(), launchedPlayer.getZ());
launchedPlayer.setDeltaMovement(velocityX, velocityY, velocityZ);
((PlayerEntityDuck) launchedPlayer).blasttravel$setCannonFlight(true);
// 直接设置速度不要延迟
launchedPlayer.setDeltaMovement(velocityX, velocityY, velocityZ);
launchedPlayer.hurtMarked = true; // 强制同步运动
((PlayerEntityDuck) launchedPlayer).blasttravel$setCannonFlight(true);
// 输出变更后状态
if (!FMLEnvironment.production) {
System.out.println(" - [A]Client: Is Passenger = " + launchedPlayer.isPassenger());
System.out.println(" - [A]Client: On Ground = " + launchedPlayer.onGround());
System.out.println(" - [A]Client: noPhysics = " + launchedPlayer.noPhysics);
System.out.println(" - [A]Client: Pose = " + launchedPlayer.getPose());
System.out.println(" - [A]Client: riding entity = " + launchedPlayer.getVehicle());
System.out.println(" - [A]Client: Bounding Box = " + launchedPlayer.getBoundingBox());
System.out.println(" - [A]Client: Delta Movement = " + launchedPlayer.getDeltaMovement());
}
});
// 记录发射后状态
if (!FMLEnvironment.production) {
System.out.println(" - [A]Client: Is Passenger = " + launchedPlayer.isPassenger());
System.out.println(" - [A]Client: Delta Movement = " + launchedPlayer.getDeltaMovement());
}
}
}
// 处理炮实体效果
Entity entity = clientPacketListener.getLevel().getEntity(cannonId);
if (entity instanceof CannonEntity cannonEntity) {
cannonEntity.fireClient();

View File

@ -11,9 +11,6 @@ import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier;
public record StopCannonFlightClientPayload(int flyingId) {
public StopCannonFlightClientPayload(FriendlyByteBuf buf) {
this(buf.readInt());
}
public StopCannonFlightClientPayload(Player flying) {
this(flying.getId());
}

View File

@ -13,9 +13,7 @@ public record RequestFirePayload(int cannonId) {
public RequestFirePayload(CannonEntity cannonEntity) {
this(cannonEntity.getId());
}
public RequestFirePayload(FriendlyByteBuf buf) {
this(buf.readInt());
}
public void write(FriendlyByteBuf buf) {
buf.writeInt(cannonId);
}

View File

@ -62,7 +62,12 @@ versionRange = "${minecraft_version_range}"
ordering = "NONE"
side = "BOTH"
[[dependencies."${mod_id}"]]
versionRange = "[0.2.1+1.20-fabrge,)"
versionRange = "[0.2.3,)"
modId = "jsonem"
mandatory = false
side = "CLIENT"
[[dependencies."${mod_id}"]]
versionRange = "[0.0.17,)"
modId = "lib39"
mandatory = false
side = "BOTH"

View File

@ -1,10 +0,0 @@
{
"container.blasttravelreborn.cannon_container_menu": "Cannon",
"item.blasttravelreborn.cannon": "Cannon",
"entity.blasttravelreborn.cannon": "Cannon",
"death.attack.blasttravelreborn.cannon": "%s was knocked out by a flying %s",
"dialog.blasttravelreborn.full_cannon": "Cannon is full!",
"dialog.blasttravelreborn.no_gunpowder": "Cannon has no gunpowder!",
"mount.blasttravelreborn.cannon.onboard": "Press %s to Exit, or %s to Fire"
}

View File

@ -1,6 +0,0 @@
{
"parent": "item/generated",
"textures": {
"layer0": "blasttravelreborn:item/cannon"
}
}

View File

@ -1,13 +0,0 @@
{
"textures": [
"blasttravelreborn:blast_smoke_0",
"blasttravelreborn:blast_smoke_1",
"blasttravelreborn:blast_smoke_2",
"blasttravelreborn:blast_smoke_3",
"blasttravelreborn:blast_smoke_4",
"blasttravelreborn:blast_smoke_5",
"blasttravelreborn:blast_smoke_6",
"blasttravelreborn:blast_smoke_7",
"blasttravelreborn:blast_smoke_8"
]
}

View File

@ -6,10 +6,10 @@
"compatibilityLevel": "JAVA_17",
"mixins": [
"LivingEntityAccess",
"PlayerEntityMixin",
"PlayerEntityMixinClient"
"PlayerEntityMixin"
],
"client": [
"PlayerEntityMixinClient",
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin",
"PlayerEntityModelMixin",

View File

@ -1,19 +0,0 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
" A",
"BA ",
"CDE"
],
"key": {
"A": {"item": "minecraft:iron_block"},
"B": {"item": "minecraft:string"},
"C": {"tag": "minecraft:stone_crafting_materials"},
"D": {"item": "minecraft:stick"},
"E": {"tag": "minecraft:logs"}
},
"result": {
"item": "blasttravelreborn:cannon",
"count": 1
}
}