Compare commits

...
15 changed files with 149 additions and 49 deletions
@@ -62,6 +62,7 @@ object CobblemonMemories {
val WANDER_CONTROL = register<CobblemonWanderControl>("wander_control")
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
val RECENT_CATCH_ATTEMPT = register<UUID>("recent_catch_attempt")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -79,7 +79,6 @@ interface TaskConfig {
cobblemonResource("switch_to_panic_when_hostiles_nearby") to SwitchToPanicWhenHostilesNearbyTaskConfig::class.java,
cobblemonResource("calm_down") to CalmDownTaskConfig::class.java,
cobblemonResource("walk_away_from_avoid_target") to WalkAwayFromAvoidTargetTaskConfig::class.java,
cobblemonResource("flee_nearest_hostile") to FleeNearestHostileTaskConfig::class.java,
cobblemonResource("flee_attacker") to FleeAttackerTaskConfig::class.java,
cobblemonResource("fly_in_circles") to FlyInCirclesTaskConfig::class.java,
cobblemonResource("run_script") to RunScript::class.java,
@@ -160,7 +160,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("great_ball", MultiplierModifier(1.5F))
createDefault("ultra_ball", MultiplierModifier(2F))
createDefault("master_ball", GuaranteedModifier())
createDefault("safari_ball", CatchRateModifiers.SAFARI)
createDefault("safari_ball", CatchRateModifiers.SAFARI, stealthFactor = 0.75f)
createDefault("fast_ball", BaseStatModifier(Stats.SPEED, { it >= 100 }, 4F))
createDefault("level_ball", CatchRateModifiers.LEVEL)
createDefault("lure_ball", CatchRateModifiers.LURE)
@@ -237,11 +237,12 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
model2d: ResourceLocation = cobblemonResource(name),
model3d: ResourceLocation = cobblemonResource("item/${name}_model"),
throwPower: Float = 1.25f,
ancient: Boolean = false
ancient: Boolean = false,
stealthFactor: Float = 0.0f,
): PokeBall {
val identifier = cobblemonResource(name)
//val finalModifiers = if (appendUltraBeastPenalty) modifiers + listOf(LabelModifier(0.1F, true, CobblemonPokemonLabels.ULTRA_BEAST)) else modifiers
val pokeball = PokeBall(identifier, modifier, effects, waterDragValue, model2d, model3d, throwPower, ancient)
val pokeball = PokeBall(identifier, modifier, effects, waterDragValue, model2d, model3d, throwPower, ancient, stealthFactor)
this.defaults[identifier] = pokeball
return pokeball
}
@@ -29,9 +29,11 @@ object BattleRegistry {
.registerTypeAdapter(ShowdownMoveset::class.java, ShowdownMovesetAdapter)
.create()
private val battleMap = ConcurrentHashMap<UUID, PokemonBattle>()
private val battleLock = ConcurrentHashMap<UUID, Boolean>()
fun onServerStarted() {
battleMap.clear()
battleLock.clear()
}
fun onPlayerDisconnect(player: ServerPlayer) {
@@ -205,12 +207,18 @@ object BattleRegistry {
startShowdown(battle)
}
if (!canPreempt) start().also { return SuccessfulBattleStart(battle) }
if (!canPreempt) start().also {
side1.actors.forEach { actor -> battleLock.remove(actor.uuid) }
side2.actors.forEach { actor -> battleLock.remove(actor.uuid) }
return SuccessfulBattleStart(battle)
}
val preBattleEvent = BattleStartedEvent.Pre(battle)
CobblemonEvents.BATTLE_STARTED_PRE.postThen(preBattleEvent) {
start()
CobblemonEvents.BATTLE_STARTED_POST.post(BattleStartedEvent.Post(battle))
side1.actors.forEach { actor -> battleLock.remove(actor.uuid) }
side2.actors.forEach { actor -> battleLock.remove(actor.uuid) }
return SuccessfulBattleStart(battle)
}
return ErroredBattleStart(mutableSetOf(BattleStartError.canceledByEvent(preBattleEvent.reason)))
@@ -229,6 +237,19 @@ object BattleRegistry {
return battleMap.values.find { it.getActor(serverPlayer) != null }
}
fun checkBattleLock(serverPlayer: ServerPlayer, ancientIsOkay: Boolean): Boolean =
if (ancientIsOkay)
battleLock.get(serverPlayer.uuid) == false
else
battleLock.containsKey(serverPlayer.uuid)
fun lockPlayer(serverPlayer: ServerPlayer, ancient: Boolean) {
battleLock.put(serverPlayer.uuid, ancient)
}
fun unlockPlayer(serverPlayer: ServerPlayer) {
battleLock.remove(serverPlayer.uuid)
}
fun getBattleByParticipatingPlayerId(playerId: UUID): PokemonBattle? {
return battleMap.values.find { playerId in it.playerUUIDs }
}
@@ -249,7 +249,7 @@ class ForcePassActionResponse : ShowdownActionResponse(ShowdownActionResponseTyp
return false
}
return activeBattlePokemon.actor.expectingPassActions.size > 0
return activeBattlePokemon.actor.expectingPassActions.isNotEmpty()
}
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "pass"
@@ -13,7 +13,9 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities.EMPTY_POKEBALL
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokeball.PokeBallCaptureCalculatedEvent
import com.cobblemon.mod.common.api.events.pokeball.ThrownPokeballHitEvent
@@ -28,8 +30,10 @@ import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleCaptureAction
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.BattleTypes
@@ -72,7 +76,6 @@ import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.EntityHitResult
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import java.util.concurrent.CompletableFuture
@@ -104,7 +107,8 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
override val schedulingTracker = SchedulingTracker()
var capturingPokemon: PokemonEntity? = null
val captureFuture = CompletableFuture<Boolean>()
var captureFuture = CompletableFuture<Boolean>()
var capturePostAction: (Boolean) -> Unit = {}
var captureState: CaptureState
get() = CaptureState.entries[entityData.get(CAPTURE_STATE).toInt()]
set(value) { entityData.set(CAPTURE_STATE, value.ordinal.toByte()) }
@@ -179,20 +183,28 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
var pokeBall: PokeBall = PokeBalls.POKE_BALL
override fun onHitBlock(hitResult: BlockHitResult) {
if (captureState == CaptureState.NOT) {
if (level().isServerSide()) {
super.onHitBlock(hitResult)
level().sendParticlesServer(ParticleTypes.CLOUD, hitResult.location, 2, hitResult.location.subtract(position()).normalize().scale(-0.1), 0.0)
level().playSoundServer(position(), SoundEvents.WOOD_PLACE, pitch = 2.5F)
discard()
val player = this.owner as? ServerPlayer
if (player?.isCreative == false) {
spawnAtLocation(defaultItem)
when (captureState) {
CaptureState.FALL -> {
if (level().isServerSide()) {
beginCapture()
}
}
} else {
setNoGravity(false)
deltaMovement = Vec3.ZERO
CaptureState.NOT -> {
if (level().isServerSide()) {
super.onHitBlock(hitResult)
level().sendParticlesServer(ParticleTypes.CLOUD, hitResult.location, 2, hitResult.location.subtract(position()).normalize().scale(-0.1), 0.0)
level().playSoundServer(position(), SoundEvents.WOOD_PLACE, pitch = 2.5F)
discard()
val player = this.owner as? ServerPlayer
if (player?.isCreative == false) {
spawnAtLocation(defaultItem)
}
}
}
else -> {
isNoGravity = false
deltaMovement = Vec3.ZERO
}
}
}
@@ -255,7 +267,13 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
owner?.sendSystemMessage(lang("capture.busy", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
} else if (owner is ServerPlayer && BattleRegistry.getBattleByParticipatingPlayer(owner) != null) {
owner.sendSystemMessage(lang("you_in_battle").red())
owner.sendSystemMessage(lang("capture.you_in_battle").red())
return drop()
} else if (owner is ServerPlayer && BattleRegistry.checkBattleLock(owner, pokeBall.ancient)) {
owner.sendSystemMessage(lang("capture.you_already_capturing").red())
return drop()
} else if (pokemonEntity.brain.hasMemoryValue(CobblemonMemories.RECENT_CATCH_ATTEMPT)) {
owner?.sendSystemMessage(lang("capture.enraged", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
}
@@ -279,6 +297,29 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
super.onHitEntity(hitResult)
}
private fun inBattleCatchAction(battle: PokemonBattle, thrower: LivingEntity, pokemonEntity: PokemonEntity) {
val throwerActor = battle.getActor(thrower.uuid)
val hitActor = battle.actors.find { it.isForPokemon(pokemonEntity) }
val hitBattlePokemon = hitActor?.activePokemon?.find { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
if (throwerActor == null || hitActor == null || hitBattlePokemon == null) {
return // Weird, shouldn't be possible
}
val pokemonName = hitBattlePokemon.battlePokemon?.getName() ?: "error".red()
battle.broadcastChatMessage(
lang(
"capture.attempted_capture",
throwerActor.getName(),
pokeBall.item().description,
pokemonName
).yellow()
)
battle.dispatchWaiting(2F) { battle.broadcastChatMessage(lang("capture.broke_free", pokemonName).red()) }
battle.dispatchGo { throwerActor.forceChoose(ForcePassActionResponse()) }
}
private fun drop() {
val owner = owner
discard()
@@ -352,6 +393,11 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
if (pokemon.pokemon.isWild() && pokemon.isAlive && !captureFuture.isDone) {
pokemon.discard()
discard()
capturePostAction(true)
val owner = owner
if (owner != null && owner is ServerPlayer) {
BattleRegistry.unlockPlayer(owner)
}
captureFuture.complete(true)
val party = player.party()
pokemon.pokemon.caughtBall = pokeBall
@@ -419,6 +465,11 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
after(seconds = 0.1F) {
pokemon.beamMode = 0
pokemon.busyLocks.remove(this)
capturePostAction(false)
val owner = owner
if (owner != null && owner is ServerPlayer) {
BattleRegistry.unlockPlayer(owner)
}
captureFuture.complete(false)
val ballType =
this.pokeBall.name.path.lowercase().replace("_", "")
@@ -442,6 +493,10 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
private fun attemptCatch(pokemonEntity: PokemonEntity) {
pokemonEntity.busyLocks.add(this)
val owner = owner
if (owner != null && owner is ServerPlayer) {
BattleRegistry.lockPlayer(owner, pokeBall.ancient)
}
val displace = deltaMovement
captureState = CaptureState.HIT
val mul = if (random.nextBoolean()) 1 else -1
@@ -456,7 +511,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
after(seconds = 0.7F) {
// Start beaming them up.
deltaMovement = Vec3.ZERO
setNoGravity(true)
isNoGravity = true
level().playSoundServer(position(), CobblemonSounds.POKE_BALL_RECALL, volume = 0.6F)
pokemonEntity.beamMode = 3
}
@@ -471,7 +526,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
// If it was still falling after a second and a half, just assume it's landed because we can't wait all day.
if (captureState == CaptureState.FALL) {
deltaMovement = Vec3.ZERO
setNoGravity(true)
isNoGravity = true
setOnGround(true)
beginCapture()
}
@@ -479,25 +534,40 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
}
}
override fun onHit(hitResult: HitResult) {
super.onHit(hitResult)
if (captureState == CaptureState.FALL && hitResult.type == HitResult.Type.BLOCK) {
if (level().isServerSide()) {
beginCapture()
}
}
}
// We have hit the ground, time to stop falling and start shaking! Calculate capture.
fun beginCapture() {
// We have hit the ground, time to stop falling and start shaking! Calculate capture.
capturingPokemon?.setPositionSafely(position())
val capturingPokemon = this.capturingPokemon ?: return
capturingPokemon.setPositionSafely(position())
val thrower = owner as LivingEntity
val captureResult = Cobblemon.config.captureCalculator.processCapture(thrower, this, capturingPokemon!!).let {
val event = PokeBallCaptureCalculatedEvent(thrower = thrower, pokemonEntity = capturingPokemon!!, pokeBallEntity = this, captureResult = it)
val captureResult = Cobblemon.config.captureCalculator.processCapture(thrower, this, capturingPokemon).let {
val event = PokeBallCaptureCalculatedEvent(thrower = thrower, pokemonEntity = capturingPokemon, pokeBallEntity = this, captureResult = it)
CobblemonEvents.POKE_BALL_CAPTURE_CALCULATED.post(event)
event.captureResult
}
if (thrower is ServerPlayer) {
val battle = (capturingPokemon.delegate as PokemonServerDelegate).getBattle()
if (battle == null && !pokeBall.ancient) {
capturingPokemon.brain.setMemoryWithExpiry(CobblemonMemories.RECENT_CATCH_ATTEMPT, thrower.uuid, 30 * 20)
this.capturePostAction = {
if (!it) {
if (this.pokeBall.stealthFactor > 0.0f && this.random.nextFloat() < this.pokeBall.stealthFactor) {
thrower.sendSystemMessage(lang("capture.didnt_notice", capturingPokemon.exposedSpecies.translatedName).green())
} else {
thrower.sendSystemMessage(lang("capture.got_enraged", capturingPokemon.exposedSpecies.translatedName).red())
val lead = thrower.party().firstOrNull {it.entity != null}?.uuid
if (lead != null) {
BattleBuilder.pve(thrower, capturingPokemon, lead).ifSuccessful { battle ->
inBattleCatchAction(battle, thrower, capturingPokemon)
}
}
}
}
}
}
}
var rollsRemaining = captureResult.numberOfShakes
if (rollsRemaining == 4) {
rollsRemaining--
@@ -530,11 +600,12 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
}
.build()
}
override fun getCurrentPoseType(): PoseType {
return PoseType.NONE
}
override fun canUsePortal(allowVehicles: Boolean) = false
override fun canUsePortal(allowVehicles: Boolean) = captureState == CaptureState.NOT
override fun getAddEntityPacket(entityTrackerEntry: ServerEntity): Packet<ClientGamePacketListener> =
ClientboundCustomPayloadPacket(
@@ -40,13 +40,15 @@ open class PokeBall(
val model2d: ResourceLocation,
val model3d: ResourceLocation,
val throwPower: Float,
val ancient: Boolean
val ancient: Boolean,
val stealthFactor: Float,
) {
val struct = QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name.toString()) }
.addFunction("water_drag_value") { DoubleValue(waterDragValue) }
.addFunction("throw_power") { DoubleValue(throwPower) }
.addFunction("is_ancient") { DoubleValue(ancient) }
.addFunction("stealth_factor") { DoubleValue(stealthFactor) }
// .addFunction("item") { } // requires a registry which is hard
// This gets attached during item registry
@@ -296,9 +296,7 @@ object PokemonBrain {
fun onCaptureFailed(pokemonEntity: PokemonEntity, capturer: Entity) {
if (pokemonEntity.pokemon.isWild() && pokemonEntity.battleId == null && capturer is LivingEntity) {
pokemonEntity.brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
pokemonEntity.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET)
pokemonEntity.getBrain().setMemoryWithExpiry(MemoryModuleType.AVOID_TARGET, capturer, AVOID_MEMORY_DURATION.sample(pokemonEntity.random).toLong())
}
}
}
@@ -2743,7 +2743,11 @@
"cobblemon.capture.not_wild": "That %1$s is not a wild Pokémon.",
"cobblemon.capture.in_battle": "That %1$s is in a battle and cannot be caught by you.",
"cobblemon.capture.busy": "The %1$s is busy!",
"cobblemon.capture.enraged": "The %1$s is enraged and can't be caught outside of battle!",
"cobblemon.capture.didnt_notice": "The %1$s didn't notice your catch attempt",
"cobblemon.capture.got_enraged": "Your capture attempt enraged the %1$s!",
"cobblemon.capture.you_in_battle": "You are in a battle.",
"cobblemon.capture.you_already_capturing": "You can only capture multiple Pokémon at the same time with ancient balls.",
"cobblemon.capture.not_single": "You can only capture wild Pokémon in a 1v1 battle.",
"cobblemon.capture.cannot_be_caught": "This Pokémon cannot be captured.",
"cobblemon.you_in_battle": "You can't do that while in a battle!",
@@ -34,7 +34,6 @@
"switch_to_panic_when_hostiles_nearby",
"calm_down",
"flee_attacker",
"flee_nearest_hostile",
"run_script",
"look_in_direction",
"path_to_hive",
@@ -23,12 +23,9 @@
}
],
"1": [
"cobblemon:flee_nearest_hostile"
],
"2": [
"cobblemon:walk_away_from_avoid_target"
],
"3": [
"2": [
{
"type": "cobblemon:wander",
"speedMultiplier": {
@@ -21,6 +21,11 @@
"behaviours": [
"cobblemon:pokemon_picks_up_items"
]
},
{
"type": "cobblemon:add_memories_and_sensors",
"condition": "!q.entity.is_in_party",
"memories": ["cobblemon:recent_catch_attempt"]
}
]
}
@@ -22,7 +22,8 @@
"type": "cobblemon:add_memories_and_sensors",
"memories": [
"minecraft:avoid_target",
"cobblemon:avoid_target_data"
"cobblemon:avoid_target_data",
"minecraft:flee_target"
]
},
{
@@ -20,7 +20,8 @@
"type": "cobblemon:add_memories_and_sensors",
"memories": [
"minecraft:attack_target",
"cobblemon:attack_target_data"
"cobblemon:attack_target_data",
"cobblemon:recent_catch_attempt"
]
},
{
@@ -17,9 +17,9 @@ loom {
val clientConfig = runConfigs.getByName("client")
clientConfig.runDir = "runClient"
// clientConfig.vmArg("-Dmixin.debug=true")
// clientConfig.programArg("--username=AshKetchum")
clientConfig.programArg("--username=AshKetchum")
//This is AshKetchum's UUID so you get an Ash Ketchum skin
// clientConfig.programArg("--uuid=93e4e551-589a-41cb-ab2d-435266c8e035")
clientConfig.programArg("--uuid=93e4e551-589a-41cb-ab2d-435266c8e035")
val serverConfig = runConfigs.getByName("server")
serverConfig.runDir = "runServer"