Compare commits

...
Author SHA1 Message Date
Aram 9cdf91c97f Added TODO: 2026-01-04 20:38:47 +01:00
Aram 3f5b659616 Rename 2026-01-04 20:37:19 +01:00
hammy275 ee20f012e6 Interact Setup and Ball or Entity Depending on if Hovered 2025-12-16 20:55:49 -05:00
hammy275 12dcc98cf9 Select Pokemon Hovered Over 2025-12-16 20:16:14 -05:00
Aram 8c92defc90 Fixed Applying bait in VR 2025-12-15 13:21:10 +01:00
Aram 95509df022 Found the issue 2025-12-12 01:31:29 +01:00
Aram bb20890b26 Created an Locomotion Proximity Check, added TODO i need to fix once headset charges 2025-12-11 23:21:21 +01:00
hammy275 50d60674bb Pokemon Attached to Arm 2025-12-10 20:58:30 -05:00
hammy275 ff994f69f2 Render a Pokemon at the Crosshair 2025-12-10 19:26:45 -05:00
hammy275 73448d4737 Not Working Skeleton Code 2025-12-08 19:29:36 -05:00
Aram 42099d3ae5 Cleanup 2025-12-08 14:08:38 +01:00
Aram bc23ab8eb4 Merge branch 'main' into VR-support 2025-12-08 10:33:53 +01:00
Aram 6c860d8e93 Reeling in while swinging backwards also works!!!! 2025-12-07 23:06:59 +01:00
Aram de6b713adf Casting your pokerod using arm swings work in VR 2025-12-07 21:26:48 +01:00
Aram 6f5c2575f3 Refactor to Client 2025-12-07 00:41:41 +01:00
Aram b23afd17c2 Small Refactoring 2025-12-07 00:34:22 +01:00
Aram 74541c1bcb Updated Vivecraft 1 final time with the new Listener System 2025-12-07 00:00:53 +01:00
Aram 56c3ff55ca 90% + ACCURACY REACHED IN VR 2025-12-05 23:45:22 +01:00
Aram c5b82d1ad4 Current Pokeball VR Throwing - 85% Accuracy, Aiming for 90%+ 2025-12-05 23:37:33 +01:00
Aram 1237b51059 Merge branch 'main' into VR-support 2025-12-05 23:07:42 +01:00
Aram 9529fe66ac Updated Vivecraft internally 2025-12-05 21:14:35 +01:00
Aram 7058ac21cb Improved Pokeball throwing in VR!!!!!!! 2025-12-05 20:34:14 +01:00
Aram b5d0cddc82 Throwing a Pokeball works!!!! 2025-12-05 18:47:31 +01:00
Aram 2f918220e9 Setup the first prototype of Pokeball Throwing in VR 2025-12-05 01:15:46 +01:00
33 changed files with 2198 additions and 246 deletions
+2
View File
@@ -54,6 +54,8 @@ dependencies {
// Flywheel has no common dep so just pick one and don't use any platform specific code in common
// modCompileOnly(libs.flywheelFabric)
modCompileOnly(files("temp/vivecraft-1.21.1-1.3.4.jar")) // TODO: Switch this to a proper dependency when Vivecraft gets a maven repo
// Showdown
modCompileOnly(libs.graal)
@@ -42,8 +42,8 @@ import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.stats.CobblemonStats
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
@@ -76,12 +76,8 @@ import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.EntityCallbackHandler
import com.cobblemon.mod.common.events.CallbackHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
import com.cobblemon.mod.common.events.StatHandler
import com.cobblemon.mod.common.events.*
import com.cobblemon.mod.common.integration.vivecraft.CobblemonVR
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
@@ -135,10 +131,12 @@ object Cobblemon {
const val MODID = CobblemonBuildDetails.MOD_ID
const val VERSION = CobblemonBuildDetails.VERSION
const val CONFIG_PATH = "config/$MODID/main.json"
@JvmField
val LOGGER: Logger = LogManager.getLogger()
lateinit var implementation: CobblemonImplementation
@Deprecated("This field is now a config value", ReplaceWith("Cobblemon.config.captureCalculator"))
var captureCalculator: CaptureCalculator
get() = this.config.captureCalculator
@@ -168,21 +166,49 @@ object Cobblemon {
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
val statistics: CobblemonStats = CobblemonStats
var cobblemonVR: CobblemonVR? = null
@JvmStatic
val builtinPacks = listOf<CobblemonPack>(
CobblemonPack(id = "adorncompatibility", name = "Adorn Compatibility", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED, neededMods = setOf("adorn")),
CobblemonPack(id = "gyaradosjump", name = "Gyarados Jump Patterns", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
CobblemonPack(id = "regionbiasforms", name = "Region Bias Forms", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
CobblemonPack(id = "uniqueshinyforms", name = "Shinies for Magikarp Jump", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.NORMAL),
CobblemonPack(
id = "adorncompatibility",
name = "Adorn Compatibility",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED,
neededMods = setOf("adorn")
),
CobblemonPack(
id = "gyaradosjump",
name = "Gyarados Jump Patterns",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED
),
CobblemonPack(
id = "regionbiasforms",
name = "Region Bias Forms",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED
),
CobblemonPack(
id = "uniqueshinyforms",
name = "Shinies for Magikarp Jump",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.NORMAL
),
CobblemonPack(id = "repurposedstructurescobblemon", name = "Repurposed Structures Cobblemon", packType = PackType.SERVER_DATA, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED, neededMods = setOf("repurposed_structures")),
CobblemonPack(
id = "repurposedstructurescobblemon",
name = "Repurposed Structures Cobblemon",
packType = PackType.SERVER_DATA,
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED,
neededMods = setOf("repurposed_structures")
),
)
fun preInitialize(implementation: CobblemonImplementation) {
this.implementation = implementation
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if(CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if (CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
if (CobblemonBuildDetails.SNAPSHOT) {
this.LOGGER.info(" - Git Commit: ${smallCommitHash()} (https://gitlab.com/cable-mc/cobblemon/-/commit/${CobblemonBuildDetails.GIT_COMMIT})")
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
@@ -246,7 +272,10 @@ object Cobblemon {
}
PlatformEvents.RIGHT_CLICK_ENTITY.subscribe { event ->
if (event.player.getItemInHand(event.hand).item is NameTagItem && event.entity.type.`is`(CobblemonEntityTypeTags.CANNOT_HAVE_NAME_TAG)) {
if (event.player.getItemInHand(event.hand).item is NameTagItem && event.entity.type.`is`(
CobblemonEntityTypeTags.CANNOT_HAVE_NAME_TAG
)
) {
event.cancel()
}
}
@@ -257,7 +286,10 @@ object Cobblemon {
pokemon.lockedEvolutions
.filterIsInstance<BlockClickEvolution>()
.forEach { evolution ->
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
evolution.attemptEvolution(
pokemon,
BlockClickEvolution.BlockInteractionContext(block, player.level())
)
}
}
}
@@ -317,6 +349,11 @@ object Cobblemon {
SlowpokeTailRegrowthSpeciesFeatureProvider
)
if (implementation.isModInstalled("vivecraft")) { // VR integration
cobblemonVR = CobblemonVR()
cobblemonVR?.init()
}
CustomPokemonProperty.register(UncatchableProperty)
CustomPokemonProperty.register(BattleCloneProperty)
CustomPokemonProperty.register(PokemonFlagProperty)
@@ -336,7 +373,7 @@ object Cobblemon {
}
PlatformEvents.SERVER_TICK_POST.subscribe {
ServerTaskTracker.update(1/20F)
ServerTaskTracker.update(1 / 20F)
ServerRealTimeTaskTracker.update()
}
PlatformEvents.SERVER_TICK_PRE.subscribe {
@@ -380,10 +417,22 @@ object Cobblemon {
.applyConnectionString(ConnectionString(config.mongoDBConnectionString))
.build()
mongoClient = MongoClients.create(mongoClientSettings)
val generalMongoFactory = CachedPlayerDataStoreFactory(PlayerDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PlayerDataCollection"))
val generalMongoFactory = CachedPlayerDataStoreFactory(
PlayerDataMongoBackend(
mongoClient,
config.mongoDBDatabaseName,
"PlayerDataCollection"
)
)
generalMongoFactory.setup(server)
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
val pokedexMongoFactory = CachedPlayerDataStoreFactory(
DexDataMongoBackend(
mongoClient,
config.mongoDBDatabaseName,
"PokeDexCollection"
)
)
pokedexMongoFactory.setup(server)
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
@@ -505,6 +554,7 @@ object Cobblemon {
field.set(config, field.get(defaultConfig))
}
}
is IntConstraint -> {
var value = field.get(config)
if (value is Int) {
@@ -557,20 +607,76 @@ object Cobblemon {
}
private fun registerArgumentTypes() {
this.implementation.registerCommandArgument(cobblemonResource("pokemon"), SpeciesArgumentType::class, SingletonArgumentInfo.contextFree(SpeciesArgumentType::species))
this.implementation.registerCommandArgument(cobblemonResource("pokemon_properties"), PokemonPropertiesArgumentType::class, SingletonArgumentInfo.contextFree(PokemonPropertiesArgumentType::properties))
this.implementation.registerCommandArgument(cobblemonResource("spawn_bucket"), SpawnBucketArgumentType::class, SingletonArgumentInfo.contextFree(SpawnBucketArgumentType::spawnBucket))
this.implementation.registerCommandArgument(cobblemonResource("move"), MoveArgumentType::class, SingletonArgumentInfo.contextFree(MoveArgumentType::move))
this.implementation.registerCommandArgument(cobblemonResource("party_slot"), PartySlotArgumentType::class, SingletonArgumentInfo.contextFree(PartySlotArgumentType::partySlot))
this.implementation.registerCommandArgument(cobblemonResource("pokemon_store"), PokemonStoreArgumentType::class, SingletonArgumentInfo.contextFree(PokemonStoreArgumentType::pokemonStore))
this.implementation.registerCommandArgument(cobblemonResource("dialogue"), DialogueArgumentType::class, SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue))
this.implementation.registerCommandArgument(cobblemonResource("form"), FormArgumentType::class, SingletonArgumentInfo.contextFree(FormArgumentType::form))
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
this.implementation.registerCommandArgument(cobblemonResource("wallpaper"), UnlockablePCBoxWallpaperArgumentType::class, SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper))
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
this.implementation.registerCommandArgument(
cobblemonResource("pokemon"),
SpeciesArgumentType::class,
SingletonArgumentInfo.contextFree(SpeciesArgumentType::species)
)
this.implementation.registerCommandArgument(
cobblemonResource("pokemon_properties"),
PokemonPropertiesArgumentType::class,
SingletonArgumentInfo.contextFree(PokemonPropertiesArgumentType::properties)
)
this.implementation.registerCommandArgument(
cobblemonResource("spawn_bucket"),
SpawnBucketArgumentType::class,
SingletonArgumentInfo.contextFree(SpawnBucketArgumentType::spawnBucket)
)
this.implementation.registerCommandArgument(
cobblemonResource("move"),
MoveArgumentType::class,
SingletonArgumentInfo.contextFree(MoveArgumentType::move)
)
this.implementation.registerCommandArgument(
cobblemonResource("party_slot"),
PartySlotArgumentType::class,
SingletonArgumentInfo.contextFree(PartySlotArgumentType::partySlot)
)
this.implementation.registerCommandArgument(
cobblemonResource("pokemon_store"),
PokemonStoreArgumentType::class,
SingletonArgumentInfo.contextFree(PokemonStoreArgumentType::pokemonStore)
)
this.implementation.registerCommandArgument(
cobblemonResource("dialogue"),
DialogueArgumentType::class,
SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue)
)
this.implementation.registerCommandArgument(
cobblemonResource("form"),
FormArgumentType::class,
SingletonArgumentInfo.contextFree(FormArgumentType::form)
)
this.implementation.registerCommandArgument(
cobblemonResource("dex"),
DexArgumentType::class,
SingletonArgumentInfo.contextFree(DexArgumentType::dex)
)
this.implementation.registerCommandArgument(
cobblemonResource("npc_class"),
NPCClassArgumentType::class,
SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass)
)
this.implementation.registerCommandArgument(
cobblemonResource("wallpaper"),
UnlockablePCBoxWallpaperArgumentType::class,
SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper)
)
this.implementation.registerCommandArgument(
cobblemonResource("mark"),
MarkArgumentType::class,
SingletonArgumentInfo.contextFree(MarkArgumentType::mark)
)
this.implementation.registerCommandArgument(
cobblemonResource("transform_type"),
TransformTypeArgumentType::class,
SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType)
)
this.implementation.registerCommandArgument(
cobblemonResource("model_part"),
ModelPartArgumentType::class,
SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart)
)
}
}
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon.cobblemonVR
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonClientImplementation
@@ -27,6 +29,7 @@ import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.integration.vivecraft.CobblemonVRClient
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
@@ -72,6 +75,7 @@ import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.integration.vivecraft.CobblemonVR
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.isLookingAt
@@ -152,6 +156,10 @@ object CobblemonClient {
this.registerTooltipManagers()
this.registerMenuScreens()
if (Cobblemon.implementation.isModInstalled("vivecraft")) { // VR integration
CobblemonVRClient.init()
}
LOGGER.info("Registering custom BuiltinItemRenderers")
CobblemonBuiltinItemRendererRegistry.register(CobblemonItems.POKEMON_MODEL, PokemonItemRenderer())
@@ -187,7 +195,9 @@ object CobblemonClient {
nearbyPokemon?.forEach { entity ->
if (entity is PokemonEntity && !entity.isSilent && !entity.passengers.contains(player)) {
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(player)
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(
player
)
entity.delegate.spawnAspectParticle()
}
}
@@ -369,7 +379,10 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(
CobblemonBlockEntities.POKE_SNACK,
::PokeSnackBlockEntityRenderer
)
}
private fun registerEntityRenderers() {
@@ -398,7 +411,11 @@ object CobblemonClient {
private fun registerItemColors() {
implementation.registerItemColors(AprijuiceItemColorProvider, *CobblemonItems.aprijuices.toTypedArray())
implementation.registerItemColors(PokeSnackItemColorProvider, CobblemonItems.POKE_SNACK, CobblemonItems.POKE_CAKE)
implementation.registerItemColors(
PokeSnackItemColorProvider,
CobblemonItems.POKE_SNACK,
CobblemonItems.POKE_CAKE
)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.client.integration.vivecraft
import com.cobblemon.mod.common.client.integration.vivecraft.party.PartyOnArm
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityManager
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityRegistration
import com.cobblemon.mod.common.client.integration.vivecraft.listener.proximity.PokeRodBaitListener
import org.vivecraft.api.data.VRBodyPart
class CobblemonVRClient {
companion object {
fun init() {
PartyOnArm.init()
ProximityManager.init()
ProximityManager.register(
ProximityRegistration(
bodyA = VRBodyPart.MAIN_HAND,
bodyB = VRBodyPart.OFF_HAND,
distanceThreshold = 0.25f,
angleThreshold = 120f,
listener = PokeRodBaitListener("Main-OffHand")
)
)
}
}
}
@@ -0,0 +1,40 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.proximity
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityListener
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityRegistration
import com.cobblemon.mod.common.net.messages.server.pokerod.ApplyBaitPacket
import net.minecraft.client.Minecraft
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.ItemStack
import org.vivecraft.api.data.VRBodyPart
import org.vivecraft.api.data.VRPose
class PokeRodBaitListener(private val name: String) : ProximityListener {
//TODO: Also add that if 1 is Rod the other needs to be seasoning
override fun isActive(registration: ProximityRegistration): Boolean {
val player = Minecraft.getInstance().player ?: return false
val handA: InteractionHand = VRBodyPart.toInteractionHand(registration.bodyA) ?: return false
val itemStackA: ItemStack = player.getItemInHand(handA)
val handB: InteractionHand = VRBodyPart.toInteractionHand(registration.bodyB) ?: return false
val itemStackB: ItemStack = player.getItemInHand(handB)
return itemStackA.`is`(CobblemonItemTags.POKE_RODS) || itemStackB.`is`(CobblemonItemTags.POKE_RODS)
}
override fun onEnter(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
Cobblemon.LOGGER.info("[$name] Enter: $bodyA <-> $bodyB")
}
override fun onExit(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
Cobblemon.LOGGER.info("[$name] Exit: $bodyA <-> $bodyB")
}
override fun onTick(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
Cobblemon.LOGGER.info("[$name] Proximity: $bodyA <-> $bodyB")
CobblemonNetwork.sendToServer(ApplyBaitPacket())
}
}
@@ -0,0 +1,237 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
import com.cobblemon.mod.common.Cobblemon
import net.minecraft.client.Minecraft
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.vivecraft.api.client.VRClientAPI
import org.vivecraft.api.client.tracker.swing.SwingContext
import org.vivecraft.api.client.tracker.swing.SwingListener
import org.vivecraft.api.data.VRBodyPart
import kotlin.math.abs
/**
* Listener that classifies directional swings (up, down, forward, backward, left, right)
* using Vivecraft VR pose history and invokes handler methods for each direction.
*
* @param forwardDotThreshold Dot product threshold used to classify forward/back/left/right directions.
* @param historyTicks Number of ticks used to compute velocity from historical poses.
*/
abstract class DirectionalSwingListener(
private val forwardDotThreshold: Double = 0.3,
private val historyTicks: Int = 10
) : SwingListener {
protected val minecraft: Minecraft = Minecraft.getInstance()
protected val clientData: VRClientAPI = VRClientAPI.instance()
/**
* Compute the net movement vector for the specified body part over the given number of ticks.
* Returns null if historical pose data is unavailable, insufficient, or movement is negligible.
*
* @param bodyPart The VR body part to compute movement for.
* @param ticks The number of ticks of history to consider.
* @return The movement vector or null if unavailable/insignificant.
*/
private fun changeForVelocity(bodyPart: VRBodyPart, ticks: Int = historyTicks): Vec3? {
val history = clientData.historicalVRPoses ?: return null
if (history.ticksOfHistory() < ticks) return null
val delta = history.netMovement(bodyPart, ticks)
delta?.lengthSqr()?.let { if (it < 1e-6) return null }
return delta
}
/**
* Called when an upward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the upward action was handled, false otherwise.
*/
protected open fun onUpwardSwing(
useHand: InteractionHand,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: upward movement. player={}, hand={}",
player, useHand
)
return true
}
/**
* Called when a downward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the downward action was handled, false otherwise.
*/
protected open fun onDownwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: downward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Called when a forward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the forward action was handled, false otherwise.
*/
protected open fun onForwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
return true
}
/**
* Called when a backward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the backward action was handled, false otherwise.
*/
protected open fun onBackwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: backward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Called when a leftward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the leftward action was handled, false otherwise.
*/
protected open fun onLeftSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: leftward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Called when a rightward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the rightward action was handled, false otherwise.
*/
protected open fun onRightSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: rightward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Checks if the given item stack is valid for processing by this swing listener.
* Subclasses must implement this to specify which items they handle.
* @param itemStack The item stack to validate.
* @return True if the item stack is valid, false otherwise.
*/
protected abstract fun isValidItem(itemStack: ItemStack): Boolean
/**
* Called when a swing ends. Validates the held item, computes hand velocity from historical poses,
* handles vertical swings first (upward/downward) and then classifies horizontal direction:
* forward/backward by dot with player look direction; left/right by projecting onto a right vector
* computed from world up and forward. Ambiguous swings are ignored.
*
* @param context Vivecraft swing context containing body part information.
*/
override fun onSwingEnd(context: SwingContext?) {
val player = minecraft.player ?: return
val ctx = context ?: return
val useHand: InteractionHand = VRBodyPart.toInteractionHand(ctx.bodyPart) ?: return
val itemStack: ItemStack = player.getItemInHand(useHand)
if (!isValidItem(itemStack)) return
val handVel = changeForVelocity(ctx.bodyPart) ?: run {
Cobblemon.LOGGER.info(
"Ignoring swing: insufficient historical pose data for velocity. bodyPart={}, player={}",
ctx.bodyPart, player
)
return
}
val vertical = handVel.y
if (vertical > 0.0) {
if (onUpwardSwing(useHand, player)) return
} else if (vertical < 0.0) {
if (onDownwardSwing(useHand, handVel, player)) return
}
val forward = player.lookAngle.normalize()
val handDir = handVel.normalize()
val forwardDot = handDir.dot(forward)
val worldUp = Vec3(0.0, 1.0, 0.0)
val right = forward.cross(worldUp).let {
if (it.lengthSqr() < 1e-6) Vec3(1.0, 0.0, 0.0) else it.normalize()
}
val rightDot = handDir.dot(right)
if (forwardDot > forwardDotThreshold) {
if (onForwardSwing(useHand, handVel, player)) return
return
}
if (forwardDot < -forwardDotThreshold) {
if (onBackwardSwing(useHand, handVel, player)) return
return
}
if (abs(rightDot) > forwardDotThreshold) {
if (rightDot > 0.0) {
if (onRightSwing(useHand, handVel, player)) return
} else {
if (onLeftSwing(useHand, handVel, player)) return
}
return
}
Cobblemon.LOGGER.info(
"Ignoring swing: ambiguous direction. forwardDot={}, rightDot={}, handVel={}, player={}",
forwardDot, rightDot, handVel, player
)
}
}
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PokeBallSwingListener : DirectionalSwingListener() {
override fun isValidItem(itemStack: ItemStack): Boolean =
itemStack.`is`(CobblemonItemTags.POKE_BALLS)
override fun onForwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
CobblemonNetwork.sendToServer(SendOutPokeBallPacket())
return true
}
override fun onDownwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
CobblemonNetwork.sendToServer(SendOutPokeBallPacket())
return super.onDownwardSwing(useHand, handVel, player)
}
}
@@ -0,0 +1,31 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
import com.cobblemon.mod.common.net.messages.server.pokerod.ReelPokeRodPacket
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PokeRodSwingListener : DirectionalSwingListener() {
override fun isValidItem(itemStack: ItemStack): Boolean =
itemStack.`is`(CobblemonItemTags.POKE_RODS)
override fun onUpwardSwing(useHand: InteractionHand, player: Player): Boolean {
CobblemonNetwork.sendToServer(ReelPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
return super.onUpwardSwing(useHand, player)
}
override fun onForwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
CobblemonNetwork.sendToServer(CastPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
return super.onForwardSwing(useHand, handVel, player)
}
override fun onDownwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
CobblemonNetwork.sendToServer(CastPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
return super.onDownwardSwing(useHand, handVel, player)
}
}
@@ -0,0 +1,188 @@
package com.cobblemon.mod.common.client.integration.vivecraft.party
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.ClientTickEvent
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.platform.events.RenderEvent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.renderer.LightTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import org.vivecraft.api.VRAPI
import org.vivecraft.api.client.InteractModule
import org.vivecraft.api.client.VRClientAPI
import org.vivecraft.api.data.VRBodyPartData
import java.util.function.Consumer
class PartyOnArm : InteractModule {
val fakePartyEntities: Array<Entity?> = arrayOfNulls(6)
var selectedIndex = -1
private fun onTick(tickEvent: ClientTickEvent.Post) {
val client = tickEvent.client
client.player ?: return
val playerData = VRClientAPI.instance().postTickWorldPose ?: return
val offHandData = playerData.offHand
// Keep up-to-date fakePartyEntities, which is done so we always have a Pokemon or PokeBall entity available to
// render and keep it ticking so we have proper animations.
val party = CobblemonClient.storage.party
// Handle the selected index, placing either a Pokemon or null
if (selectedIndex > -1) {
val actual = party.get(selectedIndex)
if (actual == null) {
fakePartyEntities[selectedIndex] = null
} else if (fakePartyEntities[selectedIndex] is PokemonEntity) {
fakePartyEntities[selectedIndex]!!.tick()
} else {
fakePartyEntities[selectedIndex] = PokemonEntity(client.level!!, actual)
}
}
// Handle the other indices, placing a pokeball or null
for (i in 0..5) {
if (i != selectedIndex) {
val actual = party.get(i)
if (actual == null) {
fakePartyEntities[i] = null
} else if (fakePartyEntities[i] !is EmptyPokeBallEntity) {
val pokeBall = EmptyPokeBallEntity(actual.caughtBall, client.level!!)
pokeBall.captureState = EmptyPokeBallEntity.CaptureState.FALL
fakePartyEntities[i] = pokeBall
} else {
fakePartyEntities[i]!!.tick()
}
}
}
// Check primary hand for overlap with any of the Pokemon
val centerPos = getCenterPos(offHandData)
selectedIndex = -1
for (i in 0..5) {
if (party.get(i) != null) {
val pokemonPos = getPokemonPos(centerPos, playerData.offHand, i)
if (playerData.mainHand.pos.distanceToSqr(pokemonPos) < DIST_SELECT_SQR) {
selectedIndex = i
break
}
}
}
}
private fun onRender(renderEvent: RenderEvent) {
if (renderEvent.stage != RenderEvent.Stage.AFTER_ENTITIES) return
val mc = Minecraft.getInstance()
val bufferSource = mc.renderBuffers().bufferSource()
val guiGraphics = GuiGraphics(mc, mc.renderBuffers().bufferSource())
val cameraPos = renderEvent.camera.getPosition()
val poseStack = guiGraphics.pose()
val partialTick = renderEvent.tickCounter.getGameTimeDeltaPartialTick(true)
val offHandData = VRClientAPI.instance().getWorldRenderPose()?.offHand ?: return
val centerPos = getCenterPos(offHandData)
for (i in 0..5) {
val pokemon = fakePartyEntities[i]
if (pokemon != null) {
poseStack.pushPose()
val pokemonPos = getPokemonPos(centerPos, offHandData, i)
val renderPos = Vec3(
-cameraPos.x + pokemonPos.x,
-cameraPos.y + pokemonPos.y,
-cameraPos.z + pokemonPos.z,
)
poseStack.translate(renderPos.x, renderPos.y, renderPos.z)
poseStack.scale(0.05f, 0.05f, 0.05f)
poseStack.mulPose(Axis.YN.rotation(offHandData.yaw.toFloat()))
poseStack.mulPose(Axis.XN.rotation(offHandData.pitch.toFloat()))
poseStack.mulPose(Axis.ZP.rotation(offHandData.roll.toFloat() + Math.PI.toFloat()))
mc.entityRenderDispatcher.getRenderer(pokemon).render(pokemon, 0f, partialTick, poseStack, bufferSource,
LightTexture.FULL_BRIGHT)
poseStack.popPose()
}
}
}
private fun getCenterPos(pose: VRBodyPartData): Vec3 {
return pose.pos.subtract(pose.dir.scale(0.125))
}
private fun getPokemonPos(centerPos: Vec3, controllerData: VRBodyPartData, index: Int): Vec3 {
var pos = centerPos
// Move to bottom of hand
var downVecF = Vector3f(0f, -0.0625f, 0f)
downVecF.rotate(Axis.ZP.rotation(controllerData.roll.toFloat()))
downVecF.rotate(Axis.XN.rotation(controllerData.pitch.toFloat()))
downVecF.rotate(Axis.YN.rotation(controllerData.yaw.toFloat()))
pos = pos.add(Vec3(downVecF.x.toDouble(), downVecF.y.toDouble(), downVecF.z.toDouble()))
// Move right or left based on index
val rightF = Vector3f(0f, 0f, 1f) // Default forward vector of Minecraft
rightF.rotate(Axis.YN.rotation(Math.PI.toFloat() / 2f))
rightF.rotate(Axis.ZP.rotation(controllerData.roll.toFloat()))
rightF.rotate(Axis.XN.rotation(controllerData.pitch.toFloat()))
rightF.rotate(Axis.YN.rotation(controllerData.yaw.toFloat()))
val right = Vec3(rightF.x.toDouble(), rightF.y.toDouble(), rightF.z.toDouble())
if (index > 2) {
pos = pos.add(right.scale(-0.0625))
} else {
pos = pos.add(right.scale(0.0625))
}
// Offset by -hand direction if on left, 0 if center, or +hand direction if right
return pos.add(controllerData.dir.scale(DIST_BETWEEN_HORIZONTAL * (index % 3 - 1)))
}
override fun getId(): ResourceLocation {
return INTERACT_ID
}
override fun isActive(
player: LocalPlayer,
hand: InteractionHand,
handPosition: Vec3
): Boolean {
return hand == InteractionHand.MAIN_HAND && selectedIndex > -1
}
override fun onPress(
player: LocalPlayer,
hand: InteractionHand
): Boolean {
// UUID lookup is done at press time since the Pokemon may have switched since initially hovered over, such
// as via commands.
val uuid = CobblemonClient.storage.party.get(selectedIndex)?.uuid ?: return false
CobblemonClient.storage.switchToPokemon(uuid)
return true
}
companion object {
const val DIST_BETWEEN_HORIZONTAL: Double = 0.125
const val DIST_BETWEEN_VERTICAL: Float = 0.05f
const val DIST_SELECT_SQR: Double = 0.1 * 0.1
val INTERACT_ID = ResourceLocation(Cobblemon.MODID, "party_on_arm_interact_module")
private val INSTANCE = PartyOnArm()
fun init() {
PlatformEvents.RENDER.subscribe(Consumer { renderEvent: RenderEvent? -> INSTANCE.onRender(renderEvent!!) })
PlatformEvents.CLIENT_TICK_POST.subscribe(Consumer { tickEvent: ClientTickEvent.Post? ->
INSTANCE.onTick(
tickEvent!!
)
})
VRAPI.instance().register(INSTANCE)
}
}
}
@@ -0,0 +1,12 @@
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
import org.vivecraft.api.data.VRBodyPart
import org.vivecraft.api.data.VRPose
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
interface ProximityListener {
fun isActive(registration: ProximityRegistration): Boolean = true
fun onEnter(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
fun onExit(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
fun onTick(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
}
@@ -0,0 +1,79 @@
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
import com.cobblemon.mod.common.platform.events.ClientTickEvent
import com.cobblemon.mod.common.platform.events.PlatformEvents
import org.vivecraft.api.client.VRClientAPI
import kotlin.math.acos
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
class ProximityManager {
private val registrations = mutableListOf<ProximityRegistration>()
private val previousInProximity = mutableMapOf<ProximityRegistration, Boolean>()
private val api: VRClientAPI = VRClientAPI.instance()
fun register(registration: ProximityRegistration) {
registrations += registration
previousInProximity.putIfAbsent(registration, false)
}
fun unregister(registration: ProximityRegistration) {
registrations -= registration
previousInProximity.remove(registration)
}
fun clear() {
registrations.clear()
previousInProximity.clear()
}
fun isActive(registration: ProximityRegistration): Boolean {
return registration.listener.isActive(registration)
}
fun onTick(tickEvent: ClientTickEvent.Post) {
val vrPose = api.getPostTickWorldPose() ?: return
for (reg in registrations) {
if (!isActive(reg)) continue
val poseA = vrPose.getBodyPartData(reg.bodyA) ?: continue
val poseB = vrPose.getBodyPartData(reg.bodyB) ?: continue
val distance = poseA.pos.distanceTo(poseB.pos)
val dot = poseA.dir.dot(poseB.dir).coerceIn(-1.0, 1.0)
val angleDeg = Math.toDegrees(acos(dot)).toFloat()
val angleOk = angleDeg <= reg.angleThreshold
val nowInProximity = distance <= reg.distanceThreshold && angleOk
val wasInProximity = previousInProximity[reg] ?: false
if (!wasInProximity && nowInProximity) {
reg.listener.onEnter(reg.bodyA, reg.bodyB, vrPose)
} else if (wasInProximity && !nowInProximity) {
reg.listener.onExit(reg.bodyA, reg.bodyB, vrPose)
}
if (nowInProximity) {
reg.listener.onTick(reg.bodyA, reg.bodyB, vrPose)
}
previousInProximity[reg] = nowInProximity
}
}
companion object {
private val INSTANCE = ProximityManager()
fun init() {
PlatformEvents.CLIENT_TICK_POST.subscribe { tickEvent: ClientTickEvent.Post? ->
INSTANCE.onTick(
tickEvent!!
)
}
}
fun register(registration: ProximityRegistration) = INSTANCE.register(registration)
fun unregister(registration: ProximityRegistration) = INSTANCE.unregister(registration)
fun clearAll() = INSTANCE.clear()
}
}
@@ -0,0 +1,12 @@
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
import org.vivecraft.api.data.VRBodyPart
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
data class ProximityRegistration(
val bodyA: VRBodyPart,
val bodyB: VRBodyPart,
val distanceThreshold: Float,
val angleThreshold: Float = 180f,
val listener: ProximityListener
)
@@ -0,0 +1,16 @@
package com.cobblemon.mod.common.integration.vivecraft
import com.cobblemon.mod.common.client.integration.vivecraft.listener.swing.PokeBallSwingListener
import com.cobblemon.mod.common.client.integration.vivecraft.listener.swing.PokeRodSwingListener
import org.vivecraft.api.VRAPI
class CobblemonVR {
fun init() {
VRAPI.instance()
.register(PokeBallSwingListener())
VRAPI.instance()
.register(PokeRodSwingListener())
}
}
@@ -23,7 +23,7 @@ import net.minecraft.world.level.Level
import kotlin.math.cos
class PokeBallItem(
val pokeBall: PokeBall
val pokeBall: PokeBall
) : CobblemonItem(Properties().apply {
when (pokeBall.name) {
// Master balls are a netherite product and should be fire immune
@@ -43,7 +43,7 @@ class PokeBallItem(
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
private fun throwPokeBall(world: Level, player: ServerPlayer) {
fun throwPokeBall(world: Level, player: ServerPlayer) {
val pokeBallEntity = EmptyPokeBallEntity(pokeBall, player.level(), player).apply {
val overhandFactor: Float = if (player.xRot < 0) {
5f * cos(player.xRot.toRadians())
@@ -57,4 +57,5 @@ class PokeBallItem(
}
world.addFreshEntity(pokeBallEntity)
}
}
@@ -75,7 +75,8 @@ class PokerodItem(val pokeRodId: ResourceLocation, settings: Properties) : Fishi
if (baitCount > 1) {
baitStack.count = baitCount - 1
stack.set(CobblemonItemComponents.BAIT, RodBaitComponent(baitStack)
stack.set(
CobblemonItemComponents.BAIT, RodBaitComponent(baitStack)
)
}
}
@@ -149,90 +150,163 @@ class PokerodItem(val pokeRodId: ResourceLocation, settings: Properties) : Fishi
}
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
val itemStack = user.getItemInHand(hand)
val offHandItem = user.getItemInHand(InteractionHand.OFF_HAND)
// Check if offhand item is valid bait and the rod is not in use, if so then apply bait from offhand
if (!world.isClientSide && user.fishing == null && SpawnBaitEffects.isFishingBait(offHandItem)) {
CobblemonEvents.BAIT_SET_PRE.postThen(BaitSetEvent(itemStack, offHandItem), { event ->
return InteractionResultHolder.fail(itemStack)
}, {
playAttachSound(user)
attemptApplyOffhandBait(world, user, itemStack, offHandItem)?.let { return it }
// if there is bait on the rod already then drop it on the ground before applying offhand bait
val baitStack = getBaitStackOnRod(itemStack)
if (user.fishing != null) {
attemptReel(world, user, hand, itemStack)?.let { return it }
} else {
attemptCast(world, user, hand, itemStack)?.let { return it }
}
if (baitStack != ItemStack.EMPTY && offHandItem.item != baitStack.item) {
user.drop(baitStack, true) // Drop the full stack
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
// apply single bait item from offhand
val singleBait = offHandItem.copy()
singleBait.count = 1
setBait(itemStack, singleBait)
offHandItem.shrink(1)
fun applyBait(
rodStack: ItemStack,
baitSource: ItemStack,
player: Player?
): Boolean {
if (baitSource.isEmpty || !SpawnBaitEffects.isFishingBait(baitSource)) return false
val existingBait = getBaitStackOnRod(rodStack)
if (!existingBait.isEmpty && existingBait.item == baitSource.item) return false
var applied = false
CobblemonEvents.BAIT_SET_PRE.postThen(
BaitSetEvent(rodStack, baitSource),
{ return false },
{
if (!existingBait.isEmpty && player != null) {
player.drop(existingBait, true)
}
})
val singleBait = baitSource.copy()
singleBait.count = 1
setBait(rodStack, singleBait)
baitSource.shrink(1)
applied = true
}
)
return applied
}
fun attemptApplyOffhandBait(
world: Level,
user: Player,
rodStack: ItemStack,
offHandItem: ItemStack
): InteractionResultHolder<ItemStack>? {
if (world.isClientSide) return null
if (user.fishing != null) return null
val applied = applyBait(
rodStack = rodStack,
baitSource = offHandItem,
player = user
)
if (applied) {
playAttachSound(user)
return InteractionResultHolder.sidedSuccess(rodStack, false)
}
var i: Int
if (user.fishing != null) { // if the bobber is out yet
if (!world.isClientSide) {
CobblemonEvents.POKEROD_REEL.postThen(
PokerodReelEvent(user, itemStack),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
i = user.fishing!!.retrieve(itemStack)
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
world.playSoundServer(
Vec3(user.x,
user.y,
user.z),
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
}
)
}
return null
}
world.playSound(null as Player?, user.x, user.y, user.z, CobblemonSounds.FISHING_ROD_REEL_IN, SoundSource.PLAYERS, 1.0f, 1.0f)
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
} else { // if the bobber is not out yet
fun attemptReel(
world: Level,
user: Player,
hand: InteractionHand,
itemStack: ItemStack
): InteractionResultHolder<ItemStack>? {
if (user.fishing == null) return null
if (!world.isClientSide) {
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
val bobberEntity = PokeRodFishingBobberEntity(
user,
pokeRodId,
getBaitStackOnRod(itemStack),
world,
luckLevel,
lureLevel,
itemStack
)
CobblemonEvents.POKEROD_CAST_PRE.postThen(
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
world.addFreshEntity(bobberEntity)
var baitId = getBaitStackOnRod(itemStack).takeUnless { it.isEmpty }?.itemHolder?.unwrapKey()?.orElse(null)?.location() ?: cobblemonResource("empty_bait")
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
CobblemonEvents.POKEROD_CAST_POST.post(
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
)
}
)
}
user.awardStat(Stats.ITEM_USED.get(this))
user.gameEvent(GameEvent.ITEM_INTERACT_START)
if (!world.isClientSide) {
CobblemonEvents.POKEROD_REEL.postThen(
PokerodReelEvent(user, itemStack),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
val i = user.fishing!!.retrieve(itemStack)
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
world.playSoundServer(
Vec3(user.x, user.y, user.z),
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
}
)
}
world.playSound(
null as Player?,
user.x,
user.y,
user.z,
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f
)
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
fun attemptCast(
world: Level,
user: Player,
hand: InteractionHand,
itemStack: ItemStack
): InteractionResultHolder<ItemStack>? {
if (user.fishing != null) return null
if (!world.isClientSide) {
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE)
.map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }
.orElse(0)
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA)
.map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }
.orElse(0)
val bobberEntity = PokeRodFishingBobberEntity(
user,
pokeRodId,
getBaitStackOnRod(itemStack),
world,
luckLevel,
lureLevel,
itemStack
)
CobblemonEvents.POKEROD_CAST_PRE.postThen(
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
world.addFreshEntity(bobberEntity)
val baitId = getBaitStackOnRod(itemStack).takeUnless { it.isEmpty }
?.itemHolder?.unwrapKey()?.orElse(null)?.location()
?: cobblemonResource("empty_bait")
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
CobblemonEvents.POKEROD_CAST_POST.post(
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
)
}
)
}
user.awardStat(Stats.ITEM_USED.get(this))
user.gameEvent(GameEvent.ITEM_INTERACT_START)
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
@@ -0,0 +1,16 @@
package com.cobblemon.mod.common.net.messages.server.pokeball
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class SendOutPokeBallPacket() : NetworkPacket<SendOutPokeBallPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
}
companion object {
val ID = cobblemonResource("send_out_pokeball")
fun decode(buffer: RegistryFriendlyByteBuf) = SendOutPokeBallPacket()
}
}
@@ -0,0 +1,16 @@
package com.cobblemon.mod.common.net.messages.server.pokerod
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class ApplyBaitPacket() : NetworkPacket<ApplyBaitPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
}
companion object {
val ID = cobblemonResource("apply_bait")
fun decode(buffer: RegistryFriendlyByteBuf) = ApplyBaitPacket()
}
}
@@ -0,0 +1,17 @@
package com.cobblemon.mod.common.net.messages.server.pokerod
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class CastPokeRodPacket(val mainHand: Boolean) : NetworkPacket<CastPokeRodPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBoolean(mainHand)
}
companion object {
val ID = cobblemonResource("cast_pokerod")
fun decode(buffer: RegistryFriendlyByteBuf) = CastPokeRodPacket(buffer.readBoolean())
}
}
@@ -0,0 +1,17 @@
package com.cobblemon.mod.common.net.messages.server.pokerod
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class ReelPokeRodPacket(val mainHand: Boolean) : NetworkPacket<ReelPokeRodPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBoolean(mainHand)
}
companion object {
val ID = cobblemonResource("reel_pokerod")
fun decode(buffer: RegistryFriendlyByteBuf) = ReelPokeRodPacket(buffer.readBoolean())
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.pokeball
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
import com.cobblemon.mod.common.util.isServerSide
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object SendOutPokeBallHandler : ServerNetworkPacketHandler<SendOutPokeBallPacket> {
override fun handle(packet: SendOutPokeBallPacket, server: MinecraftServer, player: ServerPlayer) {
(player.mainHandItem.item as? PokeBallItem)?.let { pokeball ->
if (player.level().isServerSide()) {
pokeball.throwPokeBall(player.level(), player)
}
// TODO: player.mainHandItem.consume(1, player)
}
}
}
@@ -0,0 +1,41 @@
package com.cobblemon.mod.common.net.serverhandling.pokerod
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.net.messages.server.pokerod.ApplyBaitPacket
import com.cobblemon.mod.common.util.isServerSide
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
object ApplyBaitHandler : ServerNetworkPacketHandler<ApplyBaitPacket> {
override fun handle(packet: ApplyBaitPacket, server: MinecraftServer, player: ServerPlayer) {
val mainHandItem = player.getItemInHand(InteractionHand.MAIN_HAND)
val offHandItem = player.getItemInHand(InteractionHand.OFF_HAND)
println("[ApplyBaitHandler] Main hand: ${mainHandItem.item}")
println("[ApplyBaitHandler] Off hand: ${offHandItem.item}")
(mainHandItem.item as? PokerodItem)?.let { pokerod ->
if (player.level().isServerSide()) {
pokerod.applyBait(
mainHandItem,
offHandItem,
player
)
}
}
(offHandItem.item as? PokerodItem)?.let { pokerod ->
if (player.level().isServerSide()) {
pokerod.applyBait(
offHandItem,
mainHandItem,
player
)
}
}
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.pokerod
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
import com.cobblemon.mod.common.util.isServerSide
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
object CastPokeRodHandler : ServerNetworkPacketHandler<CastPokeRodPacket> {
override fun handle(packet: CastPokeRodPacket, server: MinecraftServer, player: ServerPlayer) {
val hand = if (packet.mainHand) InteractionHand.MAIN_HAND else InteractionHand.OFF_HAND
(player.getItemInHand(hand).item as? PokerodItem)?.let { pokerod ->
if (player.level().isServerSide()) pokerod.attemptCast(
player.level(),
player,
hand,
player.getItemInHand(hand)
)
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.pokerod
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
import com.cobblemon.mod.common.net.messages.server.pokerod.ReelPokeRodPacket
import com.cobblemon.mod.common.util.isServerSide
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
object ReelPokeRodHandler : ServerNetworkPacketHandler<ReelPokeRodPacket> {
override fun handle(packet: ReelPokeRodPacket, server: MinecraftServer, player: ServerPlayer) {
val hand = if (packet.mainHand) InteractionHand.MAIN_HAND else InteractionHand.OFF_HAND
(player.getItemInHand(hand).item as? PokerodItem)?.let { pokerod ->
if (player.level().isServerSide()) pokerod.attemptReel(
player.level(),
player,
hand,
player.getItemInHand(hand)
)
}
}
}
@@ -32,6 +32,7 @@ data class RenderEvent(
/** Represents when a layer is rendered by [LevelRenderer.renderLevel]. Ordinal corresponds with its render order. */
enum class Stage {
AFTER_ENTITIES,
TRANSLUCENT
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.util
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.sqrt
/**
* For conversion from Vec3dto BlockPos, loses accuracy
@@ -22,9 +23,17 @@ fun Vec3.toBlockPos(): BlockPos {
fun Vec3.toVec3f(): Vector3f = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
fun Vector3f.toVec3d(): Vec3 =
Vec3(x.toDouble(), y.toDouble(), z.toDouble())
fun Vector3f.set(vec3d: Vec3): Vector3f {
x = vec3d.x.toFloat()
y = vec3d.y.toFloat()
z = vec3d.z.toFloat()
return this
}
fun Vector3f.distance(other: Vector3f): Float {
val dx = other.x - x
val dy = other.y - y
val dz = other.z - z
return sqrt(dx * dx + dy * dy + dz * dz)
}
Binary file not shown.
+10
View File
@@ -51,6 +51,16 @@ dependencies {
modImplementation(libs.fabric.loader)
modApi(libs.fabric.api)
modApi(libs.bundles.fabric)
// VR Support
modRuntimeOnly(files("temp/vivecraft-1.21.1-1.3.4-fabric.jar"))
runtimeOnly("com.illposed.osc:javaosc-core:0.9")
runtimeOnly("com.electronwill.night-config:toml:3.6.6")
runtimeOnly("com.electronwill.night-config:core:3.6.6")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-linux")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-macos")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-windows")
modCompileOnly(libs.bundles.common.integrations.compileOnly) {
isTransitive = false
@@ -170,6 +170,18 @@ class CobblemonFabricClient: ClientModInitializer, CobblemonClientImplementation
)
)
}
WorldRenderEvents.AFTER_ENTITIES.register { context -> PlatformEvents.RENDER.post(
RenderEvent(
stage = RenderEvent.Stage.AFTER_ENTITIES,
levelRenderer = context.worldRenderer(),
poseStack = context.matrixStack() ?: PoseStack(),
modelViewMatrix = context.positionMatrix(),
projectionMatrix = context.projectionMatrix(),
tickCounter = context.tickCounter(),
camera = context.camera()
)
)
}
CobblemonModelPredicateRegistry.registerPredicates()
}
Binary file not shown.
+8
View File
@@ -39,6 +39,14 @@ dependencies {
//include(libs.flywheelForge)
modLocalRuntime(libs.neoforge.debugutils)
modRuntimeOnly(libs.bundles.neoforge.integrations.runtimeOnly)
// VR Support
modRuntimeOnly(files("temp/vivecraft-1.21.1-1.3.4-neoforge.jar"))
runtimeOnly("com.illposed.osc:javaosc-core:0.9")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-linux")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-macos")
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-windows")
implementation(libs.neo.kotlin.forge)
@@ -74,6 +74,7 @@ object NeoForgeClientPlatformEventHandler {
fun onRenderLevelStageEvent(event: RenderLevelStageEvent) {
val stage = when (event.stage) {
RenderLevelStageEvent.Stage.AFTER_PARTICLES -> RenderEvent.Stage.TRANSLUCENT
RenderLevelStageEvent.Stage.AFTER_ENTITIES -> RenderEvent.Stage.AFTER_ENTITIES
else -> return
}
Binary file not shown.