mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-11-14 04:08:53 -05:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdb37c70f6 | ||
|
|
e052947d7a | ||
|
|
8ee18f7da4 | ||
|
|
0ac418ec2c | ||
|
|
240804e201 | ||
|
|
d3e1f6be9d | ||
|
|
6d7760ce30 | ||
|
|
75e951191a | ||
|
|
2157e9c09e | ||
|
|
74ef1a61f9 | ||
|
|
23526824e8 | ||
|
|
859e586503 | ||
|
|
a9b6c07ac9 | ||
|
|
82548e0624 | ||
|
|
552a405e2e | ||
|
|
08918956db | ||
|
|
c139a1fea9 | ||
|
|
040ae26f22 | ||
|
|
4c9b4a4f1f | ||
|
|
7d34f83a8f | ||
|
|
8d89a37d93 | ||
|
|
65c5748c77 | ||
|
|
d4821c15bd | ||
|
|
c753ca54f1 | ||
|
|
ef581d1aa4 | ||
|
|
1f4224b029 |
No files matched your search
@@ -2,6 +2,7 @@
|
||||
## [1.7.4 (MONTH Xth, 2026)](#1-7-4)
|
||||
|
||||
### Additions
|
||||
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`)
|
||||
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
|
||||
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
|
||||
- Added new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
|
||||
@@ -37,15 +38,21 @@
|
||||
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
|
||||
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
|
||||
- Fixed NPCs switching pokemon very often during battles
|
||||
- Fixed Campfire pot causing crashes under certain circumstances
|
||||
- Fixed Type Gems not being consumed upon use
|
||||
- Fixed Ancient Pokeball's to their respective modifiers
|
||||
- Fixed a Server warning when recalling Pokemon
|
||||
|
||||
### Developer
|
||||
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
|
||||
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
|
||||
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts
|
||||
|
||||
### Molang & Datapacks
|
||||
- q.item.is_enchanted()
|
||||
- q.item.has_enchantment(minecraft:sharpness, 3)
|
||||
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
|
||||
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
@@ -55,7 +55,7 @@ dependencies {
|
||||
// modCompileOnly(libs.flywheelFabric)
|
||||
|
||||
// Showdown
|
||||
modCompileOnly(libs.graal)
|
||||
modCompileOnly(libs.graal.core)
|
||||
|
||||
// Data Storage
|
||||
modCompileOnly(libs.bundles.mongo)
|
||||
|
||||
@@ -101,7 +101,7 @@ public abstract class ServerEntityMixin {
|
||||
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
|
||||
}
|
||||
|
||||
private void cobblemon$broadcast(NetworkPacket<?> packet) {
|
||||
public void cobblemon$broadcast(NetworkPacket<?> packet) {
|
||||
if (!(entity.level() instanceof ServerLevel level)) return;
|
||||
ChunkMap chunkMap = level.getChunkSource().chunkMap;
|
||||
Int2ObjectMap<?> entityMap = ((ChunkMapAccessor) chunkMap).getEntityMap();
|
||||
|
||||
@@ -28,6 +28,8 @@ object CobblemonActivities {
|
||||
@JvmField
|
||||
val POKEMON_HERD = register(Activity("pokemon_herd"))
|
||||
@JvmField
|
||||
val POKEMON_BOIDS = register(Activity("pokemon_boid"))
|
||||
@JvmField
|
||||
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.minecraft.core.UUIDUtil
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import java.util.*
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
object CobblemonMemories {
|
||||
val memories = mutableMapOf<String, MemoryModuleType<*>>()
|
||||
@@ -92,6 +93,9 @@ object CobblemonMemories {
|
||||
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
|
||||
@JvmField
|
||||
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
|
||||
/** Is the nearest player holding a diamond for debugging purposes? */
|
||||
@JvmField
|
||||
val DIAMOND_DEBUGGER = register<Player>("diamond_debugger")
|
||||
|
||||
@JvmStatic
|
||||
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.S2CUpdateObsAvoidHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
|
||||
@@ -80,6 +81,7 @@ import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallba
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
@@ -253,7 +255,11 @@ import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.Entity
|
||||
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor
|
||||
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor
|
||||
|
||||
/**
|
||||
* Registers Cobblemon network packets.
|
||||
@@ -276,6 +282,19 @@ object CobblemonNetwork {
|
||||
fun sendToAllPlayers(packet: NetworkPacket<*>) = sendPacketToPlayers(server()!!.playerList.players, packet)
|
||||
@JvmStatic
|
||||
fun sendPacketToPlayers(players: Iterable<ServerPlayer>, packet: NetworkPacket<*>) = players.forEach { sendPacketToPlayer(it, packet) }
|
||||
fun sendPacketToTrackingPlayers(entity: Entity, packet: NetworkPacket<*>) {
|
||||
val level = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val chunkMap = level.chunkSource.chunkMap
|
||||
val entityTracker = (chunkMap as ChunkMapAccessor).entityMap[entity.id]
|
||||
|
||||
if (entityTracker is TrackedEntityAccessor) {
|
||||
entityTracker.seenBy.forEach { connection ->
|
||||
sendPacketToPlayer(connection.player, packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val s2cPayloads = generateS2CPacketInfoList()
|
||||
val c2sPayloads = generateC2SPacketInfoList()
|
||||
@@ -483,6 +502,7 @@ object CobblemonNetwork {
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateObsAvoidPacket.ID, ClientboundUpdateObsAvoidPacket::decode, S2CUpdateObsAvoidHandler))
|
||||
|
||||
// Riding
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
|
||||
|
||||
@@ -49,6 +49,9 @@ object CobblemonSensors {
|
||||
@JvmField
|
||||
val NEARBY_SWEET_BERRY_BUSH = register("nearby_sweet_berry_bush", ::SweetBerryBushSensor)
|
||||
|
||||
@JvmField
|
||||
val PLAYER_DEBUGGING = register("player_debugging", ::PlayerDebuggingSensor)
|
||||
|
||||
@JvmField
|
||||
val POKEMON_NEARBY_WANTED_ITEM = register("pokemon_nearby_wanted_item", ::PokemonItemSensor)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.CobblemonSensors
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.WakeUpTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
class BoidsTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
if (entity !is PokemonEntity) {
|
||||
return null
|
||||
}
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.POKEMON_DROWSY, CobblemonMemories.POKEMON_SLEEPING)
|
||||
behaviourConfigurationContext.addSensors(CobblemonSensors.POKEMON_DROWSY)
|
||||
return WrapperLivingEntityTask(WakeUpTask.create(), PokemonEntity::class.java)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.BoidsMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
|
||||
class SwitchFromBoidTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, time ->
|
||||
if(entity !is PokemonEntity) return@Trigger false
|
||||
if (entity.moveControl !is PokemonMoveControl) {
|
||||
entity.moveControl = PokemonMoveControl(entity)
|
||||
}
|
||||
entity.brain.updateActivityFromSchedule(world.dayTime, world.gameTime)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.CobblemonSensors
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.BoidsMoveControl
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class SwitchToBoidTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
behaviourConfigurationContext.addSensors(CobblemonSensors.PLAYER_DEBUGGING)
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.present(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, time ->
|
||||
if(entity !is PokemonEntity) return@Trigger false
|
||||
val player = entity.brain.getMemory(CobblemonMemories.DIAMOND_DEBUGGER).orElse(null)
|
||||
if (entity.moveControl !is BoidsMoveControl && player is LivingEntity) {
|
||||
entity.moveControl = BoidsMoveControl(entity, player)
|
||||
} else {
|
||||
return@Trigger false
|
||||
}
|
||||
entity.brain.setActiveActivityIfPossible(CobblemonActivities.POKEMON_BOIDS)
|
||||
// SpawnSnowstormEntityParticlePacket(
|
||||
// cobblemonResource("evo_particles"),
|
||||
// entity.id,
|
||||
// listOf("shiny_particles", "middle")).sendToPlayersAround(
|
||||
// entity.x,
|
||||
// entity.y,
|
||||
// entity.z,
|
||||
// 64.0,
|
||||
// entity.level().dimension()
|
||||
// )
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,8 @@ interface TaskConfig {
|
||||
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
|
||||
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
|
||||
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
|
||||
cobblemonResource("switch_from_boid") to SwitchFromBoidTaskConfig::class.java,
|
||||
cobblemonResource("switch_to_boid") to SwitchToBoidTaskConfig::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,18 +22,21 @@ import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.BattleEvSource
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.white
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.BattleCaptureAction
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.BattleSide
|
||||
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
import com.cobblemon.mod.common.battles.ForfeitActionResponse
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
|
||||
@@ -48,7 +51,9 @@ import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.requirements.DefeatRequirement
|
||||
@@ -137,6 +142,9 @@ open class PokemonBattle(
|
||||
val captureActions = mutableListOf<BattleCaptureAction>()
|
||||
|
||||
val majorBattleActions = hashMapOf<UUID, BattleMessage>()
|
||||
|
||||
/** Monotonically increasing counter to track faint ordering within this battle. */
|
||||
var faintCounter = 0
|
||||
val minorBattleActions = hashMapOf<UUID, BattleMessage>()
|
||||
val contextManager = ContextManager()
|
||||
|
||||
@@ -244,12 +252,23 @@ open class PokemonBattle(
|
||||
|
||||
fun end() {
|
||||
ended = true
|
||||
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
|
||||
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
|
||||
this.actors.forEach { actor ->
|
||||
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
|
||||
actor.getSide().getOppositeSide().actors.forEach { opponent ->
|
||||
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
|
||||
val opponentPokemons = if (awardToFainted) opponent.pokemonList else opponent.pokemonList.filter { it.health > 0 }
|
||||
faintedPokemons.forEach { faintedPokemon ->
|
||||
for (opponentPokemon in opponentNonFaintedPokemons) {
|
||||
for (opponentPokemon in opponentPokemons) {
|
||||
// Faint-order check: if awarding XP to fainted pokemon, only award for enemies that fainted before this pokemon did
|
||||
if (awardToFainted) {
|
||||
val enemyFaintedAt = faintedPokemon.faintedAt ?: continue
|
||||
val opponentFaintedAt = opponentPokemon.faintedAt
|
||||
if (opponentFaintedAt != null && opponentFaintedAt <= enemyFaintedAt) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
|
||||
val pokemon = opponentPokemon.effectedPokemon
|
||||
if (facedFainted) {
|
||||
@@ -270,7 +289,8 @@ open class PokemonBattle(
|
||||
else -> continue
|
||||
}
|
||||
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
|
||||
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
|
||||
val enemyTeamWiped = actor.pokemonList.all { it.health <= 0 }
|
||||
if (experience > 0 && (enemyTeamWiped || awardOnLoss)) {
|
||||
opponent.awardExperience(opponentPokemon, experience)
|
||||
}
|
||||
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
|
||||
@@ -505,6 +525,7 @@ open class PokemonBattle(
|
||||
|
||||
fun checkForInputDispatch() {
|
||||
if (checkForfeit()) return // ignore actors that are still choosing, their choices don't matter anymore
|
||||
if (checkFleeAttempt()) return
|
||||
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none { it.mustChoose })
|
||||
if (readyToInput && captureActions.isEmpty()) {
|
||||
actors.filter { it.responses.isNotEmpty() }.forEach { it.writeShowdownResponse() }
|
||||
@@ -512,6 +533,20 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFleeAttempt(): Boolean {
|
||||
val runner = actors.find { it.responses.any { it is FleeAttemptActionResponse } } as? PlayerBattleActor ?: return false
|
||||
CobblemonEvents.BATTLE_FLEE_ATTEMPT.post(BattleFleeAttemptEvent(this, runner))
|
||||
runner.responses.clear()
|
||||
|
||||
runner.mustChoose = true
|
||||
runner.request?.let { runner.sendUpdate(BattleQueueRequestPacket(it)) }
|
||||
runner.sendUpdate(BattleMakeChoicePacket())
|
||||
|
||||
runner.entity?.sendSystemMessage(battleLang("run_prompt").white())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Forces Showdown to end the battle when a [BattleActor] chooses to forfeit. */
|
||||
private fun checkForfeit(): Boolean {
|
||||
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.events
|
||||
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
|
||||
@@ -110,6 +111,9 @@ object CobblemonEvents {
|
||||
@JvmField
|
||||
val BATTLE_STARTED_POST = EventObservable<BattleStartedEvent.Post>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_FLEE_ATTEMPT = EventObservable<BattleFleeAttemptEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_FLED = EventObservable<BattleFledEvent>()
|
||||
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.api.events.battles
|
||||
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
|
||||
/**
|
||||
* Event fired when a [PokemonBattle] is fled by a [PlayerBattleActor].
|
||||
*
|
||||
* @author saultsc
|
||||
* @since March 31th 2026
|
||||
*/
|
||||
|
||||
data class BattleFleeAttemptEvent(
|
||||
override val battle: PokemonBattle,
|
||||
val player: PlayerBattleActor
|
||||
) : BattleEvent {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"battle" to battle.struct,
|
||||
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
|
||||
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
|
||||
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
|
||||
)
|
||||
}
|
||||
@@ -1842,6 +1842,30 @@ object MoLangFunctions {
|
||||
map.put("remove_cosmetic_item") { _ ->
|
||||
pokemon.removeCosmeticItem()
|
||||
}
|
||||
map.put("marks") { _ -> pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
|
||||
map.put("has_mark") { params ->
|
||||
var hasMark = false
|
||||
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) {
|
||||
hasMark = pokemon.marks.contains(mark)
|
||||
}
|
||||
|
||||
DoubleValue(hasMark)
|
||||
}
|
||||
map.put("remove_marks") { params ->
|
||||
var removedMark = false
|
||||
for (param in params.params) {
|
||||
val identifier = param.asString().asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) {
|
||||
pokemon.exchangeMark(mark, false)
|
||||
removedMark = true
|
||||
}
|
||||
}
|
||||
|
||||
DoubleValue(removedMark)
|
||||
}
|
||||
map.put("add_marks") { params ->
|
||||
var appliedMark = false
|
||||
for (param in params.params) {
|
||||
@@ -2035,9 +2059,13 @@ object MoLangFunctions {
|
||||
}
|
||||
}
|
||||
map.put("is_sprinting") {
|
||||
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
|
||||
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
|
||||
return@ifRidingAvailableSupply moValue.functions.get("sprinting")?.apply(MoParams.EMPTY) as? MoValue ?: DoubleValue.ZERO
|
||||
if (pokemonEntity.hasControllingPassenger()) { // If its being ridden then the riding state sets the animation
|
||||
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
|
||||
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
|
||||
return@ifRidingAvailableSupply moValue.functions.get("sprinting")?.apply(MoParams.EMPTY) as? MoValue ?: DoubleValue.ZERO
|
||||
}
|
||||
} else {
|
||||
pokemonEntity.isPokemonSprinting
|
||||
}
|
||||
}
|
||||
map.put("is_drifting") {
|
||||
|
||||
@@ -289,11 +289,11 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
|
||||
createDefault("ancient_great_ball", MultiplierModifier(1.5F), ancient = true)
|
||||
createDefault("ancient_ultra_ball", MultiplierModifier(2F), ancient = true)
|
||||
createDefault("ancient_heavy_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_leaden_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_gigaton_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_leaden_ball", MultiplierModifier(1.5F), throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_gigaton_ball", MultiplierModifier(2F), throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_feather_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_wing_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_jet_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_wing_ball", MultiplierModifier(1.5F), throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_jet_ball", MultiplierModifier(2F), throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_origin_ball", GuaranteedModifier(), ancient = true)
|
||||
// Luxury ball effect, low priority as it must be triggered before soothe bell as of gen 4
|
||||
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOW) { event ->
|
||||
|
||||
@@ -314,11 +314,11 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
val result = mutableMapOf<String, String>()
|
||||
this.species.forEach {species ->
|
||||
val baseSpecies = ShowdownSpecies(species, null)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name, "")] = this.gson.toJson(baseSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name.lowercase(), "")] = this.gson.toJson(baseSpecies)
|
||||
species.forms.forEach { form ->
|
||||
if (form != species.standardForm) {
|
||||
val formSpecies = ShowdownSpecies(species, form)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name, "")] = this.gson.toJson(formSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name.lowercase(), "")] = this.gson.toJson(formSpecies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -54,8 +54,6 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
fun update() {
|
||||
val activeRide = ride.hasControllingPassenger()
|
||||
|
||||
if (!activeRide) return
|
||||
|
||||
var currRot = Vec3.ZERO
|
||||
var angDelta = Vec3.ZERO
|
||||
|
||||
@@ -153,14 +151,16 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
/******************************************************
|
||||
* Update springs
|
||||
*****************************************************/
|
||||
val driver = ride.controllingPassenger
|
||||
val vertInput = when {
|
||||
driver != null && driver.jumping-> 1.0
|
||||
driver != null && driver.isShiftKeyDown -> -1.0
|
||||
else -> 0.0
|
||||
if (activeRide) {
|
||||
val driver = ride.controllingPassenger
|
||||
val vertInput = when {
|
||||
driver != null && driver.jumping-> 1.0
|
||||
driver != null && driver.isShiftKeyDown -> -1.0
|
||||
else -> 0.0
|
||||
}
|
||||
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
|
||||
driverInputSpring.update(input, stiffness * 2, damping)
|
||||
}
|
||||
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
|
||||
driverInputSpring.update(input, stiffness * 2, damping)
|
||||
rotDeltaSpring.update(angDelta, stiffness, damping)
|
||||
velocitySpring.update(currentVelocity, stiffness, damping)
|
||||
rotSpring.update(currRot, stiffness, damping)
|
||||
|
||||
@@ -94,7 +94,8 @@ enum class ShowdownActionResponseType(val loader: (RegistryFriendlyByteBuf) -> S
|
||||
PASS({ PassActionResponse }),
|
||||
SHIFT({ ShiftActionResponse()}),
|
||||
HEAL_ITEM({ HealItemActionResponse("potion") }),
|
||||
FORFEIT({ ForfeitActionResponse() });
|
||||
FORFEIT({ ForfeitActionResponse() }),
|
||||
FLEE_ATTEMPT({ FleeAttemptActionResponse() }),
|
||||
}
|
||||
|
||||
abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
|
||||
@@ -280,6 +281,11 @@ class ForfeitActionResponse : ShowdownActionResponse(ShowdownActionResponseType.
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "forfeit"
|
||||
}
|
||||
|
||||
class FleeAttemptActionResponse : ShowdownActionResponse(ShowdownActionResponseType.FLEE_ATTEMPT) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "flee-attempt"
|
||||
}
|
||||
|
||||
class ShowdownMoveset {
|
||||
lateinit var moves: List<InBattleMove>
|
||||
var trapped = false
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import net.minecraft.CrashReport
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@@ -37,8 +38,15 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
|
||||
override fun run() {
|
||||
LOGGER.info("Starting showdown service...")
|
||||
ShowdownService.service.openConnection()
|
||||
LOGGER.info("Showdown has been started!")
|
||||
this.latch.countDown()
|
||||
try {
|
||||
ShowdownService.service.openConnection()
|
||||
LOGGER.info("Showdown has been started!")
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error("Failed to start showdown service!", e)
|
||||
} finally {
|
||||
this.latch.countDown()
|
||||
throw RuntimeException("Showdown service failed to initialize...")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
@@ -51,6 +51,7 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
|
||||
|
||||
preamble.thenAccept {
|
||||
faintingPokemon.effectedPokemon.currentHealth = 0
|
||||
faintingPokemon.faintedAt = battle.faintCounter++
|
||||
faintingPokemon.sendUpdate()
|
||||
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context, pnx))
|
||||
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
|
||||
|
||||
@@ -110,6 +110,9 @@ open class BattlePokemon(
|
||||
/** A set of all the BattlePokemon that they faced during the battle (for exp purposes) */
|
||||
val facedOpponents = mutableSetOf<BattlePokemon>()
|
||||
|
||||
/** The faint sequence number within the battle, or null if this pokemon has not fainted. */
|
||||
var faintedAt: Int? = null
|
||||
|
||||
/**
|
||||
* The [HeldItemManager] backing this [BattlePokemon].
|
||||
*/
|
||||
|
||||
@@ -151,12 +151,14 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
|
||||
override fun placeLiquid(level: LevelAccessor, pos: BlockPos, state: BlockState, fluidState: FluidState): Boolean {
|
||||
val blockEntity = level.getBlockEntity(pos)
|
||||
if (fluidState.type === Fluids.WATER && blockEntity is CampfireBlockEntity) {
|
||||
if (!level.isClientSide) {
|
||||
removePotItem(blockEntity, state, level as Level, pos, null, true)
|
||||
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
|
||||
}
|
||||
if (level is Level) {
|
||||
if (!level.isClientSide) {
|
||||
removePotItem(blockEntity, state, level, pos, null, true)
|
||||
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
|
||||
}
|
||||
|
||||
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level as Level, pos, false, true)
|
||||
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level, pos, false, true)
|
||||
}
|
||||
|
||||
level.scheduleTick(pos, fluidState.type, fluidState.type.getTickDelay(level))
|
||||
return true
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
@@ -65,7 +66,7 @@ class BattleGeneralActionSelection(
|
||||
|
||||
addOption(rank++, battleLang("ui.run"), BattleGUI.runResource) {
|
||||
CobblemonClient.battle?.minimised = true
|
||||
Minecraft.getInstance().player?.displayClientMessage(battleLang("run_prompt"), false)
|
||||
battleGUI.selectAction(request, FleeAttemptActionResponse())
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
} else {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.client.net.debug
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.render.debug.LineState
|
||||
import com.cobblemon.mod.common.client.render.debug.SteerForceRenderer
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object S2CUpdateObsAvoidHandler : ClientNetworkPacketHandler<ClientboundUpdateObsAvoidPacket> {
|
||||
override fun handle(packet: ClientboundUpdateObsAvoidPacket, client: Minecraft) {
|
||||
client.executeIfPossible {
|
||||
//ObjectAvoidanceRenderer.avoidanceLines[packet.entityId] = packet.lines
|
||||
val lineState = SteerForceRenderer.lineStates.getOrPut(packet.entityId) { LineState() }
|
||||
lineState.update(packet.lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.client.render.debug
|
||||
|
||||
import com.cobblemon.mod.common.api.net.Encodable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer
|
||||
import net.minecraft.client.Camera
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Data class currently used for storing line data for rendering in [SteerForceRenderer]
|
||||
*
|
||||
* @author Jackson
|
||||
* @since September 18th, 2025
|
||||
*/
|
||||
data class EntityDebugLine(
|
||||
val vector: Vec3, // Direction/magnitude in world space, relative to entity origin
|
||||
val r: Float,
|
||||
val g: Float,
|
||||
val b: Float,
|
||||
val a: Float,
|
||||
) : Encodable {
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeVec3(vector)
|
||||
buffer.writeFloat(r)
|
||||
buffer.writeFloat(g)
|
||||
buffer.writeFloat(b)
|
||||
buffer.writeFloat(a)
|
||||
}
|
||||
|
||||
companion object {
|
||||
operator fun invoke(vector: Vec3, r: Float = 1f, g: Float = 1f, b: Float = 1f, a: Float = 1f): EntityDebugLine {
|
||||
return EntityDebugLine(vector, r.coerceIn(0f,1f), g.coerceIn(0f,1f), b.coerceIn(0f,1f), a.coerceIn(0f,1f))
|
||||
}
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): EntityDebugLine {
|
||||
val vector = buffer.readVec3()
|
||||
return EntityDebugLine(vector, buffer.readFloat(), buffer.readFloat(), buffer.readFloat(), buffer.readFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LineState {
|
||||
/**
|
||||
* The comparison of the previous and current lines relies on the order being consistent
|
||||
*/
|
||||
var previousLines: List<EntityDebugLine> = emptyList()
|
||||
var currentLines: List<EntityDebugLine> = emptyList()
|
||||
var lastReceivedLineTick: Long? = null
|
||||
|
||||
fun tick() {
|
||||
previousLines = currentLines
|
||||
}
|
||||
|
||||
fun update(newLines: List<EntityDebugLine>) {
|
||||
lastReceivedLineTick = Minecraft.getInstance().level?.gameTime
|
||||
currentLines = newLines
|
||||
if (previousLines.isEmpty()) previousLines = newLines
|
||||
}
|
||||
|
||||
// entityPos: interpolated world position of the entity this tick
|
||||
fun draw(vc: VertexConsumer, pose: PoseStack.Pose, partialTick: Float, camera: Camera, entity: PokemonEntity, partialTicks: Float) {
|
||||
previousLines.zip(currentLines).forEach { (prev, curr) ->
|
||||
val iVec = prev.vector.lerp(curr.vector, partialTick.toDouble())
|
||||
val iR = prev.r.lerp(curr.r, partialTick)
|
||||
val iG = prev.g.lerp(curr.g, partialTick)
|
||||
val iB = prev.b.lerp(curr.b, partialTick)
|
||||
val iA = prev.a.lerp(curr.a, partialTick)
|
||||
|
||||
// Interpolated world position — same method used everywhere in the renderer
|
||||
val worldStart = Vec3(
|
||||
Mth.lerp(partialTicks.toDouble(), entity.xOld, entity.x),
|
||||
Mth.lerp(partialTicks.toDouble(), entity.yOld, entity.y),
|
||||
Mth.lerp(partialTicks.toDouble(), entity.zOld, entity.z)
|
||||
)
|
||||
val worldEnd = worldStart.add(iVec)
|
||||
|
||||
val relStart = worldStart.subtract(camera.position)
|
||||
val relEnd = worldEnd.subtract(camera.position)
|
||||
|
||||
// Normal is the normalized direction of the segment
|
||||
val dir = iVec.normalize()
|
||||
val nx = dir.x.toFloat()
|
||||
val ny = dir.y.toFloat()
|
||||
val nz = dir.z.toFloat()
|
||||
|
||||
vc.addVertex(pose.pose(), relStart.x.toFloat(), relStart.y.toFloat(), relStart.z.toFloat())
|
||||
.setColor(iR, iG, iB, iA)
|
||||
.setNormal(pose, nx, ny, nz)
|
||||
|
||||
vc.addVertex(pose.pose(), relEnd.x.toFloat(), relEnd.y.toFloat(), relEnd.z.toFloat())
|
||||
.setColor(iR, iG, iB, iA)
|
||||
.setNormal(pose, nx, ny, nz)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun reset() {
|
||||
previousLines = emptyList()
|
||||
currentLines = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Float.lerp(end: Float, t: Float): Float = this + (end - this) * t.coerceIn(0f, 1f)
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.client.render.debug
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.Camera
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
|
||||
object SteerForceRenderer {
|
||||
|
||||
// Lines to render attached to the uuid of entities.
|
||||
val lineStates = mutableMapOf<Int, LineState>()
|
||||
|
||||
fun registerTickHandler() {
|
||||
// Advance line States
|
||||
PlatformEvents.CLIENT_TICK_PRE.subscribe {
|
||||
lineStates.forEach { uuid, lineState ->
|
||||
lineState.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun render(poseStack: PoseStack, multiBufferSource: MultiBufferSource, partialTicks: Float, camera: Camera) {
|
||||
val matrix = poseStack.last().pose()
|
||||
val level = Minecraft.getInstance().level ?: return
|
||||
|
||||
val currTick = level.gameTime
|
||||
val ticksUntilStale = 3
|
||||
|
||||
val keysToRemove = lineStates.keys.filter { entityId ->
|
||||
val entity = level.getEntity(entityId) as? PokemonEntity
|
||||
val lastRecTick = lineStates[entityId]?.lastReceivedLineTick
|
||||
entity == null || (lastRecTick != null && (currTick - lastRecTick) > ticksUntilStale)
|
||||
}
|
||||
keysToRemove.forEach { lineStates.remove(it) }
|
||||
|
||||
// One buffer for all independent line segments
|
||||
val lineBuffer = multiBufferSource.getBuffer(RenderType.lines())
|
||||
|
||||
lineStates.forEach { entityId, lineState ->
|
||||
val entity = level.getEntity(entityId) as? PokemonEntity ?: return@forEach
|
||||
lineState.draw(lineBuffer, poseStack.last(), partialTicks, camera, entity, partialTicks)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+2
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BiWingedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
|
||||
@@ -198,6 +199,7 @@ class PidgeottoModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink, flyQuirk1, flyQuirk2),
|
||||
animations = arrayOf(
|
||||
PitchTiltAnimation(bone = getPart("body"), maxChangePerTick = 1F),
|
||||
singleBoneLook(),
|
||||
bedrock("pidgeotto", "air_fly")
|
||||
)
|
||||
|
||||
@@ -146,6 +146,12 @@ class CobblemonConfig {
|
||||
@CobblemonConfigField(Category.Battles, lang = "experience_share_multiplier", SERVER)
|
||||
var experienceShareMultiplier = .5
|
||||
|
||||
@CobblemonConfigField(Category.Battles, lang = "award_experience_to_fainted_pokemon", SERVER)
|
||||
var awardExperienceToFaintedPokemon = false
|
||||
|
||||
@CobblemonConfigField(Category.Battles, lang = "award_experience_on_battle_loss", SERVER)
|
||||
var awardExperienceOnBattleLoss = false
|
||||
|
||||
@CobblemonConfigField(Category.Battles, lang = "lucky_egg_multiplier", SERVER)
|
||||
var luckyEggMultiplier = 1.5
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.entity.ai
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.ai.OmniPathNodeMaker
|
||||
import com.cobblemon.mod.common.util.deleteNode
|
||||
import com.cobblemon.mod.common.util.getWaterAndLavaIn
|
||||
@@ -35,6 +34,7 @@ import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* A navigator designed to work with the [OmniPathNodeMaker], allowing a path that can cross land, water, and air
|
||||
@@ -49,6 +49,8 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
|
||||
var cachedCurrentNode: Node? = null
|
||||
var currentNodeDistance = 0F
|
||||
var segmentIndex: Int = 0
|
||||
var pathFollowTarget: Vec3? = null
|
||||
|
||||
data class NavigationContext(
|
||||
val onHit: (damage: DamageSource) -> Unit = {},
|
||||
@@ -160,6 +162,65 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
}
|
||||
|
||||
doStuckDetection(vec3d)
|
||||
|
||||
// Gather the steer target from the segment pathing
|
||||
// TODO: this shouldn't live here
|
||||
val currentPath = path ?: return
|
||||
if (!currentPath.isDone) {
|
||||
segmentIndex = segmentIndex.coerceIn(0, max(currentPath.nodeCount - 2, 0))
|
||||
pathFollowTarget = getPathSteerTarget(currentPath, mob)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a steering target for the current path
|
||||
* This video explains it well: https://www.youtube.com/watch?v=rlZYT-uvmGQ
|
||||
* Uses Reynolds path following for velocity steered entities
|
||||
*/
|
||||
fun getPathSteerTarget(
|
||||
path: Path,
|
||||
mob: Mob
|
||||
): Vec3 {
|
||||
// If this is the final segment then just seek towards the end
|
||||
if (segmentIndex >= path.nodeCount - 1) return path.getNode(path.nodeCount - 1).asVec3().add(0.5, 0.0, 0.5)
|
||||
|
||||
// Determine future position to use in steering calculations
|
||||
val lookAheadDist = 1.0 //TODO: Scale to look ahead a certain amount of ticks probably
|
||||
val futurePos = mob.position().add(mob.deltaMovement.normalize().scale(lookAheadDist))
|
||||
|
||||
// Gather the current segment points
|
||||
var pointS1 = path.getNode(segmentIndex).asVec3().add(0.5, 0.0, 0.5) // Add the 0.5's to get the blockface center as blockpos is on a corner
|
||||
var pointS2 = path.getNode(segmentIndex + 1).asVec3().add(0.5, 0.0, 0.5)
|
||||
|
||||
// Project the future point onto the current segment
|
||||
var vecA = futurePos.subtract(pointS1)
|
||||
var vecB = pointS2.subtract(pointS1)
|
||||
var normB = vecB.normalize()
|
||||
val lenB = vecB.length()
|
||||
var projAB = max(vecA.dot(normB), 0.0) // make at least 0 as negative means we are projecting behind the segment which we don't want
|
||||
|
||||
// If the projection goes off the front of the segment then advance to the next segment and recalculate the projection
|
||||
if (projAB > lenB && segmentIndex < path.nodeCount - 2) {
|
||||
segmentIndex++
|
||||
pointS1 = path.getNode(segmentIndex).asVec3().add(0.5, 0.0, 0.5)
|
||||
pointS2 = path.getNode(segmentIndex + 1).asVec3().add(0.5, 0.0, 0.5)
|
||||
vecA = futurePos.subtract(pointS1)
|
||||
vecB = pointS2.subtract(pointS1)
|
||||
normB = vecB.normalize()
|
||||
projAB = max(vecA.dot(normB), 0.0)
|
||||
}
|
||||
|
||||
// Get the projected point on the segment
|
||||
val projPoint = pointS1.add(normB.scale(projAB ))
|
||||
|
||||
// Determine if the future position is too far from the path by getting its distance from its projected position on the current path segment
|
||||
val futureDistFromPath = projPoint.distanceTo(futurePos)
|
||||
val acceptableDist = mob.bbWidth / 3.0 //TODO: figure out what this really needs to be
|
||||
|
||||
val pushForward = 0.1 // Advance the projected point along the segment to avoid going backwards
|
||||
val seekPoint = if (futureDistFromPath < acceptableDist) futurePos.add(normB.scale(pushForward)) else projPoint.add(normB.scale(pushForward))
|
||||
|
||||
return seekPoint
|
||||
}
|
||||
|
||||
fun isAirborne(world: Level, pos: BlockPos) =
|
||||
@@ -223,6 +284,8 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
|
||||
fun moveTo(x: Double, y: Double, z: Double, speed: Double = 1.0, navigationContext: NavigationContext) {
|
||||
this.navigationContext = navigationContext
|
||||
segmentIndex = 0
|
||||
pathFollowTarget = null
|
||||
this.moveTo(x, y, z, speed)
|
||||
}
|
||||
|
||||
@@ -277,6 +340,8 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
}
|
||||
|
||||
override fun moveTo(path: Path?, speed: Double): Boolean {
|
||||
segmentIndex = 0
|
||||
pathFollowTarget = null
|
||||
if (path != null && path.nodeCount > 0) {
|
||||
val node = path.getNode(0)!!
|
||||
// pokemonEntity.discard()
|
||||
@@ -399,6 +464,8 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
this.cachedCurrentNode = null
|
||||
path = null
|
||||
nodeEvaluator.done()
|
||||
segmentIndex = 0
|
||||
pathFollowTarget = null
|
||||
// In case a path is cancelled instead of completed, check if we should stop flying
|
||||
if (pather.couldStopFlying() && !isAirborne(entity.level(), entity.blockPosition())) {
|
||||
pather.setFlying(false)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.entity.ai.steering
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToTrackingPlayers
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
// Runs a list of SteerRules in priority order, consuming a force budget.
|
||||
// High priority rules get their full contribution first. Lower priority
|
||||
// rules only get what budget remains.
|
||||
class SteerArbitrator(
|
||||
val entity: PokemonEntity,
|
||||
val maxForce: Double,
|
||||
val rules: MutableList<SteerRule> = mutableListOf()
|
||||
) {
|
||||
|
||||
private var sortedRules: List<SteerRule> = emptyList()
|
||||
private var dirty = true
|
||||
|
||||
fun addRule(rule: SteerRule) { rules.add(rule); dirty = true }
|
||||
fun removeRule(rule: SteerRule) { rules.remove(rule); dirty = true }
|
||||
fun clearRules() { rules.clear(); dirty = true }
|
||||
|
||||
fun calculate(debug: Boolean = false): Vec3 {
|
||||
if (dirty) {
|
||||
sortedRules = rules.sortedByDescending { it.priority }
|
||||
dirty = false
|
||||
}
|
||||
|
||||
var remaining = maxForce
|
||||
var result = Vec3.ZERO
|
||||
val debugLines = if (debug) mutableListOf<EntityDebugLine>() else null
|
||||
|
||||
for (rule in sortedRules) {
|
||||
if (remaining <= 0.0) break
|
||||
|
||||
val force = rule.calculate() ?: continue
|
||||
val scaled = force.scale(rule.weight.toDouble())
|
||||
val len = scaled.length()
|
||||
if (len <= 0.0) continue
|
||||
|
||||
val contribution = if (len <= remaining) {
|
||||
remaining -= len
|
||||
scaled
|
||||
} else {
|
||||
val partial = remaining
|
||||
remaining = 0.0
|
||||
scaled.normalize().scale(partial)
|
||||
}
|
||||
|
||||
result = result.add(contribution)
|
||||
|
||||
debugLines?.let {
|
||||
val (r, g, b) = rule.debugColor
|
||||
it.add(EntityDebugLine(contribution.scale(50.0), r, g, b, 1.0f))
|
||||
}
|
||||
}
|
||||
|
||||
debugLines?.let {
|
||||
if (result.lengthSqr() > 0.0)
|
||||
it.add(EntityDebugLine(result.scale(50.0), 1f, 1f, 1f, 1.5f))
|
||||
if (it.isNotEmpty())
|
||||
sendPacketToTrackingPlayers(entity, ClientboundUpdateObsAvoidPacket(entity.id, it))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.entity.ai.steering
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
// A per-tick steering force contributor evaluated by SteerArbitrator.
|
||||
// Return null from calculate() if the rule has nothing to say this tick.
|
||||
interface SteerRule {
|
||||
|
||||
val entity: PokemonEntity
|
||||
|
||||
// Higher = evaluated first, consumes force budget before lower priority rules.
|
||||
// 90-100: obstacle avoidance
|
||||
// 60-89: threat / boundary
|
||||
// 30-59: social (separation, alignment, cohesion)
|
||||
// 0-29: goal / wander
|
||||
val priority: Int
|
||||
|
||||
// Multiplied against the rule output before budget accounting.
|
||||
val weight: Float
|
||||
|
||||
// RGB 0-1 for the debug line drawn by SteerArbitrator.
|
||||
val debugColor: Triple<Float, Float, Float> get() = Triple(1f, 1f, 1f)
|
||||
|
||||
fun calculate(): Vec3?
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.math.cos
|
||||
|
||||
// Forward AABB projection obstacle avoidance. Stateful — one instance per entity.
|
||||
class ObstacleAvoidRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 100,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(1f, 0f, 0f),
|
||||
val maxForce: Double = 0.02,
|
||||
val lookAheadTicks: Int = 10,
|
||||
val smoothingAlpha: Double = 0.25
|
||||
) : SteerRule {
|
||||
|
||||
private var prevForce: Vec3 = Vec3.ZERO
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val topSpeed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
|
||||
val forwardVec = if (entity.deltaMovement.horizontalDistanceSqr() < 0.001) {
|
||||
val yaw = Math.toRadians(entity.yRot.toDouble())
|
||||
Vec3(-sin(yaw), 0.0, cos(yaw))
|
||||
} else {
|
||||
Vec3(entity.deltaMovement.x, 0.0, entity.deltaMovement.z).normalize()
|
||||
}
|
||||
|
||||
val lookAheadDist = max(entity.deltaMovement.length() * lookAheadTicks, entity.bbWidth / 2.0)
|
||||
val collBox = entity.boundingBox.move(forwardVec.scale(lookAheadDist))
|
||||
val shapes = entity.level().getBlockCollisions(entity, collBox)
|
||||
|
||||
val nearestShape = shapes.minByOrNull { shape ->
|
||||
shape.bounds().center.distanceToSqr(entity.position())
|
||||
}
|
||||
|
||||
if (nearestShape == null) {
|
||||
prevForce = prevForce.lerp(Vec3.ZERO, smoothingAlpha)
|
||||
return if (prevForce.lengthSqr() < 1e-6) null else prevForce
|
||||
}
|
||||
|
||||
val obsCenter = nearestShape.bounds().center
|
||||
val toObs = obsCenter.subtract(entity.position())
|
||||
val projVec = forwardVec.scale(toObs.dot(forwardVec))
|
||||
val lateralComponent = toObs.subtract(projVec)
|
||||
|
||||
val avoidForce = if (lateralComponent.lengthSqr() < 0.001) {
|
||||
// Dead ahead — turn right
|
||||
val rightVec = forwardVec.cross(Vec3(0.0, 1.0, 0.0))
|
||||
entity.steerToTarget(entity.position().add(rightVec), topSpeed, maxForce, horzOnly = true)
|
||||
} else {
|
||||
var avoidanceVec = Vec3(lateralComponent.x * -1.0, 0.0, lateralComponent.z * -1.0)
|
||||
val avoidStrength = sqrt(1.0 - (avoidanceVec.length() / (entity.bbWidth / 2.0 + 0.5)).coerceIn(0.0, 0.7))
|
||||
val avoidancePoint = entity.position().add(avoidanceVec)
|
||||
entity.steerToTarget(avoidancePoint, topSpeed, maxForce * avoidStrength, horzOnly = true)
|
||||
}
|
||||
|
||||
prevForce = prevForce.lerp(avoidForce, smoothingAlpha)
|
||||
return prevForce
|
||||
}
|
||||
}
|
||||
@@ -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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
// Steers toward a target position. Reads speed from PokemonMoveControl.steerSpeed.
|
||||
// Return null from targetProvider to deactivate for a tick.
|
||||
class SeekRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 10,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(0f, 1f, 1f),
|
||||
val maxForce: Double,
|
||||
val horzOnly: Boolean = false,
|
||||
val arrivalRange: Double? = null,
|
||||
val targetProvider: (PokemonEntity) -> Vec3?
|
||||
) : SteerRule {
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val target = targetProvider(entity) ?: return null
|
||||
val speed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
return entity.steerToTarget(target, speed, maxForce, arrivalRange, horzOnly)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.sin
|
||||
|
||||
// Steers toward a point directly ahead of the entity's current velocity/yaw.
|
||||
// Useful as a baseline test rule.
|
||||
class WalkForwardRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 10,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(0f, 1f, 0f),
|
||||
val maxForce: Double = 0.02
|
||||
) : SteerRule {
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val speed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
|
||||
val forwardVec = if (entity.deltaMovement.horizontalDistanceSqr() < 0.001) {
|
||||
val yaw = Math.toRadians(entity.yRot.toDouble())
|
||||
Vec3(-sin(yaw), 0.0, cos(yaw))
|
||||
} else {
|
||||
Vec3(entity.deltaMovement.x, 0.0, entity.deltaMovement.z).normalize()
|
||||
}
|
||||
|
||||
val pointAhead = entity.position().add(forwardVec)
|
||||
return entity.steerToTarget(pointAhead, speed, maxForce, horzOnly = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cos(d: Double) = kotlin.math.cos(d)
|
||||
private fun sin(d: Double) = kotlin.math.sin(d)
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
// Reynolds wander — projects a sphere ahead and offsets a point on it each tick.
|
||||
// Stateful: holds current wander angles, one instance per entity.
|
||||
// https://slsdo.github.io/steering-behaviors/
|
||||
class WanderRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 10,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(1f, 0.5f, 0f),
|
||||
val maxForce: Double = 0.02,
|
||||
val wanderDisp: Double = 15.0,
|
||||
val centerDistance: Double = 4.0,
|
||||
val wanderRadius: Double = 4.0,
|
||||
val horzOnly: Boolean = false //TODO: all of these should be checks if flying or walking
|
||||
) : SteerRule {
|
||||
|
||||
var currHorzWander = 0.0
|
||||
var currVertWander = 0.0
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val speed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
|
||||
val normVec = if (entity.deltaMovement.lengthSqr() > 0.0001) {
|
||||
entity.lookAngle.normalize()
|
||||
} else {
|
||||
Vec3(
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0)
|
||||
).normalize()
|
||||
}
|
||||
|
||||
var wanderOff = normVec
|
||||
|
||||
// Clamp vertical wander when getting too steep
|
||||
val currPitchS = sin(Mth.atan2(wanderOff.y, wanderOff.horizontalDistance()))
|
||||
if (currPitchS >= 0.5f || currPitchS <= -0.5f) currVertWander = 0.0
|
||||
|
||||
wanderOff = wanderOff.xRot(Math.toRadians(currVertWander).toFloat())
|
||||
wanderOff = wanderOff.yRot(Math.toRadians(currHorzWander).toFloat())
|
||||
|
||||
val wanderCenter = entity.position().add(normVec.scale(centerDistance))
|
||||
val wanderPoint = wanderCenter.add(wanderOff.scale(wanderRadius))
|
||||
|
||||
// Advance wander angles
|
||||
currVertWander = Mth.wrapDegrees(currVertWander + Random.nextDouble(-wanderDisp, wanderDisp))
|
||||
currHorzWander = Mth.wrapDegrees(currHorzWander + Random.nextDouble(-wanderDisp, wanderDisp))
|
||||
|
||||
val force = entity.steerToTarget(wanderPoint, speed, maxForce)
|
||||
return if (horzOnly) Vec3(force.x, 0.0, force.z) else force
|
||||
}
|
||||
}
|
||||
@@ -175,10 +175,7 @@ import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.*
|
||||
|
||||
@Suppress("unused")
|
||||
open class PokemonEntity(
|
||||
@@ -214,6 +211,7 @@ open class PokemonEntity(
|
||||
@JvmStatic var RIDE_BOOSTS = SynchedEntityData.defineId(PokemonEntity::class.java, RideBoostsDataSerializer)
|
||||
@JvmStatic var RIDE_STAMINA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
|
||||
@JvmStatic var SCALE_MODIFIER = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
|
||||
@JvmStatic val IS_SPRINTING = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BOOLEAN)
|
||||
|
||||
const val BATTLE_LOCK = "battle"
|
||||
const val EVOLUTION_LOCK = "evolving"
|
||||
@@ -293,6 +291,9 @@ open class PokemonEntity(
|
||||
var shownItem: ItemStack
|
||||
get() = entityData.get(SHOWN_HELD_ITEM)
|
||||
set(value) = entityData.set(SHOWN_HELD_ITEM, value)
|
||||
var isPokemonSprinting: Boolean
|
||||
get() = entityData.get(IS_SPRINTING)
|
||||
set(value) { entityData.set(IS_SPRINTING, value) }
|
||||
|
||||
var lastLightningBoltUUID: UUID? = null
|
||||
|
||||
@@ -461,6 +462,7 @@ open class PokemonEntity(
|
||||
builder.define(RIDE_BOOSTS, emptyMap())
|
||||
builder.define(RIDE_STAMINA, 1F)
|
||||
builder.define(SCALE_MODIFIER, 1F)
|
||||
builder.define(IS_SPRINTING, false)
|
||||
}
|
||||
|
||||
override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {
|
||||
@@ -623,8 +625,12 @@ open class PokemonEntity(
|
||||
isPokemonFlying = flyDist - flyDistO > 0.005F
|
||||
isPokemonWalking = walkDist - walkDistO > 0.005F
|
||||
|
||||
if (passengers.isNotEmpty() && level().isClientSide) {
|
||||
rideSoundManager.tick()
|
||||
// TODO: Figure out a better way to allow for this. Or maybe just always do the animations data accumulation its really not that bad
|
||||
if (this.pokemon.species.name.lowercase() == "charizard" || (passengers.isNotEmpty() && level().isClientSide)) {
|
||||
// This is so ugly why am I doing it like this... even if its for a test this is low
|
||||
if (passengers.isNotEmpty() && level().isClientSide) {
|
||||
rideSoundManager.tick()
|
||||
}
|
||||
ridingAnimationData.update()
|
||||
} else if (!passengers.isNotEmpty() && level().isClientSide) {
|
||||
rideSoundManager.stop()
|
||||
@@ -2151,7 +2157,9 @@ open class PokemonEntity(
|
||||
override fun stopSeenByPlayer(player: ServerPlayer) {
|
||||
if (this.ownerUUID == player.uuid && tethering == null) {
|
||||
// queuedToDespawn = true
|
||||
this.remove(RemovalReason.DISCARDED)
|
||||
if (this.removalReason != RemovalReason.DISCARDED) {
|
||||
this.remove(RemovalReason.DISCARDED)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToTrackingPlayers
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.control.MoveControl
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.level.ClipContext
|
||||
import net.minecraft.world.phys.HitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class BoidsMoveControl(val pokemonEntity: PokemonEntity, val target: LivingEntity) : MoveControl(pokemonEntity) {
|
||||
companion object {
|
||||
const val VERY_CLOSE = 2.500000277905201E-3
|
||||
const val MAX_SPEED = 0.3 / 2
|
||||
const val MAX_FORCE = 0.02
|
||||
const val PREDATOR_MAX_SPEED = 0.6
|
||||
// const val MAX_SPEED = 0.2
|
||||
// const val MAX_FORCE = 0.013
|
||||
}
|
||||
|
||||
// TODO: might need to hold its own velocity so minecraft doesn't do its nonsense to it?
|
||||
// Can handle collisions ourselves as well
|
||||
var speed: Double? = null
|
||||
var drawLines: MutableList<EntityDebugLine> = mutableListOf()
|
||||
|
||||
// wander vars
|
||||
var currHorzWander = 0.0
|
||||
var currVertWander = 0.0
|
||||
val wanderDisp = 15.0 // The degrees of change from tick to tick in wander angle
|
||||
|
||||
override fun tick() {
|
||||
// Clear since the info is sent and is now stale
|
||||
drawLines.clear()
|
||||
|
||||
// Steering constants
|
||||
val orbitRadius = 16.0
|
||||
val targetPos = target.position().add(0.0, 4.0, 0.0)
|
||||
val arrivalRange = 2.0 // in blocks
|
||||
|
||||
val species = pokemonEntity.pokemon.species.name
|
||||
// Set this to ensure the flying pose is active
|
||||
|
||||
// ignore all the friction stuff from outside
|
||||
speed?.let { pokemonEntity.setDeltaMovement(pokemonEntity.deltaMovement.normalize().scale(it)) }
|
||||
|
||||
// Constrain to rectangular boundary
|
||||
val cubeWidth = 16.0
|
||||
val cubeHeight = 16.0
|
||||
val cubeCenter = target.position().add(0.0, cubeHeight / 2.0, 0.0)
|
||||
//forceRectBoundary(cubeCenter, cubeWidth, cubeHeight)
|
||||
|
||||
// Apply boid forces
|
||||
|
||||
// Obstacle Avoidance
|
||||
val whiskerRange = 1.0
|
||||
val avoidanceAngle = 20.0 // x degrees to the left, right, up, and down
|
||||
//val obsAvoidForce = whisObsAvoid(whiskerRange, avoidanceAngle)
|
||||
|
||||
// if (obsAvoidForce != null) {
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(obsAvoidForce.scale(0.5))
|
||||
// }
|
||||
|
||||
// TODO: obviously don't hardcode each pokemon. Remove this after testing is finished.
|
||||
if (pokemonEntity.pokemon.species.name.lowercase() == "charizard") {
|
||||
pokemonEntity.setFlying(true)
|
||||
pokemonEntity.resetFallDistance()
|
||||
seekPrey(pokemonEntity)
|
||||
} else if (pokemonEntity.pokemon.species.name.lowercase() == "garchomp") {
|
||||
pokemonEntity.setFlying(false)
|
||||
val walkSpeed = pokemonEntity.runtime.resolveDouble(pokemonEntity.behaviour.moving.walk.walkSpeed) * 1.5
|
||||
val steerForce = pokemonEntity.steerToTarget(target.position(), walkSpeed, walkSpeed / 12)
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce.x, 0.0, steerForce.z)
|
||||
// speed?.let { this.mob.setZza(speed!!.toFloat()) }
|
||||
} else if (pokemonEntity.pokemon.species.name.lowercase() == "skiddo") {
|
||||
pokemonEntity.setFlying(false)
|
||||
//wander(pokemonEntity,1.0)
|
||||
// val steerForce = pokemonEntity.steerToTargetForce(target.position(), MAX_SPEED, MAX_FORCE)
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce.x, 0.0, steerForce.z)
|
||||
aviodPredators(pokemonEntity, includePlayer = true)
|
||||
boid(isFlyingOrSwimming = false)
|
||||
} else {
|
||||
pokemonEntity.setFlying(true)
|
||||
pokemonEntity.resetFallDistance()
|
||||
wander(pokemonEntity,0.8)
|
||||
boid()
|
||||
aviodPredators(pokemonEntity, includePlayer = true)
|
||||
|
||||
// Force towards ground
|
||||
|
||||
|
||||
forceRectBoundary(pokemonEntity, cubeCenter, cubeWidth, cubeHeight)
|
||||
}
|
||||
|
||||
// val avoidForce = avoidTarget(target.position(), MAX_SPEED, MAX_FORCE, 10.0)
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(avoidForce.scale(1.0))
|
||||
|
||||
// Apply steering force to current movement
|
||||
// val dm = pokemonEntity.deltaMovement
|
||||
// val w_pos = pokemonEntity.position().add(Vec3(dm.x, 0.0, dm.z))
|
||||
// val steerForce = steerToTargetForce(w_pos, MAX_SPEED, MAX_FORCE)
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce)
|
||||
|
||||
// Take note of speed again to avoid minecraft's friction logic
|
||||
speed = pokemonEntity.deltaMovement.length() * 0.98
|
||||
|
||||
// Test out what this setting of speed does.
|
||||
//speed?.let { this.mob.speed = speed!!.toFloat() }
|
||||
|
||||
// Limit upwards speed to be half of max speed. The boids kept bumping into the top of the box more so than birds
|
||||
// would. Honestly birds probably mostly want to move in the x,z plane anyhow
|
||||
// pokemonEntity.deltaMovement = Vec3(
|
||||
// pokemonEntity.deltaMovement.x,
|
||||
// min(pokemonEntity.deltaMovement.y, MAX_SPEED * 0.5),
|
||||
// pokemonEntity.deltaMovement.y
|
||||
// ).normalize().scale(speed!!)
|
||||
|
||||
pokemonEntity.orientEntityToTravel(0.4f)
|
||||
|
||||
if (false && drawLines.isNotEmpty()) { //TODO: Re-enable
|
||||
sendPacketToTrackingPlayers(pokemonEntity, ClientboundUpdateObsAvoidPacket(pokemonEntity.id, drawLines))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun aviodPredators(entity: PokemonEntity, includePlayer: Boolean = false) {
|
||||
val fearRadius = 5.0
|
||||
val brain = entity.brain
|
||||
val predators = brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).map { nearbyEntities ->
|
||||
nearbyEntities.filterIsInstance<PokemonEntity>().filter { pokemon ->
|
||||
pokemon.brain.activeNonCoreActivity.orElse(null) == CobblemonActivities.POKEMON_BOIDS &&
|
||||
pokemon.pokemon.species.name.lowercase() == "charizard" &&
|
||||
entity.position().subtract(pokemon.position()).length() <= fearRadius
|
||||
}
|
||||
}.orElse(emptyList())
|
||||
|
||||
var totalEvasionVec = Vec3.ZERO
|
||||
var predatorCount = predators.size
|
||||
|
||||
// If the player should be included in avoidance then add the player as a predator.
|
||||
if (includePlayer) {
|
||||
totalEvasionVec = totalEvasionVec.add(entity.avoidTarget(target.position(), MAX_SPEED * 1.2, MAX_FORCE * 2, fearRadius))
|
||||
predatorCount += 1
|
||||
}
|
||||
// Average evasion forces for all predators in the area and apply it to the prey entity
|
||||
if (predatorCount > 0) {
|
||||
for (predator in predators) {
|
||||
totalEvasionVec = totalEvasionVec.add(entity.avoidTarget(predator.position(), MAX_SPEED * 1.2, MAX_FORCE * 2, fearRadius))
|
||||
}
|
||||
totalEvasionVec = totalEvasionVec.scale(1.0 / predatorCount)
|
||||
if (entity.isFlying() || entity.isSwimming) {
|
||||
entity.deltaMovement = entity.deltaMovement.add(totalEvasionVec)
|
||||
} else {
|
||||
entity.deltaMovement = entity.deltaMovement.add(Vec3(totalEvasionVec.x, 0.0, totalEvasionVec.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************************************************
|
||||
Seek the closest prey and pursue
|
||||
*************************************************************************************************************/
|
||||
private fun seekPrey(predator: PokemonEntity) {
|
||||
val SeekRadius = 20.0
|
||||
val brain = predator.brain
|
||||
// Do calcs for head height as it looks better this way than a charizard flying crotch first at a group of pidgeys
|
||||
val kindaHeadHeight = predator.position()
|
||||
|
||||
val prey = brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).map { nearbyEntities ->
|
||||
nearbyEntities.filterIsInstance<PokemonEntity>().filter { pokemon ->
|
||||
pokemon.brain.activeNonCoreActivity.orElse(null) == CobblemonActivities.POKEMON_BOIDS &&
|
||||
(pokemon.pokemon.species.name.lowercase() == "pidgey" || pokemon.pokemon.species.name.lowercase() == "skiddo") &&
|
||||
kindaHeadHeight.subtract(pokemon.position()).length() <= SeekRadius
|
||||
}
|
||||
}.orElse(emptyList())
|
||||
|
||||
// Find the closest prey and seek towards it
|
||||
val nearestPrey = prey.minByOrNull { pokePrey ->
|
||||
kindaHeadHeight.subtract(pokePrey.position()).length()
|
||||
}
|
||||
nearestPrey?.let { prey ->
|
||||
|
||||
// Do the actual attack
|
||||
// TODO: make this not dumb
|
||||
if(kindaHeadHeight.subtract(nearestPrey.position()).length() < 0.5 + predator.bbWidth + nearestPrey.bbWidth) {
|
||||
//predator.doHurtTarget(nearestPrey)
|
||||
}
|
||||
|
||||
// Steer towards the target
|
||||
val steerForce = predator.steerToTarget(prey.position(), PREDATOR_MAX_SPEED, MAX_FORCE)
|
||||
predator.deltaMovement = predator.deltaMovement.add(steerForce)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************************************************
|
||||
Whisker Obstacle Avoidance
|
||||
This function ray casts in a cone in front of the entity
|
||||
*************************************************************************************************************/
|
||||
private fun whisObsAvoid(range: Double, lookWidth: Double): Vec3? {
|
||||
|
||||
// --- 1. Build the Orientation Matrix ---
|
||||
val forward = pokemonEntity.lookAngle.scale(range)
|
||||
// val tempUp = if (abs(forward.y) > 0.999) Vec3(0.0, 0.0, 1.0) else Vec3(0.0, 1.0, 0.0)
|
||||
// val right = forward.cross(tempUp).normalize()
|
||||
// val up = right.cross(forward).normalize()
|
||||
//
|
||||
// val orientation = Matrix3f()
|
||||
// .setColumn(0, Vector3f(right.x.toFloat(), right.y.toFloat(), right.z.toFloat()))
|
||||
// .setColumn(1, Vector3f(up.x.toFloat(), up.y.toFloat(), up.z.toFloat()))
|
||||
// .setColumn(2, Vector3f(forward.x.toFloat(), forward.y.toFloat(), forward.z.toFloat()))
|
||||
|
||||
// --- 2. Define Whiskers in Simple, Local Space ---
|
||||
val radLookWidth = lookWidth.toRadians().toFloat()
|
||||
// val localWhiskers: List<Vector3f> = listOf(
|
||||
// Vector3f(0f, 0f, 1f), // Forward
|
||||
//// Vector3f(sin(radLookWidth), 0f, cos(radLookWidth)), // Left
|
||||
//// Vector3f(-sin(radLookWidth), 0f, cos(radLookWidth)), // Right
|
||||
//// Vector3f(0f, sin(radLookWidth), cos(radLookWidth)), // Up
|
||||
//// Vector3f(0f, -sin(radLookWidth), cos(radLookWidth)) // Down
|
||||
// )
|
||||
//
|
||||
// // --- 3. Transform Local Whiskers to World Space using the Matrix ---
|
||||
// val whiskerOffsets = localWhiskers.map { localVec ->
|
||||
// orientation.transform(localVec, Vector3f()).toVec3d().scale(range) // Transform and store in a new vector
|
||||
// }
|
||||
|
||||
// Obtain all points to be raycasted to
|
||||
val scaledLookVec = pokemonEntity.deltaMovement.normalize().scale(range)
|
||||
val lookPoints: List<Vec3> = listOf(
|
||||
forward,
|
||||
// forward.yRot(radLookWidth),
|
||||
// forward.yRot(-radLookWidth),
|
||||
// forward.xRot(90.toRadians()),
|
||||
// forward.xRot((-90).toRadians()),
|
||||
)
|
||||
|
||||
|
||||
// iterate through each point and raycast to it starting from the entities pos (may need to be from somewhere else)
|
||||
// If a position is hit then add its avoidance force to the totalAvoidance forces.
|
||||
var totalAvoidance: Vec3? = null
|
||||
val level = pokemonEntity.level()
|
||||
val startPos = pokemonEntity.position()
|
||||
for (point in lookPoints) {
|
||||
val endPos = startPos.add(point)
|
||||
val hit = level.clip(
|
||||
ClipContext(
|
||||
startPos,
|
||||
endPos,
|
||||
ClipContext.Block.OUTLINE, // If this is being weird then go back to COLLIDER
|
||||
ClipContext.Fluid.ANY, // Birds should probably avoid fluid
|
||||
pokemonEntity
|
||||
)
|
||||
)
|
||||
|
||||
// Add the whisker to the debug lines to be drawn
|
||||
drawLines.add(EntityDebugLine(endPos, 1.0f, 0.0f, 0.0f))
|
||||
//visualizeRay(level, startPos, endPos, hit.type != HitResult.Type.MISS)
|
||||
|
||||
if (hit.type != HitResult.Type.MISS) {
|
||||
// Get the aviodance vector as the vector from the block detection point
|
||||
// to the pokemon.
|
||||
// Also inversely scale the size of the vector based on the distance the pokemon
|
||||
// is to the object. This means close obstacles are avoided more strongly
|
||||
val hitDist = hit.location.distanceTo(pokemonEntity.eyePosition)
|
||||
val avoidVec = startPos.subtract(endPos).normalize() //.scale(1.0 - (hitDist / range))
|
||||
|
||||
// Add avoid vec to total
|
||||
totalAvoidance = totalAvoidance?.add(avoidVec) ?: avoidVec
|
||||
}
|
||||
}
|
||||
|
||||
// // Scale all forces down to the max foce
|
||||
// if (totalAvoidance != null) {
|
||||
// // Cap the max total avoidance desire
|
||||
// if (totalAvoidance.length() > MAX_SPEED) totalAvoidance = totalAvoidance.normalize().scale(MAX_SPEED)
|
||||
// // Convert to Reynolds steering force
|
||||
// totalAvoidance = totalAvoidance.subtract(pokemonEntity.deltaMovement)
|
||||
// // Cap the max total avoidance force
|
||||
// if (totalAvoidance.length() > MAX_FORCE) totalAvoidance = totalAvoidance.normalize().scale(MAX_FORCE)
|
||||
// } // Is this next one even cleaner?
|
||||
|
||||
return totalAvoidance?.let { desiredVelocity ->
|
||||
// Cap the desired velocity (using lengthSqr is more efficient than length)
|
||||
val cappedVelocity = if (desiredVelocity.lengthSqr() > MAX_SPEED * MAX_SPEED) {
|
||||
desiredVelocity.normalize().scale(MAX_SPEED)
|
||||
} else {
|
||||
desiredVelocity
|
||||
}
|
||||
|
||||
// Convert to Reynolds steering force
|
||||
val steeringForce = cappedVelocity.subtract(pokemonEntity.deltaMovement)
|
||||
|
||||
// Cap the final steering force
|
||||
if (steeringForce.lengthSqr() > MAX_FORCE * MAX_FORCE) {
|
||||
steeringForce.normalize().scale(MAX_FORCE)
|
||||
} else {
|
||||
steeringForce
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun wander(entity: PokemonEntity, forceScale: Double = 1.0) {
|
||||
// Wander behaviour
|
||||
/*************************************************************************************************************
|
||||
* The wander behaviour is based of Reynolds wander behaviour which can be seen in the below link under wander
|
||||
* This version however is adapted to 3dimensions.
|
||||
* https://slsdo.github.io/steering-behaviors/
|
||||
*************************************************************************************************************/
|
||||
// Get curr steering positions in left/right and up/down offsets
|
||||
val normVec = if(entity.deltaMovement.lengthSqr() > 0.0001) {
|
||||
entity.lookAngle.normalize()
|
||||
} else {
|
||||
Vec3(
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0)
|
||||
).normalize()
|
||||
}
|
||||
|
||||
var wanderOff = normVec
|
||||
|
||||
val currPitchS = sin(Mth.atan2(wanderOff.y, wanderOff.horizontalDistance()))
|
||||
if (currPitchS >= 0.5f || currPitchS <= -0.5f) { //TODO: make this y limit class wide
|
||||
this.currVertWander = 0.0
|
||||
}
|
||||
wanderOff = wanderOff.xRot(this.currVertWander.toRadians())
|
||||
wanderOff = wanderOff.yRot(this.currHorzWander.toRadians())
|
||||
|
||||
// Base the center off the current direction of travel
|
||||
val centerDistance = 4.0 // How many blocks the wander 'sphere' is in front of the entity
|
||||
val wanderCenter = entity.position().add(normVec.scale(centerDistance))
|
||||
// Get the wander circle's radius
|
||||
val wanderRadius = 4.0
|
||||
|
||||
val wanderPoint = wanderCenter.add(wanderOff.scale(wanderRadius))
|
||||
|
||||
// change wander points
|
||||
val randVert = Random.nextDouble(-wanderDisp, wanderDisp)
|
||||
val randHorz = Random.nextDouble(-wanderDisp, wanderDisp)
|
||||
currVertWander = Mth.wrapDegrees(randVert + currVertWander)
|
||||
currHorzWander = Mth.wrapDegrees(randHorz + currHorzWander)
|
||||
|
||||
drawLines.add(EntityDebugLine(wanderPoint.subtract(pokemonEntity.position()), 1.0f, 0.0f, 0.0f, 1.0f)) // Add WanderPoint
|
||||
drawLines.add(EntityDebugLine(wanderCenter.subtract(pokemonEntity.position()), 0.0f, 1.0f, 0.0f, 1.0f)) // Add wander center
|
||||
val wanderForce = entity.steerToTarget(wanderPoint, MAX_SPEED, MAX_FORCE)
|
||||
if(pokemonEntity.isFlying() || pokemonEntity.isSwimming) {
|
||||
entity.deltaMovement = pokemonEntity.deltaMovement.add(wanderForce.scale(forceScale))
|
||||
} else {
|
||||
entity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(wanderForce.x, 0.0, wanderForce.z).scale(forceScale))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun boid(isFlyingOrSwimming: Boolean = true) {
|
||||
// TODO: maybe move this to a sensor that lives spins up on the boid activity creation
|
||||
// Get a list of other boid entities in this entity's vicinity
|
||||
val brain = pokemonEntity.brain
|
||||
val boids = brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).map { nearbyEntities ->
|
||||
nearbyEntities.filterIsInstance<PokemonEntity>().filter { pokemon ->
|
||||
pokemon.brain.activeNonCoreActivity.orElse(null) == CobblemonActivities.POKEMON_BOIDS
|
||||
}
|
||||
}.orElse(emptyList())
|
||||
|
||||
// separation
|
||||
val separationForce = separation(boids)?.scale(0.8)
|
||||
separationForce?.let { force ->
|
||||
if( isFlyingOrSwimming) {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
} else {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(force.x, 0.0, force.z))
|
||||
}
|
||||
}
|
||||
// alignment
|
||||
val alignmentForce = alignment(boids)?.scale(0.0)
|
||||
alignmentForce?.let { force ->
|
||||
if( isFlyingOrSwimming) {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
} else {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(force.x, 0.0, force.z))
|
||||
}
|
||||
}
|
||||
|
||||
// cohesion
|
||||
val cohesionForce = cohesion(boids)?.scale(0.6)
|
||||
cohesionForce?.let { force ->
|
||||
if( isFlyingOrSwimming) {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
} else {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(force.x, 0.0, force.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun separation(boids: List<PokemonEntity>): Vec3? {
|
||||
var seperationVec: Vec3? = null
|
||||
|
||||
// Gather the total separation vec of all close entities
|
||||
boids.forEach { boid ->
|
||||
val toTargetVec = pokemonEntity.position().subtract(boid.position())
|
||||
val desiredSeperation = boid.bbWidth * 2.0 // Base the seperation distance on the model width
|
||||
val currDist = toTargetVec.length()
|
||||
if (currDist < desiredSeperation) {
|
||||
if (seperationVec == null) seperationVec = Vec3.ZERO
|
||||
// Make the seperation force inversely proprotional to the serperation distance
|
||||
// Also, scale the force by the bbwidth of the model so
|
||||
seperationVec = seperationVec.add(toTargetVec.normalize().scale((1.0 - (currDist / desiredSeperation)) ))
|
||||
}
|
||||
}
|
||||
|
||||
if (seperationVec != null) {
|
||||
//if (seperationVec.length() > MAX_SPEED) seperationVec = seperationVec.normalize().scale(MAX_SPEED)
|
||||
seperationVec = seperationVec.normalize().scale(MAX_SPEED)
|
||||
seperationVec = seperationVec.subtract(pokemonEntity.deltaMovement)
|
||||
if (seperationVec.length() > MAX_FORCE) {
|
||||
seperationVec = seperationVec.normalize().scale(MAX_FORCE)
|
||||
}
|
||||
}
|
||||
return seperationVec
|
||||
}
|
||||
|
||||
private fun alignment(boids: List<PokemonEntity>): Vec3? {
|
||||
val neighborDist = 4.0
|
||||
var summedVel: Vec3? = null
|
||||
var alignmentVec: Vec3? = null
|
||||
var boidCount = 0
|
||||
|
||||
// Gather the average position of the boid
|
||||
boids.forEach { boid ->
|
||||
val vecToOther = boid.position().subtract(this.pokemonEntity.position())
|
||||
val distToOther = vecToOther.length()
|
||||
val widthOfViewInDeg = 180
|
||||
val isInSightLine = pokemonEntity.deltaMovement.normalize().dot(vecToOther) > cos((widthOfViewInDeg / 2.0).toRadians())
|
||||
|
||||
if (isInSightLine && distToOther < neighborDist) {
|
||||
if (summedVel == null) summedVel = Vec3.ZERO
|
||||
summedVel = summedVel.add(boid.deltaMovement.normalize())
|
||||
boidCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (summedVel != null) {
|
||||
// Get the average vel/alignment by dividing by the number of boids sampled
|
||||
val averageVel = summedVel.scale(1.0 / boidCount)
|
||||
|
||||
//alignmentVec = if (averageVel.length() > MAX_SPEED) averageVel.normalize().scale(MAX_SPEED) else averageVel
|
||||
alignmentVec = averageVel.normalize().scale(MAX_SPEED)
|
||||
alignmentVec = alignmentVec.subtract(pokemonEntity.deltaMovement)
|
||||
if (alignmentVec.length() > MAX_FORCE) {
|
||||
alignmentVec = alignmentVec.normalize().scale(MAX_FORCE)
|
||||
}
|
||||
}
|
||||
return alignmentVec
|
||||
}
|
||||
|
||||
private fun cohesion(boids: List<PokemonEntity>): Vec3? {
|
||||
//val neighborDist = 5.0
|
||||
var summedPos: Vec3? = null
|
||||
var cohesionVec: Vec3? = null
|
||||
var boidCount = 0
|
||||
val cohesionRadius = 8.0
|
||||
|
||||
// Gather the average position of the boid
|
||||
boids.forEach { boid ->
|
||||
val vecToOther = boid.position().subtract(this.pokemonEntity.position())
|
||||
val distToOther = vecToOther.length()
|
||||
val widthOfViewInDeg = 360
|
||||
val isInSightLine = pokemonEntity.deltaMovement.normalize().dot(vecToOther) > cos((widthOfViewInDeg / 2.0).toRadians())
|
||||
|
||||
if (isInSightLine && distToOther < cohesionRadius) { // Only use members that are within a certain radius and eyeline
|
||||
if (summedPos == null) summedPos = Vec3.ZERO
|
||||
summedPos = summedPos.add(boid.position())
|
||||
boidCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (summedPos != null) {
|
||||
// Get the average position by dividing by the number of boids sampled
|
||||
val averagePos = summedPos.scale(1.0 / boidCount)
|
||||
// Get the vector towards that average position and scale
|
||||
cohesionVec = averagePos.subtract(pokemonEntity.position())
|
||||
//if (cohesionVec.length() > MAX_SPEED) cohesionVec = cohesionVec.normalize().scale(MAX_SPEED)
|
||||
cohesionVec = cohesionVec.normalize().scale(MAX_SPEED)
|
||||
cohesionVec = cohesionVec.subtract(pokemonEntity.deltaMovement)
|
||||
if (cohesionVec.length() > MAX_FORCE) {
|
||||
cohesionVec = cohesionVec.normalize().scale(MAX_FORCE)
|
||||
}
|
||||
}
|
||||
return cohesionVec
|
||||
}
|
||||
|
||||
private fun forceRectBoundary(entity: PokemonEntity, basePos: Vec3, rectWidth: Double, rectHeight: Double) {
|
||||
val maxBoundaryForce = MAX_FORCE //* 2.0
|
||||
val correctiveForce = getRectCorrectiveForce(basePos, rectWidth, rectHeight)
|
||||
if (correctiveForce != null) {
|
||||
//val correctiveForce = steerToTargetForce(correctivePos, speedLimit, maxForce)
|
||||
var steer = correctiveForce.subtract(entity.deltaMovement)
|
||||
if (steer.length() > maxBoundaryForce) {
|
||||
steer = steer.normalize().scale(maxBoundaryForce)
|
||||
}
|
||||
entity.setDeltaMovement(entity.deltaMovement.add(steer))
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the desired force to correct the entity back into the rectangle area
|
||||
private fun getRectCorrectiveForce( basePos: Vec3, rectWidth: Double, rectHeight: Double): Vec3? {
|
||||
val entityPos = pokemonEntity.position()
|
||||
val posFromBase = entityPos.subtract(basePos)
|
||||
val cubeHalfWidth = rectWidth / 2.0 // Useful as the basePoint is in the middle of the cube
|
||||
val cubeHalfHeight = rectHeight / 2.0
|
||||
|
||||
// Check the three possible faces for exceeding the boundary (x, y, and z faces of cube)
|
||||
val xExceed = max(0.0, abs(posFromBase.x) - cubeHalfWidth)
|
||||
val yExceed = max(0.0, abs(posFromBase.y) - cubeHalfHeight)
|
||||
val zExceed = max(0.0, abs(posFromBase.z) - cubeHalfWidth)
|
||||
|
||||
return if ( xExceed == 0.0 && yExceed == 0.0 && zExceed == 0.0) {
|
||||
null
|
||||
} else {
|
||||
var desiredPos = pokemonEntity.position().add(pokemonEntity.deltaMovement) // Add delta movement so we aren't slowing this guy down
|
||||
if (xExceed != 0.0) desiredPos = Vec3( basePos.x, desiredPos.y, desiredPos.z)
|
||||
if (yExceed != 0.0) desiredPos = Vec3( desiredPos.x, basePos.y, desiredPos.z)
|
||||
if (zExceed != 0.0) desiredPos = Vec3( desiredPos.x, desiredPos.y, basePos.z)
|
||||
return desiredPos.subtract(pokemonEntity.position()).normalize().scale(MAX_SPEED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//val desiredScale = if (distToTarget > slowDownRadius) speedLimit else (distToTarget/slowDownRadius).pow(2) * speedLimit
|
||||
//desiredVec = desiredVec.normalize().scale(desiredScale)
|
||||
+322
-12
@@ -9,30 +9,47 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.ai
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToTrackingPlayers
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine.Companion.invoke
|
||||
import com.cobblemon.mod.common.entity.PlatformType
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.ai.OmniPathNavigation
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerArbitrator
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.ObstacleAvoidRule
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.SeekRule
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.WanderRule
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.WalkForwardRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import com.cobblemon.mod.common.util.getWaterAndLavaIn
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.tags.BlockTags
|
||||
import net.minecraft.tags.FluidTags
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.ai.control.MoveControl
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.random.Random
|
||||
|
||||
class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemonEntity) {
|
||||
companion object {
|
||||
@@ -50,6 +67,41 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
var bankUpwardsBlocksPerTick = 0F
|
||||
var bankRightDegreesPerTick = 1F
|
||||
|
||||
var obsCorrectDir = 0F
|
||||
var drawLines: MutableList<EntityDebugLine> = mutableListOf()
|
||||
var prevForce: Vec3? = null
|
||||
|
||||
var steerSpeed = this.gatherTopSpeed(pokemonEntity)
|
||||
private val steerArbitrator by lazy {
|
||||
val steerArbitrator = SteerArbitrator(
|
||||
entity = pokemonEntity,
|
||||
maxForce = 0.02,
|
||||
rules = mutableListOf(
|
||||
ObstacleAvoidRule(
|
||||
entity = pokemonEntity,
|
||||
debugColor = Triple(1f, 0f, 0f),
|
||||
),
|
||||
SeekRule(
|
||||
entity = pokemonEntity,
|
||||
priority = 10,
|
||||
maxForce = 0.02,
|
||||
debugColor = Triple(0f, 1f, 0f),
|
||||
horzOnly = true,
|
||||
targetProvider = { e ->
|
||||
(e.navigation as? OmniPathNavigation)?.pathFollowTarget //TODO: maybe change this seems silly. Probably want a rule that checks explicitly?
|
||||
}
|
||||
),
|
||||
WanderRule(
|
||||
entity = pokemonEntity,
|
||||
debugColor = Triple(0f, 1f, 1f),
|
||||
horzOnly = true
|
||||
)
|
||||
)
|
||||
)
|
||||
steerArbitrator
|
||||
}
|
||||
|
||||
|
||||
fun startBanking(
|
||||
forwardBlocksPerTick: Float = 0F,
|
||||
upwardsBlocksPerTick: Float = 0F,
|
||||
@@ -73,6 +125,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
mob.setDiscardFriction(false) // ignored while trying to move but if stopped or doing something else this needs reenabled
|
||||
if (banking && operation != Operation.WAIT) {
|
||||
banking = false
|
||||
}
|
||||
@@ -102,6 +155,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
val baseSpeed = mob.getAttributeValue(Attributes.MOVEMENT_SPEED).toFloat() * this.speedModifier.toFloat()
|
||||
val adjustedSpeed = baseSpeed * mediumSpeed
|
||||
|
||||
|
||||
if (banking) {
|
||||
pokemonEntity.yRot += bankRightDegreesPerTick
|
||||
pokemonEntity.travel(Vec3(0.0, bankUpwardsBlocksPerTick.toDouble(), bankForwardBlocksPerTick.toDouble()).scale(mediumSpeed.toDouble()))
|
||||
@@ -110,11 +164,142 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
if (bankDurationTicks == 0) {
|
||||
banking = false
|
||||
|
||||
operation = Operation.WAIT
|
||||
}
|
||||
}
|
||||
// Here we call the wander on every pokemon no matter what then they wander then leave
|
||||
// Just ignore all operatations and do whatever is being tested.
|
||||
if (true) {
|
||||
// val horzOnly = true
|
||||
//
|
||||
// var topSpeed = gatherTopSpeed(pokemonEntity)
|
||||
// var maxForce = 0.02 // Arbitrarily chosen as of right now.
|
||||
// pokemonEntity.isPokemonSprinting = false
|
||||
//
|
||||
// // grab forward travel vector direction
|
||||
// val forwardVec = if (pokemonEntity.deltaMovement.horizontalDistanceSqr() < 0.001) {
|
||||
// // No meaningful velocity so derive from yaw orientation instead
|
||||
// val yaw = Math.toRadians(pokemonEntity.yRot.toDouble())
|
||||
// Vec3(-sin(yaw), 0.0, cos(yaw))
|
||||
// } else {
|
||||
// if (horzOnly) Vec3(pokemonEntity.deltaMovement.x, 0.0, pokemonEntity.deltaMovement.z).normalize()
|
||||
// else pokemonEntity.deltaMovement.normalize()
|
||||
// }
|
||||
// val pointAhead = pokemonEntity.position().add(forwardVec)
|
||||
// val aheadForce = pokemonEntity.steerToTarget(pointAhead, topSpeed, maxForce, horzOnly = horzOnly)
|
||||
//
|
||||
//
|
||||
//// val player = pokemonEntity.level().getNearestPlayer(pokemonEntity, 10.0) ?: return
|
||||
//// // Flee direction — away from player, flattened to XZ
|
||||
//// val toPlayerVec = pokemonEntity.position().subtract(player.position())
|
||||
////
|
||||
//// if (toPlayerVec.length() < 6.0) {
|
||||
//// pokemonEntity.isPokemonSprinting = true
|
||||
////// topSpeed = 0.4 // hardcoded high value
|
||||
////// maxForce = 0.04
|
||||
//// topSpeed *= 2.0f
|
||||
//// maxForce *= 2
|
||||
//// }
|
||||
////
|
||||
//// val fleePoint = pokemonEntity.position().add(toPlayerVec)
|
||||
//// val aheadForce = pokemonEntity.steerToTarget(fleePoint, topSpeed, maxForce, horzOnly = horzOnly)
|
||||
//
|
||||
// // For now just move forward by seeking towards a point ahead
|
||||
// val lookAheadTicks = 10 // How far in the future of this entities current movement to look ahead for collisions
|
||||
// val lookAheadDist = max(pokemonEntity.deltaMovement.length() * lookAheadTicks, pokemonEntity.bbWidth / 2.0) // At least always look ahead the width of the mons own hitbox
|
||||
//
|
||||
// // Get the collision box
|
||||
// val inflationAmount = 0.0 //pokemonEntity.bbWidth / 5.0 //TODO: is this needed? it seems to help a bit keeping them off walls but would stop them from entering gaps they truly could
|
||||
// val collBox = pokemonEntity.boundingBox.move(forwardVec.scale(lookAheadDist)).inflate(inflationAmount, 0.0, inflationAmount) // Inflate a bit
|
||||
// val shapes = pokemonEntity.level().getBlockCollisions(pokemonEntity, collBox)
|
||||
//
|
||||
// // Gather the nearest shape
|
||||
// val nearestShape = shapes.minByOrNull { shape ->
|
||||
// shape.bounds().center.distanceToSqr(pokemonEntity.position())
|
||||
// }
|
||||
//
|
||||
// var avoidancForce = Vec3.ZERO
|
||||
//
|
||||
// if (nearestShape != null) {
|
||||
// var hasLeftForce = false
|
||||
// var hasRightForce = false
|
||||
// val right = Vec3(-forwardVec.z, 0.0, forwardVec.x) //TODO: expand to 3 dimensions
|
||||
//
|
||||
// val obsCenter = nearestShape.bounds().center
|
||||
// val toObs = obsCenter.subtract(pokemonEntity.position())
|
||||
//
|
||||
// // Get the lateral avoidance point to seek to (the avoidance force really)
|
||||
// // It is the negated vector of toObs - Proj(toObs->forwardVec)
|
||||
// val projVec = forwardVec.scale(toObs.dot(forwardVec)) // Can do this because forwardVec is normalized
|
||||
// val lateralComponent = toObs.subtract(projVec) // TODO: moved back so that two opposing forces turn the dude around
|
||||
//
|
||||
// avoidancForce = if (lateralComponent.lengthSqr() < 0.001) { // If its dead ahead then just turn right
|
||||
// val rightVec = forwardVec.cross(Vec3(0.0,1.0,0.0))
|
||||
// obsCorrectDir = 1F
|
||||
// pokemonEntity.steerToTarget(pokemonEntity.position().add(rightVec), topSpeed, maxForce, horzOnly = horzOnly)
|
||||
// } else {
|
||||
// // Scale the strength of the avoidance vec based off how close to being dead center it is
|
||||
//
|
||||
// var avoidanceVec = lateralComponent.scale(-1.0)
|
||||
// if (horzOnly) avoidanceVec = Vec3(avoidanceVec.x, 0.0, avoidanceVec.z)
|
||||
// //var avoidStrength = (abs(toObs.dot(avoidanceVec)) / (pokemonEntity.bbWidth / 2.0) ).coerceIn(0.0, 1.0)
|
||||
//
|
||||
// // 0.5 to add half the block width allowance since if it catches the corner of a block it still gets the center
|
||||
// var avoidStrength = sqrt(1 - (avoidanceVec.length() / (pokemonEntity.bbWidth / 2.0 + 0.5) ).coerceIn(0.0, 0.7))
|
||||
// // Track which side this force is pushing toward
|
||||
// //val newObsCorrectDir = if (avoidanceVec.normalize().dot(right) > 0) 1F else -1f
|
||||
//
|
||||
// // If the new direction is opposite the last direction then invert it
|
||||
//// if (obsCorrectDir == 0F) {
|
||||
//// obsCorrectDir = newObsCorrectDir
|
||||
//// } else if (newObsCorrectDir != obsCorrectDir) {
|
||||
//// avoidanceVec = avoidanceVec.scale(-1.0)
|
||||
//// //avoidStrength = 1.0
|
||||
//// }
|
||||
//
|
||||
// val avoidancePoint = pokemonEntity.position().add(avoidanceVec)
|
||||
// pokemonEntity.steerToTarget(avoidancePoint, topSpeed, maxForce * avoidStrength, horzOnly = horzOnly)
|
||||
// }
|
||||
// } else {
|
||||
// obsCorrectDir = 0F
|
||||
// }
|
||||
//
|
||||
// // accumulte forces
|
||||
// // TODO: steertoTarget also checks stuff like this. Should that be different? The forces could compound here
|
||||
// val accumForce = aheadForce.scale(0.5).add(avoidancForce.scale(1.0))
|
||||
// var finalForce = if( avoidancForce == Vec3.ZERO) aheadForce
|
||||
// else avoidancForce
|
||||
//
|
||||
// prevForce?.let { finalForce = it.lerp(finalForce, 0.1) }
|
||||
// prevForce = finalForce
|
||||
//
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(finalForce)
|
||||
//
|
||||
// // Must be called to make sure we have complete control over our entities velocity.
|
||||
// mob.speed = 0f
|
||||
// mob.setZza(0f)
|
||||
// mob.setXxa(0f)
|
||||
// mob.setDiscardFriction(true)
|
||||
// pokemonEntity.orientEntityToTravel(1.0f)
|
||||
// pokemonEntity.setFlying(false)
|
||||
//
|
||||
// drawLines.add(EntityDebugLine(forwardVec))
|
||||
// if (drawLines.isNotEmpty()) {
|
||||
// sendPacketToTrackingPlayers(pokemonEntity, ClientboundUpdateObsAvoidPacket(pokemonEntity.id, drawLines.toList()))
|
||||
// drawLines.clear()
|
||||
// }
|
||||
|
||||
if (operation == Operation.STRAFE) {
|
||||
steerSpeed = gatherTopSpeed(pokemonEntity)
|
||||
val force = steerArbitrator.calculate(debug = true)
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
mob.speed = 0f
|
||||
mob.setZza(0f)
|
||||
mob.setXxa(0f)
|
||||
mob.setDiscardFriction(true)
|
||||
pokemonEntity.orientEntityToTravel(1.0f)
|
||||
|
||||
} else if (operation == Operation.STRAFE) {
|
||||
var movingDistanceTotal = Mth.sqrt(strafeForwards * strafeForwards + strafeRight * strafeRight)
|
||||
if (movingDistanceTotal < 1.0f) {
|
||||
movingDistanceTotal = 1.0f
|
||||
@@ -161,10 +346,14 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
val horizontalDistanceFromTarget = xDist * xDist + zDist * zDist
|
||||
val closeHorizontally = horizontalDistanceFromTarget < VERY_CLOSE
|
||||
if (!closeHorizontally) {
|
||||
val angleToTarget = Mth.atan2(zDist, xDist).toDegrees() - 90.0f
|
||||
val currentMovingAngle = mob.yRot
|
||||
val steppedAngle = Mth.approachDegrees(currentMovingAngle, angleToTarget, 100 * mediumSpeed)
|
||||
mob.yRot = steppedAngle
|
||||
// NEW
|
||||
pokemonEntity.orientEntityToTravel(1.0f)
|
||||
|
||||
// OLD
|
||||
// val angleToTarget = Mth.atan2(zDist, xDist).toDegrees() - 90.0f
|
||||
// val currentMovingAngle = mob.yRot
|
||||
// val steppedAngle = Mth.approachDegrees(currentMovingAngle, angleToTarget, 100 * mediumSpeed)
|
||||
// mob.yRot = steppedAngle
|
||||
}
|
||||
|
||||
val (inWater, inLava) = mob.level().getWaterAndLavaIn(mob.boundingBox)
|
||||
@@ -191,15 +380,101 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
|
||||
val scale = min(adjustedSpeed.toDouble(), fullDistance.length())
|
||||
|
||||
mob.deltaMovement = direction.scale(scale)
|
||||
val maxForce = 0.02 // 0.02
|
||||
|
||||
// Gather the point to be steered to
|
||||
val nav = pokemonEntity.navigation
|
||||
nav.pathFollowTarget?.let { rt ->
|
||||
wantedX = rt.x
|
||||
wantedZ = rt.z
|
||||
wantedY = rt.y
|
||||
}
|
||||
// + 0.05 for dealing with swimming out of water, they otherwise get stuck on the lip
|
||||
val steerForce = pokemonEntity.steerToTarget(Vec3(wantedX, refine(wantedY + 0.05), wantedZ), scale, maxForce)
|
||||
mob.deltaMovement = mob.deltaMovement.add(steerForce)
|
||||
|
||||
//mob.deltaMovement = direction.scale(scale)
|
||||
|
||||
xDist = fullDistance.x
|
||||
zDist = fullDistance.z
|
||||
yDist = fullDistance.y
|
||||
} else {
|
||||
// division is to slow the speed down a bit so they don't overshoot when they get there.
|
||||
val forwardSpeed = min(adjustedSpeed, max(horizontalDistanceFromTarget.toFloat() / 2, 0.15F))
|
||||
mob.speed = forwardSpeed
|
||||
var forwardSpeed = adjustedSpeed
|
||||
var forceDiv = 1 / 5.0 // Portion of max speed that force can apply every tick
|
||||
var minForce = 0.02 // If its not at least this it breaks quite a few things. looking at you snorlax
|
||||
|
||||
val finalGoal: BlockPos? = pokemonEntity.navigation.path?.target
|
||||
val distanceToFinalGoal = finalGoal?.let {
|
||||
pokemonEntity.position().distanceTo(it.center).toFloat()
|
||||
} ?: 0.0f
|
||||
|
||||
if (distanceToFinalGoal > 8f) { //TODO: scale this to hitbox size
|
||||
pokemonEntity.isPokemonSprinting = true
|
||||
forwardSpeed *= 1.4f
|
||||
forceDiv = 1 / 10.0
|
||||
minForce *= 1.0 //TODO: figure out what to actually do for this
|
||||
} else {
|
||||
pokemonEntity.isPokemonSprinting = false
|
||||
}
|
||||
|
||||
// New way
|
||||
val blockFriction = mob.level()
|
||||
.getBlockState(mob.getBlockPosBelowThatAffectsMyMovement())
|
||||
.block.getFriction()
|
||||
val frictionInfluencedSpeed = if (mob.onGround()) {
|
||||
forwardSpeed * (0.21600002f / (blockFriction * blockFriction * blockFriction))
|
||||
} else {
|
||||
0.02f // getFlyingSpeed() default for non-player controlled. Also hopefully our checks above this caught this case
|
||||
}
|
||||
|
||||
|
||||
val baseForce = (forwardSpeed * 0.98) * frictionInfluencedSpeed
|
||||
|
||||
// We find where the equilibrium between vanilla friction and the input force usually is to get a topSpeed for this mon.
|
||||
// Could be replaced in the future with direct topSpeed calculations in the pokemon species form
|
||||
val frictionAppliedAfter = blockFriction * 0.91
|
||||
val topSpeed = baseForce / (1 - frictionAppliedAfter)
|
||||
val currSpeed = pokemonEntity.deltaMovement.length()
|
||||
|
||||
//TODO: is this needed?
|
||||
// if (!pokemonEntity.isPokemonSprinting && currSpeed > topSpeed) { // TODO: this might mess up some stuff like tnt pushing mobs around
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.normalize().scale(lerp(currSpeed, topSpeed, 0.1))
|
||||
// }
|
||||
|
||||
// Get a force that looks natural
|
||||
val maxForce = minForce //max(baseForce * forceDiv, minForce)
|
||||
|
||||
// Gather the point to be steered to
|
||||
val nav = pokemonEntity.navigation
|
||||
nav.pathFollowTarget?.let { rt ->
|
||||
wantedX = rt.x
|
||||
wantedZ = rt.z
|
||||
wantedY = rt.y
|
||||
}
|
||||
|
||||
val steerForce = pokemonEntity.steerToTarget(
|
||||
Vec3(wantedX, wantedY, wantedZ),
|
||||
topSpeed, // Is never reached as minecraft relies on top speed being obtained when force=friction
|
||||
maxForce,
|
||||
horzOnly = true
|
||||
)
|
||||
|
||||
// If this happens a lot then we have issues
|
||||
val mdt = LivingEntity.MIN_MOVEMENT_DISTANCE
|
||||
if ( abs(steerForce.x) < mdt || abs(steerForce.y) < mdt || abs(steerForce.z) < mdt) {
|
||||
//Cobblemon.LOGGER.info("INFO: Steer Force dimension smaller than minimum movement distance value") //TODO: figure this out
|
||||
}
|
||||
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce)
|
||||
//pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(0.0103766, 0.0, 0.0))
|
||||
mob.speed = 0f
|
||||
mob.setZza(0f)
|
||||
mob.setXxa(0f)
|
||||
mob.setDiscardFriction(true)
|
||||
|
||||
// Old way
|
||||
// mob.speed = forwardSpeed
|
||||
}
|
||||
|
||||
if (!verticalHandled) {
|
||||
@@ -280,6 +555,41 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
}
|
||||
|
||||
fun gatherTopSpeed( pokemonEntity: PokemonEntity): Double {
|
||||
val behaviour = pokemonEntity.behaviour
|
||||
val mediumSpeed = runtime.resolveFloat(if (pokemonEntity.getCurrentPoseType() in setOf(PoseType.FLY, PoseType.HOVER)) {
|
||||
behaviour.moving.fly.flySpeedHorizontal
|
||||
} else if (pokemonEntity.isEyeInFluid(FluidTags.WATER) || pokemonEntity.isEyeInFluid(FluidTags.LAVA)) {
|
||||
behaviour.moving.swim.swimSpeed
|
||||
} else {
|
||||
behaviour.moving.walk.walkSpeed
|
||||
}) * 2.5F //TODO: idk why this is 2.5 but I think we're stuck with it
|
||||
|
||||
val baseSpeed = mob.getAttributeValue(Attributes.MOVEMENT_SPEED).toFloat() * 0.4f //TODO: does this ever need to not be hardcoded?
|
||||
val adjustedSpeed = baseSpeed * mediumSpeed
|
||||
|
||||
val forwardSpeed = adjustedSpeed
|
||||
|
||||
val blockFriction = mob.level()
|
||||
.getBlockState(mob.getBlockPosBelowThatAffectsMyMovement())
|
||||
.block.getFriction()
|
||||
val frictionInfluencedSpeed = if (mob.onGround()) {
|
||||
forwardSpeed * (0.21600002f / (blockFriction * blockFriction * blockFriction))
|
||||
} else {
|
||||
0.02f // getFlyingSpeed() default for non-player controlled
|
||||
}
|
||||
|
||||
// Force applied every tick through the usual vanilla route
|
||||
val baseForce = (forwardSpeed * 0.98) * frictionInfluencedSpeed
|
||||
|
||||
// We find where the equilibrium between vanilla friction and the input force usually is to get a topSpeed for this mon.
|
||||
// Could be replaced in the future with direct topSpeed calculations in the pokemon species form
|
||||
val frictionAppliedAfter = blockFriction * 0.91
|
||||
val topSpeed = baseForce / (1 - frictionAppliedAfter)
|
||||
|
||||
return topSpeed
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the ideal water level y position of a given pokemon during battle.
|
||||
* This is separate for the eye height check and is dependent on if the pokemon is rafted or swimming.
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.sensors
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class PlayerDebuggingSensor : Sensor<LivingEntity>(SCAN_INTERVAL) {
|
||||
|
||||
override fun doTick(level: ServerLevel, entity: LivingEntity) {
|
||||
var diamondHolder: LivingEntity? = null
|
||||
diamondHolder = entity.brain.getMemory(CobblemonMemories.DIAMOND_DEBUGGER).orElse(null)?.let {
|
||||
if(it.mainHandItem.`is`(Items.DIAMOND)) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (diamondHolder == null) {
|
||||
val nearEntities = entity.brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).orElse(mutableListOf())
|
||||
diamondHolder = nearEntities?.find { entity ->
|
||||
entity.mainHandItem.`is`(Items.DIAMOND)
|
||||
}
|
||||
}
|
||||
|
||||
if (diamondHolder != null && diamondHolder is Player) {
|
||||
entity.brain.setMemory(CobblemonMemories.DIAMOND_DEBUGGER, diamondHolder)
|
||||
} else {
|
||||
entity.brain.eraseMemory(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
}
|
||||
}
|
||||
|
||||
override fun requires(): Set<MemoryModuleType<*>> {
|
||||
return setOf(
|
||||
CobblemonMemories.DIAMOND_DEBUGGER,
|
||||
MemoryModuleType.NEAREST_LIVING_ENTITIES
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_INTERVAL = 10
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
///*
|
||||
// * 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.entity.pokemon.ai.tasks;
|
||||
//
|
||||
//import com.cobblemon.mod.common.CobblemonMemories
|
||||
//import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
//import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
//import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
//import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
//import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
//import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
//import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
//
|
||||
//object BoidsTask {
|
||||
//fun create(): OneShot<PokemonEntity> {
|
||||
// return BehaviorBuilder.create {
|
||||
// it.group(
|
||||
// it.absent(CobblemonMemories.POKEMON_DROWSY)
|
||||
// ).apply(it) { _ ->
|
||||
// Trigger { world, entity, _ ->
|
||||
// if (entity.pokemon.status?.status == Statuses.SLEEP && entity.pokemon.storeCoordinates.get()?.store !is PartyStore) {
|
||||
// entity.pokemon.status = null
|
||||
// entity.brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
|
||||
// entity.brain.useDefaultActivity()
|
||||
// return@Trigger true
|
||||
// } else {
|
||||
// return@Trigger false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.extensions
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sign
|
||||
|
||||
|
||||
/*************************************************************************************************************
|
||||
Steering to Target
|
||||
Detailed in: https://www.red3d.com/cwr/steer/gdc99/
|
||||
*************************************************************************************************************/
|
||||
fun PokemonEntity.steerToTarget(
|
||||
targetPos: Vec3,
|
||||
maxSpeed: Double,
|
||||
maxForce: Double,
|
||||
arrivalRange: Double? = null,
|
||||
horzOnly: Boolean = false
|
||||
): Vec3 {
|
||||
val cPos = if (horzOnly) Vec3(this.position().x, 0.0, this.position().z) else this.position()
|
||||
val tPos = if (horzOnly) Vec3(targetPos.x, 0.0, targetPos.z) else targetPos
|
||||
|
||||
// Ensure we can't have a negative range here
|
||||
val absArrivalRange = arrivalRange?.let { abs(arrivalRange) }
|
||||
|
||||
// Calculate the vector from the pokemons current position to the
|
||||
// target position. Set its desired magnitude to max speed or a
|
||||
// portion of it if within the arrivalRange
|
||||
val toTargetVec = tPos.subtract(cPos)
|
||||
|
||||
val distToTarget = toTargetVec.length()
|
||||
val desiredSpeed = if (absArrivalRange != null && distToTarget < absArrivalRange) (distToTarget/absArrivalRange) * maxSpeed else maxSpeed
|
||||
val desiredVec = toTargetVec.normalize().scale(desiredSpeed)
|
||||
|
||||
var steer = if (horzOnly) desiredVec.subtract(Vec3(this.deltaMovement.x, 0.0, this.deltaMovement.z))
|
||||
else desiredVec.subtract(this.deltaMovement)
|
||||
|
||||
if (steer.length() > maxForce) {
|
||||
steer = steer.normalize().scale(maxForce)
|
||||
}
|
||||
|
||||
return steer
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************************
|
||||
Steering Target Avoidance
|
||||
Detailed in: https://www.red3d.com/cwr/steer/gdc99/
|
||||
*************************************************************************************************************/
|
||||
fun PokemonEntity.avoidTarget(
|
||||
targetPos: Vec3,
|
||||
maxSpeed: Double,
|
||||
maxForce: Double,
|
||||
avoidanceRange: Double? = null
|
||||
): Vec3 {
|
||||
|
||||
// Calculate the vector from the targets current position to the
|
||||
// pokemon position. Set its desired magnitude to max speed scaled
|
||||
// linearly from 1.0 when right next to the target and 0.0 when on the
|
||||
// very edge of the avoidance Range
|
||||
var fromTargetVec = this.position().subtract(targetPos)
|
||||
|
||||
// Calculate distance to the target
|
||||
val distToTarget = fromTargetVec.length()
|
||||
if (distToTarget < 1.0E-4) {fromTargetVec = Vec3(0.0, 1.0, 0.0)} // Ensure the avoidance vector can't be ZERO
|
||||
|
||||
// Return no force otherwise a desired vector will be calculated that slows the velocity.
|
||||
if (avoidanceRange != null && distToTarget >= avoidanceRange) return Vec3.ZERO
|
||||
|
||||
// Scale the avoidance force linearly
|
||||
val desiredSpeed = when {
|
||||
avoidanceRange == null -> maxSpeed // If no avoidance range then just always flee as fast as you can
|
||||
avoidanceRange == 0.0 -> maxSpeed // Avoid weird divide by zero nonsense
|
||||
distToTarget < avoidanceRange -> (1.0 - (distToTarget / avoidanceRange)) * maxSpeed
|
||||
else -> 0.0
|
||||
}
|
||||
//TODO: determine if this should be scaled by distance. Probably not
|
||||
val desiredVec = fromTargetVec.normalize() //.scale(desiredSpeed)
|
||||
|
||||
// Steer the pokemon towards the desired vector
|
||||
var steer = desiredVec.subtract(this.deltaMovement)
|
||||
if (steer.length() > maxForce) {
|
||||
steer = steer.normalize().scale(maxForce)
|
||||
}
|
||||
|
||||
return steer
|
||||
}
|
||||
|
||||
/*************************************************************************************************************
|
||||
Lerp the mobs yRot to its direction of travel
|
||||
*************************************************************************************************************/
|
||||
fun PokemonEntity.orientEntityToTravel(k: Float = 1.0f) {
|
||||
val interpAmount = k.coerceIn(0.0f, 1.0f) //TODO: This probably shouldn't be a lerp like this.
|
||||
if (this.deltaMovement.length() < 0.01) return //LivingEntity.MIN_MOVEMENT_DISTANCE) return
|
||||
|
||||
// Ensure the entity is facing the direction it is moving in both pitch and yaw
|
||||
val normVec = this.deltaMovement.normalize()
|
||||
val targetAngle = Mth.atan2(normVec.z, normVec.x).toDegrees() - 90.0f
|
||||
val currAngle = this.yRot
|
||||
|
||||
val angleDiff = Mth.wrapDegrees(targetAngle - currAngle)
|
||||
val maxPerTickRotation = 30.0f // set really high to ignore for now 15.0f // TODO: is something like this needed or should it be a clientside spring or something?
|
||||
val effRot = min(abs(angleDiff), maxPerTickRotation) * sign(angleDiff)
|
||||
this.yRot = currAngle + (effRot * interpAmount)
|
||||
//entity.xRot = asin(normVec.y.toFloat()).toDegrees()
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.messages.client.debug
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeCollection
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Packet sent to the client to update the obstacle avoidance lines to be rendered
|
||||
* while debugging
|
||||
*
|
||||
* @author Jackson
|
||||
* @since September 15th, 2025
|
||||
*/
|
||||
class ClientboundUpdateObsAvoidPacket(val entityId: Int, val lines: List<EntityDebugLine>) : NetworkPacket<ClientboundUpdateObsAvoidPacket> {
|
||||
override val id: ResourceLocation = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeInt(entityId)
|
||||
buffer.writeCollection(lines) { _, linePair ->
|
||||
linePair.encode(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("obstacle_avoidance_lines")
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): ClientboundUpdateObsAvoidPacket {
|
||||
val entityId = buffer.readInt()
|
||||
val lines = buffer.readList { buf ->
|
||||
EntityDebugLine.decode(buf as RegistryFriendlyByteBuf)
|
||||
}
|
||||
return ClientboundUpdateObsAvoidPacket(entityId, lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,8 @@ object PokemonBrain {
|
||||
CobblemonSensors.NEARBY_GROWABLE_CROPS,
|
||||
CobblemonSensors.NEARBY_BEE_HIVE,
|
||||
CobblemonSensors.NEARBY_FLOWER,
|
||||
CobblemonSensors.NEARBY_SWEET_BERRY_BUSH
|
||||
CobblemonSensors.NEARBY_SWEET_BERRY_BUSH,
|
||||
CobblemonSensors.PLAYER_DEBUGGING
|
||||
|
||||
// CobblemonSensors.BATTLING_POKEMON,
|
||||
// CobblemonSensors.NPC_BATTLING
|
||||
@@ -179,6 +180,7 @@ object PokemonBrain {
|
||||
MemoryModuleType.HURT_BY_ENTITY,
|
||||
MemoryModuleType.NEAREST_PLAYERS,
|
||||
MemoryModuleType.NEAREST_VISIBLE_PLAYER,
|
||||
MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER,
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.ATTACK_COOLING_DOWN,
|
||||
CobblemonMemories.POKEMON_DROWSY,
|
||||
@@ -201,7 +203,8 @@ object PokemonBrain {
|
||||
CobblemonMemories.HERD_LEADER,
|
||||
CobblemonMemories.HERD_SIZE,
|
||||
CobblemonMemories.ATTACK_TARGET_DATA,
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH,
|
||||
CobblemonMemories.DIAMOND_DEBUGGER
|
||||
)
|
||||
|
||||
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
|
||||
|
||||
+7
-2
@@ -200,6 +200,10 @@
|
||||
{
|
||||
"condition": "q.is_holding_item",
|
||||
"animation": "q.bedrock('charizard', 'hold_item')"
|
||||
},
|
||||
{
|
||||
"condition": "!q.is_ridden",
|
||||
"animation": "q.pitch_tilt('body', 0.8)"
|
||||
}
|
||||
],
|
||||
"quirks": [
|
||||
@@ -217,9 +221,10 @@
|
||||
"q.look('neck4', 0.35, 0.35, 5, -5, 15, -15)",
|
||||
"q.look('head', 0.4, 0.25, 5, -5, 10, -10)",
|
||||
"q.bedrock('charizard', 'air_fly')",
|
||||
"q.bedrock('charizard', 'ride_air_fly')",
|
||||
{
|
||||
"condition": "q.is_ridden",
|
||||
"animation": "q.bedrock('charizard', 'ride_air_fly')"
|
||||
"condition": "!q.is_ridden",
|
||||
"animation": "q.pitch_tilt('body', 0.8)"
|
||||
},
|
||||
{
|
||||
"condition": "q.is_holding_item",
|
||||
|
||||
+11
-2
@@ -65,7 +65,9 @@
|
||||
},
|
||||
"hover": {
|
||||
"poseTypes": ["HOVER"],
|
||||
"animations": ["q.look('neck')", "q.bedrock('pidgeot', 'air_idle')"],
|
||||
"animations": [
|
||||
"q.look('neck')", "q.bedrock('pidgeot', 'air_idle')"
|
||||
],
|
||||
"transformedParts": [
|
||||
{
|
||||
"part": "wing_open_left",
|
||||
@@ -91,7 +93,14 @@
|
||||
"fly": {
|
||||
"poseTypes": ["FLY"],
|
||||
"condition": "!q.is_gliding",
|
||||
"animations": ["q.look('neck')", "q.bedrock('pidgeot', 'air_idle')"],
|
||||
"animations": [
|
||||
"q.look('neck')",
|
||||
"q.bedrock('pidgeot', 'air_idle')",
|
||||
{
|
||||
"condition": "!q.is_ridden",
|
||||
"animation": "q.pitch_tilt('body', 0.8)"
|
||||
}
|
||||
],
|
||||
"transformedParts": [
|
||||
{
|
||||
"part": "wing_open_left",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"item.cobblemon.npc_editor": "NPC's Trainer Card",
|
||||
|
||||
"item.cobblemon.link_cable": "Link Cable",
|
||||
"item.cobblemon.link_cable.tooltip": "Evolves pokemon that require trading",
|
||||
"item.cobblemon.link_cable.tooltip": "Evolves Pokémon that require trading",
|
||||
"item.cobblemon.dragon_scale": "Dragon Scale",
|
||||
"item.cobblemon.dragon_scale.tooltip": "Evolves Seadra into Kingdra when held while trading",
|
||||
"item.cobblemon.kings_rock": "Kings Rock",
|
||||
@@ -423,7 +423,7 @@
|
||||
"item.cobblemon.razor_fang.tooltip_1": "When the holder successfully inflicts damage, the target may also flinch",
|
||||
"item.cobblemon.razor_fang.tooltip_2": "Evolves Gligar into Gliscor when held while leveling up during the night",
|
||||
"item.cobblemon.red_card": "Red Card",
|
||||
"item.cobblemon.red_card.tooltip_1": "Pokemon that damage the holder are forced to switch out",
|
||||
"item.cobblemon.red_card.tooltip_1": "Pokémon that damage the holder are forced to switch out",
|
||||
"item.cobblemon.red_card.tooltip_2": "Consumed after use",
|
||||
"item.cobblemon.ring_target": "Ring Target",
|
||||
"item.cobblemon.ring_target.tooltip": "The holder's type immunities are removed",
|
||||
@@ -489,7 +489,7 @@
|
||||
"item.cobblemon.white_herb.tooltip_1": "Restores any lowered stat in battle",
|
||||
"item.cobblemon.white_herb.tooltip_2": "Consumed after use",
|
||||
"item.cobblemon.bright_powder": "Bright Powder",
|
||||
"item.cobblemon.bright_powder.tooltip": "Reduces the accuracy of enemy pokemon's moves by 10%",
|
||||
"item.cobblemon.bright_powder.tooltip": "Reduces the accuracy of enemy Pokémon's moves by 10%",
|
||||
"item.cobblemon.metal_powder": "Metal Powder",
|
||||
"item.cobblemon.metal_powder.tooltip": "Raises an untransformed Ditto's Defense and Sp. Def by 50%",
|
||||
"item.cobblemon.quick_powder": "Quick Powder",
|
||||
@@ -2494,6 +2494,10 @@
|
||||
"cobblemon.config.ui.allow_experience_from_pvp.tooltip": "Whether to allow Exp. gain from PvP battles.",
|
||||
"cobblemon.config.ui.experience_share_multiplier": "Experience Share Multiplier",
|
||||
"cobblemon.config.ui.experience_share_multiplier.tooltip": "Multiplier of how much of the total Exp is given to a Pokémon holding an Exp. Share.",
|
||||
"cobblemon.config.ui.award_experience_to_fainted_pokemon": "Award Exp. to Fainted Pokémon",
|
||||
"cobblemon.config.ui.award_experience_to_fainted_pokemon.tooltip": "Whether fainted Pokémon receive Exp. for opponents they helped defeat before fainting.",
|
||||
"cobblemon.config.ui.award_experience_on_battle_loss": "Award Exp. on Battle Loss",
|
||||
"cobblemon.config.ui.award_experience_on_battle_loss.tooltip": "Whether Pokémon receive Exp. for opponents they defeated even if the battle is lost. Requires 'Award Exp. to Fainted Pokémon' to be enabled for fainted Pokémon to benefit.",
|
||||
"cobblemon.config.ui.lucky_egg_multiplier": "Lucky Egg Multiplier",
|
||||
"cobblemon.config.ui.lucky_egg_multiplier.tooltip": "Multiplier of how much of the total Exp is given to a Pokémon holding a Lucky Egg.",
|
||||
"cobblemon.config.ui.allow_spectating": "Allow Spectating",
|
||||
@@ -2604,7 +2608,7 @@
|
||||
"cobblemon.config.ui.summary_profile_animations.always_animate.tooltip": "Entire party animates unless fainted",
|
||||
|
||||
"cobblemon.config.ui.third_person_view_bobbing": "Third Person View Bobbing",
|
||||
"cobblemon.config.ui.third_person_view_bobbing.tooltip": "If true then use default Minecraft behavior to follow directly behind the player's head. If false then follow a fixed offset from the pokemon.",
|
||||
"cobblemon.config.ui.third_person_view_bobbing.tooltip": "If true then use default Minecraft behavior to follow directly behind the player's head. If false then follow a fixed offset from the Pokémon.",
|
||||
"cobblemon.config.ui.invert_pitch": "Invert Pitch",
|
||||
"cobblemon.config.ui.invert_pitch.tooltip": "Invert the direction for pitch (up and down).",
|
||||
"cobblemon.config.ui.invert_yaw": "Invert Yaw",
|
||||
@@ -3893,6 +3897,9 @@
|
||||
"cobblemon.behaviour.pokemon_bee.name": "Pollinates Flowers",
|
||||
"cobblemon.behaviour.pokemon_bee.desc": "Pollinates flowers and adds honey to hives and Saccharine tree leaves.",
|
||||
|
||||
"cobblemon.behaviour.pokemon_boids.name": "Boids",
|
||||
"cobblemon.behaviour.pokemon_boids.desc": "Boids Around.",
|
||||
|
||||
"cobblemon.behaviour.pokemon_picks_up_items.name": "Picks Up Items",
|
||||
"cobblemon.behaviour.pokemon_picks_up_items.desc": "The Pokémon will pick up items that it cares about.",
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "cobblemon.behaviour.pokemon_boids.name",
|
||||
"description": "cobblemon.behaviour.pokemon_boids.desc",
|
||||
"entityType": "cobblemon:pokemon",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "idle",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -219,6 +219,26 @@
|
||||
"rouseChance": 0.0042
|
||||
}
|
||||
},
|
||||
"ai": [
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "core",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"drops": {
|
||||
"amount": 4,
|
||||
"entries": [
|
||||
|
||||
@@ -89,6 +89,26 @@
|
||||
"willFlee": false
|
||||
}
|
||||
},
|
||||
"ai": [
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "core",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"drops": {
|
||||
"amount": 6,
|
||||
"entries": [
|
||||
|
||||
@@ -110,6 +110,24 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "core",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"drops": {
|
||||
|
||||
@@ -121,6 +121,26 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"ai": [
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "core",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"drops": {
|
||||
"amount": 3,
|
||||
"entries": [
|
||||
|
||||
@@ -246,6 +246,26 @@
|
||||
"rouseChance": 0.0042
|
||||
}
|
||||
},
|
||||
"ai": [
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "core",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"drops": {
|
||||
"amount": 2,
|
||||
"entries": [
|
||||
|
||||
@@ -175,6 +175,26 @@
|
||||
"rouseChance": 0.0042
|
||||
}
|
||||
},
|
||||
"ai": [
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "core",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_to_boid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "cobblemon:pokemon_boid",
|
||||
"tasksByPriority": {
|
||||
"0": [
|
||||
"switch_from_boid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"drops": {
|
||||
"amount": 3,
|
||||
"entries": [
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"riding": {
|
||||
"behaviours": {
|
||||
"LAND": {
|
||||
"key": "cobblemon:land/minekart",
|
||||
"key": "cobblemon:land/horse",
|
||||
"rideSounds": [],
|
||||
"stats": {
|
||||
"ACCELERATION": "0-5",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"values": [
|
||||
"#cobblemon:berries",
|
||||
"#cobblemon:herbs",
|
||||
"#cobblemon:type_gems",
|
||||
"minecraft:snowball"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"#cobblemon:berries"
|
||||
"#cobblemon:berries",
|
||||
"#cobblemon:type_gems"
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"values": [
|
||||
"#cobblemon:berries",
|
||||
"#cobblemon:herbs",
|
||||
"#cobblemon:type_gems",
|
||||
"minecraft:snowball"
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:black_forest",
|
||||
"id": "biomeswevegone:ironwood_gour",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
|
||||
+6
-2
@@ -38,11 +38,15 @@
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:aspen_boreal",
|
||||
"id": "biomeswevegone:howling_peaks",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:howling_peaks",
|
||||
"id": "biomeswevegone:overgrowth_woodlands",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:temperate_grove",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
|
||||
@@ -78,6 +78,10 @@
|
||||
"id": "biomeswevegone:coconino_meadow",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:forgotten_forest",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:mojave_desert",
|
||||
"required": false
|
||||
@@ -91,7 +95,7 @@
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:white_mangrove_marshes",
|
||||
"id": "biomeswevegone:temperate_grove",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
|
||||
+14
-2
@@ -59,6 +59,14 @@
|
||||
"id": "biomeswevegone:coniferous_forest",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:crimson_tundra",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:eroded_borealis",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:frosted_coniferous_forest",
|
||||
"required": false
|
||||
@@ -72,11 +80,15 @@
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:rose_fields",
|
||||
"id": "biomeswevegone:maple_taiga",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:weeping_witch_forest",
|
||||
"id": "biomeswevegone:redwood_thicket",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:rose_fields",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
"id": "biomesoplenty:seasonal_orchard",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:pumpkin_valley",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "blooming_biosphere:autumnal_forest",
|
||||
"required": false
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:bamboo_jungle",
|
||||
{
|
||||
"id": "biomeswevegone:crag_gardens",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "wythers:bamboo_jungle_canyon",
|
||||
"required": false
|
||||
|
||||
@@ -42,14 +42,6 @@
|
||||
"id": "biomesoplenty:orchard",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:amaranth_fields",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:allium_fields",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:rose_fields",
|
||||
"required": false
|
||||
@@ -58,10 +50,6 @@
|
||||
"id": "biomeswevegone:skyris_vale",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:orchard",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "terralith:blooming_plateau",
|
||||
"required": false
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
"id": "biomesoplenty:mystic_grove",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:skyris_vale",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "terralith:amethyst_canyon",
|
||||
"required": false
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
"id": "blooming_biosphere:rainforest",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:forgotten_forest",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:tropical_rainforest",
|
||||
"required": false
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
"id": "biomesoplenty:rocky_shrubland",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:firecracker_chaparral",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:sierra_badlands",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "blooming_biosphere:chaparral",
|
||||
"required": false
|
||||
|
||||
@@ -18,6 +18,18 @@
|
||||
"id": "biomeswevegone:ebony_woods",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:overgrowth_woodlands",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:pale_bog",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:weeping_witch_forest",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "wythers:ancient_taiga",
|
||||
"required": false
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
"id": "biomesoplenty:tundra",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "biomeswevegone:crimson_tundra",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "clifftree:bog",
|
||||
"required": false
|
||||
|
||||
+11
-2
@@ -59,15 +59,24 @@ dependencies {
|
||||
modImplementation(libs.bundles.fabric.integrations.implementation)
|
||||
modRuntimeOnly(libs.bundles.fabric.integrations.runtimeOnly)
|
||||
modRuntimeOnly(libs.bundles.mongo)
|
||||
bundle(libs.bundles.mongo)
|
||||
|
||||
// modImplementation(libs.flywheelFabric)
|
||||
// include(libs.flywheelFabric)
|
||||
|
||||
listOf(
|
||||
libs.fabric.kotlin,
|
||||
libs.bundles.graal,
|
||||
libs.bundles.mongo
|
||||
).forEach {
|
||||
include(it)
|
||||
}
|
||||
|
||||
// Added to make graal available in dev
|
||||
modRuntimeOnly(libs.bundles.graal)
|
||||
|
||||
include(libs.fabric.kotlin)
|
||||
|
||||
listOf(
|
||||
libs.graal,
|
||||
libs.molang
|
||||
).forEach {
|
||||
bundle(it)
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.CobblemonClient.reloadCodedAssets
|
||||
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinds
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexType
|
||||
import com.cobblemon.mod.common.client.render.atlas.CobblemonAtlases
|
||||
import com.cobblemon.mod.common.client.render.debug.SteerForceRenderer
|
||||
import com.cobblemon.mod.common.client.render.item.CobblemonModelPredicateRegistry
|
||||
import com.cobblemon.mod.common.item.PokedexItem
|
||||
import com.cobblemon.mod.common.particle.CobblemonParticles
|
||||
@@ -171,6 +172,24 @@ class CobblemonFabricClient: ClientModInitializer, CobblemonClientImplementation
|
||||
)
|
||||
}
|
||||
|
||||
WorldRenderEvents.LAST.register { context ->
|
||||
val poseStack = context.matrixStack()
|
||||
val multiBufferSource = context.consumers()
|
||||
val camera = context.camera()
|
||||
val partialTicks = context.tickCounter().getGameTimeDeltaPartialTick(true)
|
||||
|
||||
// Add a null check to ensure all required tools are present before rendering
|
||||
if (poseStack != null && multiBufferSource != null && camera != null) {
|
||||
SteerForceRenderer.render(
|
||||
poseStack,
|
||||
multiBufferSource,
|
||||
partialTicks,
|
||||
camera
|
||||
)
|
||||
}
|
||||
SteerForceRenderer.registerTickHandler()
|
||||
}
|
||||
|
||||
CobblemonModelPredicateRegistry.registerPredicates()
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,6 @@ tasks {
|
||||
configurations = listOf(bundle)
|
||||
mergeServiceFiles()
|
||||
|
||||
relocate ("com.mongodb", "com.cobblemon.mod.relocations.mongodb")
|
||||
relocate ("org.graalvm", "com.cobblemon.mod.relocations.graalvm")
|
||||
relocate ("com.oracle", "com.cobblemon.mod.relocations.oracle")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,20 @@ blossom = "2.1.0"
|
||||
idea-ext = "1.1.7"
|
||||
versioning = "3.1.0"
|
||||
|
||||
# Loom
|
||||
# Minecraft Development
|
||||
loom = "1.11-SNAPSHOT"
|
||||
architectury-plugin = "3.4-SNAPSHOT"
|
||||
moddev = "1.0.11"
|
||||
|
||||
# Common
|
||||
molang = "1.1.20"
|
||||
jei-api = "19.22.1.316"
|
||||
|
||||
# Graal
|
||||
graal = "22.3.0"
|
||||
icu4j = "71.1"
|
||||
jei-api = "19.22.1.316"
|
||||
regex = "22.3.0"
|
||||
truffle = "22.3.0"
|
||||
|
||||
# Fabric
|
||||
fabric-loader = "0.17.2"
|
||||
@@ -48,6 +53,7 @@ classgraph = "4.8.179"
|
||||
# General Buildscript Plugins
|
||||
kotlin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
loom = { group = "dev.architectury", name = "architectury-loom", version.ref = "loom" }
|
||||
neoforge-moddev = { group = "net.neoforged", name = "moddev-gradle", version.ref = "moddev" }
|
||||
architectury = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury-plugin" }
|
||||
shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" }
|
||||
blossom = { group = "net.kyori", name = "blossom", version.ref = "blossom" }
|
||||
@@ -62,7 +68,12 @@ kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", vers
|
||||
# Common
|
||||
jei-api = { group = "mezz.jei", name = "jei-1.21.1-common-api", version.ref = "jei-api"}
|
||||
molang = { group = "local.com.bedrockk", name = "molang", version.ref = "molang" }
|
||||
graal = { group = "org.graalvm.js", name = "js", version.ref = "graal" }
|
||||
|
||||
# Graal
|
||||
graal-core = { group = "org.graalvm.js", name = "js", version.ref = "graal" }
|
||||
graal-sdk = { group = "org.graalvm.sdk", name = "graal-sdk", version.ref = "graal" }
|
||||
graal-regex = { group = "org.graalvm.regex", name = "regex", version.ref = "graal"}
|
||||
graal-truffle = { group = "org.graalvm.truffle", name = "truffle-api", version.ref = "graal" }
|
||||
icu4j = { group = "com.ibm.icu", name = "icu4j", version.ref = "icu4j" }
|
||||
|
||||
# Fabric
|
||||
@@ -78,7 +89,7 @@ mod-menu = { group = "com.terraformersmc", name = "modmenu", version.ref = "mod-
|
||||
c2me-fabric = { group = "maven.modrinth", name = "c2me-fabric", version.ref = "c2me-fabric" }
|
||||
|
||||
# NeoForge
|
||||
neoforge = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge" }
|
||||
neoforge-api = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge" }
|
||||
neo-kotlin-forge = { group = "thedarkcolour", name = "kotlinforforge-neoforge", version.ref = "neo-kotlin-for-forge" }
|
||||
neoforge-debugutils = { group = "maven.modrinth", name = "debugutils", version.ref = "debugutils-forge"}
|
||||
jei-neoforge = { group = "mezz.jei", name = "jei-1.21.1-neoforge", version.ref = "jei-api"}
|
||||
@@ -104,3 +115,5 @@ fabric-integrations-runtimeOnly = [ "jei-fabric", "c2me-fabric" ]
|
||||
neoforge-integrations-runtimeOnly = [ "jei-neoforge", "c2me-neoforge" ]
|
||||
mongo = [ "mongo-driver-core", "mongo-driver-sync" ]
|
||||
unitTesting = [ "junit-fabric", "junit-params", "mockito-core", "mock-kotlin", "classgraph" ]
|
||||
|
||||
graal = [ "graal-core", "graal-sdk", "graal-regex", "graal-truffle", "icu4j" ]
|
||||
@@ -33,13 +33,14 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
neoForge(libs.neoforge)
|
||||
neoForge(libs.neoforge.api)
|
||||
//shadowCommon group: 'commons-io', name: 'commons-io', version: '2.6'
|
||||
//modImplementation(libs.flywheelForge)
|
||||
//include(libs.flywheelForge)
|
||||
modLocalRuntime(libs.neoforge.debugutils)
|
||||
modRuntimeOnly(libs.bundles.neoforge.integrations.runtimeOnly)
|
||||
modRuntimeOnly(libs.bundles.mongo)
|
||||
forgeRuntimeLibrary(libs.bundles.mongo)
|
||||
|
||||
implementation(libs.neo.kotlin.forge)
|
||||
|
||||
@@ -52,15 +53,21 @@ dependencies {
|
||||
testImplementation(project(":common", configuration = "namedElements"))
|
||||
|
||||
listOf(
|
||||
libs.graal,
|
||||
libs.bundles.graal,
|
||||
libs.bundles.mongo
|
||||
).forEach {
|
||||
include(it)
|
||||
}
|
||||
modRuntimeOnly(libs.bundles.graal)
|
||||
forgeRuntimeLibrary(libs.bundles.graal)
|
||||
|
||||
listOf(
|
||||
libs.molang
|
||||
).forEach {
|
||||
forgeRuntimeLibrary(it)
|
||||
bundle(it)
|
||||
}
|
||||
|
||||
forgeRuntimeLibrary(libs.bundles.mongo)
|
||||
bundle(libs.bundles.mongo)
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen
|
||||
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinds
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexType
|
||||
import com.cobblemon.mod.common.client.render.atlas.CobblemonAtlases
|
||||
import com.cobblemon.mod.common.client.render.debug.SteerForceRenderer
|
||||
import com.cobblemon.mod.common.client.render.item.CobblemonModelPredicateRegistry
|
||||
import com.cobblemon.mod.common.client.render.shader.CobblemonShaders
|
||||
import com.cobblemon.mod.common.item.PokedexItem
|
||||
@@ -68,6 +69,7 @@ import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent
|
||||
import net.neoforged.neoforge.client.event.RegisterShadersEvent
|
||||
import net.neoforged.neoforge.client.event.RenderGuiEvent
|
||||
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent
|
||||
import net.neoforged.neoforge.client.event.RenderLevelStageEvent
|
||||
import net.neoforged.neoforge.client.gui.IConfigScreenFactory
|
||||
import net.neoforged.neoforge.client.gui.VanillaGuiLayers
|
||||
import net.neoforged.neoforge.common.NeoForge
|
||||
@@ -102,6 +104,18 @@ object CobblemonNeoForgeClient : CobblemonClientImplementation {
|
||||
modContainer.registerExtensionPoint(IConfigScreenFactory::class.java, object : IConfigScreenFactory {
|
||||
override fun createScreen(modContainer: ModContainer, arg: Screen): Screen = CobblemonConfigScreen(arg)
|
||||
})
|
||||
|
||||
NeoForge.EVENT_BUS.addListener<RenderLevelStageEvent> { event ->
|
||||
if (event.stage == RenderLevelStageEvent.Stage.AFTER_PARTICLES) {
|
||||
SteerForceRenderer.render(
|
||||
event.poseStack,
|
||||
Minecraft.getInstance().renderBuffers().bufferSource(),
|
||||
event.partialTick.gameTimeDeltaTicks,
|
||||
event.camera
|
||||
)
|
||||
}
|
||||
}
|
||||
SteerForceRenderer.registerTickHandler()
|
||||
}
|
||||
|
||||
private fun onRegisterReloadListener(event: RegisterClientReloadListenersEvent) {
|
||||
|
||||
Reference in new issue
Block a user