Compare commits

..
Author SHA1 Message Date
JPAK f70d7ce336 Change how lure effects Pokerod so high lure levels no longer get stuck. 2024-12-11 23:57:12 -06:00
Polymeta 5b0a221ade Merge branch 'RaftFixes' into 'main'
Fixed reading speciesdata instead of formdata. Fixed rafts deploying on...

See merge request cable-mc/cobblemon!1359
2024-12-11 08:14:41 +00:00
JazzMcNadeandPolymeta af23b82ce5 Fixed reading speciesdata instead of formdata. Fixed rafts deploying on... 2024-12-11 08:14:41 +00:00
Hiroku 36e78a5af2 Merge branch 'fix-dex-quiet-cries' into 'main'
Fix quiet cries in the Pokédex

See merge request cable-mc/cobblemon!1354
2024-12-07 22:38:12 +00:00
Guilherme PereiraandHiroku 55621b01b7 Fix quiet cries in the Pokédex 2024-12-07 22:38:12 +00:00
Paul b828fe2b3a Merge remote-tracking branch 'origin/main' 2024-12-07 20:59:45 +10:00
Paul b7b10151e1 Added get_npc_variable and set_npc_variable to player functions for convenience, updated molang for flexibility with query struct nested assignments 2024-12-07 20:59:33 +10:00
Test cac6ed843a Merge branch 'sterr/bugfixes' into 'main'
Adjusted portrait space detection in the Pokedex to prevent overlap

Closes #1055

See merge request cable-mc/cobblemon!1348
2024-12-06 18:32:20 +00:00
SterrezoandTest 8f5f40c360 Adjusted portrait space detection in the Pokedex to prevent overlap 2024-12-06 18:32:19 +00:00
Hiroku 0e586b4f38 Merge branch 'fix/ivs' into 'main'
Rework copy on properties, just clone fields rather than serialize + deserialize JSON

See merge request cable-mc/cobblemon!1344
2024-12-06 12:38:01 +00:00
Paul 62dd487448 Removed platform independent JSON datapack loading because Fabric has fixed the bug, closes #1184 2024-12-06 20:06:50 +10:00
Nick 04ecd9189c It would seem a particular build step was forgotten 2024-12-05 21:37:34 -08:00
Nick 55be22e942 Revert previous change, update stats differently instead 2024-12-05 21:29:13 -08:00
Hiroku f2e1b3dab4 Merge branch 'pokeedit-hidden-ability-false' into 'main'
Revert Pokémon ability to normal when hiddenAbility is false

See merge request cable-mc/cobblemon!1351
2024-12-06 00:51:21 +00:00
Paul 66f98ebf12 Added v.npc for on_player_wins and on_player_loses script hooks, v.npc to on_defeat and on_victory hooks 2024-12-05 20:43:04 +10:00
Paul c1842e42b0 Fixed run_molang script hooks not running multi-expressions, closes #1224 2024-12-05 19:14:49 +10:00
Hiroku e6c2f241d4 Merge branch 'MoreDynamicRafts' into 'main'
MoreDynamicRafts

See merge request cable-mc/cobblemon!1347
2024-12-05 08:39:13 +00:00
JazzMcNade 0f9c282e1f Merge branch 'main' into MoreDynamicRafts 2024-12-04 21:23:45 -06:00
JazzMcNade 7bd5d77b60 Fixed naming conventions, removed some unnecessary variables. 2024-12-04 21:22:35 -06:00
Guilherme Pereira f7b4d4e65a add changelog of when hiddenability=false on pokespawn and pokeedit commands 2024-12-04 22:23:28 -03:00
Guilherme Pereira e78f403198 set pokemon ability to normal when property is false 2024-12-04 21:43:07 -03:00
Test 4ca91cc8d8 Merge branch 'pokeedit-nickname-completion' into 'main'
Add nickname completion suggestion for pokeedit and pokespawn commands

See merge request cable-mc/cobblemon!1350
2024-12-05 00:20:08 +00:00
Test 1518181f6b Merge branch 'sound' into 'main'
Move sounds + Pokedex volume adjustments + missing sound fixes

See merge request cable-mc/cobblemon!1341
2024-12-05 00:14:06 +00:00
Hiroku b915237c8d Merge branch 'more-npc-dakka' into 'main'
Even more npc dakka

See merge request cable-mc/cobblemon!1330
2024-12-04 21:52:38 +00:00
Guilherme Pereira bb41a72067 add nickname completion suggestion for properties argument type 2024-12-04 16:29:36 -03:00
Paul fbb8492c75 Added ScriptPartyProvider 2024-12-04 22:36:17 +10:00
Paul c07aef04b1 Merge remote-tracking branch 'origin/main' into more-npc-dakka 2024-12-04 22:05:11 +10:00
Paul 6ff4befb04 Possible fix for tick watchdog hang 2024-12-04 22:00:56 +10:00
Paul a858d4f532 Use emptyList default rather than null for party provider player list, update doco, corrected LookInDirectionTask's extra start conditions 2024-12-04 21:13:38 +10:00
Hiroku 28fc273b26 Merge branch 'gimmi_tower_typo_fix' into 'main'
Fixing something i found in the processor lists

See merge request cable-mc/cobblemon!1345
2024-12-04 10:47:14 +00:00
JazzMcNade fc89757a09 Tab spacing 2024-12-04 01:16:00 -06:00
JazzMcNade 243c9abfd1 Rafts can now can (un)deploy upon touching water or a solid surface. 2024-12-04 01:11:02 -06:00
Slippy b871645e9e Fixing something i found in the processor lists 2024-12-03 11:23:40 -06:00
Paul c79916d8a0 More handy NPC presets, add player context to dynamic party molang environments, separated applied aspects from variation aspects for API 2024-12-03 21:20:31 +10:00
Nick d503d4a1eb Rework copy on properties, just clone fields rather than serialize + deserialize JSON 2024-12-02 19:10:30 -08:00
Paul a0f75dd090 Added is_chunk_loaded_at molang to level, added deeper support to json -> component deserialization 2024-12-02 09:46:52 +10:00
Sterrezo 5eceaa849c Quick fix to Mud Sport particles 2024-12-01 16:12:53 +01:00
Sterrezo 0e0233993a Merge branch 'main' into sound 2024-12-01 15:34:51 +01:00
Sterrezo 56ed5a6412 Added a sound to Kinesis 2024-12-01 15:30:47 +01:00
Paul f2e5ecef66 Conflicts begone 2024-12-01 17:00:03 +10:00
Paul ad9e85923a Merge remote-tracking branch 'origin/main' into more-npc-dakka
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
2024-12-01 16:57:06 +10:00
Paul bf461297b2 Corrected multiple little inaccuracies around molang and flows 2024-12-01 12:09:11 +10:00
Sterrezo 5965d5a81b Psychiiiiiiic 2024-12-01 01:58:23 +01:00
Sterrezo 4e02ca310d Added sounds for Ember, fixed Ember missing an impact and corrected Psychic's timing again 2024-12-01 00:44:35 +01:00
Sterrezo 3260271d3c Adjusted Psychic 2024-11-30 23:58:20 +01:00
Sterrezo a77863adab Implemented sounds for new move particles 2024-11-30 18:07:30 +01:00
Sterrezo 455cfeb894 Missing sound fixes for particles 2024-11-30 17:29:42 +01:00
Sterrezo fd3c614c2c Pokedex sound balancing
why did mojang hardcode this
2024-11-29 14:54:29 +01:00
Paul 4f3eb7e4ba Event flows merged in and used for a few battle-related NPC presets, heaps more molang hooks 2024-11-26 21:57:55 +10:00
Paul f9a3d2dc0e Merge remote-tracking branch 'origin/event-flows' into more-npc-dakka
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/pokemon/PokemonCapturedEvent.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-11-24 21:07:50 +10:00
Paul f817c18f4f Added NPC variable editing in NPC config menu 2024-11-24 21:02:38 +10:00
Paul a7e6e638fe Added run_command molang function to general query structs and player structs 2024-05-20 09:10:04 +10:00
Paul 9ef3ea2932 Use context for flows because it's cooler, added more functions to pokemon related molang 2024-05-19 19:08:56 +10:00
Paul 38dcee3327 Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into event-flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-05-19 11:27:18 +10:00
Paul 12b92d3f35 Added flow datapackable thing 2024-02-24 11:58:57 +10:00
Paul dacfb4f1ff Merge branch 'npcs' into event-flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-02-24 10:02:26 +10:00
Paul 4086ba73d3 Some structs for things 2024-02-24 09:59:27 +10:00
307 changed files with 2020 additions and 1234 deletions
+1
View File
@@ -52,6 +52,7 @@
- Added a unique set of sounds for Ancient Poké Balls.
- Added a sound for using Exp Candy and Rare Candy items.
- Added raft platforms for non-swimming, non-flying Pokémon to stand on during battles that take place on the water's surface. (Flying Pokémon will fly over water in battle, and water breathing Pokémon will swim in water during battle.)
- Added `hiddenability=false` option to `/spawnpokemon` and `/pokemonedit` commands, allowing the Pokémon's ability to be reverted to a normal ability.
### Pokémon Added
#### Gen 1
@@ -22,7 +22,7 @@ public abstract class MinecraftDedicatedServerMixin implements ServerInterface {
@Inject(method = "shouldDiscardEntity", at = @At(value = "HEAD"), cancellable = true)
public void cobblemon$allowPokemonSpawns(Entity entity, CallbackInfoReturnable<Boolean> callback) {
if(entity instanceof PokemonEntity) {
if (entity instanceof PokemonEntity) {
callback.setReturnValue(false);
}
}
@@ -81,6 +81,7 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.FlowHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
@@ -288,6 +289,8 @@ object Cobblemon {
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
FlowHandler.setup()
ifDedicatedServer {
isDedicatedServer = true
}
@@ -276,81 +276,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val CHISELED_BLACK_TUMBLESTONE_BRICKS = this.create("chiseled_black_tumblestone_bricks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_BUG_BLOCK = this.create("gem_bug_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_DARK_BLOCK = this.create("gem_dark_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_DRAGON_BLOCK = this.create("gem_dragon_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_ELECTRIC_BLOCK = this.create("gem_electric_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FAIRY_BLOCK = this.create("gem_fairy_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FIGHTING_BLOCK = this.create("gem_fighting_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FIRE_BLOCK = this.create("gem_fire_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FLYING_BLOCK = this.create("gem_flying_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_GHOST_BLOCK = this.create("gem_ghost_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_GRASS_BLOCK = this.create("gem_grass_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_GROUND_BLOCK = this.create("gem_ground_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_ICE_BLOCK = this.create("gem_ice_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_NORMAL_BLOCK = this.create("gem_normal_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_POISON_BLOCK = this.create("gem_poison_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_PSYCHIC_BLOCK = this.create("gem_psychic_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_ROCK_BLOCK = this.create("gem_rock_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_STEEL_BLOCK = this.create("gem_steel_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_WATER_BLOCK = this.create("gem_water_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_BUG_BUDDING = this.create("gem_bug_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_DARK_BUDDING = this.create("gem_dark_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_DRAGON_BUDDING = this.create("gem_dragon_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_ELECTRIC_BUDDING = this.create("gem_electric_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FAIRY_BUDDING = this.create("gem_fairy_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FIGHTING_BUDDING = this.create("gem_fighting_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FIRE_BUDDING = this.create("gem_fire_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_FLYING_BUDDING = this.create("gem_flying_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_GHOST_BUDDING = this.create("gem_ghost_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_GRASS_BUDDING = this.create("gem_grass_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_GROUND_BUDDING = this.create("gem_ground_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_ICE_BUDDING = this.create("gem_ice_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_NORMAL_BUDDING = this.create("gem_normal_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_POISON_BUDDING = this.create("gem_poison_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_PSYCHIC_BUDDING = this.create("gem_psychic_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_ROCK_BUDDING = this.create("gem_rock_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_STEEL_BUDDING = this.create("gem_steel_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val GEM_WATER_BUDDING = this.create("gem_water_budding", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val FIRE_STONE_BLOCK = this.create("fire_stone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.FIRE).strength(1.0F).sound(CobblemonSounds.EVOLUTION_STONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BIT).lightLevel{13}))
@JvmField
@@ -0,0 +1,78 @@
/*
* 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
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.net.messages.client.data.FlowRegistrySyncPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.endsWith
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
/**
* Holds all the flows that are loaded from the server's data packs. A flow is foldered under the event identifier,
* which can be Cobblemon's namespace but can also be minecraft or any other namespace someone has added hooks for.
* The value of the map is a list of MoLang scripts that will execute when that event occurs.
*
* @author Hiroku
* @since February 24th, 2024
*/
object CobblemonFlows : DataRegistry {
override val id = cobblemonResource("flows")
override val observable = SimpleObservable<CobblemonFlows>()
override val type = PackType.SERVER_DATA
override fun sync(player: ServerPlayer) {
player.sendPacket(FlowRegistrySyncPacket(clientFlows.entries))
}
val runtime by lazy { MoLangRuntime().setup() } // Lazy for if someone adds to generalFunctions in MoLangFunctions
val clientFlows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
val flows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
override fun reload(manager: ResourceManager) {
val folderBeforeNameRegex = ".*\\/([^\\/]+)\\/[^\\/]+\$".toRegex()
manager.listResources("flows") { path -> path.endsWith(CobblemonScripts.MOLANG_EXTENSION) }.forEach { (identifier, resource) ->
resource.openAsReader().use { stream ->
stream.buffered().use { reader ->
try {
val expression = reader.readText().asExpressionLike()
val event = folderBeforeNameRegex.find(identifier.path)?.groupValues?.get(1)
?: throw IllegalArgumentException("Invalid flow path: $identifier. Should have a folder structure that includes the name of the event being flowed.")
if (identifier.path.startsWith("flows/client/")) {
clientFlows.getOrPut(ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)) { mutableListOf() }.add(expression)
} else {
flows.getOrPut(ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)) { mutableListOf() }.add(expression)
}
} catch (exception: Exception) {
throw ExecutionException("Error loading MoLang script for flow: $identifier", exception)
}
}
}
}
Cobblemon.LOGGER.info("Loaded ${CobblemonScripts.scripts.size} flows and ${CobblemonScripts.clientScripts.size} client flows")
observable.emit(this)
}
fun run(eventResourceLocation: ResourceLocation, context: Map<String, MoValue>) {
flows[eventResourceLocation]?.forEach { it.resolve(runtime, context) }
}
}
@@ -171,14 +171,6 @@ interface CobblemonImplementation {
*/
fun server(): MinecraftServer?
/**
* Handles platform independent reloading of a [JsonDataRegistry].
*
* @param registry The [JsonDataRegistry] to reload.
* @param manager The [ResourceManager] to reload from.
*/
fun <T> reloadJsonRegistry(registry: JsonDataRegistry<T>, manager: ResourceManager): HashMap<ResourceLocation, T>
/**
* Registers an item to the [ComposterBlock].
*
@@ -1224,80 +1224,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
@JvmField
val GEM_BUG_BLOCK = blockItem("gem_bug_block", CobblemonBlocks.GEM_BUG_BLOCK)
@JvmField
val GEM_DARK_BLOCK = blockItem("gem_dark_block", CobblemonBlocks.GEM_DARK_BLOCK)
@JvmField
val GEM_DRAGON_BLOCK = blockItem("gem_dragon_block", CobblemonBlocks.GEM_DRAGON_BLOCK)
@JvmField
val GEM_ELECTRIC_BLOCK = blockItem("gem_electric_block", CobblemonBlocks.GEM_ELECTRIC_BLOCK)
@JvmField
val GEM_FAIRY_BLOCK = blockItem("gem_fairy_block", CobblemonBlocks.GEM_FAIRY_BLOCK)
@JvmField
val GEM_FIGHTING_BLOCK = blockItem("gem_fighting_block", CobblemonBlocks.GEM_FIGHTING_BLOCK)
@JvmField
val GEM_FIRE_BLOCK = blockItem("gem_fire_block", CobblemonBlocks.GEM_FIRE_BLOCK)
@JvmField
val GEM_FLYING_BLOCK = blockItem("gem_flying_block", CobblemonBlocks.GEM_FLYING_BLOCK)
@JvmField
val GEM_GHOST_BLOCK = blockItem("gem_ghost_block", CobblemonBlocks.GEM_GHOST_BLOCK)
@JvmField
val GEM_GRASS_BLOCK = blockItem("gem_grass_block", CobblemonBlocks.GEM_GRASS_BLOCK)
@JvmField
val GEM_GROUND_BLOCK = blockItem("gem_ground_block", CobblemonBlocks.GEM_GROUND_BLOCK)
@JvmField
val GEM_ICE_BLOCK = blockItem("gem_ice_block", CobblemonBlocks.GEM_ICE_BLOCK)
@JvmField
val GEM_NORMAL_BLOCK = blockItem("gem_normal_block", CobblemonBlocks.GEM_NORMAL_BLOCK)
@JvmField
val GEM_POISON_BLOCK = blockItem("gem_poison_block", CobblemonBlocks.GEM_POISON_BLOCK)
@JvmField
val GEM_PSYCHIC_BLOCK = blockItem("gem_psychic_block", CobblemonBlocks.GEM_PSYCHIC_BLOCK)
@JvmField
val GEM_ROCK_BLOCK = blockItem("gem_rock_block", CobblemonBlocks.GEM_ROCK_BLOCK)
@JvmField
val GEM_STEEL_BLOCK = blockItem("gem_steel_block", CobblemonBlocks.GEM_STEEL_BLOCK)
@JvmField
val GEM_WATER_BLOCK = blockItem("gem_water_block", CobblemonBlocks.GEM_WATER_BLOCK)
@JvmField
val GEM_BUG_BUDDING = blockItem("gem_bug_budding", CobblemonBlocks.GEM_BUG_BUDDING)
@JvmField
val GEM_DARK_BUDDING = blockItem("gem_dark_budding", CobblemonBlocks.GEM_DARK_BUDDING)
@JvmField
val GEM_DRAGON_BUDDING = blockItem("gem_dragon_budding", CobblemonBlocks.GEM_DRAGON_BUDDING)
@JvmField
val GEM_ELECTRIC_BUDDING = blockItem("gem_electric_budding", CobblemonBlocks.GEM_ELECTRIC_BUDDING)
@JvmField
val GEM_FAIRY_BUDDING = blockItem("gem_fairy_budding", CobblemonBlocks.GEM_FAIRY_BUDDING)
@JvmField
val GEM_FIGHTING_BUDDING = blockItem("gem_fighting_budding", CobblemonBlocks.GEM_FIGHTING_BUDDING)
@JvmField
val GEM_FIRE_BUDDING = blockItem("gem_fire_budding", CobblemonBlocks.GEM_FIRE_BUDDING)
@JvmField
val GEM_FLYING_BUDDING = blockItem("gem_flying_budding", CobblemonBlocks.GEM_FLYING_BUDDING)
@JvmField
val GEM_GHOST_BUDDING = blockItem("gem_ghost_budding", CobblemonBlocks.GEM_GHOST_BUDDING)
@JvmField
val GEM_GRASS_BUDDING = blockItem("gem_grass_budding", CobblemonBlocks.GEM_GRASS_BUDDING)
@JvmField
val GEM_GROUND_BUDDING = blockItem("gem_ground_budding", CobblemonBlocks.GEM_GROUND_BUDDING)
@JvmField
val GEM_ICE_BUDDING = blockItem("gem_ice_budding", CobblemonBlocks.GEM_ICE_BUDDING)
@JvmField
val GEM_NORMAL_BUDDING = blockItem("gem_normal_budding", CobblemonBlocks.GEM_NORMAL_BUDDING)
@JvmField
val GEM_POISON_BUDDING = blockItem("gem_poison_budding", CobblemonBlocks.GEM_POISON_BUDDING)
@JvmField
val GEM_PSYCHIC_BUDDING = blockItem("gem_psychic_budding", CobblemonBlocks.GEM_PSYCHIC_BUDDING)
@JvmField
val GEM_ROCK_BUDDING = blockItem("gem_rock_budding", CobblemonBlocks.GEM_ROCK_BUDDING)
@JvmField
val GEM_STEEL_BUDDING = blockItem("gem_steel_budding", CobblemonBlocks.GEM_STEEL_BUDDING)
@JvmField
val GEM_WATER_BUDDING = blockItem("gem_water_budding", CobblemonBlocks.GEM_WATER_BUDDING)
private fun blockItem(name: String, block: Block): BlockItem = this.create(name, BlockItem(block, Item.Properties()))
private fun noSettingsItem(name: String): CobblemonItem = this.create(name, CobblemonItem(Item.Properties()))
@@ -54,6 +54,7 @@ import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
import com.cobblemon.mod.common.client.net.trade.*
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
@@ -74,6 +75,7 @@ import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
@@ -85,7 +87,6 @@ import com.cobblemon.mod.common.net.messages.client.spawn.SpawnNPCPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokemonPacket
import com.cobblemon.mod.common.net.messages.client.starter.OpenStarterUIPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.storage.RemoveClientPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.SwapClientPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePartyPacket
@@ -94,10 +95,13 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemo
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -110,6 +114,7 @@ import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
@@ -125,7 +130,10 @@ import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonT
import com.cobblemon.mod.common.net.messages.server.storage.pc.ReleasePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.SwapPCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.UnlinkPlayerFromPCPacket
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.*
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
@@ -139,6 +147,8 @@ import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
@@ -156,21 +166,6 @@ import com.cobblemon.mod.common.net.serverhandling.storage.pc.MovePartyPokemonTo
import com.cobblemon.mod.common.net.serverhandling.storage.pc.ReleasePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.SwapPCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.UnlinkPlayerFromPCHandler
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.trade.*
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -308,6 +303,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokedexDexSyncPacket.ID, PokedexDexSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -260,6 +260,10 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
@JvmField
val MOVE_PURSUIT_TARGET = this.create("move.pursuit.target")
@JvmField
val MOVE_PSYCHIC_TARGET = this.create("move.psychic.target")
@JvmField
val IMPACT_NORMAL = this.create("impact.normal")
@@ -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.api.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.entity.ai.LookInDirectionTask
import com.cobblemon.mod.common.util.asExpression
import net.minecraft.world.entity.LivingEntity
class LookInDirectionTaskConfig : SingleTaskConfig {
var yaw: Expression = "0".asExpression()
var pitch: Expression = "0".asExpression()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
) = LookInDirectionTask(yaw, pitch)
}
@@ -172,4 +172,15 @@ The 'flee_attacker' type creates a task that will make the entity flee from its
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.
- `desiredDistance`: A MoLang expression that determines the distance from the nearest hostile that the entity should try to maintain. Defaults to 9.
### run_script
The 'run_script' type creates a task that will run a MoLang script every tick on this entity. This has the risk of being very laggy
if the script is doing expensive things. Remember to use `q.entity.world.game_time` and `math.mod` to do intermittent actions inside
the script to avoid lagging the game.
- `script`: A resource location identifying the script to run. This should be in the `molang` datapack folder.
### look_in_direction
The 'look_in_direction' type creates a task that will make the entity look in a specific direction at all times where they aren't being told to look elsewhere.
- `yaw`: A MoLang expression that determines the yaw angle (degrees) of the entity. Defaults to `0`.
- `pitch`: A MoLang expression that determines the pitch angle (degrees) of the entity. Defaults to `0`.
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.cobblemonResource
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
/**
* Runs a registered MoLang script (from the molang datapack folder) each tick.
*
* It's up to the user to not make expensive ass scripts. Remember you can access the game time
* from q.entity.level.game_time which can be used with a modulo operator to make something intermittent.
*
* @author Hiroku
* @since December 2nd, 2024
*/
class RunScript : SingleTaskConfig {
val script = cobblemonResource("dummy")
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? = BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET) // I think I need to have at least something here?
).apply(it) { _ ->
Trigger { world, entity, _ ->
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: entity.asMostSpecificMoLangValue())
return@Trigger CobblemonScripts.run(script, runtime) == DoubleValue.ONE
}
}
}
}
@@ -58,6 +58,8 @@ fun interface TaskConfig {
"calm_down" to CalmDownTaskConfig::class.java,
"flee_attacker" to FleeAttackerTaskConfig::class.java,
"flee_nearest_hostile" to FleeNearestHostileTaskConfig::class.java,
"run_script" to RunScript::class.java,
"look_in_direction" to LookInDirectionTaskConfig::class.java,
)
val runtime = MoLangRuntime().setup()
@@ -94,7 +94,6 @@ open class PokemonBattle(
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
actor.setupStruct()
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.battles.model.actor
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.ForcePassActionResponse
@@ -36,7 +37,7 @@ abstract class BattleActor(
pokemonList.forEach { it.actor = this }
}
val struct = QueryStruct(hashMapOf())
val struct = asMoLangValue()
lateinit var showdownId: String
lateinit var battle: PokemonBattle
@@ -139,10 +140,6 @@ abstract class BattleActor(
battle.writeShowdownAction(">$showdownId ${showdownMessages.joinToString()}")
}
open fun setupStruct() {
struct.addFunction("uuid") { StringValue(uuid.toString()) }
}
abstract fun getName(): MutableComponent
/**
@@ -9,8 +9,11 @@
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.File
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
@@ -42,7 +45,24 @@ interface JsonDataRegistry<T> : DataRegistry {
val resourcePath: String
override fun reload(manager: ResourceManager) {
this.reload(Cobblemon.implementation.reloadJsonRegistry(this, manager))
val data = hashMapOf<ResourceLocation, T>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
if (identifier.namespace == "pixelmon") {
return@forEach
}
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
try {
data[resolvedIdentifier] = gson.fromJson(reader, typeToken.type)
} catch (exception: Exception) {
throw ExecutionException("Error loading JSON for data: $identifier", exception)
}
}
}
}
reload(data)
}
/**
@@ -21,13 +21,13 @@ import com.cobblemon.mod.common.util.adapters.DialogueTextAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.TextAdapter
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
/**
@@ -56,7 +56,7 @@ object Dialogues : JsonDataRegistry<Dialogue> {
.registerTypeAdapter(DialogueText::class.java, DialogueTextAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(MutableComponent::class.java, TextAdapter)
.registerTypeAdapter(MutableComponent::class.java, TranslatedTextAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.also { gsonObservable.emit(it) }
.create()
@@ -19,8 +19,8 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
* @author Segfault Guy
* @since April 6th, 2023
*/
data class BattleFaintedEvent (
override val battle: PokemonBattle,
val killed: BattlePokemon,
val context: BattleContext
data class BattleFaintedEvent(
override val battle: PokemonBattle,
val killed: BattlePokemon,
val context: BattleContext
) : BattleEvent
@@ -18,8 +18,6 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
* @since March 25th 2023
*/
data class BattleFledEvent (
override val battle: PokemonBattle,
val player: PlayerBattleActor
) : BattleEvent
@@ -8,8 +8,14 @@
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
/**
* Event fired when a battle is won by some number of [BattleActor]s.
@@ -22,4 +28,20 @@ data class BattleVictoryEvent (
val winners : List<BattleActor>,
val losers : List<BattleActor>,
val wasWildCapture : Boolean
) : BattleEvent
) : BattleEvent {
val context = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
"winners" to winners.asArrayValue { it.struct },
"losers" to losers.asArrayValue { it.struct },
"was_wild_capture" to DoubleValue(wasWildCapture),
"player_winners" to winners.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"player_losers" to losers.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npc_winners" to winners.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"npc_losers" to losers.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon_winners" to winners.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
"wild_pokemon_losers" to losers.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
)
}
@@ -8,12 +8,23 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.core.registries.Registries
import net.minecraft.server.level.ServerPlayer
data class PokemonCapturedEvent (
val pokemon: Pokemon,
val player: ServerPlayer,
val pokeBallEntity: EmptyPokeBallEntity
)
) {
val context = mutableMapOf<String, MoValue>(
"pokemon" to pokemon.struct,
"player" to player.asMoLangValue(),
"poke_ball" to pokeBallEntity.struct,
"item" to player.level().itemRegistry.wrapAsHolder(pokeBallEntity.pokeBall.item).asMoLangValue(Registries.ITEM)
)
}
@@ -24,7 +24,7 @@ abstract class ParentWidget(
component: Component
): Renderable, AbstractWidget(pX, pY, pWidth, pHeight, component) {
private val children: MutableList<GuiEventListener> = mutableListOf()
val children: MutableList<GuiEventListener> = mutableListOf()
/**
* Adds Widget to the children list
@@ -22,6 +22,8 @@ import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
@@ -37,17 +39,23 @@ 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.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import java.util.UUID
import kotlin.math.sqrt
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -59,14 +67,23 @@ import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import net.minecraft.world.level.Level.ExplosionInteraction
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
@@ -81,6 +98,25 @@ import net.minecraft.world.phys.Vec3
*/
object MoLangFunctions {
val generalFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>(
"print" to java.util.function.Function { params ->
val message = params.get<MoValue>(0).asString()
Cobblemon.LOGGER.info(message)
},
"replace" to java.util.function.Function { params ->
val text = params.getString(0)
val search = params.getString(1)
val replace = params.getString(2)
return@Function StringValue(text.replace(search, replace))
},
"is_blank" to java.util.function.Function { params ->
val arg = params.get<MoValue>(0)
return@Function DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
},
"run_command" to java.util.function.Function { params ->
val command = params.getString(0)
val server = server() ?: return@Function DoubleValue.ZERO
server.commands.performPrefixedCommand(server.createCommandSourceStack(), command)
},
"is_int" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) },
"is_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() != null) },
"to_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0) },
@@ -115,19 +151,93 @@ object MoLangFunctions {
runtime.environment.variable = params.environment.variable
runtime.environment.context = params.environment.context
val script = params.getString(0).asIdentifierDefaultingNamespace()
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
CobblemonScripts.run(script, runtime) ?: DoubleValue.ZERO
},
"run_command" to java.util.function.Function { params ->
val command = params.getString(0)
server()?.let {
return@Function DoubleValue(it.commands.dispatcher.execute(command, it.createCommandSourceStack()))
} ?: return@Function DoubleValue.ZERO
"run_molang" to java.util.function.Function { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
runtime.environment.context = params.environment.context
val expression = params.getString(0).asExpressionLike()
runtime.resolve(expression)
},
)
val biomeFunctions = mutableListOf<(Holder<Biome>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
val worldFunctions = mutableListOf<(Holder<Level>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ worldHolder ->
val world = worldHolder.value()
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("game_time") { _ -> DoubleValue(world.gameTime.toDouble()) }
map.put("time_of_day") {
val time = world.dayTime % 24000
return@put DoubleValue(time.toDouble())
}
map.put("server") { _ -> server()?.asMoLangValue() ?: DoubleValue.ZERO }
map.put("is_raining_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
}
map.put("is_chunk_loaded_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
return@put DoubleValue(world.isLoaded(BlockPos(x, y, z)))
}
map.put("is_thundering") { _ -> DoubleValue(world.isThundering) }
map.put("is_raining") { _ -> DoubleValue(world.isRaining) }
map.put("set_block") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
?: run {
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
return@put DoubleValue.ZERO
}
world.setBlock(BlockPos(x, y, z), block.defaultBlockState(), Block.UPDATE_ALL)
}
map.put("get_block") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.getBlockState(BlockPos(x, y, z)).block
return@put world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
}
map.put("spawn_explosion") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val range = params.getDouble(3).toFloat()
world.explode(null, x, y, z, range, ExplosionInteraction.valueOf(params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name))
}
map.put("spawn_lightning") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, world)
lightning.setPos(x, y, z)
world.addFreshEntity(lightning)
return@put DoubleValue.ONE
}
map.put("get_entities_around") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val range = params.getDouble(3) * 2
val entities = world.getEntities(null, AABB.ofSize(Vec3(x, y, z), range, range, range))
return@put entities
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
return@mutableListOf map
}
)
val biomeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val dimensionTypeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val blockFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val dimensionTypeFunctions = mutableListOf<(Holder<DimensionType>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
val blockFunctions = mutableListOf<(Holder<Block>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
val playerFunctions = mutableListOf<(Player) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ player ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -141,7 +251,7 @@ object MoLangFunctions {
map.put("saturation_level") { _ -> DoubleValue(player.foodData.saturationLevel) }
map.put("tell") { params ->
val message = params.getString(0).text()
val overlay = params.getBooleanOrNull(1) ?: false
val overlay = params.getBooleanOrNull(1) == true
player.displayClientMessage(message, overlay)
}
map.put("teleport") { params ->
@@ -160,7 +270,12 @@ object MoLangFunctions {
environment.query = player.asMoLangValue()
environment
}
map.put("is_player") { DoubleValue.ONE }
if (player is ServerPlayer) {
map.put("run_command") { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
map.put("is_party_at_full_health") { _ ->
DoubleValue(player.party().none(Pokemon::canBeHealed)) }
map.put("can_heal_at_healer") { params ->
@@ -188,11 +303,46 @@ object MoLangFunctions {
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.put("get_npc_data") { params ->
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
return@put data.map[npcId]!!
} else {
val vars = VariableStruct()
data.map[npcId] = vars
return@put vars
}
}
map.put("get_npc_variable") { params ->
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
return@put (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
}
}
map.put("set_npc_variable") { params ->
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
(data.map[npcId] as VariableStruct).map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid)
}
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
}
map
}
)
val entityFunctions = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val entityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
@@ -201,6 +351,28 @@ object MoLangFunctions {
val source = DamageSource(entity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolder(DamageTypes.GENERIC).get())
entity.hurt(source, amount.toFloat())
}
map.put("walk_to") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity is PathfinderMob) {
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1))
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(Vec3(x, y + entity.eyeHeight, z)))
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
}
map.put("has_walk_target") { _ ->
if (entity is PathfinderMob) {
DoubleValue(entity.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
} else {
DoubleValue.ZERO
}
}
map.put("is_sneaking") { _ -> DoubleValue(entity.isShiftKeyDown) }
map.put("is_sprinting") { _ -> DoubleValue(entity.isSprinting) }
map.put("is_flying") { _ -> DoubleValue(entity.isFallFlying) }
@@ -212,8 +384,14 @@ object MoLangFunctions {
map.put("is_on_fire") { _ -> DoubleValue(entity.isOnFire) }
map.put("is_invisible") { _ -> DoubleValue(entity.isInvisible) }
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger()) }
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("distance_to_pos") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
return@put DoubleValue(sqrt(entity.distanceToSqr(Vec3(x, y, z))))
}
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("name") { _ -> StringValue(entity.effectiveName().string) }
map.put("yaw") { _ -> DoubleValue(entity.yRot.toDouble()) }
@@ -255,6 +433,21 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("get_nearby_entities") { params ->
val distance = params.getDouble(0)
val entities = entity.level().getEntities(entity, AABB.ofSize(entity.position(), distance, distance, distance))
return@put entities
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
val animation = params.getString(0)
val packet = PlayPosableAnimationPacket(entity.id, setOf(animation), emptyList())
packet.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
}
}
map
}
)
@@ -315,9 +508,9 @@ object MoLangFunctions {
}
}
return@put DoubleValue(1)
return@put DoubleValue.ONE
}
return@put DoubleValue(0)
return@put DoubleValue.ZERO
}
map.put("put_pokemon_in_healer") { params ->
val healer = npc.level().getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ZERO
@@ -336,6 +529,8 @@ object MoLangFunctions {
map.put("environment") { _ -> npc.runtime.environment }
map.put("party") { npc.party?.struct ?: DoubleValue.ZERO }
map.put("has_party") { DoubleValue(npc.party != null) }
map.put("is_npc") { DoubleValue.ONE }
map.put("can_battle") { DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false) }
map
}
)
@@ -355,10 +550,26 @@ object MoLangFunctions {
return@put actor.struct
}
map.put("stop") { _ -> battle.stop() }
map.put("actors") { battle.actors.toList().asArrayValue { it.struct } }
map
}
)
val battleActorFunctions = mutableListOf<(BattleActor) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ battleActor ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_npc") { DoubleValue(battleActor.type == ActorType.NPC) }
map.put("is_player") { DoubleValue(battleActor.type == ActorType.PLAYER) }
map.put("is_wild") { DoubleValue(battleActor.type == ActorType.WILD) }
if (battleActor is NPCBattleActor) {
map.put("npc") { battleActor.entity.struct }
} else if (battleActor is PlayerBattleActor) {
map.put("player") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
return@mutableListOf map
}
)
val pokemonFunctions = mutableListOf<(Pokemon) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pokemon ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -397,6 +608,7 @@ object MoLangFunctions {
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map.put("is_pokemon") { DoubleValue.ONE }
map
}
)
@@ -535,10 +747,68 @@ object MoLangFunctions {
}
)
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions)
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions)
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions)
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions)
val serverFunctions: MutableList<(MinecraftServer) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ server ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("get_world") { params ->
val world = server.getLevel(
ResourceKey.create(
Registries.DIMENSION,
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
)
) ?: return@put DoubleValue.ZERO
return@put world
.registryAccess()
.registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(world)
.asWorldMoLangValue()
}
map.put("broadcast") { params ->
val message = params.getString(0)
server.playerList.broadcastSystemMessage(message.text(), params.getBooleanOrNull(1) == true)
return@put DoubleValue.ONE
}
map.put("get_player_by_uuid") { params ->
val uuid = UUID.fromString(params.getString(0))
val player = server.playerList.getPlayer(uuid) ?: return@put DoubleValue.ZERO
return@put player.asMoLangValue()
}
map.put("get_player_by_username") { params ->
val username = params.getString(0)
val player = server.playerList.getPlayerByName(username) ?: return@put DoubleValue.ZERO
return@put player.asMoLangValue()
}
map.put("data") { params ->
val data = Cobblemon.molangData.load(UUID(0L, 0L))
return@put data
}
map.put("save_data") {
Cobblemon.molangData.save(UUID(0L, 0L))
return@put DoubleValue.ONE
}
// Maybe later...
// map.put("stop") { params ->
// Cobblemon.LOGGER.warn("Server is being stopped from a MoLang script.")
// server.stopServer()
// }
return@mutableListOf map
}
)
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun MinecraftServer.asMoLangValue() = ObjectValue(this).addStandardFunctions().addFunctions(serverFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Player.asMoLangValue(): ObjectValue<Player> {
val value = ObjectValue(
obj = this,
@@ -586,6 +856,17 @@ object MoLangFunctions {
return value
}
fun PokemonEntity.asMoLangValue(): ObjectValue<PokemonEntity> {
val value = ObjectValue(
obj = this,
stringify = { it.pokemon.uuid.toString() }
)
value.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(pokemonFunctions.flatMap { it(this.pokemon).entries.map { it.key to it.value } }.toMap()) // Convenience
value.addFunctions(pokemonEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun PokemonBattle.asMoLangValue(): ObjectValue<PokemonBattle> {
val value = ObjectValue(
obj = this,
@@ -595,6 +876,15 @@ object MoLangFunctions {
return value
}
fun BattleActor.asMoLangValue(): ObjectValue<BattleActor> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(battleActorFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun SpawningContext.asMoLangValue(): ObjectValue<SpawningContext> {
val value = ObjectValue(
obj = this,
@@ -604,6 +894,18 @@ object MoLangFunctions {
return value
}
/**
* Different functions exist depending on
*/
fun LivingEntity.asMostSpecificMoLangValue(): ObjectValue<out LivingEntity> {
return when (this) {
is Player -> asMoLangValue()
is PokemonEntity -> struct
is NPCEntity -> struct
else -> ObjectValue(this).also { it.addStandardFunctions().addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap()) }
}
}
fun <T> Holder<T>.asMoLangValue(key: ResourceKey<Registry<T>>): ObjectValue<Holder<T>> {
val value = ObjectValue(
obj = this,
@@ -70,7 +70,7 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
.registerTypeAdapter(NPCClass::class.java, NPCClassAdapter)
.registerTypeAdapter(Activity::class.java, ActivityAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.registerTypeAdapter(Component::class.java, TranslatedTextAdapter)
.registerTypeAdapter(WeightedAspect::class.java, WeightedAspectAdapter)
.registerTypeAdapter(BrainConfig::class.java, BrainConfigAdapter)
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
@@ -9,10 +9,12 @@
package com.cobblemon.mod.common.api.npc
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.google.gson.JsonElement
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. Completely custom party providers will only display
@@ -25,13 +27,14 @@ interface NPCPartyProvider {
companion object {
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
SimplePartyProvider.TYPE to { SimplePartyProvider() },
PoolPartyProvider.TYPE to { PoolPartyProvider() }
PoolPartyProvider.TYPE to { PoolPartyProvider() },
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
)
}
val type: String
val isStatic: Boolean
fun provide(npc: NPCEntity, level: Int): NPCPartyStore
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
fun loadFromJSON(json: JsonElement)
}
@@ -67,7 +67,7 @@ object NPCPresets : JsonDataRegistry<NPCPreset> {
.registerTypeAdapter(NPCVariationProvider::class.java, NPCVariationProviderAdapter)
.registerTypeAdapter(Activity::class.java, ActivityAdapter)
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.registerTypeAdapter(Component::class.java, TranslatedTextAdapter)
.registerTypeAdapter(BrainConfig::class.java, BrainConfigAdapter)
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
@@ -57,8 +57,8 @@ any number of presets and the properties of those presets will be merged into th
"variableName": "challenge_distance",
"displayName": "cobblemon.npc.challenge_distance",
"description": "cobblemon.npc.challenge_distance.description",
"type": "number",
"default": "5"
"type": "NUMBER",
"defaultValue": "5"
}
],
"names": [
@@ -178,19 +178,19 @@ An example situation of using this is if there is an NPC class preset that defin
challenging a player when they come within a certain range. Inside that class preset, the AI script may
need to know at what distance the player will be engaged. This can be defined in the NPC class preset as
a config variable, and therefore any NPC class that relies on this preset will have that config variable
and present it to users when they setup an NPC of that class.
and present it to users when they set up an NPC of that class.
"config": [
{
"variableName": "challenge_distance",
"displayName": "cobblemon.npc.challenge_distance",
"description": "cobblemon.npc.challenge_distance.description",
"type": "number",
"default": "5"
"displayName": "cobblemon.npc.challenge_distance.name",
"description": "cobblemon.npc.challenge_distance.desc",
"type": "NUMBER",
"defaultValue": "5"
}
]
The "type" of variable is one of: `string`, `number`, or `boolean`. The type is used to preset a more
The "type" of variable is one of: `STRING`, `NUMBER`, or `BOOLEAN`. The type is used to preset a more
appropriate input field for the user when they are setting up the NPC.
### names
@@ -298,6 +298,10 @@ depend on visual aspects of the NPC. For example, if the NPC has a variation for
certain Pokémon to be `"weight": "q.npc.has_aspect('dirty') ? -1 : 0"` which would make it a guaranteed party Pokémon for "dirty" NPCs
and impossible for all other variations of the NPC.
The MoLang environment used for the pool party provider contains q.level for the seed level, q.npc for the NPC, as well as q.players for
an array of all players that are challenging the NPC. The players list is only present for dynamic parties. If it is specifically
a single battle challenge and this is a dynamic party, there will also be a `q.player`, for convenience.
"party": {
"type": "pool",
"minPokemon": "3",
@@ -344,6 +348,33 @@ and impossible for all other variations of the NPC.
]
}
#### script
The "script" party type is a way to define a party of Pokémon using a MoLang script. The script can modify a q.party struct,
primarily using function calls such as `q.party.add_by_properties('pikachu shiny)`.
There is an `isStatic` property to control whether the party is static or dynamic. There is a `script` property which is a
resource location pointing to the script that should be located in the `molang` datapack folder.
The MoLang environment used for the script party provider contains q.level for the seed level, q.npc for the NPC, as well as q.players for
an array of all players that are challenging the NPC. The players list is only present for dynamic parties. If it is specifically
a single battle challenge and this is a dynamic party, there will also be a `q.player`, for convenience.
"party": {
"type": "script",
"isStatic": true,
"script": "cobblemon:npc_party"
}
An example script might be:
t.use_butterfree = math.random_integer(1, 2) == 1;
t.use_butterfree ? {
q.party.add_by_properties('butterfree level=' + (q.level + 3));
} : {
q.party.add_by_properties('beedrill level=' + (q.level + 3));
};
q.party.add_by_properties('pidgey');
### autoHealParty
Whether or not the NPC's party will be healed between battles. If the NPC has a dynamic party, this option will
do nothing. If the NPC has a static party and this option is false, their Pokémon will not be healed and the
@@ -13,6 +13,11 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
/**
@@ -30,10 +35,38 @@ class NPCConfigVariable(
val type: NPCVariableType = NPCVariableType.NUMBER,
val defaultValue: String = "0",
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): NPCConfigVariable {
return NPCConfigVariable(
buffer.readString(),
buffer.readText(),
buffer.readText(),
buffer.readEnum(NPCVariableType::class.java),
buffer.readString()
)
}
}
enum class NPCVariableType {
NUMBER,
STRING,
BOOLEAN
TEXT,
BOOLEAN;
fun toMoValue(value: String): MoValue {
return when (this) {
TEXT -> StringValue(value)
BOOLEAN -> DoubleValue(value.let { it == "1" || it.toBooleanStrictOrNull() == true })
else -> DoubleValue(value.toDouble())
}
}
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeString(variableName)
buffer.writeText(displayName)
buffer.writeText(description)
buffer.writeEnum(type)
buffer.writeString(defaultValue)
}
fun apply(npc: NPCEntity, value: MoValue) {
@@ -41,11 +74,6 @@ class NPCConfigVariable(
}
fun applyDefault(npc: NPCEntity) {
val value = when (type) {
NPCVariableType.STRING -> StringValue(defaultValue)
NPCVariableType.BOOLEAN -> DoubleValue(defaultValue.toBoolean())
else -> DoubleValue(defaultValue.toDouble())
}
npc.config.set(variableName.split(".").iterator(), value)
npc.config.setDirectly(variableName, type.toMoValue(defaultValue))
}
}
@@ -10,11 +10,14 @@ package com.cobblemon.mod.common.api.npc.partyproviders
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
@@ -23,6 +26,7 @@ import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. It generates a party using a more complex process
@@ -93,16 +97,22 @@ class PoolPartyProvider : NPCPartyProvider {
}
}
fun formulateParty(npc: NPCEntity, level: Int, party: NPCPartyStore) {
fun formulateParty(npc: NPCEntity, level: Int, players: List<ServerPlayer>, party: NPCPartyStore) {
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
runtime.withQueryValue("level", DoubleValue(level))
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
if (players.size == 1) {
// This is for the convenience, most cases will be pvn one player
runtime.withQueryValue("player", players.first().asMoLangValue())
}
val minPokemon = runtime.resolveInt(this.minPokemon)
val maxPokemon = runtime.resolveInt(this.maxPokemon)
var desiredPokemonCount = (minPokemon..maxPokemon).random()
val workingPool = pool.filter { level in it.npcLevels }.toMutableList()
val workingPool = pool.filter { level in it.npcLevels && runtime.resolveInt(it.selectableTimes) > 0 }.toMutableList()
val useCounts = mutableMapOf<DynamicPokemon, Int>()
while (desiredPokemonCount > 0 && workingPool.filter { it.hasWeight(runtime) }.isNotEmpty()) {
while (desiredPokemonCount > 0 && workingPool.any { it.hasWeight(runtime) }) {
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull()
?: workingPool.weightedSelection { it.getWeight(runtime) }
?: break
@@ -121,9 +131,9 @@ class PoolPartyProvider : NPCPartyProvider {
}
}
override fun provide(npc: NPCEntity, level: Int): NPCPartyStore {
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val party = NPCPartyStore(npc)
formulateParty(npc, level, party)
formulateParty(npc, level, players, party)
return party
}
}
@@ -0,0 +1,64 @@
/*
* 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.npc.partyproviders
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
class ScriptPartyProvider : NPCPartyProvider {
companion object {
const val TYPE = "script"
}
override val type = TYPE
override var isStatic: Boolean = true
var script: ResourceLocation = cobblemonResource("dummy")
override fun loadFromJSON(json: JsonElement) {
isStatic = json.asJsonObject.get("isStatic")?.asBoolean != false
script = json.asJsonObject.get("script").asString.asIdentifierDefaultingNamespace()
}
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
runtime.withQueryValue("level", DoubleValue(level))
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
if (players.size == 1) {
// This is for the convenience, most cases will be pvn one player
runtime.withQueryValue("player", players.first().asMoLangValue())
}
val npcParty = NPCPartyStore(npc)
runtime.withQueryValue("party", npcParty.struct)
if (!CobblemonScripts.scripts.containsKey(script)) {
Cobblemon.LOGGER.error("Script $script not found in script registry.")
npcParty.add("magikarp".toProperties().create())
return npcParty
}
CobblemonScripts.run(identifier = script, runtime = runtime)
return npcParty
}
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.toProperties
import com.google.gson.JsonElement
import net.minecraft.server.level.ServerPlayer
/**
* A basic party provider that just produces a [NPCPartyStore] based on a simple list of [PokemonProperties].
@@ -52,7 +53,7 @@ class SimplePartyProvider : NPCPartyProvider {
}
}
override fun provide(npc: NPCEntity, level: Int): NPCPartyStore {
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val pokemon = pokemon.map { it.copy().also { it.level = it.level ?: level }.create() }
val party = NPCPartyStore(npc)
pokemon.forEach(party::add)
@@ -36,7 +36,7 @@ import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.isInt
import com.cobblemon.mod.common.util.isDouble
import com.cobblemon.mod.common.util.isUuid
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.simplify
@@ -59,7 +59,6 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
@@ -187,10 +186,10 @@ open class PokemonProperties {
private fun parseIntProperty(keyPairs: MutableList<Pair<String, String?>>, labels: Iterable<String>): Int? {
val matchingKeyPair = getMatchedKeyPair(keyPairs, labels) ?: return null
val value = matchingKeyPair.second
return if (value == null || !value.isInt()) {
return if (value == null || !value.isDouble()) {
null
} else {
value.toInt()
value.toDouble().toInt()
}
}
@@ -790,7 +789,7 @@ open class PokemonProperties {
}
fun copy(): PokemonProperties {
return PokemonProperties().loadFromJSON(saveToJSON())
return PokemonProperties().loadFromJSON(this.saveToJSON())
}
// If the config value is at least 1, then do 1/x and use that as the property chance
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.ScriptRegistrySyncPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.contextOrEmpty
import com.cobblemon.mod.common.util.endsWith
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -64,6 +65,6 @@ object CobblemonScripts : DataRegistry {
}
fun run(identifier: ResourceLocation, runtime: MoLangRuntime): MoValue? {
return scripts[identifier]?.resolve(runtime)
return scripts[identifier]?.resolve(runtime, runtime.contextOrEmpty)
}
}
@@ -315,7 +315,7 @@ object BattleBuilder {
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
val npcParty = npcEntity.getPartyForChallenge(player)
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
@@ -8,8 +8,8 @@
package com.cobblemon.mod.common.client.gui.npc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.npc.widgets.ConfigVariableList
import com.cobblemon.mod.common.client.gui.npc.widgets.NPCRenderWidget
import com.cobblemon.mod.common.client.gui.npc.widgets.SimpleNPCTextInputWidget
import com.cobblemon.mod.common.net.messages.client.npc.dto.NPCConfigurationDTO
@@ -27,8 +27,6 @@ class NPCEditorScreen(
companion object {
const val BASE_WIDTH = 331
const val BASE_HEIGHT = 161
private val baseResource = cobblemonResource("textures/gui/npc/editor_base.png")
}
val middleX: Int
@@ -43,13 +41,13 @@ class NPCEditorScreen(
override fun init() {
super.init()
addRenderableOnly(NPCRenderWidget(leftX + 6, topY + 32, dto.npcClass, dto.aspects))
addRenderableOnly(NPCRenderWidget(leftX + 6, topY + 22, dto.npcClass, dto.aspects))
addRenderableWidget(SimpleNPCTextInputWidget(
texture = cobblemonResource("textures/gui/npc/basic_text_input.png"),
getter = { dto.npcName.string },
setter = { dto.npcName = it.text() },
x = leftX + 79,
y = topY + 14,
x = leftX + 6,
y = topY + 80,
width = 130,
height = 22
))
@@ -60,8 +58,8 @@ class NPCEditorScreen(
dto.aspects.clear()
dto.aspects.addAll(it.split(",").map { it.trim() })
},
x = leftX + 79,
y = topY + 36,
x = leftX + 6,
y = topY + 100,
width = 130,
height = 40,
maxLength = 75,
@@ -73,21 +71,23 @@ class NPCEditorScreen(
SaveNPCPacket(npcId, dto).sendToServer()
this.minecraft!!.setScreen(null)
}
.pos(leftX + BASE_WIDTH - 42, topY + BASE_HEIGHT - 18)
.pos(leftX + BASE_WIDTH - 42, topY + BASE_HEIGHT - 8)
.size(40, 16)
.build()
)
addRenderableWidget(ConfigVariableList(leftX + 200, topY + 30, this))
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
texture = baseResource,
x = leftX,
y = topY,
height = BASE_HEIGHT,
width = BASE_WIDTH
)
// blitk(
// matrixStack = context.pose(),
// texture = baseResource,
// x = leftX,
// y = topY,
// height = BASE_HEIGHT,
// width = BASE_WIDTH
// )
super.render(context, mouseX, mouseY, delta)
}
}
@@ -0,0 +1,229 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.npc.widgets
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable.NPCVariableType
import com.cobblemon.mod.common.client.gui.npc.NPCEditorScreen
import com.cobblemon.mod.common.client.gui.npc.widgets.ConfigVariableList.ConfigVariable
import com.cobblemon.mod.common.client.render.drawScaledText
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ContainerObjectSelectionList
import net.minecraft.client.gui.components.CycleButton
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.components.Tooltip
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarratableEntry
class ConfigVariableList(
val listX: Int,
val listY: Int,
val parent: NPCEditorScreen
) : ContainerObjectSelectionList<ConfigVariable>(
Minecraft.getInstance(),
WIDTH, // width
HEIGHT, // height
0, // top
SLOT_HEIGHT + SLOT_SPACING
) {
companion object {
const val WIDTH = 200
const val HEIGHT = 120
const val SLOT_WIDTH = 160
const val SLOT_HEIGHT = 33
const val SLOT_SPACING = 0
}
private var scrolling = false
override fun getRowWidth() = SLOT_WIDTH
init {
this.x = listX
this.y = listY
correctSize()
setRenderHeader(false, 0)
val npcClass = NPCClasses.getByIdentifier(parent.dto.npcClass)
if (npcClass != null) {
npcClass.config.forEach { t ->
val value = parent.dto.variables[t.variableName] ?: t.defaultValue
addEntry(ConfigVariable(t, value, this))
}
}
}
override fun getScrollbarPosition() = x + width - 3
public override fun addEntry(entry: ConfigVariable) = super.addEntry(entry)
public override fun removeEntry(entry: ConfigVariable) = super.removeEntry(entry)
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
correctSize()
super.renderWidget(context, mouseX, mouseY, partialTicks)
// Scroll Overlay
// blitk(
// matrixStack = context.pose(),
// texture = scrollOverlayResource,
// x = listX,
// y = listY - 13,
// height = 131,
// width = WIDTH
// )
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
updateScrollingState(mouseX, mouseY)
if (scrolling) {
focused = getEntryAtPosition(mouseX, mouseY)
isDragging = true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (scrolling) {
if (mouseY < this.listY) {
scrollAmount = 0.0
} else if (mouseY > bottom) {
scrollAmount = maxScroll.toDouble()
} else {
scrollAmount += deltaY
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
scrolling = mouseX >= this.scrollbarPosition.toDouble()
&& mouseX < (this.scrollbarPosition + 3).toDouble()
&& mouseY >= listY
&& mouseY < bottom
}
override fun renderListBackground(context: GuiGraphics) {}
private fun correctSize() {
setRectangle(WIDTH, HEIGHT, listX, (listY - 4))
// setX(listX)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
class ConfigVariable(val variable: NPCConfigVariable, val value: String, private val parent: ConfigVariableList) : Entry<ConfigVariable>() {
val client: Minecraft = Minecraft.getInstance()
var _focused = false
var children = mutableListOf<GuiEventListener>()
var textValue = value
var booleanValue = value.let { it.toDoubleOrNull() == 1.0 || it.toBooleanStrictOrNull() == true }
val editBox = EditBox(
client.font,
parent.listX,
parent.listY + 4,
if (variable.type == NPCVariableType.NUMBER) SLOT_WIDTH - 100 else SLOT_WIDTH,
SLOT_HEIGHT - 8,
variable.displayName,
).also {
it.tooltip = Tooltip.create(variable.description)
it.height = SLOT_HEIGHT - 16
it.setMaxLength(250)
it.value = value
it.setResponder {
textValue = it
parent.parent.dto.variables[variable.variableName] = it
}
if (variable.type == NPCVariableType.NUMBER) {
it.setFilter { value -> value.toDoubleOrNull() != null || value.isBlank() || value == "." || value == "-" }
}
}
val cycleButton = CycleButton.onOffBuilder(booleanValue).create(variable.displayName) { _, value ->
booleanValue = value
parent.parent.dto.variables[variable.variableName] = if (value) "1" else "0"
}.also {
it.height = SLOT_HEIGHT - 14
it.width = SLOT_WIDTH - 8
it.tooltip = Tooltip.create(variable.description)
}
init {
if (variable.type == NPCVariableType.BOOLEAN) {
children.add(cycleButton)
} else {
children.add(editBox)
}
}
override fun isFocused() = _focused
override fun setFocused(focused: Boolean) {
this._focused = focused
}
override fun children() = children
override fun narratables(): List<NarratableEntry> {
return children.filterIsInstance<NarratableEntry>()
}
override fun render(
context: GuiGraphics,
index: Int,
rowTop: Int,
rowLeft: Int,
rowWidth: Int,
rowHeight: Int,
mouseX: Int,
mouseY: Int,
isHovered: Boolean,
partialTicks: Float
) {
val x = rowLeft - 4
val y = rowTop
val matrixStack = context.pose()
// blitk(
// matrixStack = matrixStack,
// texture = slotResource,
// x = x,
// y = y,
// height = SLOT_HEIGHT,
// width = rowWidth,
// vOffset = if (isHovered) SLOT_HEIGHT else 0,
// textureHeight = SLOT_HEIGHT * 2
// )
if (variable.type == NPCVariableType.BOOLEAN) {
cycleButton.x = x
cycleButton.y = y + 6
cycleButton.render(context, mouseX, mouseY, partialTicks)
} else {
drawScaledText(
context = context,
text = variable.displayName.visualOrderText,
x = x,
y = y + 4
)
editBox.x = x
editBox.y = y + 14
editBox.render(context, mouseX, mouseY, partialTicks)
}
}
// override fun mouseClicked(mouseX: Double, mouseY: Double, delta: Int): Boolean {
// if (moveButton.isHovered(mouseX, mouseY) && canUnpasture()) {
// moveButton.onPress()
// return true
// }
// return false
// }
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.gui.ParentWidget
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
@@ -688,6 +689,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
fun isWithinPortraitSpace(mouseX: Double, mouseY: Double): Boolean =
mouseX.toInt() in pX + 15..(pX + 15 + PORTRAIT_POKE_BALL_WIDTH)
&& mouseY.toInt() in pY + 25..(pY + 25 + PORTRAIT_POKE_BALL_HEIGHT)
&& !children.any { it.isMouseOver(mouseX, mouseY) && it is ScaledButton }
private fun isSelectedPokemonOwned(): Boolean {
return currentEntry?.let { CobblemonClient.clientPokedexData.getKnowledgeForSpecies(it.speciesId) } == PokedexEntryProgress.CAUGHT
@@ -105,7 +105,7 @@ class BedrockSoundKeyframe(
if (entity != null) {
entity.level().playLocalSound(entity, soundEvent, entity.soundSource, 1F, 1F)
} else {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1F))
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1F, 1F))
}
}
}
@@ -122,12 +122,11 @@ class PokemonRenderer(
}
if(entity.platform != PlatformType.NONE) {
drawPlatform(
poseMatrix,
entity,
(entity.delegate as PokemonClientDelegate).entityScaleModifier,
partialTicks,
buffer,
packedLight,
poseMatrix,
entity,
(entity.delegate as PokemonClientDelegate).entityScaleModifier,
buffer,
packedLight,
)
// keeps the pokemon's root on the raft
poseMatrix.translate(0.0, 0.25 * (entity.delegate as PokemonClientDelegate).entityScaleModifier, 0.0)
@@ -442,11 +441,10 @@ class PokemonRenderer(
matrixStack: PoseStack,
entity: PokemonEntity,
scale: Float = 1F,
partialTicks: Float,
buff: MultiBufferSource,
packedLight: Int,
) {
val (modelResource, textureResource) = PlatformType.GetModelWithTexture(entity.platform)
val (modelResource, textureResource) = PlatformType.getModelWithTexture(entity.platform)
val model = MiscModelRepository.modelOf(modelResource) ?: return
matrixStack.pushPose()
matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.data
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonFlows
import com.cobblemon.mod.common.CobblemonBrainConfigs
import com.cobblemon.mod.common.CobblemonMechanics
import com.cobblemon.mod.common.api.abilities.Abilities
@@ -91,6 +92,7 @@ object CobblemonDataProvider : DataProvider {
this.register(DexEntryAdditions)
this.register(Dexes)
this.register(DexAdditions)
this.register(CobblemonFlows)
CobblemonSpawnPools.load()
this.register(PokeRods)
@@ -9,10 +9,8 @@
package com.cobblemon.mod.common.entity
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import java.text.Normalizer
import java.util.EnumSet
/**
@@ -31,10 +29,9 @@ enum class PlatformType {
companion object {
val ALL_POSES = EnumSet.allOf(PlatformType::class.java)
val WATER = EnumSet.of(WATER_XS, WATER_S, WATER_M, WATER_L, WATER_XL)
fun GetModelWithTexture(type: PlatformType) : Pair<ResourceLocation, ResourceLocation> {
fun getModelWithTexture(type: PlatformType) : Pair<ResourceLocation, ResourceLocation> {
return when (type) {
WATER_XS -> Pair(cobblemonResource("water_platform_xs.geo"), cobblemonResource("textures/platforms/water_platform_xs.png"))
WATER_S -> Pair(cobblemonResource("water_platform_s.geo"), cobblemonResource("textures/platforms/water_platform_s.png"))
@@ -45,7 +42,7 @@ enum class PlatformType {
}
}
fun GetPlatformTypeForPokemon(form: FormData) : PlatformType {
fun getPlatformTypeForPokemon(form: FormData) : PlatformType {
val width = form.hitbox.width * form.baseScale
return if (width <= 0.511) {
@@ -16,7 +16,7 @@ import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
object AttackAngryAtTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.ANGRY_AT),
it.registered(MemoryModuleType.ATTACK_TARGET)
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.entity.ai
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
import com.google.common.collect.ImmutableMap
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
class LookInDirectionTask(
val yaw: Expression = "0".asExpression(),
val pitch: Expression = "0".asExpression()
) : Behavior<LivingEntity>(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT), 0, 0) {
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return !entity.brain.getMemory(MemoryModuleType.LOOK_TARGET).isPresent
}
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
return owner is PathfinderMob && !owner.brain.getMemory(MemoryModuleType.LOOK_TARGET).isPresent
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
}
override fun tick(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
entity as PathfinderMob
val runtime = MoLangRuntime().setup()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val yaw = (runtime.resolveFloat(this.yaw) + 90) * Math.PI / 180.0
val pitch = runtime.resolveFloat(this.pitch) * Math.PI / 180.0 * -1
val xDisp = Math.cos(yaw)
val zDisp = Math.sin(yaw)
val yDisp = Math.sin(pitch)
entity.lookControl.setLookAt(entity.x + xDisp, entity.y + entity.eyeHeight + yDisp, entity.z + zDisp)
}
}
@@ -350,7 +350,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
entityData.set(CAUGHT_FISH, true)
}
} else if (this.waitCountdown > 0) {
this.waitCountdown -= i
this.waitCountdown -= i + lureLevel
var f = 0.15f
if (this.waitCountdown < 20) {
f += (20 - this.waitCountdown).toFloat() * 0.05f
@@ -392,10 +392,9 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
// set the time it takes to wait for a hooked item or pokemon
this.waitCountdown = Mth.nextInt(random, 100, 600)
this.waitCountdown -= this.lureLevel * 20 * 5
if (this.waitCountdown < 0)
this.waitCountdown = 0
if (this.waitCountdown <= 0)
this.waitCountdown = 1
else {
// check for the bait on the hook and see if the waitCountdown is reduced
if (checkReduceBiteTime(bobberBait))
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.entity.npc
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -49,11 +48,6 @@ class NPCBattleActor(
skill
)
override fun setupStruct() {
super.setupStruct()
struct.addFunction("skill") { DoubleValue(skill) }
}
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
if (packet is BattleEndPacket) {
@@ -126,17 +126,20 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
var allowProjectileHits: Boolean? = null
fun getPartyForChallenge(player: ServerPlayer): NPCPartyStore? {
fun getPartyForChallenge(players: List<ServerPlayer>): NPCPartyStore? {
val party = this.party
return if (party != null) {
party
} else if (npc.party?.isStatic == false) {
npc.party?.provide(this, level)
npc.party?.provide(this, level, players)
} else {
null
}
}
/** Oi, dev, you no touch this one. This one is for [com.cobblemon.mod.common.api.npc.variation.NPCVariationProvider]s. */
val variationAspects = mutableSetOf<String>()
/** You can add to this one if you want, that's ok. */
val appliedAspects = mutableSetOf<String>()
override val delegate = if (world.isClientSide) {
com.cobblemon.mod.common.client.entity.NPCClientDelegate()
@@ -283,7 +286,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
override fun getBrain() = super.getBrain() as Brain<NPCEntity>
fun updateAspects() {
entityData.set(ASPECTS, appliedAspects)
entityData.set(ASPECTS, appliedAspects + variationAspects)
}
fun isInBattle() = battleIds.isNotEmpty()
@@ -317,6 +320,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
nbt.put(DataKeys.NPC_LEVEL, IntTag.valueOf(level))
nbt.putString(DataKeys.NPC_CLASS, npc.id.toString())
nbt.put(DataKeys.NPC_ASPECTS, ListTag().also { list -> appliedAspects.forEach { list.add(StringTag.valueOf(it)) } })
nbt.put(DataKeys.NPC_VARIATION_ASPECTS, ListTag().also { list -> variationAspects.forEach { list.add(StringTag.valueOf(it)) } })
interaction?.let {
val interactionNBT = CompoundTag()
interactionNBT.putString(DataKeys.NPC_INTERACT_TYPE, it.type)
@@ -374,6 +378,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
data = MoLangFunctions.readMoValueFromNBT(nbt.getCompound(DataKeys.NPC_DATA)) as VariableStruct
config = if (nbt.contains(DataKeys.NPC_CONFIG)) MoLangFunctions.readMoValueFromNBT(nbt.getCompound(DataKeys.NPC_CONFIG)) as VariableStruct else VariableStruct()
appliedAspects.addAll(nbt.getList(DataKeys.NPC_ASPECTS, Tag.TAG_STRING.toInt()).map { it.asString })
variationAspects.addAll(nbt.getList(DataKeys.NPC_VARIATION_ASPECTS, Tag.TAG_STRING.toInt()).map { it.asString })
nbt.getCompound(DataKeys.NPC_INTERACTION).takeIf { !it.isEmpty }?.let { nbt ->
val type = nbt.getString("type")
val configType = NPCInteractConfiguration.types[type] ?: return@let
@@ -449,10 +454,10 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
}
fun initialize(level: Int) {
appliedAspects.clear()
variationAspects.clear()
entityData.set(LEVEL, level)
npc.config.forEach { it.applyDefault(this) }
npc.variations.values.forEach { this.appliedAspects.addAll(it.provideAspects(this)) }
npc.variations.values.forEach { this.variationAspects.addAll(it.provideAspects(this)) }
if (party == null || npc.party != null) {
party = npc.party?.takeIf { it.isStatic }?.provide(this, level)
}
@@ -46,6 +46,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.*
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
@@ -118,6 +119,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
override val struct = QueryStruct(hashMapOf())
.addFunction("capture_state") { StringValue(captureState.name) }
.addFunction("ball_type") { StringValue(pokeBall.name.toString()) }
.addFunction("aspects") { aspects.asArrayValue { StringValue(it) } }
override fun defineSynchedData(builder: SynchedEntityData.Builder) {
pokeBall = PokeBalls.POKE_BALL
@@ -8,9 +8,11 @@
package com.cobblemon.mod.common.entity.pokemon
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.entity.PokemonSender
@@ -24,6 +26,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addEntityFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addPokemonEntityFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addPokemonFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.net.serializers.PlatformTypeDataSerializer
import com.cobblemon.mod.common.api.net.serializers.PoseTypeDataSerializer
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
@@ -73,14 +76,28 @@ import com.cobblemon.mod.common.pokemon.activestate.InactivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.ai.FormPokemonBehaviour
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.feature.StashHandler
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.getBitForByte
import com.cobblemon.mod.common.util.giveOrDropItemStack
import com.cobblemon.mod.common.util.isPokemonEntity
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.setBitForByte
import com.cobblemon.mod.common.util.toNbtList
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.serialization.Codec
import java.util.EnumSet
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtUtils
import net.minecraft.nbt.StringTag
import net.minecraft.network.chat.Component
@@ -89,14 +106,10 @@ 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
import com.mojang.serialization.Codec
import net.minecraft.nbt.NbtOps
import net.minecraft.network.protocol.game.DebugPackets
import java.util.*
import java.util.concurrent.CompletableFuture
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerEntity
import net.minecraft.server.level.ServerLevel
@@ -109,7 +122,15 @@ import net.minecraft.world.InteractionResult
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.*
import net.minecraft.world.entity.AgeableMob
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.Pose
import net.minecraft.world.entity.Shearable
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.control.MoveControl
@@ -267,11 +288,12 @@ open class PokemonEntity(
get() = entityData.get(PLATFORM_TYPE)
set(value) { entityData.set(PLATFORM_TYPE, value) }
override val struct: QueryStruct = QueryStruct(hashMapOf())
.addStandardFunctions()
.addEntityFunctions(this)
.addPokemonFunctions(pokemon)
.addPokemonEntityFunctions(this)
override val struct: ObjectValue<PokemonEntity> = ObjectValue(this).also {
it.addStandardFunctions()
.addEntityFunctions(this)
.addPokemonFunctions(pokemon)
.addPokemonEntityFunctions(this)
}
init {
@@ -367,9 +389,21 @@ open class PokemonEntity(
override fun tick() {
super.tick()
if (!this.isBattling && this.beamMode == 0 && this.isBattleClone()) {
discard()
return
if (isBattling) {
// Deploy a platform if a non-wild Pokemon is touching water but not underwater.
// This can't be done in the BattleMovementGoal as the sleep goal will override it.
if (ticksLived > 5 && platform == PlatformType.NONE
&& this.ownerUUID != null
&& isInWater && !isUnderWater
&& !exposedForm.behaviour.moving.swim.canBreatheUnderwater && !exposedForm.behaviour.moving.swim.canWalkOnWater) {
platform = PlatformType.getPlatformTypeForPokemon((exposedForm))
}
} else {
// Battle clone destruction
if (this.beamMode == 0 && this.isBattleClone()) {
discard()
return
}
}
// We will be handling idle logic ourselves thank you
@@ -1138,13 +1172,12 @@ open class PokemonEntity(
var blockPos = BlockPos(pos.x.toInt(), pos.y.toInt(), pos.z.toInt())
var blockLookCount = 5
var foundSurface = false
val species = this.exposedSpecies
val form = this.exposedForm
var result = pos
if (this.level().isWaterAt(blockPos)) {
// look upward for a water surface
var testPos = blockPos
if (!species.behaviour.moving.swim.canBreatheUnderwater) {
if (!form.behaviour.moving.swim.canBreatheUnderwater || form.behaviour.moving.fly.canFly) {
// move sendout pos to surface if it's near
for (i in 0..blockLookCount) {
// Try to find a surface...
@@ -1185,15 +1218,15 @@ open class PokemonEntity(
}
}
if (foundSurface) {
val canFly = species.behaviour.moving.fly.canFly
val canFly = form.behaviour.moving.fly.canFly
if (canFly) {
val hasHeadRoom = !collidesWithBlock(Vec3(blockPos.x.toDouble(), (result.y + 1), (blockPos.z).toDouble()))
if (hasHeadRoom) {
result = Vec3(result.x, result.y + 1.0, result.z)
}
} else if (species.behaviour.moving.swim.canBreatheUnderwater && !species.behaviour.moving.swim.canWalkOnWater) {
} else if (form.behaviour.moving.swim.canBreatheUnderwater && !form.behaviour.moving.swim.canWalkOnWater) {
// Use half hitbox height for swimmers
val halfHeight = species.hitbox.height * species.baseScale / 2.0
val halfHeight = form.hitbox.height * form.baseScale / 2.0
for (i in 1..halfHeight.toInt()) {
blockPos = blockPos.below()
if (!this.level().isWaterAt(blockPos) || !this.level().getBlockState(blockPos).getCollisionShape(this.level(), blockPos).isEmpty) {
@@ -1202,7 +1235,7 @@ open class PokemonEntity(
}
result = Vec3(result.x, result.y + halfHeight - halfHeight.toInt(), result.z)
} else {
platform = if (species.behaviour.moving.swim.canWalkOnWater || collidesWithBlock(Vec3(result.x, result.y, result.z))) PlatformType.NONE else PlatformType.GetPlatformTypeForPokemon(form)
platform = if (form.behaviour.moving.swim.canWalkOnWater || collidesWithBlock(Vec3(result.x, result.y, result.z))) PlatformType.NONE else PlatformType.getPlatformTypeForPokemon(form)
}
}
this.platform = platform
@@ -201,7 +201,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
// Borrowing hard from Minecraft's Boat logic
if (this.pokemonEntity.isBattling && mob.isInWater) {
var e = 0.0F
val exposedSpecies = this.pokemonEntity.exposedSpecies
val exposedForm = this.pokemonEntity.exposedForm
if (isUnderwater()) {
if (this.pokemonEntity.platform != PlatformType.NONE) {
// Float up if on a platform
@@ -211,7 +211,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
if (this.pokemonEntity.platform != PlatformType.NONE ) {
// Hold Steady
e = ((this.waterLevel - this.pokemonEntity.y) / this.pokemonEntity.bbHeight).toFloat()
} else if (exposedSpecies.behaviour.moving.swim.canBreatheUnderwater) {
} else if (exposedForm.behaviour.moving.swim.canBreatheUnderwater) {
// allow swimmers to sink a bit into the water
e = -1.5F
}
@@ -230,7 +230,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
*/
private fun getStableBattleFloatHeight(): Double {
val aABB: AABB = this.pokemonEntity.boundingBox
return if (this.pokemonEntity.exposedSpecies.behaviour.moving.swim.canBreatheUnderwater) ((aABB.maxY - aABB.minY) / 2.0) else 0.05
return if (this.pokemonEntity.exposedForm.behaviour.moving.swim.canBreatheUnderwater) ((aABB.maxY - aABB.minY) / 2.0) else 0.05
}
/*
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.entity.pokemon.ai.goals
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -43,7 +44,7 @@ class PokemonInBattleMovementGoal(val entity: PokemonEntity, val range: Int) : G
entity.lookControl.setLookAt(closestPokemonEntity.x, closestPokemonEntity.eyeY, closestPokemonEntity.z)
}
if (entity.exposedSpecies.behaviour.moving.fly.canFly) {
if (entity.exposedForm.behaviour.moving.fly.canFly) {
// Flyer logic
if (!hasMovedToPos) {
entity.navigation.moveTo(battlePos!!.x, battlePos!!.y, battlePos!!.z, 1.0)
@@ -54,5 +55,9 @@ class PokemonInBattleMovementGoal(val entity: PokemonEntity, val range: Int) : G
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
}
}
if (entity.platform != PlatformType.NONE && entity.onGround()) {
// If the pokemon is on a non-fluid surface, remove the platform.
entity.platform = PlatformType.NONE
}
}
}
@@ -47,7 +47,7 @@ class IllusionEffect(
override fun revert(entity: PokemonEntity, future: CompletableFuture<PokemonEntity>) {
entity.effects.mockEffect = null
if (!entity.exposedSpecies.behaviour.moving.fly.canFly && entity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) {
if (!entity.exposedForm.behaviour.moving.fly.canFly && entity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) {
// Transitioning from a flying form to a non-flying form.
// If we were flying, need to turn the behavior flag off or the pokemon will continue to float in the air.
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
@@ -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.events
import com.cobblemon.mod.common.CobblemonFlows
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.util.cobblemonResource
/**
* Handles the registration of the default Cobblemon event hooks into flows.
*/
object FlowHandler {
fun setup() {
CobblemonEvents.POKEMON_CAPTURED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_captured"), it.context) }
CobblemonEvents.BATTLE_VICTORY.subscribe { CobblemonFlows.run(cobblemonResource("battle_victory"), it.context) }
}
}
@@ -228,44 +228,6 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.DARK_GEM)
entries.accept(CobblemonItems.STEEL_GEM)
entries.accept(CobblemonItems.FAIRY_GEM)
entries.accept(CobblemonItems.GEM_NORMAL_BLOCK)
entries.accept(CobblemonItems.GEM_FIRE_BLOCK)
entries.accept(CobblemonItems.GEM_WATER_BLOCK)
entries.accept(CobblemonItems.GEM_GRASS_BLOCK)
entries.accept(CobblemonItems.GEM_ELECTRIC_BLOCK)
entries.accept(CobblemonItems.GEM_ICE_BLOCK)
entries.accept(CobblemonItems.GEM_FIGHTING_BLOCK)
entries.accept(CobblemonItems.GEM_POISON_BLOCK)
entries.accept(CobblemonItems.GEM_GROUND_BLOCK)
entries.accept(CobblemonItems.GEM_FLYING_BLOCK)
entries.accept(CobblemonItems.GEM_PSYCHIC_BLOCK)
entries.accept(CobblemonItems.GEM_BUG_BLOCK)
entries.accept(CobblemonItems.GEM_ROCK_BLOCK)
entries.accept(CobblemonItems.GEM_GHOST_BLOCK)
entries.accept(CobblemonItems.GEM_DRAGON_BLOCK)
entries.accept(CobblemonItems.GEM_DARK_BLOCK)
entries.accept(CobblemonItems.GEM_STEEL_BLOCK)
entries.accept(CobblemonItems.GEM_FAIRY_BLOCK)
entries.accept(CobblemonItems.GEM_NORMAL_BUDDING)
entries.accept(CobblemonItems.GEM_FIRE_BUDDING)
entries.accept(CobblemonItems.GEM_WATER_BUDDING)
entries.accept(CobblemonItems.GEM_GRASS_BUDDING)
entries.accept(CobblemonItems.GEM_ELECTRIC_BUDDING)
entries.accept(CobblemonItems.GEM_ICE_BUDDING)
entries.accept(CobblemonItems.GEM_FIGHTING_BUDDING)
entries.accept(CobblemonItems.GEM_POISON_BUDDING)
entries.accept(CobblemonItems.GEM_GROUND_BUDDING)
entries.accept(CobblemonItems.GEM_FLYING_BUDDING)
entries.accept(CobblemonItems.GEM_PSYCHIC_BUDDING)
entries.accept(CobblemonItems.GEM_BUG_BUDDING)
entries.accept(CobblemonItems.GEM_ROCK_BUDDING)
entries.accept(CobblemonItems.GEM_GHOST_BUDDING)
entries.accept(CobblemonItems.GEM_DRAGON_BUDDING)
entries.accept(CobblemonItems.GEM_DARK_BUDDING)
entries.accept(CobblemonItems.GEM_STEEL_BUDDING)
entries.accept(CobblemonItems.GEM_FAIRY_BUDDING)
}
private fun blockEntries(displayContext: ItemDisplayParameters, entries: Output) {
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.data
import com.cobblemon.mod.common.CobblemonFlows
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A packet that synchronizes the flow registry with the client.
*
* @author Hiroku
* @since February 24th, 2024
*/
class FlowRegistrySyncPacket(entries: Collection<Map.Entry<ResourceLocation, List<ExpressionLike>>>) : DataRegistrySyncPacket<Map.Entry<ResourceLocation, List<ExpressionLike>>, FlowRegistrySyncPacket>(entries){
companion object {
val ID = cobblemonResource("flow_registry_sync")
fun decode(buffer: RegistryFriendlyByteBuf): FlowRegistrySyncPacket = FlowRegistrySyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: Map.Entry<ResourceLocation, List<ExpressionLike>>) {
buffer.writeIdentifier(entry.key)
buffer.writeCollection(entry.value) { _, expression -> buffer.writeString(expression.toString()) }
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): Map.Entry<ResourceLocation, List<ExpressionLike>> {
val key = buffer.readIdentifier()
val value = buffer.readList { buffer.readString().asExpressionLike() }
return object : Map.Entry<ResourceLocation, List<ExpressionLike>> {
override val key = key
override val value = value
}
}
override fun synchronizeDecoded(entries: Collection<Map.Entry<ResourceLocation, List<ExpressionLike>>>) {
entries.map { (identifier, flows) ->
val existing = CobblemonFlows.clientFlows.getOrPut(identifier) { mutableListOf() }
existing += flows
}
}
}
@@ -16,7 +16,13 @@ import com.cobblemon.mod.common.api.npc.configuration.NPCBattleConfiguration
import com.cobblemon.mod.common.api.npc.configuration.NPCInteractConfiguration
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import io.netty.buffer.ByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
@@ -29,6 +35,7 @@ class NPCConfigurationDTO : Encodable, Decodable {
var interactionInherited: Boolean = false
var interaction: NPCInteractConfiguration? = null
var aspects: MutableSet<String> = mutableSetOf()
var variables: MutableMap<String, String> = mutableMapOf()
constructor()
@@ -39,6 +46,7 @@ class NPCConfigurationDTO : Encodable, Decodable {
interactionInherited = npcEntity.interaction == null
interaction = npcEntity.interaction ?: npcEntity.npc.interaction
aspects = npcEntity.appliedAspects
variables = npcEntity.config.map.map { it.key to it.value.asString() }.toMap().toMutableMap()
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -51,6 +59,11 @@ class NPCConfigurationDTO : Encodable, Decodable {
value.encode(buffer)
}
buffer.writeCollection(aspects, ByteBuf::writeString)
buffer.writeMap(
variables,
{ _, it -> buffer.writeString(it) },
{ _, it -> buffer.writeString(it) },
)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -64,6 +77,10 @@ class NPCConfigurationDTO : Encodable, Decodable {
configType.clazz.getConstructor().newInstance().also { it.decode(buffer) }
}
aspects = buffer.readList { buffer.readString() }.toMutableSet()
variables = buffer.readMap(
{ buffer.readString() },
{ buffer.readString() },
).toMutableMap()
}
fun apply(entity: NPCEntity) {
@@ -78,5 +95,9 @@ class NPCConfigurationDTO : Encodable, Decodable {
}
entity.appliedAspects.clear()
entity.appliedAspects.addAll(aspects)
variables.forEach { (key, value) ->
val variable = entity.npc.config.find { it.variableName == key } ?: return@forEach
entity.config.setDirectly(key, variable.type.toMoValue(value))
}
}
}
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.pokeball
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokeball.catching.CaptureEffect
import com.cobblemon.mod.common.api.pokeball.catching.CatchRateModifier
@@ -39,6 +42,12 @@ open class PokeBall(
val throwPower: Float,
val ancient: Boolean
) {
val struct = QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name.toString()) }
.addFunction("water_drag_value") { DoubleValue(waterDragValue) }
.addFunction("throw_power") { DoubleValue(throwPower) }
.addFunction("is_ancient") { DoubleValue(ancient) }
// .addFunction("item") { } // requires a registry which is hard
// This gets attached during item registry
internal lateinit var item: PokeBallItem
@@ -612,7 +612,7 @@ open class Pokemon : ShowdownIdentifiable {
if (owner is LivingEntity) {
owner.swing(InteractionHand.MAIN_HAND, true)
val spawnDirection: Vec3
val spawnYaw: Double
var spawnYaw: Double
if (battleId != null) {
// Look for an opposing opponent to face
val battle = Cobblemon.battleRegistry.getBattle(battleId)
@@ -640,6 +640,14 @@ open class Pokemon : ShowdownIdentifiable {
spawnDirection = position.subtract(owner.position())
spawnYaw = atan2(spawnDirection.z, spawnDirection.x) * 180.0 / PI + 102.5
}
// In some edge cases, I suspect we are producing NaN's here. This actually leads into a really big problem.
// Why? Because on tick, LivingEntity tries to bring rotations back within one loop around 0-360 using while loops.
// NaN resists arithmetic, so the while loops run forever and the server thread will hang trying to normalize
// this angle. Better to catch it here and correct it. Y'know. If this is actually the problem. I believe!
if (!spawnYaw.isFinite()) {
spawnYaw = 0.0
}
it.entityData.set(PokemonEntity.SPAWN_DIRECTION, spawnYaw.toFloat())
}
if (owner != null) {
@@ -94,8 +94,7 @@ abstract class PokemonStats : Iterable<Map.Entry<Stat, Int>> {
Stats.PERMANENT.forEach { stat ->
val identifier = this.cleanStatIdentifier(stat.identifier)
this[stat] = json.get(identifier)?.asInt?.coerceIn(this.acceptableRange)
?: this.defaultValue
json.get(identifier)?.asInt?.coerceIn(this.acceptableRange)?.let { this.stats[stat] = it }
}
return this
}
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.pokemon
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.Priority
@@ -161,6 +164,21 @@ class Species : ClientDataSynchronizer<Species>, ShowdownIdentifiable {
var lightingData: LightingData? = null
private set
@Transient
val struct = QueryStruct(hashMapOf())
.addFunction("identifier") { StringValue(this.resourceIdentifier.toString()) }
.addFunction("primary_type") { StringValue(this.primaryType.name) }
.addFunction("secondary_type") { StringValue(this.secondaryType?.name ?: "null") }
.addFunction("experience_group") { StringValue(this.experienceGroup.name) }
.addFunction("height") { DoubleValue(this.height) }
.addFunction("weight") { DoubleValue(this.weight) }
.addFunction("base_scale") { DoubleValue(this.baseScale) }
.addFunction("hitbox_width") { DoubleValue(this.hitbox.width) }
.addFunction("hitbox_height") { DoubleValue(this.hitbox.height) }
.addFunction("hitbox_fixed") { DoubleValue(this.hitbox.fixed) }
.addFunction("catch_rate") { DoubleValue(this.catchRate) }
fun initialize() {
Cobblemon.statProvider.provide(this)
this.forms.forEach { it.initialize(this) }
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.pokemon.properties
import com.cobblemon.mod.common.api.abilities.CommonAbilityType
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -23,15 +24,20 @@ import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
object HiddenAbilityPropertyType : CustomPokemonPropertyType<HiddenAbilityProperty> {
override val keys = setOf("hiddenability", "ha")
override val needsKey = true
override fun fromString(value: String?) = HiddenAbilityProperty()
override fun examples() = emptySet<String>()
override fun fromString(value: String?) =
when {
value == null || value.lowercase() in listOf("true", "yes") -> HiddenAbilityProperty(true)
value.lowercase() in listOf("false", "no") -> HiddenAbilityProperty(false)
else -> null
}
override fun examples() = setOf("yes", "no")
}
class HiddenAbilityProperty : CustomPokemonProperty {
class HiddenAbilityProperty (var value: Boolean) : CustomPokemonProperty {
override fun asString() = "hiddenability"
override fun apply(pokemon: Pokemon) {
val possible = pokemon.form.abilities.mapping.flatMap { it.value }
.filter { it.type == HiddenAbilityType }
.filter { it.type == getAbilityType() }
val picked = possible.randomOrNull() ?: return
pokemon.updateAbility(picked.template.create(false))
}
@@ -39,5 +45,7 @@ class HiddenAbilityProperty : CustomPokemonProperty {
override fun matches(pokemon: Pokemon) = pokemon.form.abilities.mapping
.flatMap { it.value }
.find { it.template == pokemon.ability.template }
?.type == HiddenAbilityType
?.type == getAbilityType()
private fun getAbilityType() = if (this.value) HiddenAbilityType else CommonAbilityType
}
@@ -146,6 +146,7 @@ internal object PropertiesCompletionProvider : DataRegistry {
this.inject(setOf("tradeable"), setOf("yes", "no"))
this.inject(setOf("originaltrainer", "ot"), setOf(""))
this.inject(setOf("originaltrainertype", "ottype"), setOf("None", "Player", "NPC"))
this.inject(setOf("nickname", "nick"), setOf(""))
Stats.PERMANENT.forEach{ stat ->
val statName = stat.toString().lowercase()
@@ -202,6 +202,7 @@ object DataKeys {
const val NPC_PARTY = "Party"
const val NPC_PARTY_TYPE = "PartyType"
const val NPC_ASPECTS = "AppliedAspects"
const val NPC_VARIATION_ASPECTS = "VariationAspects"
const val NPC_DATA = "Data"
const val NPC_CONFIG = "Config"
const val NPC_INTERACTION = "Interaction"
@@ -37,6 +37,7 @@ fun String.asTranslated() = Component.translatable(this)
fun String.asResource() = ResourceLocation.parse(this)
fun String.asTranslated(vararg data: Any) = Component.translatable(this, *data)
fun String.isInt() = this.toIntOrNull() != null
fun String.isDouble() = this.toDoubleOrNull() != null
fun String.isHigherVersion(other: String): Boolean {
val thisSplits = split(".")
val thatSplits = other.split(".")
@@ -41,7 +41,7 @@ fun MoLangRuntime.resolve(expression: Expression, context: Map<String, MoValue>
}
// expression.evaluate(MoScope(), environment)
} catch (e: Exception) {
throw IllegalArgumentException("Unable to parse expression: ${expression.getString()}", e)
throw IllegalArgumentException("Unable to evaluate expression: ${expression.getString()}", e)
}
fun MoLangRuntime.resolveDouble(expression: Expression, context: Map<String, MoValue> = contextOrEmpty): Double = resolve(expression, context).asDouble()
fun MoLangRuntime.resolveFloat(expression: Expression, context: Map<String, MoValue> = contextOrEmpty): Float = resolve(expression, context).asDouble().toFloat()
@@ -210,8 +210,16 @@ fun MoLangEnvironment.cloneFrom(other: MoLangEnvironment): MoLangEnvironment {
return this
}
fun BlockPos.toArrayStruct() = ArrayStruct().apply {
map["0"] = DoubleValue(x)
map["1"] = DoubleValue(y)
map["2"] = DoubleValue(z)
fun BlockPos.toArrayStruct() = listOf(x, y, z).asArrayValue(::DoubleValue)
fun <T> Collection<T>.asArrayValue(mapper: (T) -> MoValue): ArrayStruct {
val array = ArrayStruct()
forEachIndexed { index, value -> array.setDirectly("$index", mapper(value)) }
return array
}
fun Iterable<MoValue>.asArrayValue(): ArrayStruct {
val array = ArrayStruct()
forEachIndexed { index, value -> array.setDirectly("$index", value) }
return array
}
@@ -28,6 +28,7 @@ import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.phys.AABB
@@ -153,6 +154,8 @@ val Level.enchantmentRegistry: Registry<Enchantment>
get() = registryAccess().registryOrThrow(Registries.ENCHANTMENT)
val Level.activityRegistry: Registry<Activity>
get() = registryAccess().registryOrThrow(Registries.ACTIVITY)
val Level.blockRegistry: Registry<Block>
get() = registryAccess().registryOrThrow(Registries.BLOCK)
fun Vec3.traceDownwards(
world: Level,
@@ -13,21 +13,26 @@ import com.cobblemon.mod.common.api.dialogue.ExpressionLikeDialogueText
import com.cobblemon.mod.common.api.dialogue.WrappedDialogueText
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asTranslated
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import com.mojang.serialization.JsonOps
import java.lang.reflect.Type
import net.minecraft.network.chat.ComponentSerialization
object DialogueTextAdapter : JsonDeserializer<DialogueText> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): DialogueText {
return if (json.isJsonPrimitive) {
WrappedDialogueText(json.asString.text())
WrappedDialogueText(json.asString.asTranslated())
} else if (json.isJsonArray) {
ExpressionLikeDialogueText(json.asJsonArray.map { it.asString }.asExpressionLike())
} else {
val obj = json.asJsonObject
val typeId = obj.get("type").asString
val typeId = obj.get("type")?.asString ?: run {
return WrappedDialogueText(ComponentSerialization.CODEC.decode(JsonOps.INSTANCE, json).result().get().first.copy())
}
val clazz = DialogueText.types[typeId] ?: throw JsonParseException("Unknown dialogue text type $typeId")
context.deserialize(json, clazz)
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.npc.NPCPreset
import com.cobblemon.mod.common.api.npc.NPCPresets
import com.cobblemon.mod.common.api.npc.configuration.NPCBattleConfiguration
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable
import com.cobblemon.mod.common.api.npc.configuration.NPCInteractConfiguration
import com.cobblemon.mod.common.api.npc.variation.NPCVariationProvider
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
@@ -60,6 +61,10 @@ object NPCClassAdapter : JsonDeserializer<NPCClass> {
obj.get("battleConfiguration")?.let { npcClass.battleConfiguration = ctx.deserialize(it, NPCBattleConfiguration::class.java) }
obj.get("interaction")?.let { npcClass.interaction = ctx.deserialize(it, NPCInteractConfiguration::class.java) }
obj.get("canDespawn")?.let { npcClass.canDespawn = it.asBoolean }
obj.get("config")?.let {
val obj = it.asJsonArray
obj.forEach { npcClass.config.add(ctx.deserialize(it, NPCConfigVariable::class.java)) }
}
obj.get("variations")?.let {
val obj = it.asJsonObject
obj.entrySet().forEach { (key, value) ->
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.util.asTranslated
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.mojang.serialization.JsonOps
import java.lang.reflect.Type
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentSerialization
/**
* Kinda like [TextAdapter] but it treats them as translatables.
*
* @author Hiroku
* @since November 26th, 2024
*/
object TranslatedTextAdapter : JsonDeserializer<Component> {
override fun deserialize(json: JsonElement, typeOfT: Type, ctx: JsonDeserializationContext) : Component {
return if (json.isJsonObject) {
ComponentSerialization.CODEC.decode(JsonOps.INSTANCE, json).result().get().first
} else {
json.asString.asTranslated()
}
}
}
@@ -11,8 +11,8 @@ package com.cobblemon.mod.common.util.codec
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.MapCodec
import net.minecraft.resources.ResourceLocation
import java.util.Optional
import net.minecraft.resources.ResourceLocation
//Codec.optionalFieldOf uses Java Optionals, which cant be passed into constructors using Kotlin optionals.
fun <T> Codec<T>.kotlinOptionalFieldOf(name: String): MapCodec<T?> {
@@ -11,7 +11,7 @@
"events": {
"poisonHit": {
"sound_effect": {
"event_name": "cobblemon:status.poison.actor"
"event_name": "cobblemon:status.nonvolatile.poison.actor"
}
}
},
@@ -11,7 +11,7 @@
"events": {
"toxicHit": {
"sound_effect": {
"event_name": "cobblemon:status.badlypoison.actor"
"event_name": "cobblemon:status.nonvolatile.toxpoison.actor"
}
}
},
@@ -8,6 +8,13 @@
"texture": "textures/particles/bite"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.bite.target"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
@@ -15,6 +22,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.25]
},
@@ -8,6 +8,13 @@
"texture": "textures/particles/bite"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.crunch.target"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
@@ -15,6 +22,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.25]
},
@@ -8,6 +8,13 @@
"texture": "textures/particles/ember"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.ember.actor"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 50,
@@ -16,6 +23,9 @@
"minecraft:emitter_lifetime_once": {
"active_time": 0.45
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": ["math.random(-0.41,0.41)*(v.emitter_age*3.5)", "math.random(-0.41,0.41)*(v.emitter_age*3.5)", "math.random(-0.35,0.35)-v.emitter_age*7"],
"direction": ["math.random(-0.45,0.45)*(v.emitter_age*3)", "math.random(-0.45,0.45)*(v.emitter_age*3)", "math.random(-2.05,-0.55)"]
@@ -8,6 +8,13 @@
"texture": "textures/particles/ember"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.ember.target"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 220,
@@ -16,6 +23,9 @@
"minecraft:emitter_lifetime_once": {
"active_time": 0.2
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.3
@@ -8,6 +8,13 @@
"texture": "textures/particles/fang"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.hyperfang.target"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
@@ -15,6 +22,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.25]
},
@@ -8,6 +8,13 @@
"texture": "textures/particles/spoon"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.kinesis.actor"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
@@ -15,6 +22,9 @@
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, "-1.1/math.clamp(q.entity_scale*1.1-0.1,1,3)"]
},
@@ -22,6 +22,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0.1, -0.2]
},
@@ -1,63 +1,97 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:mudsport_target",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/mud_splash"
}
},
"curves": {
"variable.mudsize": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [1, 1, 1, 1, 1, 1, 0.5, 0]
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.5
},
"minecraft:emitter_shape_point": {
"offset": [0, 0.04, 0],
"direction": ["math.random(-10,10)*1.1", 10, "math.random(-10,10)*1.1"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_initial_spin": {
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_initial_speed": "math.random(6.3,9.2)*0.4",
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -7, 0],
"linear_drag_coefficient": 2
},
"minecraft:particle_appearance_billboard": {
"size": ["(0.2+(v.particle_random_1*0.1))*v.mudsize", "(0.2+(v.particle_random_1*0.1))*v.mudsize"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 8,
"texture_height": 56,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [8, 8],
"step_UV": [0, 8],
"frames_per_second": 12,
"max_frame": 10
}
}
},
"minecraft:particle_motion_collision": {
"collision_drag": 1.1,
"coefficient_of_restitution": 0.3,
"collision_radius": 0.01
}
}
}
}
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:mudsport_target",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/mud_splash"
}
},
"curves": {
"variable.mudsize": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [
1,
1,
1,
1,
1,
1,
0.5,
0
]
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.5
},
"minecraft:emitter_shape_point": {
"offset": [
0,
0.04,
0
],
"direction": [
"math.random(-10,10)*1.1",
10,
"math.random(-10,10)*1.1"
]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_initial_spin": {
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_initial_speed": "math.random(6.3,9.2)*0.4",
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [
0,
-7,
0
],
"linear_drag_coefficient": 2
},
"minecraft:particle_appearance_billboard": {
"size": [
"(0.2+(v.particle_random_1*0.1))*v.mudsize",
"(0.2+(v.particle_random_1*0.1))*v.mudsize"
],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 8,
"texture_height": 56,
"flipbook": {
"base_UV": [
0,
0
],
"size_UV": [
8,
8
],
"step_UV": [
0,
8
],
"frames_per_second": 12,
"max_frame": 8,
"loop": "true"
}
}
},
"minecraft:particle_motion_collision": {
"collision_drag": 1.1,
"coefficient_of_restitution": 0.3,
"collision_radius": 0.01
}
}
}
}
@@ -8,6 +8,13 @@
"texture": "textures/particles/psychicsend"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.psychic.actor"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
@@ -18,6 +25,9 @@
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.3]
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
@@ -8,13 +8,6 @@
"texture": "textures/particles/pursuit"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.pursuit.target"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 3,
@@ -23,9 +16,6 @@
"minecraft:emitter_lifetime_once": {
"active_time": 0.75
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, "-0.3/math.clamp(q.entity_scale*1.1-0.1,1,9)"]
},
@@ -16,6 +16,13 @@
"nodes": [0, 0.63, 0.9, 0.98, 1, 1, 1, 1]
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.sing.actor"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 3,
@@ -24,6 +31,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 2.75
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.15],
"direction": ["math.sin(v.emitter_age*400)*2", "math.cos(v.emitter_age*390)*2", "math.random(-15,-10)"]
@@ -16,6 +16,13 @@
"nodes": [0, 0.66, 0.94, 1, 1, 1, 1, 1, 0.93, 0.66, 0]
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.superfang.actor"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 10,
@@ -24,6 +31,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 0.8
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_disc": {
"offset": [0, "(0.2+(q.entity_height*1)-(q.entity_height*(v.particle_random_2*0.2)))/0.85", 0],
"radius": "(q.entity_radius-(q.entity_radius*(v.particle_random_2*0.3)))/0.85",
@@ -8,6 +8,13 @@
"texture": "textures/particles/fang"
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.superfang.target"
}
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
@@ -15,6 +22,9 @@
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event_sound"
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.25]
},
@@ -16,6 +16,13 @@
"nodes": [1, 1, 1, 1, 1, 1, 0.5, 0]
}
},
"events": {
"event_sound": {
"sound_effect": {
"event_name": "cobblemon:move.watersport.actor"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 500,
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_bug_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_bug_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_dark_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_dark_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_dragon_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_dragon_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_electric_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_electric_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_fairy_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_fairy_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_fighting_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_fighting_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_fire_block"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_fire_budding"
}
}
}
@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "cobblemon:block/gem_flying_block"
}
}
}

Some files were not shown because too many files have changed in this diff Show More