Phase 3: Road Planner GUI with 2D map interface, drawing tools, and profile selection
This commit is contained in:
@@ -70,6 +70,7 @@ repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://maven.minecraftforge.net/")
|
||||
maven("https://maven.neoforged.net/releases/")
|
||||
maven("https://maven.wispforest.io/releases/") { name = "Wisp Forest (owo-lib)" }
|
||||
}
|
||||
|
||||
val loaderVersion: String by project
|
||||
@@ -90,6 +91,10 @@ dependencies {
|
||||
|
||||
modImplementation("net.fabricmc:fabric-loader:${loaderVersion}")
|
||||
modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricApiVersion}")
|
||||
|
||||
// owo-lib for GUI
|
||||
modImplementation("io.wispforest:owo-lib:0.12.15+1.21")
|
||||
include("io.wispforest:owo-sentinel:0.12.15+1.21")
|
||||
}
|
||||
|
||||
"neoforge" -> {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.bnair.roadrunner
|
||||
|
||||
import com.bnair.roadrunner.registry.ModItems
|
||||
|
||||
//? if fabric {
|
||||
import net.fabricmc.api.ModInitializer
|
||||
//?}
|
||||
@@ -11,6 +13,7 @@ internal const val MOD_ID = "roadrunner"
|
||||
|
||||
internal fun initializeMod() {
|
||||
println("RoadRunner Mod has been initialized.")
|
||||
ModItems.init()
|
||||
}
|
||||
|
||||
//? if fabric {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bnair.roadrunner.client
|
||||
|
||||
import com.bnair.roadrunner.MOD_ID
|
||||
import com.bnair.roadrunner.client.gui.RoadPlannerScreen
|
||||
import com.bnair.roadrunner.registry.ModItems
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
//? if fabric {
|
||||
import net.fabricmc.api.ClientModInitializer
|
||||
import net.fabricmc.fabric.api.event.player.UseItemCallback
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
//?}
|
||||
|
||||
//? if fabric {
|
||||
class RoadRunnerClient : ClientModInitializer {
|
||||
override fun onInitializeClient() {
|
||||
println("$MOD_ID client initialized")
|
||||
|
||||
UseItemCallback.EVENT.register { player, world, hand ->
|
||||
val stack = player.getItemInHand(hand)
|
||||
if (stack.item == ModItems.ROAD_PLANNER && world.isClientSide) {
|
||||
Minecraft.getInstance().setScreen(RoadPlannerScreen())
|
||||
InteractionResultHolder.success(stack)
|
||||
} else {
|
||||
InteractionResultHolder.pass(stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//?}
|
||||
@@ -0,0 +1,414 @@
|
||||
package com.bnair.roadrunner.client.gui
|
||||
|
||||
import com.bnair.roadrunner.math.BezierCurve
|
||||
import com.bnair.roadrunner.math.Vec3d
|
||||
import com.bnair.roadrunner.road.RoadNetwork
|
||||
import com.bnair.roadrunner.road.RoadProfile
|
||||
import com.bnair.roadrunner.road.RoadSegment
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.network.chat.Component
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import kotlin.math.floor
|
||||
|
||||
enum class DrawingTool {
|
||||
SELECT,
|
||||
STRAIGHT,
|
||||
CURVE
|
||||
}
|
||||
|
||||
class RoadPlannerScreen : Screen(Component.literal("Road Planner")) {
|
||||
private var currentTool = DrawingTool.STRAIGHT
|
||||
private var currentProfile = RoadProfile.highway()
|
||||
|
||||
private var mapCenterX = 0.0
|
||||
private var mapCenterZ = 0.0
|
||||
private var mapScale = 2.0
|
||||
|
||||
private val plannedSegments = mutableListOf<PlannedSegment>()
|
||||
private var currentSegment: PlannedSegment? = null
|
||||
|
||||
private var isDragging = false
|
||||
private var lastMouseX = 0.0
|
||||
private var lastMouseY = 0.0
|
||||
|
||||
private val toolbarHeight = 40
|
||||
private val sidebarWidth = 150
|
||||
|
||||
data class PlannedSegment(
|
||||
val points: MutableList<ScreenPoint> = mutableListOf(),
|
||||
var profile: RoadProfile = RoadProfile.highway(),
|
||||
var isComplete: Boolean = false
|
||||
)
|
||||
|
||||
data class ScreenPoint(var screenX: Double, var screenY: Double)
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
val player = Minecraft.getInstance().player ?: return
|
||||
mapCenterX = player.x
|
||||
mapCenterZ = player.z
|
||||
}
|
||||
|
||||
override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
renderBackground(graphics, mouseX, mouseY, partialTick)
|
||||
|
||||
renderMap(graphics, mouseX, mouseY)
|
||||
renderToolbar(graphics, mouseX, mouseY)
|
||||
renderSidebar(graphics, mouseX, mouseY)
|
||||
renderCurrentDrawing(graphics, mouseX, mouseY)
|
||||
renderPlannedSegments(graphics)
|
||||
|
||||
super.render(graphics, mouseX, mouseY, partialTick)
|
||||
}
|
||||
|
||||
private fun renderMap(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
|
||||
val mapLeft = sidebarWidth
|
||||
val mapTop = toolbarHeight
|
||||
val mapWidth = width - sidebarWidth
|
||||
val mapHeight = height - toolbarHeight
|
||||
|
||||
graphics.fill(mapLeft, mapTop, width, height, 0xFF1a1a2e.toInt())
|
||||
|
||||
renderGrid(graphics, mapLeft, mapTop, mapWidth, mapHeight)
|
||||
|
||||
val playerScreenX = worldToScreenX(mapCenterX, mapLeft, mapWidth)
|
||||
val playerScreenY = worldToScreenZ(mapCenterZ, mapTop, mapHeight)
|
||||
graphics.fill(
|
||||
playerScreenX.toInt() - 3,
|
||||
playerScreenY.toInt() - 3,
|
||||
playerScreenX.toInt() + 3,
|
||||
playerScreenY.toInt() + 3,
|
||||
0xFF00FF00.toInt()
|
||||
)
|
||||
|
||||
val worldX = screenToWorldX(mouseX.toDouble(), mapLeft, mapWidth)
|
||||
val worldZ = screenToWorldZ(mouseY.toDouble(), mapTop, mapHeight)
|
||||
graphics.drawString(
|
||||
font,
|
||||
"X: ${floor(worldX).toInt()}, Z: ${floor(worldZ).toInt()}",
|
||||
mapLeft + 5,
|
||||
height - 15,
|
||||
0xFFFFFF
|
||||
)
|
||||
}
|
||||
|
||||
private fun renderGrid(graphics: GuiGraphics, mapLeft: Int, mapTop: Int, mapWidth: Int, mapHeight: Int) {
|
||||
val gridSpacing = 16.0 * mapScale
|
||||
|
||||
val startWorldX = screenToWorldX(mapLeft.toDouble(), mapLeft, mapWidth)
|
||||
val endWorldX = screenToWorldX((mapLeft + mapWidth).toDouble(), mapLeft, mapWidth)
|
||||
val startWorldZ = screenToWorldZ(mapTop.toDouble(), mapTop, mapHeight)
|
||||
val endWorldZ = screenToWorldZ((mapTop + mapHeight).toDouble(), mapTop, mapHeight)
|
||||
|
||||
val gridStartX = floor(startWorldX / 16) * 16
|
||||
val gridStartZ = floor(startWorldZ / 16) * 16
|
||||
|
||||
var x = gridStartX
|
||||
while (x <= endWorldX) {
|
||||
val screenX = worldToScreenX(x, mapLeft, mapWidth).toInt()
|
||||
if (screenX in mapLeft until (mapLeft + mapWidth)) {
|
||||
val color = if (x.toInt() % 64 == 0) 0x40FFFFFF else 0x20FFFFFF
|
||||
graphics.fill(screenX, mapTop, screenX + 1, mapTop + mapHeight, color)
|
||||
}
|
||||
x += 16
|
||||
}
|
||||
|
||||
var z = gridStartZ
|
||||
while (z <= endWorldZ) {
|
||||
val screenY = worldToScreenZ(z, mapTop, mapHeight).toInt()
|
||||
if (screenY in mapTop until (mapTop + mapHeight)) {
|
||||
val color = if (z.toInt() % 64 == 0) 0x40FFFFFF else 0x20FFFFFF
|
||||
graphics.fill(mapLeft, screenY, mapLeft + mapWidth, screenY + 1, color)
|
||||
}
|
||||
z += 16
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderToolbar(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
|
||||
graphics.fill(0, 0, width, toolbarHeight, 0xFF2d2d44.toInt())
|
||||
|
||||
val tools = listOf(
|
||||
Triple(DrawingTool.SELECT, "Select", 10),
|
||||
Triple(DrawingTool.STRAIGHT, "Straight", 80),
|
||||
Triple(DrawingTool.CURVE, "Curve", 160)
|
||||
)
|
||||
|
||||
tools.forEach { (tool, name, x) ->
|
||||
val isSelected = currentTool == tool
|
||||
val bgColor = if (isSelected) 0xFF4a4a6a.toInt() else 0xFF3a3a5a.toInt()
|
||||
val isHovered = mouseX in x until (x + 60) && mouseY in 5 until 35
|
||||
|
||||
graphics.fill(x, 5, x + 60, 35, if (isHovered) 0xFF5a5a7a.toInt() else bgColor)
|
||||
graphics.drawCenteredString(font, name, x + 30, 15, 0xFFFFFF)
|
||||
}
|
||||
|
||||
graphics.drawString(font, "Scroll to zoom | Drag to pan", 250, 15, 0xAAAAAA)
|
||||
}
|
||||
|
||||
private fun renderSidebar(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
|
||||
graphics.fill(0, toolbarHeight, sidebarWidth, height, 0xFF252538.toInt())
|
||||
|
||||
graphics.drawString(font, "Road Profile", 10, toolbarHeight + 10, 0xFFFFFF)
|
||||
|
||||
val profiles = listOf(
|
||||
"Simple" to RoadProfile.singleLaneEachWay(),
|
||||
"Highway 2L" to RoadProfile.highway(2),
|
||||
"Highway 3L" to RoadProfile.highway(3),
|
||||
"With Transit" to RoadProfile.withPublicTransport()
|
||||
)
|
||||
|
||||
profiles.forEachIndexed { index, (name, profile) ->
|
||||
val y = toolbarHeight + 30 + index * 25
|
||||
val isSelected = currentProfile.totalWidth == profile.totalWidth
|
||||
val bgColor = if (isSelected) 0xFF4a4a6a.toInt() else 0xFF3a3a5a.toInt()
|
||||
val isHovered = mouseX in 5 until (sidebarWidth - 5) && mouseY in y until (y + 20)
|
||||
|
||||
graphics.fill(5, y, sidebarWidth - 5, y + 20, if (isHovered) 0xFF5a5a7a.toInt() else bgColor)
|
||||
graphics.drawString(font, name, 10, y + 6, 0xFFFFFF)
|
||||
}
|
||||
|
||||
graphics.drawString(font, "Segments: ${plannedSegments.size}", 10, height - 60, 0xAAAAAA)
|
||||
|
||||
val buildButtonY = height - 35
|
||||
val isHovered = mouseX in 10 until (sidebarWidth - 10) && mouseY in buildButtonY until (buildButtonY + 25)
|
||||
graphics.fill(10, buildButtonY, sidebarWidth - 10, buildButtonY + 25,
|
||||
if (isHovered) 0xFF22AA22.toInt() else 0xFF118811.toInt())
|
||||
graphics.drawCenteredString(font, "BUILD", sidebarWidth / 2, buildButtonY + 8, 0xFFFFFF)
|
||||
}
|
||||
|
||||
private fun renderCurrentDrawing(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
|
||||
val segment = currentSegment ?: return
|
||||
|
||||
segment.points.forEach { point ->
|
||||
graphics.fill(
|
||||
point.screenX.toInt() - 4,
|
||||
point.screenY.toInt() - 4,
|
||||
point.screenX.toInt() + 4,
|
||||
point.screenY.toInt() + 4,
|
||||
0xFFFF6600.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (segment.points.isNotEmpty()) {
|
||||
val lastPoint = segment.points.last()
|
||||
graphics.fill(
|
||||
lastPoint.screenX.toInt(),
|
||||
lastPoint.screenY.toInt(),
|
||||
mouseX,
|
||||
mouseY,
|
||||
0x80FF6600.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderPlannedSegments(graphics: GuiGraphics) {
|
||||
val mapLeft = sidebarWidth
|
||||
val mapTop = toolbarHeight
|
||||
val mapWidth = width - sidebarWidth
|
||||
val mapHeight = height - toolbarHeight
|
||||
|
||||
plannedSegments.forEach { segment ->
|
||||
if (segment.points.size >= 2) {
|
||||
val curve = createCurveFromPoints(segment.points, mapLeft, mapTop, mapWidth, mapHeight)
|
||||
val samples = curve.sample(50)
|
||||
|
||||
for (i in 0 until samples.size - 1) {
|
||||
val p1 = samples[i]
|
||||
val p2 = samples[i + 1]
|
||||
|
||||
val sx1 = worldToScreenX(p1.x, mapLeft, mapWidth).toInt()
|
||||
val sy1 = worldToScreenZ(p1.z, mapTop, mapHeight).toInt()
|
||||
val sx2 = worldToScreenX(p2.x, mapLeft, mapWidth).toInt()
|
||||
val sy2 = worldToScreenZ(p2.z, mapTop, mapHeight).toInt()
|
||||
|
||||
drawLine(graphics, sx1, sy1, sx2, sy2, 0xFF4488FF.toInt(), segment.profile.totalWidth.toInt() / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawLine(graphics: GuiGraphics, x1: Int, y1: Int, x2: Int, y2: Int, color: Int, thickness: Int = 2) {
|
||||
val dx = x2 - x1
|
||||
val dy = y2 - y1
|
||||
val steps = maxOf(kotlin.math.abs(dx), kotlin.math.abs(dy), 1)
|
||||
|
||||
for (i in 0..steps) {
|
||||
val t = i.toFloat() / steps
|
||||
val x = (x1 + dx * t).toInt()
|
||||
val y = (y1 + dy * t).toInt()
|
||||
graphics.fill(x - thickness, y - thickness, x + thickness, y + thickness, color)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
|
||||
if (mouseY < toolbarHeight) {
|
||||
handleToolbarClick(mouseX.toInt(), mouseY.toInt())
|
||||
return true
|
||||
}
|
||||
|
||||
if (mouseX < sidebarWidth) {
|
||||
handleSidebarClick(mouseX.toInt(), mouseY.toInt())
|
||||
return true
|
||||
}
|
||||
|
||||
if (currentTool == DrawingTool.STRAIGHT || currentTool == DrawingTool.CURVE) {
|
||||
if (currentSegment == null) {
|
||||
currentSegment = PlannedSegment(profile = currentProfile)
|
||||
}
|
||||
currentSegment?.points?.add(ScreenPoint(mouseX, mouseY))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT) {
|
||||
currentSegment?.let { segment ->
|
||||
if (segment.points.size >= 2) {
|
||||
segment.isComplete = true
|
||||
plannedSegments.add(segment)
|
||||
}
|
||||
currentSegment = null
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_MIDDLE) {
|
||||
isDragging = true
|
||||
lastMouseX = mouseX
|
||||
lastMouseY = mouseY
|
||||
return true
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_MIDDLE) {
|
||||
isDragging = false
|
||||
return true
|
||||
}
|
||||
return super.mouseReleased(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean {
|
||||
if (isDragging) {
|
||||
mapCenterX -= dragX * mapScale
|
||||
mapCenterZ -= dragY * mapScale
|
||||
return true
|
||||
}
|
||||
return super.mouseDragged(mouseX, mouseY, button, dragX, dragY)
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
|
||||
val zoomFactor = if (verticalAmount > 0) 0.8 else 1.25
|
||||
mapScale = (mapScale * zoomFactor).coerceIn(0.5, 20.0)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleToolbarClick(mouseX: Int, mouseY: Int) {
|
||||
when {
|
||||
mouseX in 10 until 70 -> currentTool = DrawingTool.SELECT
|
||||
mouseX in 80 until 140 -> currentTool = DrawingTool.STRAIGHT
|
||||
mouseX in 160 until 220 -> currentTool = DrawingTool.CURVE
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSidebarClick(mouseX: Int, mouseY: Int) {
|
||||
val profiles = listOf(
|
||||
RoadProfile.singleLaneEachWay(),
|
||||
RoadProfile.highway(2),
|
||||
RoadProfile.highway(3),
|
||||
RoadProfile.withPublicTransport()
|
||||
)
|
||||
|
||||
profiles.forEachIndexed { index, profile ->
|
||||
val y = toolbarHeight + 30 + index * 25
|
||||
if (mouseY in y until (y + 20)) {
|
||||
currentProfile = profile
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val buildButtonY = height - 35
|
||||
if (mouseY in buildButtonY until (buildButtonY + 25)) {
|
||||
buildRoads()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoads() {
|
||||
val mapLeft = sidebarWidth
|
||||
val mapTop = toolbarHeight
|
||||
val mapWidth = width - sidebarWidth
|
||||
val mapHeight = height - toolbarHeight
|
||||
|
||||
val network = RoadNetwork()
|
||||
|
||||
plannedSegments.forEach { segment ->
|
||||
if (segment.points.size >= 2) {
|
||||
val curve = createCurveFromPoints(segment.points, mapLeft, mapTop, mapWidth, mapHeight)
|
||||
network.addSegment(RoadSegment(curve, segment.profile))
|
||||
}
|
||||
}
|
||||
|
||||
val blocks = network.getAllBlocks()
|
||||
val costs = network.estimateMaterialCost()
|
||||
|
||||
println("Road network planned:")
|
||||
println(" Total blocks: ${blocks.size}")
|
||||
println(" Materials needed:")
|
||||
costs.forEach { (material, count) ->
|
||||
println(" $material: $count")
|
||||
}
|
||||
|
||||
Minecraft.getInstance().player?.sendSystemMessage(
|
||||
Component.literal("Planned ${plannedSegments.size} road segments (${blocks.size} blocks)")
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCurveFromPoints(points: List<ScreenPoint>, mapLeft: Int, mapTop: Int, mapWidth: Int, mapHeight: Int): BezierCurve {
|
||||
val worldPoints = points.map { point ->
|
||||
Vec3d(
|
||||
screenToWorldX(point.screenX, mapLeft, mapWidth),
|
||||
64.0,
|
||||
screenToWorldZ(point.screenY, mapTop, mapHeight)
|
||||
)
|
||||
}
|
||||
|
||||
return when {
|
||||
worldPoints.size == 2 -> BezierCurve.line(worldPoints[0], worldPoints[1])
|
||||
worldPoints.size >= 4 -> BezierCurve(
|
||||
worldPoints[0],
|
||||
worldPoints[1],
|
||||
worldPoints[worldPoints.size - 2],
|
||||
worldPoints.last()
|
||||
)
|
||||
else -> {
|
||||
val p0 = worldPoints[0]
|
||||
val p3 = worldPoints.last()
|
||||
val mid = if (worldPoints.size > 2) worldPoints[1] else (p0 + p3) / 2.0
|
||||
BezierCurve(p0, mid, mid, p3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun worldToScreenX(worldX: Double, mapLeft: Int, mapWidth: Int): Double {
|
||||
return mapLeft + mapWidth / 2.0 + (worldX - mapCenterX) / mapScale
|
||||
}
|
||||
|
||||
private fun worldToScreenZ(worldZ: Double, mapTop: Int, mapHeight: Int): Double {
|
||||
return mapTop + mapHeight / 2.0 + (worldZ - mapCenterZ) / mapScale
|
||||
}
|
||||
|
||||
private fun screenToWorldX(screenX: Double, mapLeft: Int, mapWidth: Int): Double {
|
||||
return mapCenterX + (screenX - mapLeft - mapWidth / 2.0) * mapScale
|
||||
}
|
||||
|
||||
private fun screenToWorldZ(screenY: Double, mapTop: Int, mapHeight: Int): Double {
|
||||
return mapCenterZ + (screenY - mapTop - mapHeight / 2.0) * mapScale
|
||||
}
|
||||
|
||||
override fun isPauseScreen() = false
|
||||
}
|
||||
39
src/main/kotlin/com/bnair/roadrunner/registry/ModItems.kt
Normal file
39
src/main/kotlin/com/bnair/roadrunner/registry/ModItems.kt
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.bnair.roadrunner.registry
|
||||
|
||||
import com.bnair.roadrunner.MOD_ID
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.CreativeModeTabs
|
||||
import net.minecraft.world.item.Item
|
||||
|
||||
//? if fabric {
|
||||
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents
|
||||
//?}
|
||||
|
||||
object ModItems {
|
||||
private val ITEMS = mutableMapOf<String, Item>()
|
||||
|
||||
val ROAD_PLANNER: Item = register("road_planner", Item(Item.Properties().stacksTo(1)))
|
||||
|
||||
private fun register(name: String, item: Item): Item {
|
||||
ITEMS[name] = item
|
||||
return item
|
||||
}
|
||||
|
||||
fun init() {
|
||||
//? if fabric {
|
||||
ITEMS.forEach { (name, item) ->
|
||||
Registry.register(
|
||||
BuiltInRegistries.ITEM,
|
||||
ResourceLocation.fromNamespaceAndPath(MOD_ID, name),
|
||||
item
|
||||
)
|
||||
}
|
||||
|
||||
ItemGroupEvents.modifyEntriesEvent(CreativeModeTabs.TOOLS_AND_UTILITIES).register { content ->
|
||||
content.accept(ROAD_PLANNER)
|
||||
}
|
||||
//?}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"com.bnair.roadrunner.RoadRunnerFabric"
|
||||
],
|
||||
"client": [
|
||||
"com.bnair.roadrunner.client.RoadRunnerClient"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
@@ -23,7 +26,8 @@
|
||||
"depends": {
|
||||
"fabric": ">=${loader_version}",
|
||||
"fabric-api": "*",
|
||||
"minecraft": "${minecraft_version}"
|
||||
"minecraft": "${minecraft_version}",
|
||||
"owo-lib": "*"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
|
||||
Reference in New Issue
Block a user