diff --git a/CHANGELOG.md b/CHANGELOG.md index 862bfbe9b1..d7b60e0cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/common/src/main/java/com/cobblemon/mod/common/mixin/ItemPropertiesMixin.java b/common/src/main/java/com/cobblemon/mod/common/mixin/ItemPropertiesMixin.java new file mode 100644 index 0000000000..71f3e3b31d --- /dev/null +++ b/common/src/main/java/com/cobblemon/mod/common/mixin/ItemPropertiesMixin.java @@ -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 cir) { + if (!(livingEntity instanceof Player) || ((Player) livingEntity).fishing instanceof PokeRodFishingBobberEntity) { + cir.setReturnValue(0.0f); + } + } +} diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt b/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt index 7799a58e6f..f649dc0a23 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt @@ -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) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/CalmDownTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/CalmDownTaskConfig.kt new file mode 100644 index 0000000000..dd493fc1f7 --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/CalmDownTaskConfig.kt @@ -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? { + runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf())) + if (!runtime.resolveBoolean(condition)) return null + return VillagerCalmDown.create() + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/ExitBattleWhenHurtTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/ExitBattleWhenHurtTaskConfig.kt new file mode 100644 index 0000000000..38a7300980 --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/ExitBattleWhenHurtTaskConfig.kt @@ -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? { + 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) } } + } + } + }*/ + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/FleeAttackerTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/FleeAttackerTaskConfig.kt new file mode 100644 index 0000000000..89677f676b --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/FleeAttackerTaskConfig.kt @@ -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? { + 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 + ) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/FleeNearestHostileTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/FleeNearestHostileTaskConfig.kt new file mode 100644 index 0000000000..bf38e6c75c --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/FleeNearestHostileTaskConfig.kt @@ -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? { + 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 + ) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/README.md b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/README.md index a09a207b09..ccea004781 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/README.md +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/README.md @@ -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. \ No newline at end of file +- `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. \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromActionEffectTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromActionEffectTaskConfig.kt index b72b82bdd8..4a57031db7 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromActionEffectTaskConfig.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromActionEffectTaskConfig.kt @@ -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 + } + } + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromChattingTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromChattingTaskConfig.kt index f7468b4a3b..a6042f5f6b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromChattingTaskConfig.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromChattingTaskConfig.kt @@ -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 } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromFightTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromFightTaskConfig.kt index 391e6c41e9..8e93613062 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromFightTaskConfig.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromFightTaskConfig.kt @@ -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 } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromNPCBattleTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromNPCBattleTaskConfig.kt index c8df3b155f..13e5ca4282 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromNPCBattleTaskConfig.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchFromNPCBattleTaskConfig.kt @@ -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? { - 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 + } + } + } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchToPanicWhenHostilesNearbyTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchToPanicWhenHostilesNearbyTaskConfig.kt new file mode 100644 index 0000000000..959947045f --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchToPanicWhenHostilesNearbyTaskConfig.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchToPanicWhenHurtTaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchToPanicWhenHurtTaskConfig.kt new file mode 100644 index 0000000000..700dab9748 --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/SwitchToPanicWhenHurtTaskConfig.kt @@ -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? { + 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 + } + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt index bb3e9b38ed..d6048713c9 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt @@ -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() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/PokemonBattle.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/PokemonBattle.kt index 3fc37c722d..a863d73aba 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/PokemonBattle.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/PokemonBattle.kt @@ -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 } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/actor/BattleActor.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/actor/BattleActor.kt index 2b0c33067a..dc18137570 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/actor/BattleActor.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/actor/BattleActor.kt @@ -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() var canDynamax = false diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt index 0aa09d0986..44bf291e53 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt @@ -100,7 +100,7 @@ object CobblemonEvents { // instructions @JvmField - val MEGA_EVOLUTON = EventObservable() + val MEGA_EVOLUTION = EventObservable() @JvmField val TERASTALLIZATION = EventObservable() @JvmField diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/fishing/FishingBait.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/fishing/FishingBait.kt index a3418baaa0..d62eff606c 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/fishing/FishingBait.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/fishing/FishingBait.kt @@ -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, chance: Double, value: Double) : this(type, subcategory.orElse(null), chance, value) + companion object { val CODEC = RecordCodecBuilder.create { 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) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt index 74cb1c66f3..07f1759a4a 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt @@ -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>() 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>>( { pokemon -> val map = hashMapOf>() + 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>>( + { store -> + val map = hashMapOf>() + map.put("add") { params -> + val pokemon = params.get>(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>>( { party -> val map = hashMapOf>() 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>>( + { pc -> + val map = hashMapOf>() + 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>>( + { spawningContext -> + val map = hashMapOf>() + 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.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions) fun Holder.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions) fun Holder.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 { + val queryStruct = ObjectValue(this) + queryStruct.addFunctions(pokemonFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap()) + return queryStruct + } + + fun PartyStore.asMoLangValue(): ObjectValue { + 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 { + 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 { val value = ObjectValue( obj = this, @@ -403,6 +595,15 @@ object MoLangFunctions { return value } + fun SpawningContext.asMoLangValue(): ObjectValue { + 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 Holder.asMoLangValue(key: ResourceKey>): ObjectValue> { val value = ObjectValue( obj = this, diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCClass.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCClass.kt index 7a2456d739..028a448476 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCClass.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCClass.kt @@ -51,6 +51,9 @@ class NPCClass { var battleTheme: ResourceLocation? = null var ai: MutableList = 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() } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCPreset.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCPreset.kt index a67d3c262d..0e0e41f79d 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCPreset.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/NPCPreset.kt @@ -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 } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/README.md b/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/README.md index f01cd50c65..ceb6bdf247 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/README.md +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/npc/README.md @@ -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). \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt index 76a2b4a364..10dfe6f637 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt @@ -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) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionLevel.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionLevel.kt index 14f51ea3b1..94e726f6aa 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionLevel.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionLevel.kt @@ -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 } + } + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionValidator.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionValidator.kt index 6d494e4185..76756e2ddf 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionValidator.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/permission/PermissionValidator.kt @@ -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))) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/pokeball/catching/modifiers/CatchRateModifiers.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/pokeball/catching/modifiers/CatchRateModifiers.kt index 757ac82c74..2b03c8c753 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/pokeball/catching/modifiers/CatchRateModifiers.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/pokeball/catching/modifiers/CatchRateModifiers.kt @@ -133,7 +133,7 @@ object CatchRateModifiers { */ val LURE = WorldStateModifier { _, entity -> if (FishingSpawnCause.FISHED_ASPECT in entity.aspects) - 4f + 4F else 1F } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnDetailPresets.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnDetailPresets.kt index 94b149e5a1..066c1d7210 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnDetailPresets.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnDetailPresets.kt @@ -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 { .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>() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnLoader.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnLoader.kt index c06209e127..4fc09defff 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnLoader.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawnLoader.kt @@ -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>? = null diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/context/SpawningContext.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/context/SpawningContext.kt index 1e8403b37c..209332c739 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/context/SpawningContext.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/context/SpawningContext.kt @@ -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 } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnAction.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnAction.kt index 218235f7cb..0c1b9f6991 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnAction.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnAction.kt @@ -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(ctx, detail) { - override fun run(): EntitySpawnResult { +class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction(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 } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnDetail.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnDetail.kt index 1972cb2831..15e237f06d 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnDetail.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/detail/NPCSpawnDetail.kt @@ -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 = emptySet() + val minLevel: Expression = "1".asExpression() + val maxLevel: Expression = "100".asExpression() override fun doSpawn(ctx: SpawningContext) = NPCSpawnAction(ctx, this) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/party/PartyStore.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/party/PartyStore.kt index b51f025893..057ba938b0 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/party/PartyStore.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/party/PartyStore.kt @@ -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() { /** A list of player UUIDs representing players that are observing this store. This is NOT serialized/deserialized. */ var observerUUIDs = mutableListOf() + 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] } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/pc/PCStore.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/pc/PCStore.kt index 2f364f177d..b0ddb3a19a 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/pc/PCStore.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/pc/PCStore.kt @@ -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) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/BattleBuilder.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/BattleBuilder.kt index 483b6470a0..edd7242c56 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/BattleBuilder.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/BattleBuilder.kt @@ -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) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/actor/PlayerBattleActor.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/actor/PlayerBattleActor.kt index 0efcc1dd00..98f8ef9316 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/actor/PlayerBattleActor.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/actor/PlayerBattleActor.kt @@ -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 + pokemonList: List, ) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor { 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 } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/InitializeInstruction.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/InitializeInstruction.kt index 3ad6cd08f7..dd62ff0c14 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/InitializeInstruction.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/InitializeInstruction.kt @@ -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 diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/MegaInstruction.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/MegaInstruction.kt index c1a92f4488..3157a3316b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/MegaInstruction.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/MegaInstruction.kt @@ -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 } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/pokemon/BattlePokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/pokemon/BattlePokemon.kt index 00bea5495d..d15419d610 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/pokemon/BattlePokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/pokemon/BattlePokemon.kt @@ -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 } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/block/entity/DisplayCaseBlockEntity.kt b/common/src/main/kotlin/com/cobblemon/mod/common/block/entity/DisplayCaseBlockEntity.kt index 7c3c33633c..f912bbe0bd 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/block/entity/DisplayCaseBlockEntity.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/block/entity/DisplayCaseBlockEntity.kt @@ -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) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt index e232b56fc3..3bddfe2b9b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt @@ -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!!) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt index 0b5c3b2073..9d89249e70 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt @@ -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 diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleSwitchPokemonSelection.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleSwitchPokemonSelection.kt index 957d91d846..efff9394d7 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleSwitchPokemonSelection.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleSwitchPokemonSelection.kt @@ -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") diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleTargetSelection.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleTargetSelection.kt index 6cfb863877..c73d349fc9 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleTargetSelection.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleTargetSelection.kt @@ -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") diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/PokemonInfoWidget.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/PokemonInfoWidget.kt index f368addf74..0db3dfbd2b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/PokemonInfoWidget.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/PokemonInfoWidget.kt @@ -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] diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/SearchWidget.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/SearchWidget.kt index 30be7fcd59..fd3c3da67b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/SearchWidget.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/widgets/SearchWidget.kt @@ -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 + ) + } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/widgets/screens/SummaryTab.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/widgets/screens/SummaryTab.kt index 1f115c5cdc..775ee0edfe 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/widgets/screens/SummaryTab.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/widgets/screens/SummaryTab.kt @@ -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 ) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/entity/PokeBobberEntityRenderer.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/entity/PokeBobberEntityRenderer.kt index 99b739ca6c..b074535cba 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/entity/PokeBobberEntityRenderer.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/entity/PokeBobberEntityRenderer.kt @@ -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) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/item/CobblemonModelPredicateRegistry.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/item/CobblemonModelPredicateRegistry.kt index 7d9a86771d..e9ada0b8d8 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/item/CobblemonModelPredicateRegistry.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/item/CobblemonModelPredicateRegistry.kt @@ -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 } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/command/NPCDeleteCommand.kt b/common/src/main/kotlin/com/cobblemon/mod/common/command/NPCDeleteCommand.kt new file mode 100644 index 0000000000..2a6483895e --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/command/NPCDeleteCommand.kt @@ -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) { + dispatcher.register( + Commands.literal("npcdelete") + .requiresWithPermission(CobblemonPermissions.NPC_DELETE) { it.player != null } + .executes { execute(it, it.source.playerOrException) }) + } + + private fun execute(context: CommandContext, 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 + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/fishing/PokeRodFishingBobberEntity.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/fishing/PokeRodFishingBobberEntity.kt index f6d6244b76..7c6969c9ef 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/fishing/PokeRodFishingBobberEntity.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/entity/fishing/PokeRodFishingBobberEntity.kt @@ -72,6 +72,8 @@ import net.minecraft.world.phys.Vec3 class PokeRodFishingBobberEntity(type: EntityType, 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= 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, losers: List) { super.win(otherWinners, losers) npc.playAnimation(NPCEntity.WIN_ANIMATION) - if (npc.npc.autoHealParty) { - npc.party?.heal() - } } override fun lose(winners: List, otherLosers: List) { super.lose(winners, otherLosers) npc.playAnimation(NPCEntity.LOSE_ANIMATION) - if (npc.npc.autoHealParty) { - npc.party?.heal() - } // winners.forEach { // rewards.forEach { // winner give reward :)) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/NPCEntity.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/NPCEntity.kt index ad3556d6ff..fc02966944 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/NPCEntity.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/NPCEntity.kt @@ -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> = 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) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/ai/SwitchFromActionEffectTask.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/ai/SwitchFromActionEffectTask.kt deleted file mode 100644 index d95f9ab1b1..0000000000 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/ai/SwitchFromActionEffectTask.kt +++ /dev/null @@ -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 { - return BehaviorBuilder.create { - it.group( - it.absent(CobblemonMemories.ACTIVE_ACTION_EFFECT) - ).apply(it) { _ -> - Trigger { world, entity, time -> - entity.brain.setActiveActivityIfPossible(activity) - return@Trigger true - } - } - } - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/ai/SwitchFromBattleTask.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/ai/SwitchFromBattleTask.kt deleted file mode 100644 index 87a25e857e..0000000000 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/ai/SwitchFromBattleTask.kt +++ /dev/null @@ -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 { - return BehaviorBuilder.create { - it.group(it.absent(CobblemonMemories.NPC_BATTLING)).apply(it) { _ -> - Trigger { _, entity, _ -> - entity.brain.setActiveActivityIfPossible(activity) - true - } - } - } - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt index d412201415..9787bdce0c 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt @@ -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() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/permission/LaxPermissionValidator.kt b/common/src/main/kotlin/com/cobblemon/mod/common/permission/LaxPermissionValidator.kt index 50aa3be806..b21a672e58 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/permission/LaxPermissionValidator.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/permission/LaxPermissionValidator.kt @@ -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) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index c1d7f4c510..0f996d0c76 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -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>() val anyChangeObservable = SimpleObservable() + val struct = ObjectValue(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 = 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. * diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt index 8c5834b0e0..c641b11f6b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt @@ -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" diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt index 63cfedae01..89eabf7053 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt @@ -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.asExpressionLike() = joinToString(separator = "\n").asExpressionLike() @@ -182,6 +177,7 @@ fun List.resolveObject(runtime: MoLangRuntime, context: Map 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 } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassAdapter.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassAdapter.kt index 702b634443..124d6ff95f 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassAdapter.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassAdapter.kt @@ -73,6 +73,9 @@ object NPCClassAdapter : JsonDeserializer { obj.get("battleTheme")?.let { npcClass.battleTheme = it.asString.asIdentifierDefaultingNamespace() } obj.get("ai")?.let { npcClass.ai.addAll(it.asJsonArray.map { 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 } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassReferenceAdapter.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassReferenceAdapter.kt new file mode 100644 index 0000000000..c76d6bf895 --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/adapters/NPCClassReferenceAdapter.kt @@ -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 { + 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") + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_bobber_big_ripple.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_bobber_big_ripple.particle.json new file mode 100644 index 0000000000..83a6da96ca --- /dev/null +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_bobber_big_ripple.particle.json @@ -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" + } + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_bobber_ripple.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_bobber_ripple.particle.json new file mode 100644 index 0000000000..d5f8426f5c --- /dev/null +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_bobber_ripple.particle.json @@ -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" + } + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_surface_ripple.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_surface_ripple.particle.json new file mode 100644 index 0000000000..d1c6bdab8f --- /dev/null +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_surface_ripple.particle.json @@ -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" + } + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_surface_wake.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_surface_wake.particle.json new file mode 100644 index 0000000000..5aca922daa --- /dev/null +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/fishing_surface_wake.particle.json @@ -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" + } + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/splash.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/splash.particle.json new file mode 100644 index 0000000000..0ba0c710f4 --- /dev/null +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/fishing/splash.particle.json @@ -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 + } + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_actor.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_actor.particle.json index 301d9fb649..6131c92ae1 100644 --- a/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_actor.particle.json +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_actor.particle.json @@ -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)"] diff --git a/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_impact.particle.json b/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_impact.particle.json index a7fa0d17e8..23327cde36 100644 --- a/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_impact.particle.json +++ b/common/src/main/resources/assets/cobblemon/bedrock/particles/moves/sand_attack/sandattack_impact.particle.json @@ -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 diff --git a/common/src/main/resources/assets/cobblemon/lang/en_us.json b/common/src/main/resources/assets/cobblemon/lang/en_us.json index bc64d25336..499cca8b4f 100644 --- a/common/src/main/resources/assets/cobblemon/lang/en_us.json +++ b/common/src/main/resources/assets/cobblemon/lang/en_us.json @@ -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", diff --git a/common/src/main/resources/assets/cobblemon/particles/snowstorm.json b/common/src/main/resources/assets/cobblemon/particles/snowstorm.json index f7d903f6c3..68925c79df 100644 --- a/common/src/main/resources/assets/cobblemon/particles/snowstorm.json +++ b/common/src/main/resources/assets/cobblemon/particles/snowstorm.json @@ -107,6 +107,8 @@ "cobblemon:spiral", "cobblemon:mud_bubble", "cobblemon:mud_splash", - "cobblemon:splash" + "cobblemon:splash", + "cobblemon:rain_splash", + "cobblemon:water_ripple" ] } \ No newline at end of file diff --git a/common/src/main/resources/assets/cobblemon/sounds.json b/common/src/main/resources/assets/cobblemon/sounds.json index 7f623e4604..8b47c48d81 100644 --- a/common/src/main/resources/assets/cobblemon/sounds.json +++ b/common/src/main/resources/assets/cobblemon/sounds.json @@ -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": { diff --git a/common/src/main/resources/assets/cobblemon/sounds/move/sandattack/sand_attack_actor.ogg b/common/src/main/resources/assets/cobblemon/sounds/move/sandattack/sand_attack_actor.ogg deleted file mode 100644 index e5a2cbbae2..0000000000 Binary files a/common/src/main/resources/assets/cobblemon/sounds/move/sandattack/sand_attack_actor.ogg and /dev/null differ diff --git a/common/src/main/resources/assets/cobblemon/sounds/move/sandattack/sand_attack_target.ogg b/common/src/main/resources/assets/cobblemon/sounds/move/sandattack/sand_attack_target.ogg deleted file mode 100644 index eadd3ce876..0000000000 Binary files a/common/src/main/resources/assets/cobblemon/sounds/move/sandattack/sand_attack_target.ogg and /dev/null differ diff --git a/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/pokedex_screen_scan.png b/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/pokedex_screen_scan.png index aa3599cfda..422c4de614 100644 Binary files a/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/pokedex_screen_scan.png and b/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/pokedex_screen_scan.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_notch.png b/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_notch.png index 1fd4470f43..62a15e80da 100644 Binary files a/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_notch.png and b/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_notch.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_scanlines.png b/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_scanlines.png index 41020d1140..2718612548 100644 Binary files a/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_scanlines.png and b/common/src/main/resources/assets/cobblemon/textures/gui/pokedex/scan/overlay_scanlines.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_info.png b/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_info.png index b1b397c943..d6c24109c2 100644 Binary files a/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_info.png and b/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_info.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_moves.png b/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_moves.png index 1625cfa838..a5fd4a1a9b 100644 Binary files a/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_moves.png and b/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_moves.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_stats.png b/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_stats.png index 07b42403d4..7b05fa52d1 100644 Binary files a/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_stats.png and b/common/src/main/resources/assets/cobblemon/textures/gui/summary/summary_tab_icon_stats.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/particle/rain_splash.png b/common/src/main/resources/assets/cobblemon/textures/particle/rain_splash.png new file mode 100644 index 0000000000..4d8663c0e3 Binary files /dev/null and b/common/src/main/resources/assets/cobblemon/textures/particle/rain_splash.png differ diff --git a/common/src/main/resources/assets/cobblemon/textures/particle/water_ripple.png b/common/src/main/resources/assets/cobblemon/textures/particle/water_ripple.png new file mode 100644 index 0000000000..b1fb3b7c4c Binary files /dev/null and b/common/src/main/resources/assets/cobblemon/textures/particle/water_ripple.png differ diff --git a/common/src/main/resources/data/cobblemon/brain_presets/cancels_battle_when_hurt_by_anything.json b/common/src/main/resources/data/cobblemon/brain_presets/cancels_battle_when_hurt_by_anything.json new file mode 100644 index 0000000000..edd56be067 --- /dev/null +++ b/common/src/main/resources/data/cobblemon/brain_presets/cancels_battle_when_hurt_by_anything.json @@ -0,0 +1,14 @@ +[ + { + "type": "add_tasks_to_activity", + "activity": "cobblemon:battling", + "tasksByPriority": { + "0": [ + { + "type": "exit_battle_when_hurt", + "includePassiveDamage": true + } + ] + } + } +] \ No newline at end of file diff --git a/common/src/main/resources/data/cobblemon/brain_presets/panics.json b/common/src/main/resources/data/cobblemon/brain_presets/panics.json new file mode 100644 index 0000000000..0e8279e4dd --- /dev/null +++ b/common/src/main/resources/data/cobblemon/brain_presets/panics.json @@ -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 + } + ] + } + } +] \ No newline at end of file diff --git a/common/src/main/resources/data/cobblemon/brain_presets/panics_when_hurt_by_anything.json b/common/src/main/resources/data/cobblemon/brain_presets/panics_when_hurt_by_anything.json new file mode 100644 index 0000000000..5c5b6343fd --- /dev/null +++ b/common/src/main/resources/data/cobblemon/brain_presets/panics_when_hurt_by_anything.json @@ -0,0 +1,14 @@ +[ + { + "type": "add_tasks_to_activity", + "activity": "idle", + "tasksByPriority": { + "0": [ + { + "type": "switch_to_panic_when_hurt", + "includePassiveDamage": true + } + ] + } + } +] \ No newline at end of file diff --git a/common/src/main/resources/data/cobblemon/dialogues/npc-example.json b/common/src/main/resources/data/cobblemon/dialogues/npc-example.json index 2830d8644e..9dd050689a 100644 --- a/common/src/main/resources/data/cobblemon/dialogues/npc-example.json +++ b/common/src/main/resources/data/cobblemon/dialogues/npc-example.json @@ -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": { diff --git a/common/src/main/resources/data/cobblemon/npcs/ai_test.json b/common/src/main/resources/data/cobblemon/npcs/ai_test.json index d94ff10541..2385c37a4e 100644 --- a/common/src/main/resources/data/cobblemon/npcs/ai_test.json +++ b/common/src/main/resources/data/cobblemon/npcs/ai_test.json @@ -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" ] }, { diff --git a/common/src/main/resources/mixins.cobblemon-common.json b/common/src/main/resources/mixins.cobblemon-common.json index 46a89cb085..f6b5837cd0 100644 --- a/common/src/main/resources/mixins.cobblemon-common.json +++ b/common/src/main/resources/mixins.cobblemon-common.json @@ -8,6 +8,7 @@ "EntityRenderDispatcherMixin", "GameRendererMixin", "ItemInHandRendererMixin", + "ItemPropertiesMixin", "ItemRendererMixin", "ModelLoaderMixin", "ModelPartMixin", diff --git a/fabric/src/main/kotlin/com/cobblemon/mod/fabric/permission/FabricPermissionValidator.kt b/fabric/src/main/kotlin/com/cobblemon/mod/fabric/permission/FabricPermissionValidator.kt index 469a62bd9d..282bf9b996 100644 --- a/fabric/src/main/kotlin/com/cobblemon/mod/fabric/permission/FabricPermissionValidator.kt +++ b/fabric/src/main/kotlin/com/cobblemon/mod/fabric/permission/FabricPermissionValidator.kt @@ -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) } \ No newline at end of file diff --git a/neoforge/src/main/kotlin/com/cobblemon/mod/neoforge/permission/ForgePermissionValidator.kt b/neoforge/src/main/kotlin/com/cobblemon/mod/neoforge/permission/ForgePermissionValidator.kt index c3a4942e35..70c97ee422 100644 --- a/neoforge/src/main/kotlin/com/cobblemon/mod/neoforge/permission/ForgePermissionValidator.kt +++ b/neoforge/src/main/kotlin/com/cobblemon/mod/neoforge/permission/ForgePermissionValidator.kt @@ -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 })