Merge branch 'main' into implement_pokemon

This commit is contained in:
FrankTheFarmer
2024-11-23 20:30:18 +10:00
90 changed files with 1406 additions and 269 deletions
+2
View File
@@ -329,6 +329,7 @@
- Clicking the summary interface exit button while the swap moves or evolve screen is open will cause the interface to switch back to the party screen. The button will exit the interface otherwise.
### Fixes
- Fixed awarding Pokémon experience upon forfeiting battles.
- Scaled down Amaura's fetus model to avoid clipping through the tank while animating.
- Fixed Cubone's cry not having a sound.
- Fixed the sendout sound erroneously playing when a wild Pokémon breaks out of a Poké ball.
@@ -406,6 +407,7 @@
- Fixed aspect tracking for Advancements.
- Fixed illusion not copying aspects or caught ball.
- Prevent summary stats tab from making sounds when clicking on an already open tab.
- Fixed display case not being able to be fed items from underneath.
### Developer
- `SpawnCause` is now an implementation of `SpawningInfluence`.
@@ -0,0 +1,33 @@
/*
* 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.mixin;
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemProperties.class)
public class ItemPropertiesMixin {
// Mixins into the fishing rod's register() lambda expression
// Without this, the fishing rod item model appears cast when using a PokeRod in the other hand
@Inject(method = "method_27883", at = @At(value = "RETURN", ordinal = 1), cancellable = true)
private static void cobblemon$preventRodMixup(ItemStack itemStack, ClientLevel clientLevel, LivingEntity livingEntity, int i, CallbackInfoReturnable<Float> cir) {
if (!(livingEntity instanceof Player) || ((Player) livingEntity).fishing instanceof PokeRodFishingBobberEntity) {
cir.setReturnValue(0.0f);
}
}
}
@@ -52,6 +52,7 @@ object CobblemonCommands {
BedrockParticleCommand.register(dispatcher)
OpenDialogueCommand.register(dispatcher)
NPCEditCommand.register(dispatcher)
NPCDeleteCommand.register(dispatcher)
AbandonMultiTeam.register(dispatcher)
FreezePokemonCommand.register(dispatcher)
ApplyPlayerTextureCommand.register(dispatcher)
@@ -0,0 +1,31 @@
/*
* 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.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.VillagerCalmDown
class CalmDownTaskConfig : SingleTaskConfig {
var condition = "true".asExpression()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
if (!runtime.resolveBoolean(condition)) return null
return VillagerCalmDown.create()
}
}
@@ -0,0 +1,75 @@
/*
* 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.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
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.ai.memory.MemoryModuleType
class ExitBattleWhenHurtTaskConfig : SingleTaskConfig {
var condition = "true".asExpression()
var includePassiveDamage = true
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
if (!runtime.resolveBoolean(condition)) return null
if (entity is NPCEntity) {
fun cancelNPCBattles(npcEntity: NPCEntity): Boolean {
val battles = npcEntity.battleIds.mapNotNull(BattleRegistry::getBattle)
battles.forEach { it.end() }
return battles.isNotEmpty()
}
return if (includePassiveDamage) {
BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.HURT_BY), it.present(CobblemonMemories.NPC_BATTLING))
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelNPCBattles(entity as NPCEntity) } }
}
} else {
BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.HURT_BY_ENTITY), it.present(CobblemonMemories.NPC_BATTLING))
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelNPCBattles(entity as NPCEntity) } }
}
}
} else { return null }/* else if (entity is PokemonEntity) { TODO uncomment when pokemon have brain :)
fun cancelPokemonBattle(pokemonEntity: PokemonEntity): Boolean {
val battle = BattleRegistry.getBattle(pokemonEntity.battleId ?: return false)
battle?.end()
return battle != null
}
return if (includePassiveDamage) {
BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.HURT_BY), it.present(CobblemonMemories.POKEMON_BATTLING))
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelPokemonBattle(entity as PokemonEntity) } }
}
} else {
BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.HURT_BY_ENTITY), it.present(CobblemonMemories.POKEMON_BATTLING))
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelPokemonBattle(entity as PokemonEntity) } }
}
}
}*/
}
}
@@ -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.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class FleeAttackerTaskConfig : SingleTaskConfig {
var condition = "true".asExpression()
var speedMultiplier = "0.5".asExpression()
var desiredDistance = "9".asExpression()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
if (!runtime.resolveBoolean(condition) || entity !is PathfinderMob) return null
val speedMultiplier = runtime.resolveFloat(speedMultiplier)
val desiredDistance = runtime.resolveInt(desiredDistance)
return WrapperLivingEntityTask(
SetWalkTargetAwayFrom.entity(MemoryModuleType.HURT_BY_ENTITY, speedMultiplier, desiredDistance, false),
PathfinderMob::class.java
)
}
}
@@ -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.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class FleeNearestHostileTaskConfig : SingleTaskConfig {
var condition = "true".asExpression()
var speedMultiplier = "0.5".asExpression()
var desiredDistance = "9".asExpression()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
if (!runtime.resolveBoolean(condition) || entity !is PathfinderMob) return null
val speedMultiplier = runtime.resolveFloat(speedMultiplier)
val desiredDistance = runtime.resolveInt(desiredDistance)
return WrapperLivingEntityTask(
SetWalkTargetAwayFrom.entity(MemoryModuleType.NEAREST_HOSTILE, speedMultiplier, desiredDistance, false),
PathfinderMob::class.java
)
}
}
@@ -89,20 +89,24 @@ on the idle activity so that it automatically transitions activities when a batt
### switch_npc_from_battle
The 'switch_npc_from_battle' type creates a task that will switch NPC entities from the battle activity when it is no longer in a battle. You should
put this in the battle activity so that it automatically transitions activities when a battle ends. If the NPC is in multiple battles, this
will only activate when no battles remain.
- `activity`: The activity to switch to when the NPC is no longer in a battle. Defaults to `minecraft:idle`.
will only activate when no battles remain. It will rely on the brain schedule to decide what activity to switch back to (usually idle).
### look_at_battling_pokemon
The 'look_at_battling_pokemon' type creates a task that will set the entity's look target to switch between the battling Pokémon.
- `minDurationTicks`: A MoLang expression that determines the minimum number of ticks that the entity will look at a specific battling Pokémon. Defaults to 40.
- `maxDurationTicks`: A MoLang expression that determines the maximum number of ticks that the entity will look at a specific battling Pokémon. Defaults to 80.
### exit_battle_when_hurt
The 'exit_battle_when_hurt' type creates a task that will make the entity exit the battle when it is hurt.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `includePassiveDamage`: A boolean that determines if passive damage (like poison or cacti) should trigger this task. Defaults to true.
### switch_to_chatting
The 'switch_to_chatting' type creates a task that will switch the entity to the chatting activity when a dialogue has been opened with that NPC.
### switch_from_chatting
The 'switch_from_chatting' type creates a task that will switch the entity from the chatting activity when the dialogue has been closed with that NPC.
- `activity`: The activity to switch to when the NPC is no longer chatting. Defaults to `minecraft:idle`.
It will rely on the brain schedule to decide what activity to switch back to (usually idle).
### look_at_speaker
The 'look_at_speaker' type creates a task that will set the entity's look target to the speaker of the dialogue.
@@ -112,7 +116,7 @@ The 'switch_to_action_effect' type creates a task that will switch the entity to
### switch_from_action_effect
The 'switch_from_action_effect' type creates a task that will switch the entity from the action effect activity when it is no longer performing an action effect.
- `activity`: The activity to switch to when the NPC is no longer performing an action effect. Defaults to `minecraft:idle`.
It will rely on the brain schedule to decide what activity to switch back to (usually idle).
### switch_to_fight
The 'switch_to_fight' type creates a task that will switch the entity to the fight activity when it has an attack target.
@@ -120,8 +124,7 @@ The 'switch_to_fight' type creates a task that will switch the entity to the fig
- `activity`: The activity to switch to when the NPC is in a fight. Defaults to `minecraft:fight`.
### switch_from_fight
The 'switch_from_fight' type creates a task that will switch the entity from the fight activity when it no longer has an attack target.
- `activity`: The activity to switch to when the NPC is no longer in a fight. Defaults to `minecraft:idle`.
The 'switch_from_fight' type creates a task that will switch the entity from the fight activity when it no longer has an attack target. It will rely on the brain schedule to decide what activity to switch back to (usually idle).
### get_angry_at_attacker
The 'get_angry_at_attacker' type creates a task that will make the entity get angry at the attacker when it is attacked.
@@ -143,4 +146,30 @@ The 'move_to_attack_target' type creates a task that will make the entity move t
The 'melee_attack' type creates a task that will make the entity perform a melee attack on its attack target when it is close enough. The damage dealt is determined by entity properties, which cannot be easily configured at time of writing.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `range`: A MoLang expression that determines the maximum distance from the attack target that the entity can be to perform the attack. Defaults to 1.5.
- `cooldownTicks`: A MoLang expression that determines the number of ticks to wait before performing another attack. Defaults to 30.
- `cooldownTicks`: A MoLang expression that determines the number of ticks to wait before performing another attack. Defaults to 30.
### switch_to_panic_when_hurt
The 'switch_to_panic_when_hurt' type creates a task that will switch the entity to the panic activity when it is hurt.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `includePassiveDamage`: A boolean that determines if passive damage (like poison or cacti) should trigger this task. Defaults to false.
### switch_to_panic_when_hostiles_nearby
The 'switch_to_panic_when_hostiles_nearby' type creates a task that will switch the entity to the panic activity when hostiles are nearby. The definition of nearby depends
on the entity type and this piggy-backs off the Minecraft list of aggressive mobs and the right distances.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
### calm_down
The 'calm_down' type creates a task that will make the entity calm down when it is no longer being hurt and is far from hostiles and its attacker.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
### flee_attacker
The 'flee_attacker' type creates a task that will make the entity flee from its attacker when it is hurt.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the attacker. Defaults to 0.5 which is an elevated speed to usual.
- `desiredDistance`: A MoLang expression that determines the distance from the attacker that the entity should try to maintain. Defaults to 9.
### flee_nearest_hostile
The 'flee_nearest_hostile' type creates a task that will make the entity flee from the nearest hostile entity when it is hurt.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the nearest hostile. Defaults to 0.5 which is an elevated speed to usual.
- `desiredDistance`: A MoLang expression that determines the distance from the nearest hostile that the entity should try to maintain. Defaults to 9.
@@ -8,15 +8,24 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.entity.npc.ai.SwitchFromActionEffectTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.schedule.Activity
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
class SwitchFromActionEffectTaskConfig : SingleTaskConfig {
val activity = Activity.IDLE
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
) = SwitchFromActionEffectTask.create(activity)
) = BehaviorBuilder.create {
it.group(
it.absent(CobblemonMemories.ACTIVE_ACTION_EFFECT)
).apply(it) { _ ->
Trigger { level, entity, _ ->
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
return@Trigger true
}
}
}
}
@@ -13,10 +13,8 @@ import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.schedule.Activity
class SwitchFromChattingTaskConfig : SingleTaskConfig {
val activity = Activity.IDLE
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
@@ -24,8 +22,8 @@ class SwitchFromChattingTaskConfig : SingleTaskConfig {
it.group(
it.absent(CobblemonMemories.DIALOGUES)
).apply(it) { _ ->
Trigger { world, entity, _ ->
entity.brain.setActiveActivityIfPossible(activity)
Trigger { level, entity, _ ->
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
return@Trigger true
}
}
@@ -13,10 +13,8 @@ import net.minecraft.world.entity.LivingEntity
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
import net.minecraft.world.entity.schedule.Activity
class SwitchFromFightTaskConfig : SingleTaskConfig {
val activity = Activity.IDLE
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
@@ -24,8 +22,8 @@ class SwitchFromFightTaskConfig : SingleTaskConfig {
it.group(
it.absent(MemoryModuleType.ATTACK_TARGET)
).apply(it) { _ ->
Trigger { world, entity, _ ->
entity.brain.setActiveActivityIfPossible(activity)
Trigger { level, entity, _ ->
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
return@Trigger true
}
}
@@ -8,18 +8,25 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.entity.npc.ai.SwitchFromBattleTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.schedule.Activity
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
class SwitchFromNPCBattleTaskConfig : SingleTaskConfig {
val activity: Activity = Activity.IDLE
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
return SwitchFromBattleTask.create(activity)
return BehaviorBuilder.create {
it.group(it.absent(CobblemonMemories.NPC_BATTLING)).apply(it) { _ ->
Trigger { level, entity, _ ->
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
true
}
}
}
}
}
@@ -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.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.util.asExpression
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
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
import net.minecraft.world.entity.schedule.Activity
class SwitchToPanicWhenHostilesNearbyTaskConfig : SingleTaskConfig {
var condition = "true".asExpression()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
) = BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.NEAREST_HOSTILE))
.apply(it) { nearestHostile ->
Trigger { world, entity, _ ->
(entity as? PathfinderMob)?.navigation?.stop()
entity.brain.setActiveActivityIfPossible(Activity.PANIC)
return@Trigger true
}
}
}
}
@@ -0,0 +1,59 @@
/*
* 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.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
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.ai.memory.MemoryModuleType
import net.minecraft.world.entity.schedule.Activity
class SwitchToPanicWhenHurtTaskConfig : SingleTaskConfig {
var condition = "true".asExpression()
var includePassiveDamage = false
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
if (!runtime.resolveBoolean(condition)) return null
return if (includePassiveDamage) {
BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.HURT_BY))
.apply(it) { _ ->
Trigger { world, entity, _ ->
(entity as? PathfinderMob)?.navigation?.stop()
entity.brain.setActiveActivityIfPossible(Activity.PANIC)
return@Trigger true
}
}
}
} else {
BehaviorBuilder.create {
it.group(it.present(MemoryModuleType.HURT_BY_ENTITY))
.apply(it) { _ ->
Trigger { world, entity, _ ->
(entity as? PathfinderMob)?.navigation?.stop()
entity.brain.setActiveActivityIfPossible(Activity.PANIC)
return@Trigger true
}
}
}
}
}
}
@@ -52,6 +52,12 @@ fun interface TaskConfig {
"look_at_speaker" to LookAtSpeakerTaskConfig::class.java,
"switch_to_action_effect" to SwitchToActionEffectTaskConfig::class.java,
"switch_from_action_effect" to SwitchFromActionEffectTaskConfig::class.java,
"exit_battle_when_hurt" to ExitBattleWhenHurtTaskConfig::class.java,
"switch_to_panic_when_hurt" to SwitchToPanicWhenHurtTaskConfig::class.java,
"switch_to_panic_when_hostiles_nearby" to SwitchToPanicWhenHostilesNearbyTaskConfig::class.java,
"calm_down" to CalmDownTaskConfig::class.java,
"flee_attacker" to FleeAttackerTaskConfig::class.java,
"flee_nearest_hostile" to FleeNearestHostileTaskConfig::class.java,
)
val runtime = MoLangRuntime().setup()
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.battles.model
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonNetwork
@@ -22,7 +21,6 @@ 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.moves.Moves
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
@@ -51,17 +49,16 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.network.chat.Component
import com.cobblemon.mod.common.util.giveOrDropItemStack
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.resources.ResourceLocation
import java.io.File
import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedDeque
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
@@ -271,22 +268,31 @@ open class PokemonBattle(
else -> continue
}
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
if (experience > 0) {
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
opponent.awardExperience(opponentPokemon, experience)
}
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
pokemon.evs.add(stat, amount)
}
}
}
}
if(actor.itemsUsed.isNotEmpty() && actor.getPlayerUUIDs().count() > 0) {
if (actor.itemsUsed.isNotEmpty() && actor.getPlayerUUIDs().count() > 0) {
val player = actor.getPlayerUUIDs().first().getPlayer()
player?.level()?.itemRegistry.let { registry ->
actor.itemsUsed.mapNotNull { registry?.get(ResourceLocation.tryBySeparator(it.itemName.substringAfter('.'), '.')) }
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
}
}
// Heal NPC's team if enabled
if (actor is NPCBattleActor) {
if (actor.npc.npc.autoHealParty) {
actor.npc.party?.heal()
}
}
}
// Heal Mon if wild
actors.filter { it.type == ActorType.WILD }
@@ -40,6 +40,7 @@ abstract class BattleActor(
lateinit var showdownId: String
lateinit var battle: PokemonBattle
fun isInitialized() = this::battle.isInitialized && this::showdownId.isInitialized
val activePokemon = mutableListOf<ActiveBattlePokemon>()
var canDynamax = false
@@ -100,7 +100,7 @@ object CobblemonEvents {
// instructions
@JvmField
val MEGA_EVOLUTON = EventObservable<MegaEvolutionEvent>()
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@JvmField
val TERASTALLIZATION = EventObservable<TerastallizationEvent>()
@JvmField
@@ -10,16 +10,14 @@ package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BaitEffectFunctionRegistryEvent
import com.cobblemon.mod.common.api.fishing.FishingBait.Effect
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.codec.kotlinOptionalFieldOf
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.core.Registry
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.nbt.StringTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
@@ -36,11 +34,13 @@ data class FishingBait(
val chance: Double = 0.0,
val value: Double = 0.0
) {
constructor(type: ResourceLocation, subcategory: Optional<ResourceLocation>, chance: Double, value: Double) : this(type, subcategory.orElse(null), chance, value)
companion object {
val CODEC = RecordCodecBuilder.create<Effect> { instance ->
instance.group(
ResourceLocation.CODEC.fieldOf("type").forGetter { it.type },
ResourceLocation.CODEC.kotlinOptionalFieldOf("subcategory").forGetter { it.subcategory },
ResourceLocation.CODEC.optionalFieldOf("subcategory").forGetter { Optional.ofNullable(it.subcategory) },
Codec.DOUBLE.fieldOf("chance").forGetter { it.chance },
Codec.DOUBLE.fieldOf("value").forGetter { it.value }
).apply(instance, ::Effect)
@@ -27,9 +27,16 @@ import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
@@ -43,6 +50,7 @@ import java.util.UUID
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.DoubleTag
@@ -125,15 +133,12 @@ object MoLangFunctions {
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("username") { _ -> StringValue(player.gameProfile.name) }
map.put("uuid") { _ -> StringValue(player.gameProfile.id.toString()) }
map.put("data") { _ -> return@put if (player is ServerPlayer) Cobblemon.molangData.load(player.uuid) else DoubleValue(0) }
map.put("save_data") { _ -> if (player is ServerPlayer) Cobblemon.molangData.save(player.uuid) else DoubleValue(0) }
map.put("main_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.mainHandItem.item).asMoLangValue(Registries.ITEM) }
map.put("off_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.offhandItem.item).asMoLangValue(Registries.ITEM) }
map.put("face") { params -> ObjectValue(PlayerDialogueFaceProvider(player.uuid, params.getBooleanOrNull(0) != false)) }
map.put("swing_hand") { _ -> player.swing(player.usedItemHand) }
map.put("food_level") { _ -> DoubleValue(player.foodData.foodLevel) }
map.put("saturation_level") { _ -> DoubleValue(player.foodData.saturationLevel) }
// map.put("has_permission") { params -> DoubleValue(Cobblemon.permissionValidator.hasPermission(player, Permission)) }
map.put("tell") { params ->
val message = params.getString(0).text()
val overlay = params.getBooleanOrNull(1) ?: false
@@ -176,6 +181,13 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("party") { player.party().struct }
map.put("pc") { player.pc().struct }
map.put("has_permission") { params -> DoubleValue(Cobblemon.permissionValidator.hasPermission(player, params.getString(0), params.getIntOrNull(1) ?: 4)) }
map.put("data") { _ -> Cobblemon.molangData.load(player.uuid) }
map.put("save_data") { _ -> Cobblemon.molangData.save(player.uuid) }
map.put("in_battle") { DoubleValue(player.isInBattle()) }
map.put("battle") { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
}
map
}
@@ -255,6 +267,17 @@ object MoLangFunctions {
map.put("has_aspect") { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
map.put("is_doing_activity") { params ->
val identifiers = params.params.map { it.asString().asIdentifierDefaultingNamespace(namespace = "minecraft") }
val activities = identifiers.mapNotNull { identifier -> BuiltInRegistries.ACTIVITY.get(identifier) }
if (activities.isNotEmpty()) {
return@put DoubleValue(activities.any { activity -> npc.brain.isActive(activity) })
} else {
return@put DoubleValue.ZERO
}
}
map.put("run_script_on_client") { params ->
val world = npc.level()
if (world is ServerLevel) {
@@ -311,6 +334,8 @@ object MoLangFunctions {
npc.lookAt(EntityAnchorArgument.Anchor.EYES, pos)
}
map.put("environment") { _ -> npc.runtime.environment }
map.put("party") { npc.party?.struct ?: DoubleValue.ZERO }
map.put("has_party") { DoubleValue(npc.party != null) }
map
}
)
@@ -329,6 +354,7 @@ object MoLangFunctions {
val actor = battle.actors.find { it.uuid == uuid } ?: return@put DoubleValue.ZERO
return@put actor.struct
}
map.put("stop") { _ -> battle.stop() }
map
}
)
@@ -336,10 +362,30 @@ object MoLangFunctions {
val pokemonFunctions = mutableListOf<(Pokemon) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pokemon ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("id") { StringValue(pokemon.uuid.toString()) }
map.put("level") { DoubleValue(pokemon.level.toDouble()) }
map.put("max_hp") { DoubleValue(pokemon.maxHealth.toDouble()) }
map.put("current_hp") { DoubleValue(pokemon.currentHealth.toDouble()) }
map.put("friendship") { DoubleValue(pokemon.friendship.toDouble()) }
map.put("evs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.evs.getOrDefault(stat).toDouble()) }
}
struct
}
map.put("ivs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.getOrDefault(stat).toDouble()) }
}
struct
}
map.put("is_wild") { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
map.put("is_shiny") { DoubleValue(pokemon.shiny) }
map.put("form") { StringValue(pokemon.form.name) }
map.put("weight") { DoubleValue(pokemon.species.weight.toDouble()) }
map.put("matches") { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
map
}
)
@@ -355,21 +401,140 @@ object MoLangFunctions {
}
)
val pokemonStoreFunctions = mutableListOf<(PokemonStore<*>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ store ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("add") { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0)
return@put DoubleValue(store.add(pokemon.obj))
}
map.put("add_by_properties") { params ->
val props = params.getString(0).toProperties()
val player = (store as? PlayerPartyStore)?.playerUUID?.getPlayer() // Only really know for sure when it's a player party store
val pokemon = props.create(player = player)
return@put DoubleValue(store.add(pokemon))
}
map.put("find_by_properties") { params ->
val props = params.getString(0).toProperties()
val pokemon = store.find { props.matches(it) }
return@put pokemon?.asStruct() ?: DoubleValue.ZERO
}
map.put("find_all_by_properties") { params ->
val props = params.getString(0).toProperties()
val pokemon = store.filter { props.matches(it) }
return@put ArrayStruct(pokemon.mapIndexed { index, value -> "$index" to value.asStruct() }.toMap())
}
map.put("find_by_id") { params ->
val id = params.getString(0).asUUID
val pokemon = store.find { it.uuid == id }
return@put pokemon?.asStruct() ?: DoubleValue.ZERO
}
map.put("remove_by_id") { params ->
val id = params.getString(0).asUUID
val pokemon = store.find { it.uuid == id } ?: return@put DoubleValue.ZERO
return@put DoubleValue(store.remove(pokemon))
}
map.put("average_level") { _ ->
var numberOfPokemon = 0
var totalLevel = 0
for (pokemon in store) {
totalLevel += pokemon.level
numberOfPokemon++
}
if (numberOfPokemon == 0) {
return@put DoubleValue.ZERO
}
return@put DoubleValue(totalLevel.toDouble() / numberOfPokemon)
}
map.put("count") { _ -> DoubleValue(store.count()) }
map.put("count_by_properties") { params ->
val props = params.getString(0).toProperties()
return@put DoubleValue(store.count { props.matches(it) })
}
map.put("highest_level") {
val highest = store.maxOfOrNull { it.level } ?: 0
return@put DoubleValue(highest)
}
map.put("lowest_level") {
val lowest = store.minOfOrNull { it.level } ?: 0
return@put DoubleValue(lowest)
}
map.put("heal") {
for (pokemon in store) {
pokemon.heal()
}
return@put DoubleValue.ONE
}
map.put("healing_remainder_percent") { _ ->
var totalPercent = 0.0f
for (pokemon in store) {
totalPercent += (1.0f - (pokemon.currentHealth.toFloat() / pokemon.maxHealth))
}
DoubleValue(totalPercent)
}
map.put("has_usable_pokemon") { _ -> DoubleValue(store.any { !it.isFainted() }) }
map
}
)
val partyFunctions = mutableListOf<(PartyStore) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ party ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("get_pokemon") { params ->
val index = params.getInt(0)
val pokemon = party.get(index) ?: return@put DoubleValue.ZERO
val struct = VariableStruct()
pokemon.writeVariables(struct)
return@put struct
return@put pokemon.struct
}
map.put("healing_remainder_percent") { _ -> DoubleValue(party.getHealingRemainderPercent()) }
return@mutableListOf map
})
val pcFunctions = mutableListOf<(PCStore) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pc ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("get_pokemon") { params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = pc[PCPosition(box, slot)] ?: return@put DoubleValue.ZERO
return@put pokemon.struct
}
map.put("resize") { params ->
val newSize = params.getInt(0)
val lockNewSize = params.getBooleanOrNull(1) == true
pc.resize(newSize, lockNewSize)
return@put DoubleValue.ONE
}
map.put("get_box_count") { _ -> DoubleValue(pc.boxes.size.toDouble()) }
return@mutableListOf map
}
)
val spawningContextFunctions = mutableListOf<(SpawningContext) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ spawningContext ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(spawningContext.world).asWorldMoLangValue()
val biomeValue = spawningContext.biomeRegistry.wrapAsHolder(spawningContext.biome).asBiomeMoLangValue()
map.put("biome") { _ -> biomeValue }
map.put("world") { _ -> worldValue }
map.put("light") { _ -> DoubleValue(spawningContext.light.toDouble()) }
map.put("x") { _ -> DoubleValue(spawningContext.position.x.toDouble()) }
map.put("y") { _ -> DoubleValue(spawningContext.position.y.toDouble()) }
map.put("z") { _ -> DoubleValue(spawningContext.position.z.toDouble()) }
map.put("moon_phase") { _ -> DoubleValue(spawningContext.moonPhase.toDouble()) }
map.put("can_see_sky") { _ -> DoubleValue(spawningContext.canSeeSky) }
map.put("sky_light") { _ -> DoubleValue(spawningContext.skyLight.toDouble()) }
map.put("bucket") { _ -> StringValue(spawningContext.cause.bucket.name) }
map.put("player") { _ ->
val causeEntity = spawningContext.cause.entity ?: return@put DoubleValue.ZERO
if (causeEntity is ServerPlayer) {
return@put causeEntity.asMoLangValue()
} else {
return@put DoubleValue.ZERO
}
}
map
}
)
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions)
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions)
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions)
@@ -384,6 +549,33 @@ object MoLangFunctions {
return value
}
// We need to migrate the writeVariables thing to be all about query structs, variable doesn't make sense and I don't want to break fringe 1.6 compatibility issues close to release
fun Pokemon.asStruct(): ObjectValue<Pokemon> {
val queryStruct = ObjectValue(this)
queryStruct.addFunctions(pokemonFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return queryStruct
}
fun PartyStore.asMoLangValue(): ObjectValue<PartyStore> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(pokemonStoreFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(partyFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun PCStore.asMoLangValue(): ObjectValue<PCStore> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(pokemonStoreFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(pcFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun NPCEntity.asMoLangValue(): ObjectValue<NPCEntity> {
val value = ObjectValue(
obj = this,
@@ -403,6 +595,15 @@ object MoLangFunctions {
return value
}
fun SpawningContext.asMoLangValue(): ObjectValue<SpawningContext> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(spawningContextFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun <T> Holder<T>.asMoLangValue(key: ResourceKey<Registry<T>>): ObjectValue<Holder<T>> {
val value = ObjectValue(
obj = this,
@@ -51,6 +51,9 @@ class NPCClass {
var battleTheme: ResourceLocation? = null
var ai: MutableList<BrainConfig> = mutableListOf()
var isMovable: Boolean = true
var isInvulnerable = false
var isLeashable = true
var allowProjectileHits = true
// If you're adding stuff here, add it to NPCPreset and NPCClassAdapter too
@@ -86,6 +89,9 @@ class NPCClass {
}
buffer.writeNullable(battleTheme) { _, v -> buffer.writeIdentifier(v) }
buffer.writeBoolean(isMovable)
buffer.writeBoolean(isInvulnerable)
buffer.writeBoolean(isLeashable)
buffer.writeBoolean(allowProjectileHits)
}
fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -132,5 +138,8 @@ class NPCClass {
}
battleTheme = buffer.readNullable { buffer.readIdentifier() }
isMovable = buffer.readBoolean()
isInvulnerable = buffer.readBoolean()
isLeashable = buffer.readBoolean()
allowProjectileHits = buffer.readBoolean()
}
}
@@ -32,6 +32,9 @@ class NPCPreset {
var autoHealParty: Boolean? = null
var battleTheme: ResourceLocation? = null
var isMovable: Boolean? = null
var isInvulnerable: Boolean? = null
var isLeashable: Boolean? = null
var allowProjectileHits: Boolean? = null
fun applyTo(npcClass: NPCClass) {
resourceIdentifier?.let { npcClass.resourceIdentifier = it }
@@ -53,5 +56,8 @@ class NPCPreset {
battleTheme?.let { npcClass.battleTheme = it }
ai?.let { npcClass.ai.addAll(it) }
isMovable?.let { npcClass.isMovable = it }
isInvulnerable?.let { npcClass.isInvulnerable = it }
isLeashable?.let { npcClass.isLeashable = it }
allowProjectileHits?.let { npcClass.allowProjectileHits = it }
}
}
@@ -349,6 +349,21 @@ Whether or not the NPC's party will be healed between battles. If the NPC has a
do nothing. If the NPC has a static party and this option is false, their Pokémon will not be healed and the
NPC will have to use healing machines to heal them if they have that enabled in their AI.
### isMovable
Controls whether the NPC can be moved by players or other entities. If disabled, the NPC will remain
stationary.
### isInvulnerable
Controls whether the NPC is immune to all damage sources.
### isLeashable
Controls whether players can attach a lead to a NPC. If disabled, players will be unable to leash
the NPC and move them using leads.
### allowProjectileHits
Controls whether the NPC can be hit by projectiles. Disabling this option prevents unwanted
interactions, such as players moving the NPC with fishing rods or other projectiles.
### ai
The AI property is an array of brain configurations. This is used to configure the behaviours of the NPC. See
the documentation for brain configurations at [AI Configuration](../ai/config/README.md).
@@ -76,6 +76,7 @@ object CobblemonPermissions {
val POKEDEX = this.create("${COMMAND_PREFIX}pokedex", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val NPC_EDIT = this.create("${COMMAND_PREFIX}npcedit", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val NPC_DELETE = this.create("${COMMAND_PREFIX}npcdelete", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val FREEZE_POKEMON = this.create("${COMMAND_PREFIX}freezepokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val APPLY_PLAYER_TEXTURE = this.create("${COMMAND_PREFIX}applyplayertexture", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -22,6 +22,11 @@ enum class PermissionLevel(val numericalValue: Int) {
SPAWN_PROTECTION_BYPASS(1),
CHEAT_COMMANDS_AND_COMMAND_BLOCKS(2),
MULTIPLAYER_MANAGEMENT(3),
ALL_COMMANDS(4)
ALL_COMMANDS(4);
companion object {
fun byNumericValue(value: Int): PermissionLevel {
return entries.first { it.numericalValue == value }
}
}
}
@@ -24,7 +24,6 @@ interface PermissionValidator {
/**
* Invoked when the validator replaces the existing one in [Cobblemon.permissionValidator].
*
*/
fun initialize()
@@ -36,6 +35,14 @@ interface PermissionValidator {
* @return If the [player] has the [permission].
*/
fun hasPermission(player: ServerPlayer, permission: Permission): Boolean
/**
* Validates a permission for [ServerPlayer] based only on a permission string and a permission level.
*
* @param player The target [ServerPlayer].
* @param permission The permission string being queried such as cobblemon.command.giveallpokemon.
* @param level The permission level being queried. 4 is generally used for cheats.
*/
fun hasPermission(player: ServerPlayer, permission: String, level: Int): Boolean = hasPermission(player, CobblemonPermission(permission, PermissionLevel.byNumericValue(level)))
/**
* Validates a permission for [CommandSource].
@@ -45,5 +52,12 @@ interface PermissionValidator {
* @return If the [source] has the [permission].
*/
fun hasPermission(source: CommandSourceStack, permission: Permission): Boolean
/**
* Validates a permission for [CommandSourceStack] based only on a permission string and a permission level.
*
* @param source The target [CommandSourceStack].
* @param permission The permission string being queried such as cobblemon.command.giveallpokemon.
* @param level The permission level being queried. 4 is generally used for cheats.
*/
fun hasPermission(source: CommandSourceStack, permission: String, level: Int): Boolean = hasPermission(source, CobblemonPermission(permission, PermissionLevel.byNumericValue(level)))
}
@@ -133,7 +133,7 @@ object CatchRateModifiers {
*/
val LURE = WorldStateModifier { _, entity ->
if (FishingSpawnCause.FISHED_ASPECT in entity.aspects)
4f
4F
else
1F
}
@@ -8,9 +8,12 @@
package com.cobblemon.mod.common.api.spawning
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
@@ -69,6 +72,9 @@ object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.create()
val presetTypes = mutableMapOf<String, Class<out SpawnDetailPreset>>()
@@ -8,9 +8,12 @@
package com.cobblemon.mod.common.api.spawning
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
@@ -68,6 +71,9 @@ object SpawnLoader {
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.create()
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
@@ -8,9 +8,10 @@
package com.cobblemon.mod.common.api.spawning.context
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
@@ -55,6 +56,10 @@ abstract class SpawningContext {
}
}
@Transient
val runtime = MoLangRuntime().setup()
.also { runtime -> runtime.environment.query.addFunction("context") { getOrSetupStruct() } }
/** What caused the spawn context, as a [SpawnCause]. */
abstract val cause: SpawnCause
val spawner: Spawner
@@ -87,7 +92,7 @@ abstract class SpawningContext {
val biomeName: ResourceLocation
get() = this.biomeRegistry.getKey(biome)!!
private val struct = VariableStruct()
private var struct = QueryStruct(hashMapOf())
private var structCompiled = false
class StructureChunkCache {
@@ -168,19 +173,11 @@ abstract class SpawningContext {
return weight
}
fun getOrSetupStruct(): VariableStruct {
fun getOrSetupStruct(): QueryStruct {
if (structCompiled) {
return struct
}
struct.setDirectly("light", DoubleValue(light.toDouble()))
struct.setDirectly("x", DoubleValue(position.x.toDouble()))
struct.setDirectly("y", DoubleValue(position.y.toDouble()))
struct.setDirectly("z", DoubleValue(position.z.toDouble()))
struct.setDirectly("moon_phase", DoubleValue(moonPhase.toDouble()))
struct.setDirectly("world", ObjectValue(world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(world)))
struct.setDirectly("biome", ObjectValue(biomeRegistry.wrapAsHolder(biome)))
struct = this.asMoLangValue()
structCompiled = true
return struct
}
@@ -8,10 +8,10 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.party
import net.minecraft.server.level.ServerPlayer
import com.cobblemon.mod.common.util.resolveInt
/**
* A [SpawnAction] for creating [NPCEntity]s.
@@ -19,13 +19,15 @@ import net.minecraft.server.level.ServerPlayer
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SpawnAction<EntitySpawnResult>(ctx, detail) {
override fun run(): EntitySpawnResult {
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(ctx, detail) {
override fun createEntity(): NPCEntity {
val npc = NPCEntity(ctx.world)
npc.npc = detail.npcClass
npc.appliedAspects.addAll(detail.aspects)
val seedLevel = (ctx.cause.entity as? ServerPlayer)?.let { it.party().maxOfOrNull { it.level } } ?: 10
val minLevel = ctx.runtime.resolveInt(detail.minLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val maxLevel = ctx.runtime.resolveInt(detail.maxLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val seedLevel = (minLevel..maxLevel).random()
npc.initialize(seedLevel)
return EntitySpawnResult(listOf(npc))
return npc
}
}
@@ -8,10 +8,11 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.npc.NPCConfiguration
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
/**
@@ -30,8 +31,9 @@ class NPCSpawnDetail : SpawnDetail() {
@SerializedName(value = "npcClass", alternate = ["class", "npc"])
val npcClass: NPCClass = blankClass
val configuration: NPCConfiguration? = null
val aspects: Set<String> = emptySet()
val minLevel: Expression = "1".asExpression()
val maxLevel: Expression = "100".asExpression()
override fun doSpawn(ctx: SpawningContext) = NPCSpawnAction(ctx, this)
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.storage.party
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
@@ -25,11 +26,11 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonObject
import java.util.Collections
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerPlayer
import java.util.Collections
import net.minecraft.core.RegistryAccess
/**
* A [PokemonStore] for a party of Pokémon. This is a simple structure that by default will hold 6 nullable slots of Pokémon.
@@ -47,6 +48,8 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
/** A list of player UUIDs representing players that are observing this store. This is NOT serialized/deserialized. */
var observerUUIDs = mutableListOf<UUID>()
val struct = asMoLangValue()
override fun iterator() = slots.filterNotNull().iterator()
/** Gets the Pokémon at the specified slot. It will return null if the slot is empty or the given slot is out of bounds. */
fun get(slot: Int) = slot.takeIf { it < slots.size && it >= 0 }?.let { slots[it] }
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.storage.pc
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.BottomlessStore
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -23,11 +24,11 @@ import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.lang
import com.google.gson.JsonObject
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import java.util.*
import net.minecraft.core.RegistryAccess
/**
* The store used for PCs. It is divided into some number of [PCBox]es, and can
@@ -54,6 +55,9 @@ open class PCStore(
override fun iterator() = boxes.flatMap { it.toList() }.iterator()
override fun getObservingPlayers() = observingUUIDs.mapNotNull { it.getPlayer() }
val struct = asMoLangValue()
fun addObserver(player: ServerPlayer) {
observingUUIDs.add(player.uuid)
sendTo(player)
@@ -105,6 +105,9 @@ object BattleBuilder {
}
}
player1Actor.battleTheme = player2.getBattleTheme()
player2Actor.battleTheme = player1.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -112,8 +115,6 @@ object BattleBuilder {
side1 = BattleSide(player1Actor),
side2 = BattleSide(player2Actor)
).ifSuccessful {
player1Actor.battleTheme = player2.getBattleTheme()
player2Actor.battleTheme = player1.getBattleTheme()
it.battlePartyStores.addAll(battlePartyStores)
result = SuccessfulBattleStart(it)
}
@@ -197,19 +198,18 @@ object BattleBuilder {
playerActors.swap(2,3)
}
// TODO: less hard coding
playerActors[0].battleTheme = players[2].getBattleTheme()
playerActors[1].battleTheme = players[2].getBattleTheme()
playerActors[2].battleTheme = players[0].getBattleTheme()
playerActors[3].battleTheme = players[0].getBattleTheme()
return if (errors.isEmpty) {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActors[0], playerActors[1]),
side2 = BattleSide(playerActors[2], playerActors[3])
).ifSuccessful {
// TODO: less hard coding
playerActors[0].battleTheme = players[2].getBattleTheme()
playerActors[1].battleTheme = players[2].getBattleTheme()
playerActors[2].battleTheme = players[0].getBattleTheme()
playerActors[3].battleTheme = players[0].getBattleTheme()
it.battlePartyStores.addAll(battlePartyStores)
}
errors
@@ -272,6 +272,8 @@ object BattleBuilder {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
playerActor.battleTheme = pokemonEntity.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -282,7 +284,6 @@ object BattleBuilder {
if (!cloneParties) {
pokemonEntity.battleId = it.battleId
}
playerActor.battleTheme = pokemonEntity.getBattleTheme()
result = SuccessfulBattleStart(it)
}
result
@@ -347,6 +348,8 @@ object BattleBuilder {
)
}
playerActor.battleTheme = npcEntity.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -354,7 +357,6 @@ object BattleBuilder {
side1 = BattleSide(playerActor),
side2 = BattleSide(npcActor)
).ifSuccessful { battle ->
playerActor.battleTheme = npcEntity.getBattleTheme()
npcEntity.entityData.update(NPCEntity.BATTLE_IDS) { it + battle.battleId }
result = SuccessfulBattleStart(battle)
}
@@ -24,11 +24,12 @@ import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.phys.Vec3
class PlayerBattleActor(
uuid: UUID,
pokemonList: List<BattlePokemon>
pokemonList: List<BattlePokemon>,
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
override val initialPos: Vec3?
@@ -41,8 +42,7 @@ class PlayerBattleActor(
/** The [SoundEvent] to play to the player during a battle. Will start playing as soon as the battle starts. */
var battleTheme: SoundEvent? = null
set(value) {
if (field != value && this.battle.started)
this.sendUpdate(BattleMusicPacket(value))
if (this.isInitialized() && this.battle.started) this.sendUpdate(BattleMusicPacket(value))
field = value
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -30,7 +30,7 @@ class MegaInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("mega", pokemonName).yellow())
CobblemonEvents.MEGA_EVOLUTON.post(MegaEvolutionEvent(battle, battlePokemon))
CobblemonEvents.MEGA_EVOLUTION.post(MegaEvolutionEvent(battle, battlePokemon))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.battles.pokemon
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
@@ -107,9 +106,5 @@ open class BattlePokemon(
!isSentOut() && !willBeSwitchedIn && health > 0
}
fun writeVariables(struct: VariableStruct) {
effectedPokemon.writeVariables(struct)
}
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -173,10 +173,6 @@ class DisplayCaseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cob
override fun getMaxStackSize() = 1
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, dir: Direction?): Boolean {
if(dir == Direction.DOWN) return false
return getStack().isEmpty
}
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, dir: Direction?): Boolean = (getStack().isEmpty)
override fun canTakeItemThroughFace(slot: Int, stack: ItemStack, direction: Direction): Boolean = (direction == Direction.DOWN)
}
@@ -156,7 +156,7 @@ object CobblemonClient {
return@subscribe
}
val nearbyShinies = player?.level()?.getEntities(player, AABB.ofSize(player.position(), 16.0, 16.0, 16.0)) { (it is PokemonEntity) && it.pokemon.shiny }
nearbyShinies?.firstOrNull { player.isLookingAt(it) }.let {
nearbyShinies?.firstOrNull { player.isLookingAt(it) && !player.isSpectator }.let {
if(it is PokemonEntity)
it.delegate.spawnShinyParticle(player!!)
}
@@ -379,7 +379,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
val player = Minecraft.getInstance().player ?: return
val isWithinRange = player.position().distanceTo(currentEntity.position()) <= Cobblemon.config.shinyNoticeParticlesDistance
if (currentEntity.pokemon.shiny && currentEntity.ownerUUID == null) {
if (currentEntity.pokemon.shiny && currentEntity.ownerUUID == null && !player.isSpectator ) {
if (isWithinRange && !shined) {
playShinyEffect("cobblemon:wild_shiny_ring")
shined = true
@@ -47,8 +47,8 @@ class BattleSwitchPokemonSelection(
battleGUI,
request,
x = 0,
y = if (Minecraft.getInstance().window.guiScaledHeight > 630) Minecraft.getInstance().window.guiScaledHeight / 2 - 148 / 2
else Minecraft.getInstance().window.guiScaledHeight - 226,
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
width = Minecraft.getInstance().window.guiScaledWidth,
height = Minecraft.getInstance().window.guiScaledHeight,
battleLang("switch_pokemon")
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -47,8 +48,8 @@ class BattleTargetSelection(
battleGUI = battleGUI,
request = request,
x = 0,
y = if (Minecraft.getInstance().window.guiScaledHeight > 630) Minecraft.getInstance().window.guiScaledHeight / 2 - 148 / 2
else Minecraft.getInstance().window.guiScaledHeight - 226,
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BattleSwitchPokemonSelection.BACKGROUND_HEIGHT / 2)
else Minecraft.getInstance().window.guiScaledHeight - (BattleSwitchPokemonSelection.BACKGROUND_HEIGHT + 78),
width = 100,
height = 100,
battleLang("ui.select_move")
@@ -368,7 +368,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrices.translate(
pX.toDouble() + (POKEMON_PORTRAIT_WIDTH.toDouble() + 2)/2,
pY.toDouble() + portraitStartY - 12,
0.0
1000.0 // Prevent model from clipping into background
)
matrices.scale(scaleAmount, scaleAmount, scaleAmount)
val rotationVector = Vector3f(13F, rotationY, 0F)
@@ -398,7 +398,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
matrices.translate(0.0, 0.0, 2000.0)
if (isSelectedPokemonOwned()) {
val primaryType = type[0]
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
@@ -17,9 +18,11 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVER
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.Util
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.renderer.RenderType
import net.minecraft.network.chat.Component
class SearchWidget(
@@ -36,20 +39,24 @@ class SearchWidget(
private val searchIcon = cobblemonResource("textures/gui/pokedex/search_icon.png")
}
var focusedTime: Long = 0
init {
this.setMaxLength(24)
this.setResponder {
update.invoke()
}
focusedTime = Util.getMillis()
}
override fun setFocused(focused: Boolean) {
if (focused) focusedTime = Util.getMillis()
super.setFocused(focused)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return if (mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)) {
isFocused = true
true
} else {
false
}
if (mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)) isFocused = true
return super.mouseClicked(mouseX, mouseY, button)
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -73,29 +80,30 @@ class SearchWidget(
scale = SCALE
)
// if(text.isEmpty() && !isFocused) {
// drawScaledText(
// context = context,
// font = CobblemonResources.DEFAULT_LARGE,
// text = Component.translatable("cobblemon.ui.pokedex.search").bold(),
// x = posX.toInt() + 13,
// y = posY.toInt() + 1,
// shadow = true
// )
// }
if (cursorPosition != value.length) moveCursorToEnd(false)
val input = if (isFocused) "${value}_".text()
val showCursor = isFocused && ((Util.getMillis() - this.focusedTime) / 300L % 2L == 0L)
val input = if (isFocused) "${value}${if ((cursorPosition == value.length) && showCursor) "_" else ""}".text()
else (if(value.isEmpty()) Component.translatable("cobblemon.ui.pokedex.search") else value.text())
val startX = posX.toInt() + 13
val startY = posY.toInt() + 1
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = input.bold(),
// text = Component.translatable(if (isFocused) "${text}_" else text).bold(),
x = posX.toInt() + 13,
y = posY.toInt() + 1,
x = startX,
y = startY,
shadow = true
)
if (showCursor && !value.isEmpty() && cursorPosition != value.length) {
val startToCursorWidth = Minecraft.getInstance().font.width((input.getString(cursorPosition).text().bold()).font(CobblemonResources.DEFAULT_LARGE))
context.fill(
RenderType.guiTextHighlight(),
startX + startToCursorWidth - 1,
startY,
startX + startToCursorWidth,
startY + 9,
-3092272
)
}
}
}
}
@@ -46,8 +46,8 @@ class SummaryTab(
texture = icon,
x = (x + 21) / 0.5F,
y = (y + 3.5) / 0.5F,
width = 16,
height = 16,
width = 17,
height = 17,
scale = 0.5F
)
}
@@ -179,8 +179,8 @@ class PokeBobberEntityRenderer(context: EntityRendererProvider.Context?) : Entit
matrixStack.popPose() // close bobber rendering transforms
var armOffset = if (playerEntity.mainArm == HumanoidArm.RIGHT) 1 else -1
val itemStack = playerEntity.mainHandItem
if (itemStack.item !is PokerodItem) {
val item = playerEntity.mainHandItem.item
if ((item !is PokerodItem) || item.pokeRodId != pokeRodId) {
armOffset = -armOffset
}
val handSwingProgress = playerEntity.getAttackAnim(tickDelta)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.render.item
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import net.minecraft.client.renderer.item.ItemProperties
import net.minecraft.resources.ResourceLocation
@@ -71,17 +72,21 @@ object CobblemonModelPredicateRegistry {
rods.forEach { rod ->
ItemProperties.register(rod, ResourceLocation.parse("cast")) { stack, world, entity, seed ->
if (entity == null) {
0.0f
} else {
val isMainHand = entity.mainHandItem == stack
var isOffHand = entity.offhandItem == stack
if (entity.mainHandItem.item is PokerodItem) {
isOffHand = false
}
if (entity !is Player || entity.fishing !is PokeRodFishingBobberEntity) return@register 0.0f
if ((isMainHand || isOffHand) && entity is Player && entity.fishing != null) 1.0f else 0.0f
val rodId = entity.fishing!!.entityData.get(PokeRodFishingBobberEntity.POKEROD_ID)
val isMainHand = stack == entity.mainHandItem
var isOffHand = stack == entity.offhandItem
var mainHandItem = entity.mainHandItem.item
val isFishingWithMainHand = mainHandItem is PokerodItem && rodId == mainHandItem.pokeRodId.toString()
if (isFishingWithMainHand) {
isOffHand = false
}
if (isMainHand && isFishingWithMainHand || isOffHand) 1.0f else 0.0f
}
}
@@ -0,0 +1,42 @@
/*
* 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.command
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.requiresWithPermission
import com.cobblemon.mod.common.util.traceFirstEntityCollision
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.server.level.ServerPlayer
object NPCDeleteCommand {
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(
Commands.literal("npcdelete")
.requiresWithPermission(CobblemonPermissions.NPC_DELETE) { it.player != null }
.executes { execute(it, it.source.playerOrException) })
}
private fun execute(context: CommandContext<CommandSourceStack>, player: ServerPlayer) : Int {
val targetEntity = player.traceFirstEntityCollision(entityClass = NPCEntity::class.java)
if (targetEntity == null) {
player.sendSystemMessage(commandLang("npcedit.non_npc").red())
return 0
}
targetEntity.discard()
return Command.SINGLE_SUCCESS
}
}
@@ -72,6 +72,8 @@ import net.minecraft.world.phys.Vec3
class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity>, world: Level) : FishingHook(type, world) {
private var lastRippleSpawnTime: Long = 0
private val rippleCooldown: Long = 20
private val velocityRandom = RandomSource.create()
private var caughtFish = false
private var outOfOpenWaterTicks = 0
@@ -306,8 +308,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
val l = h * 0.04f
// todo the fish trail that leads to the bobber
serverWorld.sendParticles(ParticleTypes.FISHING, offsetX, offsetY, j, 0, l.toDouble(), 0.01, -k.toDouble(), 1.0)
serverWorld.sendParticles(ParticleTypes.FISHING, offsetX, offsetY, j, 0, -l.toDouble(), 0.01, k.toDouble(), 1.0)
particleCatchHandler(offsetX, offsetY, j, this, ResourceLocation.fromNamespaceAndPath("cobblemon", "fishing_wake"))
// create tiny splash particles for fishing trail
//particleEntityHandler(this, Identifier.of("cobblemon","bob_splash"))
}
@@ -318,6 +319,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
// create tiny splash particle when there is a bite
particleEntityHandler(this, ResourceLocation.fromNamespaceAndPath("cobblemon", "bob_splash"))
particleEntityHandler(this, ResourceLocation.fromNamespaceAndPath("cobblemon", "fishing_bobber_big_ripple"))
val m = this.y + 0.5
serverWorld.sendParticles(ParticleTypes.BUBBLE, this.x, m, this.z, (1.0f + this.bbWidth * 20.0f).toInt(), this.bbWidth.toDouble(), 0.0, this.bbWidth.toDouble(), 0.2)
@@ -365,7 +367,12 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
val j = this.z + (Mth.cos(g) * h).toDouble() * 0.1 // randomized Z value
val blockState = serverWorld.getBlockState(BlockPos.containing(d, e - 1.0, j))
if (blockState.`is`(Blocks.WATER)) {
serverWorld.sendParticles(ParticleTypes.SPLASH, d, e, j, 2 + random.nextInt(2), 0.10000000149011612, 0.0, 0.10000000149011612, 0.0)
val currentTime = serverWorld.gameTime
if (currentTime - lastRippleSpawnTime >= rippleCooldown) {
particleEntityHandler(this, ResourceLocation.fromNamespaceAndPath("cobblemon", "fishing_bobber_ripple"))
lastRippleSpawnTime = currentTime
}
particleEntityHandler(this, ResourceLocation.fromNamespaceAndPath("cobblemon", "fishing_surface_ripple"))
}
}
if (this.waitCountdown <= 0) {
@@ -778,6 +785,12 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
spawnSnowstormParticlePacket.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
}
private fun particleCatchHandler(x: Double, y: Double, z: Double, entity: Entity, particle: ResourceLocation) {
var particlePosition = Vec3(x, y, z)
val spawnSnowstormParticlePacket = SpawnSnowstormParticlePacket(particle, particlePosition)
spawnSnowstormParticlePacket.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
}
enum class TypeCaught {
ITEM,
POKEMON
@@ -79,17 +79,11 @@ class NPCBattleActor(
override fun win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {
super.win(otherWinners, losers)
npc.playAnimation(NPCEntity.WIN_ANIMATION)
if (npc.npc.autoHealParty) {
npc.party?.heal()
}
}
override fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {
super.lose(winners, otherLosers)
npc.playAnimation(NPCEntity.LOSE_ANIMATION)
if (npc.npc.autoHealParty) {
npc.party?.heal()
}
// winners.forEach {
// rewards.forEach {
// winner give reward :))
@@ -52,7 +52,6 @@ import com.google.common.collect.ImmutableList
import com.mojang.authlib.GameProfile
import com.mojang.authlib.ProfileLookupCallback
import com.mojang.serialization.Dynamic
import java.lang.Exception
import java.net.URI
import java.util.UUID
import java.util.concurrent.CompletableFuture
@@ -66,6 +65,7 @@ import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.network.protocol.game.DebugPackets
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.network.syncher.EntityDataSerializers
import net.minecraft.network.syncher.SynchedEntityData
@@ -75,6 +75,7 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.AgeableMob
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.Pose
@@ -119,6 +120,12 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
var isMovable: Boolean? = null
var isInvulnerable: Boolean? = null
var isLeashable: Boolean? = null
var allowProjectileHits: Boolean? = null
fun getPartyForChallenge(player: ServerPlayer): NPCPartyStore? {
val party = this.party
return if (party != null) {
@@ -203,7 +210,8 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
SensorType.HURT_BY,
SensorType.NEAREST_PLAYERS,
CobblemonSensors.BATTLING_POKEMON,
CobblemonSensors.NPC_BATTLING
CobblemonSensors.NPC_BATTLING,
SensorType.VILLAGER_HOSTILES
)
val MEMORY_MODULES: List<MemoryModuleType<*>> = ImmutableList.of(
@@ -223,6 +231,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
MemoryModuleType.ATTACK_COOLING_DOWN,
CobblemonMemories.DIALOGUES,
CobblemonMemories.ACTIVE_ACTION_EFFECT,
MemoryModuleType.NEAREST_HOSTILE
)
const val SEND_OUT_ANIMATION = "send_out"
@@ -294,6 +303,13 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
getBrain().tick(level() as ServerLevel, this)
}
override fun sendDebugPackets() {
super.sendDebugPackets()
DebugPackets.sendEntityBrain(this)
DebugPackets.sendGoalSelector(level(), this, this.goalSelector)
DebugPackets.sendPathFindingPacket(level(), this, this.navigation.path, this.navigation.path?.distToTarget ?: 0F)
}
override fun saveWithoutId(nbt: CompoundTag): CompoundTag {
super.saveWithoutId(nbt)
nbt.put(DataKeys.NPC_DATA, MoLangFunctions.writeMoValueToNBT(data))
@@ -333,6 +349,18 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
if (isMovable != null) {
nbt.putBoolean(DataKeys.NPC_IS_MOVABLE, isMovable)
}
val isInvulnerable = isInvulnerable
if (isInvulnerable != null) {
nbt.putBoolean(DataKeys.NPC_IS_INVULNERABLE, isInvulnerable)
}
val isLeashable = isLeashable
if (isLeashable != null) {
nbt.putBoolean(DataKeys.NPC_IS_LEASHABLE, isLeashable)
}
val allowProjectileHits = allowProjectileHits
if (allowProjectileHits != null) {
nbt.putBoolean(DataKeys.NPC_ALLOW_PROJECTILE_HITS, allowProjectileHits)
}
return nbt
}
@@ -367,6 +395,10 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
entityData.set(NPC_PLAYER_TEXTURE, NPCPlayerTexture(texture, model))
}
this.isMovable = if (nbt.contains(DataKeys.NPC_IS_MOVABLE)) nbt.getBoolean(DataKeys.NPC_IS_MOVABLE) else null
this.isInvulnerable = if (nbt.contains(DataKeys.NPC_IS_INVULNERABLE)) nbt.getBoolean(DataKeys.NPC_IS_INVULNERABLE) else null
this.isLeashable = if (nbt.contains(DataKeys.NPC_IS_LEASHABLE)) nbt.getBoolean(DataKeys.NPC_IS_LEASHABLE) else null
this.allowProjectileHits = if (nbt.contains(DataKeys.NPC_ALLOW_PROJECTILE_HITS)) nbt.getBoolean(DataKeys.NPC_ALLOW_PROJECTILE_HITS) else null
updateAspects()
}
@@ -404,6 +436,18 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
return isMovable ?: npc.isMovable
}
override fun isInvulnerableTo(source: DamageSource): Boolean {
return isInvulnerable ?: npc.isInvulnerable
}
override fun canBeLeashed(): Boolean {
return isLeashable ?: npc.isLeashable
}
override fun canBeHitByProjectile(): Boolean {
return allowProjectileHits ?: npc.allowProjectileHits
}
fun initialize(level: Int) {
appliedAspects.clear()
entityData.set(LEVEL, level)
@@ -1,32 +0,0 @@
/*
* 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.npc.ai
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.npc.NPCEntity
import net.minecraft.world.entity.LivingEntity
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.schedule.Activity
object SwitchFromActionEffectTask {
fun create(activity: Activity = Activity.IDLE): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.absent(CobblemonMemories.ACTIVE_ACTION_EFFECT)
).apply(it) { _ ->
Trigger { world, entity, time ->
entity.brain.setActiveActivityIfPossible(activity)
return@Trigger true
}
}
}
}
}
@@ -1,35 +0,0 @@
/*
* 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.npc.ai
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.world.entity.LivingEntity
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.schedule.Activity
/**
* When NPC battling memory is removed, this task swaps the NPC's activity over to idle.
*
* @author Hiroku
* @since February 24th, 2024
*/
object SwitchFromBattleTask {
fun create(activity: Activity): OneShot<LivingEntity> {
return BehaviorBuilder.create {
it.group(it.absent(CobblemonMemories.NPC_BATTLING)).apply(it) { _ ->
Trigger { _, entity, _ ->
entity.brain.setActiveActivityIfPossible(activity)
true
}
}
}
}
}
@@ -380,7 +380,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
after(seconds = 0.8F) {
if (pokemon.pokemon.shiny) {
SpawnSnowstormEntityParticlePacket(cobblemonResource("wild_shiny_ring"), pokemon.id, listOf("shiny_particles", "middle"))
SpawnSnowstormEntityParticlePacket(cobblemonResource("shiny_ring"), pokemon.id, listOf("shiny_particles", "middle"))
.sendToPlayersAround(pokemon.x, pokemon.y, pokemon.z, 32.0, pokemon.level().dimension())
}
discard()
@@ -26,4 +26,6 @@ class LaxPermissionValidator : PermissionValidator {
override fun hasPermission(player: ServerPlayer, permission: Permission) = player.hasPermissions(permission.level.numericalValue)
override fun hasPermission(source: CommandSourceStack, permission: Permission) = source.hasPermission(permission.level.numericalValue)
override fun hasPermission(player: ServerPlayer, permission: String, level: Int) = player.hasPermissions(level)
override fun hasPermission(source: CommandSourceStack, permission: String, level: Int) = source.hasPermission(level)
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.pokemon
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToPlayers
import com.cobblemon.mod.common.CobblemonSounds
@@ -23,14 +21,25 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents.FRIENDSHIP_UPDATED
import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_FAINTED
import com.cobblemon.mod.common.api.events.pokemon.*
import com.cobblemon.mod.common.api.events.pokemon.healing.PokemonHealedEvent
import com.cobblemon.mod.common.api.moves.*
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addPokemonFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.BenchedMoves
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonPropertyExtractor
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.evolution.*
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionController
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionDisplay
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionProxy
import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceSource
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
@@ -55,7 +64,6 @@ import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.types.tera.TeraType
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.datafixer.CobblemonSchemas
import com.cobblemon.mod.common.datafixer.CobblemonTypeReferences
@@ -68,7 +76,11 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_DURATION
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.THROW_DURATION
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.activestate.*
import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.InactivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.PokemonState
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.evolution.CobblemonEvolutionProxy
import com.cobblemon.mod.common.pokemon.evolution.controller.ClientEvolutionController
import com.cobblemon.mod.common.pokemon.evolution.controller.ServerEvolutionController
@@ -80,12 +92,19 @@ import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.codec.internal.*
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP2
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP3
import com.cobblemon.mod.common.util.codec.internal.PokemonP1
import com.cobblemon.mod.common.util.codec.internal.PokemonP2
import com.cobblemon.mod.common.util.codec.internal.PokemonP3
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.setPositionSafely
import com.cobblemon.mod.common.util.toBlockPos
import com.google.gson.JsonObject
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
@@ -102,7 +121,9 @@ import kotlin.math.roundToInt
import kotlin.random.Random
import net.minecraft.core.BlockPos
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.*
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.StringTag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.contents.PlainTextContents
@@ -117,10 +138,14 @@ import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.vehicle.Boat
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.CactusBlock
import net.minecraft.world.level.block.CampfireBlock
import net.minecraft.world.level.block.FireBlock
import net.minecraft.world.level.block.MagmaBlock
import net.minecraft.world.level.block.SweetBerryBushBlock
import net.minecraft.world.level.block.WitherRoseBlock
import net.minecraft.world.phys.Vec3
enum class OriginalTrainerType : StringRepresentable {
@@ -1497,6 +1522,9 @@ open class Pokemon : ShowdownIdentifiable {
private val observables = mutableListOf<Observable<*>>()
val anyChangeObservable = SimpleObservable<Pokemon>()
val struct = ObjectValue<Pokemon>(this)
.addPokemonFunctions(this)
fun markFeatureDirty(feature: SpeciesFeature) {
_features.emit(feature)
}
@@ -1505,19 +1533,6 @@ open class Pokemon : ShowdownIdentifiable {
/** Returns an [Observable] that emits Unit whenever any change is made to this Pokémon. The change itself is not included. */
fun getChangeObservable(): Observable<Pokemon> = anyChangeObservable
fun writeVariables(struct: VariableStruct) {
struct.setDirectly("level", DoubleValue(level.toDouble()))
struct.setDirectly("max_hp", DoubleValue(maxHealth.toDouble()))
struct.setDirectly("current_hp", DoubleValue(currentHealth.toDouble()))
struct.setDirectly("friendship", DoubleValue(friendship.toDouble()))
struct.setDirectly("shiny", DoubleValue(shiny))
for (stat in Stats.PERMANENT) {
struct.setDirectly("ev_${stat.showdownId}", DoubleValue(evs.getOrDefault(stat).toDouble()))
struct.setDirectly("iv_${stat.showdownId}", DoubleValue(ivs.getOrDefault(stat).toDouble()))
struct.setDirectly("stat_${stat.showdownId}", DoubleValue(getStat(stat).toDouble()))
}
}
/**
* Used for when 'this' would be called in leaking context.
*
@@ -213,6 +213,9 @@ object DataKeys {
const val NPC_LEVEL = "Level"
const val NPC_IS_MOVABLE = "IsMovable"
const val NPC_SKILL = "Skill"
const val NPC_IS_INVULNERABLE = "IsInvulnerable"
const val NPC_IS_LEASHABLE = "IsLeashable"
const val NPC_ALLOW_PROJECTILE_HITS = "AllowProjectileHits"
// PokemonProperties
const val ELEMENTAL_TYPE = "ElementalType"
@@ -16,7 +16,6 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.struct.ArrayStruct
import com.bedrockk.molang.runtime.struct.ContextStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.Cobblemon
@@ -161,15 +160,11 @@ fun String.asExpressionLike() = try {
fun Double.asExpression() = toString().asExpression() // Use the string route because it remembers the original string value for serialization
fun MoLangEnvironment.writePokemon(pokemon: Pokemon) {
val pokemonStruct = VariableStruct()
pokemon.writeVariables(pokemonStruct)
setSimpleVariable("pokemon", pokemonStruct)
setSimpleVariable("pokemon", pokemon.struct)
}
fun MoLangEnvironment.writePokemon(pokemon: BattlePokemon) {
val pokemonStruct = VariableStruct()
pokemon.writeVariables(pokemonStruct)
setSimpleVariable("pokemon", pokemonStruct)
setSimpleVariable("pokemon", pokemon.effectedPokemon.struct)
}
fun List<String>.asExpressionLike() = joinToString(separator = "\n").asExpressionLike()
@@ -182,6 +177,7 @@ fun List<Expression>.resolveObject(runtime: MoLangRuntime, context: Map<String,
fun MoParams.getStringOrNull(index: Int) = if (params.size > index) getString(index) else null
fun MoParams.getDoubleOrNull(index: Int) = if (params.size > index) getDouble(index) else null
fun MoParams.getBooleanOrNull(index: Int) = if (params.size > index) getDouble(index) == 1.0 else null
fun MoParams.getIntOrNull(index: Int) = if (params.size > index) getDouble(index).toInt() else null
fun MoLangRuntime.withQueryValue(name: String, value: MoValue): MoLangRuntime {
environment.query.functions.put(name) { value }
@@ -73,6 +73,9 @@ object NPCClassAdapter : JsonDeserializer<NPCClass> {
obj.get("battleTheme")?.let { npcClass.battleTheme = it.asString.asIdentifierDefaultingNamespace() }
obj.get("ai")?.let { npcClass.ai.addAll(it.asJsonArray.map<JsonElement, BrainConfig> { ctx.deserialize(it, BrainConfig::class.java) }.toMutableList()) }
obj.get("isMovable")?.let { npcClass.isMovable = it.asBoolean }
obj.get("isInvulnerable")?.let { npcClass.isInvulnerable = it.asBoolean }
obj.get("isLeashable")?.let { npcClass.isLeashable = it.asBoolean }
obj.get("allowProjectileHits")?.let { npcClass.allowProjectileHits = it.asBoolean }
return npcClass
}
@@ -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.util.adapters
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
object NPCClassReferenceAdapter : JsonDeserializer<NPCClass> {
override fun deserialize(json: JsonElement, typeOfT: Type, ctx: JsonDeserializationContext): NPCClass {
val resourceLocation = json.asString.asIdentifierDefaultingNamespace()
return NPCClasses.getByIdentifier(resourceLocation)
?: throw IllegalArgumentException("No such NPC class: $resourceLocation")
}
}
@@ -0,0 +1,67 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:fishing_bobber_big_ripple",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/water_ripple"
}
},
"curves": {
"variable.size": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [1, 10]
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 6,
"max_particles": 2
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.5
},
"minecraft:emitter_shape_point": {
"offset": [0, 0.05, 0]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_lifetime_events": {
"creation_event": "event_splash"
},
"minecraft:particle_initial_speed": 0,
"minecraft:particle_motion_dynamic": {},
"minecraft:particle_appearance_billboard": {
"size": ["0.4 * v.size", "0.4 * v.size"],
"facing_camera_mode": "emitter_transform_xz",
"uv": {
"texture_width": 117,
"texture_height": 13,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [13, 13],
"step_UV": [13, 0],
"frames_per_second": 12,
"max_frame": 9
}
}
},
"minecraft:particle_appearance_lighting": {},
"minecraft:particle_appearance_tinting": {
"color": {
"interpolant": "v.particle_age / v.particle_lifetime",
"gradient": {
"0.0": "#FFFFFFFF",
"0.1": "#B0FFFFFF",
"0.5": "#00FFFFFF",
"1.0": "#00FFFFFF"
}
}
}
}
}
}
@@ -0,0 +1,65 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:fishing_bobber_ripple",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/water_ripple"
}
},
"curves": {
"variable.size": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [1, 1.5]
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.5
},
"minecraft:emitter_shape_point": {
"offset": [0, 0.05, 0]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_lifetime_events": {
"creation_event": "event_splash"
},
"minecraft:particle_initial_speed": 0,
"minecraft:particle_motion_dynamic": {},
"minecraft:particle_appearance_billboard": {
"size": ["math.clamp(v.particle_random_2,0.5,0.7) * variable.size", "math.clamp(v.particle_random_2,0.5,0.7) * variable.size"],
"facing_camera_mode": "emitter_transform_xz",
"uv": {
"texture_width": 117,
"texture_height": 13,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [13, 13],
"step_UV": [13, 0],
"frames_per_second": 12,
"max_frame": 9
}
}
},
"minecraft:particle_appearance_lighting": {},
"minecraft:particle_appearance_tinting": {
"color": {
"interpolant": "v.particle_age / v.particle_lifetime",
"gradient": {
"0.0": "#FFFFFFFF",
"0.56": "#00FFFFFF",
"1.0": "#00FFFFFF"
}
}
}
}
}
}
@@ -0,0 +1,76 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:fishing_surface_ripple",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/water_ripple"
}
},
"curves": {
"variable.size": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [1, 1.5]
}
},
"events": {
"event_splash": {
"particle_effect": {
"effect": "cobblemon:splash",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": "math.random(1,3)"
},
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_shape_disc": {
"radius": "math.random(2,5)",
"plane_normal": [0, 0.05, 0],
"surface_only": true,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_lifetime_events": {
"creation_event": "event_splash"
},
"minecraft:particle_initial_speed": 0,
"minecraft:particle_motion_dynamic": {},
"minecraft:particle_appearance_billboard": {
"size": ["math.clamp(v.particle_random_1,0.6,0.8)", "math.clamp(v.particle_random_1,0.6,0.8)"],
"facing_camera_mode": "emitter_transform_xz",
"uv": {
"texture_width": 117,
"texture_height": 13,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [13, 13],
"step_UV": [13, 0],
"frames_per_second": 12,
"max_frame": 9
}
}
},
"minecraft:particle_appearance_lighting": {},
"minecraft:particle_appearance_tinting": {
"color": {
"interpolant": "v.particle_age / v.particle_lifetime",
"gradient": {
"0.0": "#8CFFFFFF",
"0.48": "#00FFFFFF",
"1.0": "#00FFFFFF"
}
}
}
}
}
}
@@ -0,0 +1,65 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:fishing_wake",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/white"
}
},
"events": {
"event_splash": {
"particle_effect": {
"effect": "cobblemon:splash",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 32
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.6
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_splash"
},
"minecraft:emitter_shape_disc": {
"offset": [0, 0.01, 0],
"radius": 0.1,
"direction": ["math.random(-10,10)", 0, "math.random(-10,10)"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.7
},
"minecraft:particle_initial_speed": 1.5,
"minecraft:particle_motion_dynamic": {
"linear_drag_coefficient": 5
},
"minecraft:particle_appearance_billboard": {
"size": [0.05, 0.05],
"facing_camera_mode": "emitter_transform_xz",
"uv": {
"texture_width": 8,
"texture_height": 8,
"uv": [0, 0],
"uv_size": [8, 8]
}
},
"minecraft:particle_appearance_lighting": {},
"minecraft:particle_appearance_tinting": {
"color": {
"interpolant": "v.particle_age / v.particle_lifetime",
"gradient": {
"0.0": "#54C8FDFF",
"0.19": "#54C8FDFF",
"0.59": "#695DB3FF",
"1.0": "#005DB3FF"
}
}
}
}
}
}
@@ -0,0 +1,44 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:splash",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/rain_splash"
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": "math.random(1,2)"
},
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_shape_point": {
"direction": ["math.random(-0.6,0.6)", 1, "math.random(-0.6,0.6)"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_initial_speed": 4,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -10, 0],
"linear_drag_coefficient": 2
},
"minecraft:particle_appearance_billboard": {
"size": [0.2, 0.2],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 8,
"texture_height": 32,
"uv": [0, "math.floor(variable.particle_random_2 * 4) * 8"],
"uv_size": [8, 8]
}
},
"minecraft:particle_motion_collision": {
"expire_on_contact": true
}
}
}
}
@@ -8,6 +8,13 @@
"texture": "textures/particles/powder"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.sandattack.actor"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 500,
@@ -16,6 +23,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0.05, "-(q.entity_radius*0.7))"],
"direction": ["math.random(-0.81,0.81)", "math.random(0.95,1.55)", "math.random(-4,-1)"]
@@ -8,7 +8,17 @@
"texture": "textures/particles/powder"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.sandattack.target"
}
}
},
"components": {
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_rate_steady": {
"spawn_rate": 500,
"max_particles": 25
@@ -1101,11 +1101,13 @@
"item.cobblemon.ganlon_berry": "Ganlon Berry",
"item.cobblemon.ganlon_berry.tooltip": "Raises holder's Defense by 1 stage when at 1/4 max HP or less",
"item.cobblemon.grepa_berry": "Grepa Berry",
"item.cobblemon.grepa_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.grepa_berry.tooltip_1": "Lowers the pokémon's Defense EVs by 10, while raising friendship",
"item.cobblemon.grepa_berry.tooltip_2": "Cannot be eaten by the holder",
"item.cobblemon.haban_berry": "Haban Berry",
"item.cobblemon.haban_berry.tooltip": "Halves damage taken from a supereffective Dragon-type attack",
"item.cobblemon.hondew_berry": "Hondew Berry",
"item.cobblemon.hondew_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.hondew_berry.tooltip_1": "Lowers the pokémon's Special Defense EVs by 10, while raising friendship",
"item.cobblemon.hondew_berry.tooltip_2": "Cannot be eaten by the holder",
"item.cobblemon.hopo_berry": "Hopo Berry",
"item.cobblemon.hopo_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.iapapa_berry": "Iapapa Berry",
@@ -1119,7 +1121,8 @@
"item.cobblemon.kee_berry": "Kee Berry",
"item.cobblemon.kee_berry.tooltip": "Raises holder's Defense by 1 stage after it is hit by a physical attack",
"item.cobblemon.kelpsy_berry": "Kelpsy Berry",
"item.cobblemon.kelpsy_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.kelpsy_berry.tooltip_1": "Lowers the pokémon's Special Attack EVs by 10, while raising friendship",
"item.cobblemon.kelpsy_berry.tooltip_2": "Cannot be eaten by the holder",
"item.cobblemon.lansat_berry": "Lansat Berry",
"item.cobblemon.lansat_berry.tooltip": "Holder gains the Focus Energy effect when at 1/4 max HP or less",
"item.cobblemon.leppa_berry": "Leppa Berry",
@@ -1161,9 +1164,11 @@
"item.cobblemon.pinap_berry": "Pinap Berry",
"item.cobblemon.pinap_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.pomeg_berry": "Pomeg Berry",
"item.cobblemon.pomeg_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.pomeg_berry.tooltip_1": "Lowers the pokémon's HP EVs by 10, while raising friendship",
"item.cobblemon.pomeg_berry.tooltip_2": "Cannot be eaten by the holder",
"item.cobblemon.qualot_berry": "Qualot Berry",
"item.cobblemon.qualot_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.qualot_berry.tooltip_1": "Lowers the pokémon's Speed EVs by 10, while raising friendship",
"item.cobblemon.qualot_berry.tooltip_2": "Cannot be eaten by the holder",
"item.cobblemon.rabuta_berry": "Rabuta Berry",
"item.cobblemon.rabuta_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.rawst_berry": "Rawst Berry",
@@ -1187,7 +1192,8 @@
"item.cobblemon.starf_berry": "Starf Berry",
"item.cobblemon.starf_berry.tooltip": "Raises a random stat by 2 when at 1/4 max HP or less",
"item.cobblemon.tamato_berry": "Tamato Berry",
"item.cobblemon.tamato_berry.tooltip": "Cannot be eaten by the holder",
"item.cobblemon.tamato_berry.tooltip_1": "Lowers the pokémon's Attack EVs by 10, while raising friendship",
"item.cobblemon.tamato_berry.tooltip_2": "Cannot be eaten by the holder",
"item.cobblemon.tanga_berry": "Tanga Berry",
"item.cobblemon.tanga_berry.tooltip": "Halves damage taken from a supereffective Bug-type attack",
"item.cobblemon.wacan_berry": "Wacan Berry",
@@ -107,6 +107,8 @@
"cobblemon:spiral",
"cobblemon:mud_bubble",
"cobblemon:mud_splash",
"cobblemon:splash"
"cobblemon:splash",
"cobblemon:rain_splash",
"cobblemon:water_ripple"
]
}
@@ -555,10 +555,10 @@
"sounds": [ { "name": "cobblemon:move/flamethrower/flamethrower_target", "volume": 0.8 } ]
},
"move.sandattack.actor": {
"sounds": [ { "name": "cobblemon:move/sandattack/sand_attack_actor", "volume": 0.8 } ]
"sounds": [ { "name": "cobblemon:move/sand_attack/sand_attack_actor", "volume": 0.8 } ]
},
"move.sandattack.target": {
"sounds": [ { "name": "cobblemon:move/sandattack/sand_attack_target", "volume": 0.8 } ]
"sounds": [ { "name": "cobblemon:move/sand_attack/sand_attack_target", "volume": 0.8 } ]
},
"move.confusion.actor": {
"sounds": [ { "name": "cobblemon:move/confusion/confusion_actor", "volume": 0.9 } ]
@@ -924,10 +924,10 @@
},
"particle.shiny_ambient_chime": {
"sounds": [
{ "name": "cobblemon:shiny/shiny_ambient_chime_1", "volume": 0.05 },
{ "name": "cobblemon:shiny/shiny_ambient_chime_2", "volume": 0.05 },
{ "name": "cobblemon:shiny/shiny_ambient_chime_3", "volume": 0.05 },
{ "name": "cobblemon:shiny/shiny_ambient_chime_4", "volume": 0.05 }
{ "name": "cobblemon:shiny/shiny_ambient_chime_1", "volume": 0.3 },
{ "name": "cobblemon:shiny/shiny_ambient_chime_2", "volume": 0.3 },
{ "name": "cobblemon:shiny/shiny_ambient_chime_3", "volume": 0.3 },
{ "name": "cobblemon:shiny/shiny_ambient_chime_4", "volume": 0.3 }
]
},
"particle.wild_shiny_ambient_chime": {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 B

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 B

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

@@ -0,0 +1,14 @@
[
{
"type": "add_tasks_to_activity",
"activity": "cobblemon:battling",
"tasksByPriority": {
"0": [
{
"type": "exit_battle_when_hurt",
"includePassiveDamage": true
}
]
}
}
]
@@ -0,0 +1,20 @@
[
{
"type": "add_tasks_to_activity",
"activity": "panic",
"tasksByPriority": {
"0": ["calm_down", "follow_walk_target"],
"1": ["flee_nearest_hostile"],
"2": ["flee_attacker"],
"3": [
{
"type": "wander",
"speedMultiplier": 0.5,
"horizontalRange": 3,
"verticalRange": 3,
"shouldWander": true
}
]
}
}
]
@@ -0,0 +1,14 @@
[
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"0": [
{
"type": "switch_to_panic_when_hurt",
"includePassiveDamage": true
}
]
}
}
]
@@ -1,5 +1,5 @@
{
"initializationAction": "q.npc.is_in_battle_with(q.player) ? q.dialogue.close()",
"initializationAction": "(q.npc.is_in_battle_with(q.player) || !q.npc.is_doing_activity('minecraft:idle', 'cobblemon:battling')) ? q.dialogue.close()",
"speakers": {
"npc": {
"name": {
@@ -14,10 +14,11 @@
"cobblemon:looks",
"cobblemon:floats",
"cobblemon:pauses_in_battle",
"cobblemon:cancels_battle_when_hurt_by_anything",
"cobblemon:uses_healing_machine",
"cobblemon:chats",
"cobblemon:retaliates",
"cobblemon:fights_melee"
"cobblemon:panics_when_hurt_by_anything",
"cobblemon:panics"
]
},
{
@@ -8,6 +8,7 @@
"EntityRenderDispatcherMixin",
"GameRendererMixin",
"ItemInHandRendererMixin",
"ItemPropertiesMixin",
"ItemRendererMixin",
"ModelLoaderMixin",
"ModelPartMixin",
@@ -23,4 +23,8 @@ class FabricPermissionValidator : PermissionValidator {
override fun hasPermission(player: ServerPlayer, permission: Permission) = Permissions.check(player, permission.literal, permission.level.numericalValue)
override fun hasPermission(source: CommandSourceStack, permission: Permission) = Permissions.check(source, permission.literal, permission.level.numericalValue)
override fun hasPermission(player: ServerPlayer, permission: String, level: Int) = Permissions.check(player, permission, level)
override fun hasPermission(source: CommandSourceStack, permission: String, level: Int) = Permissions.check(source, permission, level)
}
@@ -42,12 +42,23 @@ object ForgePermissionValidator : PermissionValidator {
return PermissionAPI.getPermission(player, node)
}
override fun hasPermission(player: ServerPlayer, permission: String, level: Int): Boolean {
val node = PermissionNode(permission.split(".").first(), permission.substringAfter("."), PermissionTypes.BOOLEAN, { player, _, _ -> player?.hasPermissions(level) == true })
return PermissionAPI.getPermission(player, node)
}
override fun hasPermission(source: CommandSourceStack, permission: Permission): Boolean {
val player = this.extractPlayerFromSource(source) ?: return source.hasPermission(permission.level.numericalValue)
val node = this.findNode(permission) ?: return source.hasPermission(permission.level.numericalValue)
return PermissionAPI.getPermission(player, node)
}
override fun hasPermission(source: CommandSourceStack, permission: String, level: Int): Boolean {
val player = this.extractPlayerFromSource(source) ?: return source.hasPermission(4)
val node = PermissionNode(permission.split(".").first(), permission.substringAfter("."), PermissionTypes.BOOLEAN, { player, _, _ -> player?.hasPermissions(level) == true })
return PermissionAPI.getPermission(player, node)
}
private fun createNodes() = CobblemonPermissions.all().map { permission ->
// 3rd arg is default value if no implementation is present essentially
val node = PermissionNode(permission.identifier, PermissionTypes.BOOLEAN, { player, _, _ -> player?.hasPermissions(permission.level.numericalValue) == true })