Merge branch 'smarterer-pokemons' into 1.21.1-breeding-pokemingle.com

This commit is contained in:
Plastered_Crab
2025-01-30 15:05:15 -08:00
1588 changed files with 41442 additions and 47905 deletions
+108 -65
View File
@@ -1,33 +1,17 @@
# Changelog
## [1.6.1 (Release TBD)](#1-6-1)
## [1.6.1 (January 26th, 2025)](#1-6-1)
### Additions
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added the `is_standing_on_blocks(depth, blocks...)` MoLang function, which returns whether the specified entity is standing on a specific block or set of blocks. Example usage: `q.is_standing_on_blocks(2, minecraft:sand)`
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added molang particle queries for getting distance to targeted entities.
- Added debug renderer for poseable entity locators.
- Added `baseScale` property to NPCs.
- Added cry for Spinda.
- Added the player to `PokerodReelEvent`.
- Pokédex and Dialogue API screens now close when the inventory keybind is pressed.
- Added Galarica Nuts, used for crafting Galarica Cuffs and Wreaths. Dropped from certain Pokémon.
- Added specific compatibility with Repurposed Structures. (Thank you, TelepathicGrunt!)
- Added an evolution method for Karrablast to evolve into Escavalier in single-player mode.
- Added crossover paintings from Close Combat: Premonition, Altar, Slumber, and Nomad.
- Added Galarica Nuts, used for crafting Galarica Cuffs and Wreaths. Dropped from certain Pokémon. Take a wild guess which.
- Added compatibility with Repurposed Structures. (Thank you, TelepathicGrunt!)
- Added an evolution method for Karrablast to evolve into Escavalier in singleplayer.
- Pokédexes can now be placed in Chiseled Bookshelves.
- Added crossover paintings from Close Combat, Premonition, Altar, Slumber, and Nomad.
### Changes
- Pokémon will now be dynamically revealed to the Pokédex as they're seen instead of revealing entire parties at the end of battle regardless.
- Unseen wild Pokémon will update their name from '???' to their real name as soon as a battle starts to reflect the battle UI showing the actual species name.
- Pokémon under the illusion effect will reveal their disguise to the Pokédex first and then the base Pokémon once the disguise is broken.
- Added more support for a variety of Fabric/NeoForge Convention tags.
- Reformatted some tags to be more consistent.
- Edited some recipes to utilize tags instead of direct item ids, for greater mod compatibility.
- Berries will drop if broken at age 0
- Improved Fortune drops on Mint Seeds
- New Slowpoke shiny texture.
- Updated drops for many Pokémon.
- Added optional box argument to the /pc command.
- Pokédex and Dialogue screens now close when the inventory keybind is pressed.
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added debug renderer for posable entity locators.
### Pokémon Added
@@ -40,10 +24,19 @@
- Escavalier
- Rufflet
- Braviary
- Foongus
- Amoonguss
#### Gen 6
- Binacle
- Barbaracle
#### Gen 7
- Dewpider
- Araquanid
- Alolan Geodude
- Alolan Graveler
- Alolan Golem
#### Gen 8
- Galarian Slowpoke
@@ -51,11 +44,18 @@
- Galarian Slowking
#### Gen 9
- Paldean Tauros's
- Paldean Tauros
### Added cries to the following Pokémon
- All Nidorans
- Shellder, Cloyster
- Pinsir
- Tyrogue, Hitmontop
- Spinda
### Animation updates for the following Pokémon
- Primeape
- Munchlax
- Munchlax
- Snorlax
- Poliwrath
- Goldeen
@@ -77,46 +77,60 @@
- Slowbro
- Slowking
- Eiscue
- Binacle
- Barbaracle
- Tauros
- Goldeen
- Seaking
- Charcadet
- Armarouge
- Ceruledge
- Pinsir
- Geodude
- Graveler
- Golem
### Cry updates for the following Pokémon
- Sceptile
### Changes
- Completely re-synced Pokémon move and stat data based on later games. Learnsets have changed considerably to maximise available moves.
- Pokémon will now be dynamically revealed to the Pokédex as they're seen in battle instead of revealing entire parties at the end of battle.
- Unseen wild Pokémon will update their name from '???' to their real name as soon as a battle starts to match how the battle UI shows the actual species name.
- Pokémon under the illusion effect will reveal their disguise to the Pokédex first and then the base Pokémon once the disguise is broken.
- Berries will now drop from berry trees if broken at age 0 so you aren't punished for mistaken planting.
- Increased Fortune drops on Mint Seeds.
- Updated Slowpoke's shiny texture.
- Updated drops for many Pokémon.
- Adjusted the evolution sound to match the timing of the particle effect.
- Made berry trees shear-able by dispenser blocks. I'm sure nobody will make unholy contraptions with this.
- Edited some recipes to utilize tags instead of direct item IDs, for better mod compatibility.
- Added more support for a variety of Fabric/NeoForge conventional tags.
- Reformatted some tags to be more consistent.
### Fixes
- Fixed Pokédex sometimes crashing when switching forms.
- Fixed Pokédex interface not transitioning out when closed.
- Fixed texture dimensions for the interact interface.
- Fixed crash related to Tom's Simple Storage mod and Fossil Machine.
- Fixed texture dimensions for the player and Pokémon interact interface.
- Fixed crash related to Tom's Simple Storage mod and the Fossil Machine.
- Fixed not being able to retrieve a fossil from the Fossil Machine with an empty hand.
- Fixed Pokémon being collidable while being captured by a Poké Ball.
- Fixed Pokémon being collidable (collidible? collissionable? kaleidoscopable?) while being captured by a Poké Ball.
- Fixed `full_party`, `own_zangoose_seviper`, `use_revive` and `use_candy` Advancement triggers.
- Fixed `healing_machine` Advancement by using the correct 1.21 trigger.
- Fix Display Cases not dropping items if destroyed through explosions.
- Fixed an issue where the first Pokémon in the pastured Pokémon list clipped into the interface.
- Fixed all Pokémon facing South on spawn.
- Parametric particle motion now works.
- Event spawned particles now work.
- Particles can now have independent coordinate spaces.
- Fixed bait being consumed when not reeling in any Pokémon.
- Fixed bait being consumed even when not reeling in any Pokémon.
- Fixed Miltank milk magically disappearing out of your bucket.
- Fixed Pokémon nicknames migrating from 1.5.2 not being displayed properly.
- Fixed capitalization in one of our config fields, but allowing typo'd version to still be valid.
- Fixed Poké Rods not working if lure or luck of the sea enchantments get removed by other mods.
- Fixed crashes related to Pokémon when they are ready to evolve while holding an enchanted item.
- Fixed crashes that sometimes occur when evolving Nincada.
- Fixed plants not being compostible on NeoForge.
- Fixed capitalization in one of our config options. It was a very important fix. Very important. Old configs are fine.
- Fixed Poké Rods not working if Lure or Luck of the Sea enchantments get removed by other mods.
- Fixed crashes related to Pokémon when they are ready to evolve while holding an enchanted item. Very specific.
- Fixed a crash that sometimes occurred when evolving Nincada.
- Fixed Cobblemon plants not being compostable on NeoForge.
- Fixed hide UI (F1 key) not hiding the party overlay.
- Fixed NPC MoLang command `player_lose_command` not working.
- Corrected misaligned tooltips with editboxes in the NPC editor screen.
- Fixed misaligned tooltips with edit boxes in the NPC editor screen.
- Fixed Pokémon riding two boats when attempting to deploy a platform on water.
- Made berry trees be shearable by dispenser blocks.
- Fixed the summary screen showing there's experience to reach the next level when at level cap.
- Fixed the summary screen showing there's experience to reach the next level when they are at the level cap.
- Fixed Pokémon forgetting moves when evolving on specific cases.
- Fixed Adorn compatibility, including improvements when using JEI/REI (Apricorn items now show up under the collapsed entries rather than standalone).
- Fixed error message appearing on battle log when using Solar Beam with Sunny Day.
@@ -124,38 +138,68 @@
- Fixed `/pokedex grant all` command not giving male/female/shininess completion for some Pokémon.
- Fixed `/pokedex grant only` and `/pokedex remove only` not respecting the form parameter passed.
- Fixed variant forms appearing incorrectly in the Pokédex when the normal form had not been unlocked.
- Fixed Pokémon occasionally being shot into the sky during battle.
- Fixed Pokémon occasionally being shot into the sky during battle. No Pokémon were harmed by this bug, probably.
- Fixed NPC editing GUI not updating aspects until a game restart.
- Fix some users being unable to open their PC if a Pokémon in said PC had a lot of pp raises beyond normal bounds
- Fix some color variants (dubwool, conkeldurr and undyed wooloo) missing in the Pokédex
- Fix invalid species or held_items causing Players to not be able to load into their world anymore (commonly happening after removing addons/mods)
- Fixed some users being unable to open their PC if a Pokémon in it had a lot of PP raises beyond normal bounds. How did you get those, anyway? Tell me or the Bellossom gets it.
- Fixed some color variants (Dubwool, Conkeldurr and Undyed wooloo) being missing in the Pokédex.
- Fixed invalid species or held items causing Players to not be able to load into their world anymore (commonly happening after removing addons/mods).
- Fixed Wooloo variants not being automatically registered in the owner's Pokédex when dyed.
- Fixed Vivichoke Dip and Leek and Potato Stew not returning a bowl upon consumption.
- Fixed Fossil Restoration Machine not accepting Hay Bales.
- Fixed Potion items so that they no longer apply over double their intended healing value.
- Fixed Fast Ball capture rate applying to all Pokémon.
- Fix "learned new move" messages appearing for already known moves on Pokémon evolutions.
- Fix Pokémon Item Models breaking shadows nearby when being placed in Display Cases or Item Frames.
- Fixed Vivichoke Dip and Leek & Potato Stew not returning a bowl upon consumption.
- Fixed Fossil Restoration Tank not accepting Hay Bales as organic material.
- Fixed Potion items applying double their intended healing value.
- Fixed Fast Ball capture bonuses applying to all Pokémon, making it the Best Ball instead of the Mediocre Edge-Case Ball.
- Fixed "learned new move" messages appearing for already-known moves on Pokémon evolutions.
- Fixed Pokémon Item Models breaking shadows nearby when being placed in Display Cases or Item Frames.
- Fixed berries not giving bonus yields when planted in their preferred biomes. I'm sure we've fixed that 5 times now.
- Fixed the NeoForge version not supporting "SodiumDynamicLights".
- Fixed players disconnecting from servers if they made changes to certain config options.
- Fixed players with shouldered Pokémon not being able to rejoin their 1.5.2 worlds using 1.6.
- Fixed `PokemonProperties` utilizing `ability=<some ability>` being treated as a forced ability even when it is a legal ability for the Pokémon.
- Fixed type formatting in Pokédex scanner mode when dual types require two lines.
- Fixed trading sometimes crashing the game or server.
- Fixed Wild shiny sounds not respecting the `shinyNoticeParticlesDistance` config setting.
- Fixed Pokémon being able to evolve mid-battle.
- Fixed NPC held items being able to be stolen by players. Don't be a thief!
- Fixed evolutions that require a held item consuming it as soon as meeting requirements when it should only be consumed upon evolution.
- Fixed Pokémon showing only the default form when selecting them as a target in battle.
- Fixed a possible error coming out of reeling fishing rods in specific situations.
- Fixed incorrect weights being used when Poké Fishing with Luck of the Sea.
- Parametric particle motion now works.
- Event-spawned particles now work.
- Particles can now have independent coordinate spaces.
### Molang & Datapacks
### Developer
- Updated the Pokédex data updated events to always include a `Pokemon` instance, and optionally a `DisguiseData` instance.
- Updated fields in `SpawnNPCPacket` and `SpawnPokemonPacket` to be visible and mutable.
- Updated `UnvalidatedPlaySoundS2CPacket` to be public instead of internal and made its fields mutable.
- Added `hideNameTag` field and `HideNPCNameTag` nbt tag to `NPCEntity` to allow hiding the name tag of the NPC.
- Added the player to `PokerodReelEvent` so you know who is doing the reeling.
### MoLang & Datapacks
- Added flows for:
- `forme_change`: Triggered when a Pokémon changes form in battle.
- `mega_evolution`: Triggered when a Pokémon mega evolves in battle. (Note: Third party mods are required for this feature currently)
- `zpower_used`: Triggered when a Pokémon uses a Z-Power move in battle. (Note: Third party mods are required for this feature currently)
- `terastallization`: Triggered when a Pokémon terastallizes in battle. (Note: Third party mods are required for this feature currently)
- `mega_evolution`: Triggered when a Pokémon mega evolves in battle. (Note: Third-party mods are required for this feature currently)
- `zpower_used`: Triggered when a Pokémon uses a Z-Power move in battle. (Note: Third-party mods are required for this feature currently)
- `terastallization`: Triggered when a Pokémon terastallizes in battle. (Note: Third-party mods are required for this feature currently)
- `battle_fainted`: Triggered when a Pokémon faints in battle.
- `battle_fled`: Triggered when a Player flees from battle.
- `battle_fled`: Triggered when a player flees from battle.
- `battle_started_pre`: Triggered when a battle starts. Cancelable!
- `battle_started_post`: Triggered when a battle starts.
- `apricorn_harvested`: Triggered when an apricorn is harvested.
- `apricorn_harvested`: Triggered when an Apricorn is harvested.
- `thrown_pokeball_hit`: Triggered when a thrown Pokéball hits a Pokémon.
- `level_up`: Triggered when a Pokémon levels up.
- `pokemon_fainted`: Triggered when a Pokémon faints.
- `pokemon_gained`: Triggered when a Player gains a Pokémon.
- Added molang functions:
- `pokemon_gained`: Triggered when a player gains a Pokémon.
- Added MoLang functions:
- For Pokémon:
- `pokemon.apply(PokemonProperties)`: Applies the given properties to the Pokémon.
- `pokemon.owner`: Returns the owner of the Pokémon or 0.0 if there is no owner or they are not online.
- For all entities:
- `entity.is_standing_on_blocks(depth, blocks...)`: Returns whether the specified entity is standing on a specific block or set of blocks. Example usage: `q.is_standing_on_blocks(2, minecraft:sand)`
- Added NPC field:
- `hideNameTag`: Hides the name tag of the NPC.
- Added `baseScale` property to NPCs.
- Added MoLang particle queries for getting distance to targeted entities.
## [1.6.0 - The Record Catch Update (December 25th, 2024)](#1-6-0)
#### "Now that there's a fishing mechanic, the mod is actually good!"
@@ -2058,5 +2102,4 @@ Thank you so much to all of our community translators that bring the mod to the
- Added species, ability, and move translations for `ko_ko`, `jp_jp`, `fr_fr`, `es_es`, `it_it`, and `zh_cn`.
## [1.0.0 (November 12th, 2022)](#1-0-0)
- Initial release.
- Initial release.
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents;
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -178,10 +179,10 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Override @Nullable
public PokedexEntityData resolvePokemonScan() {
if(CompoundTagUtilities.isShoulderPokemon(this.getShoulderEntityRight())){
if (CompoundTagUtilities.isShoulderPokemon(this.getShoulderEntityRight())){
return getDataFromShoulderPokemon(this.getShoulderEntityRight());
}
if(CompoundTagUtilities.isShoulderPokemon(this.getShoulderEntityLeft())){
if (CompoundTagUtilities.isShoulderPokemon(this.getShoulderEntityLeft())){
return getDataFromShoulderPokemon(this.getShoulderEntityLeft());
}
return null;
@@ -190,30 +191,34 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Nullable @Unique
private PokedexEntityData getDataFromShoulderPokemon(CompoundTag shoulderTag) {
CompoundTag pokemonTag = shoulderTag.getCompound(DataKeys.POKEMON);
if(pokemonTag.isEmpty()) return null;
if (pokemonTag.isEmpty()) return null;
Species species = PokemonSpecies.INSTANCE.getByIdentifier(ResourceLocation.parse(pokemonTag.getString(DataKeys.POKEMON_SPECIES_IDENTIFIER)));
if(species == null) return null;
if (species == null) return null;
String formId = pokemonTag.getString(DataKeys.POKEMON_FORM_ID);
FormData form = species.getStandardForm();
List<FormData> formList = species.getForms().stream().filter(it -> it.formOnlyShowdownId().equals(formId)).toList();
if(!formList.isEmpty()) form = formList.getFirst();
if(form == null) return null;
if (!formList.isEmpty()) form = formList.getFirst();
if (form == null) return null;
String genderString = pokemonTag.getString(DataKeys.POKEMON_GENDER);
if(genderString.isEmpty()) return null;
if (genderString.isEmpty()) return null;
Gender gender = Gender.valueOf(genderString);
boolean shiny = pokemonTag.getBoolean(DataKeys.POKEMON_SHINY);
int level = pokemonTag.getInt(DataKeys.POKEMON_LEVEL);
Set<String> aspects = shoulderTag.getList(DataKeys.SHOULDER_ASPECTS, Tag.TAG_STRING).stream().map(Tag::getAsString).collect(Collectors.toSet());
return new PokedexEntityData(
species,
form,
gender,
aspects,
shiny,
level,
this.getUUID()
);
Pokemon pokemon = new Pokemon();
if (level().isClientSide) {
pokemon.setSpecies(species);
pokemon.setForm(form);
pokemon.setGender(gender);
pokemon.setShiny(shiny);
pokemon.setLevel(level);
pokemon.setForcedAspects(aspects);
} else {
PlayerPartyStore party = Cobblemon.INSTANCE.getStorage().getParty(this.getUUID(), this.registryAccess());
pokemon = party.get(shoulderTag.getUUID(DataKeys.SHOULDER_UUID));
}
return new PokedexEntityData(pokemon, null);
}
@Override
@@ -221,7 +221,7 @@ object Cobblemon {
storage.onPlayerDataSync(it)
playerDataManager.syncAllToPlayer(it)
starterHandler.handleJoin(it)
ServerSettingsPacket(this.config.preventCompletePartyDeposit, this.config.displayEntityLevelLabel, this.config.displayEntityNameLabel, this.config.maxPokemonLevel).sendToPlayer(it)
ServerSettingsPacket(this.config.preventCompletePartyDeposit, this.config.displayEntityLevelLabel, this.config.displayEntityNameLabel, this.config.maxPokemonLevel, this.config.maxPokemonFriendship, this.config.maxDynamaxLevel).sendToPlayer(it)
}
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
PCLinkManager.removeLink(it.player.uuid)
@@ -259,6 +259,8 @@ object Cobblemon {
// Lowest priority because this applies after luxury ball bonus as of gen 4
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(Priority.LOWEST) { event ->
var increment = (event.newFriendship - event.pokemon.friendship).toFloat()
if (increment <= 0) //these affects are only meant to affect positive gains
return@subscribe
// Our Luxury ball spec is diff from official, but we will still assume these stack
if (event.pokemon.heldItemNoCopy().`is`(CobblemonItemTags.IS_FRIENDSHIP_BOOSTER)) {
increment += increment * 0.5F
@@ -0,0 +1,24 @@
/*
* 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
/**
* The medium that an entity might be in which motivates what type of wandering it should do.
*
* If you've discovered this in main and it's not being used outside of that unused extension function, delete me
*
* @author Hiroku
* @since January 25th, 2025
*/
enum class PathfindingMedium {
LAND,
WATER,
LAVA,
AIR
}
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WANDER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.AirAndWaterRandomPos
class AirWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "air_wanders", true).asExpressible()
val wanderChance = numberVariable(WANDER, "air_wander_chance", 1/(20 * 1F)).asExpressible()
val horizontalRange = numberVariable(WANDER, "horizontal_wander_range", 20).asExpressible()
val verticalRange = numberVariable(WANDER, "vertical_wander_range", 5).asExpressible()
val speedMultiplier = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
override fun getVariables(entity: LivingEntity): List<MoLangConfigVariable> {
return listOf(condition, wanderChance, horizontalRange, verticalRange, speedMultiplier).asVariables()
}
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean()) return null
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(MemoryModuleType.LOOK_TARGET)
).apply(it) { walkTarget, lookTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || entity.isInWater) {
return@Trigger false
}
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val wanderChance = wanderChance.resolveFloat()
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) return@Trigger false
val target = AirAndWaterRandomPos.getPos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt(), 2, entity.lookAngle.x, entity.lookAngle.z, 2 * Math.PI)
if (target != null) {
walkTarget.set(WalkTarget(target, speedMultiplier.resolveFloat(), 3))
lookTarget.set(BlockPosTracker(target))
return@Trigger true
}
return@Trigger false
}
}
}
}
}
@@ -39,6 +39,8 @@ interface TaskConfig {
val types = mutableMapOf<String, Class<out TaskConfig>>(
"one_of" to OneOfTaskConfig::class.java,
"wander" to WanderTaskConfig::class.java,
"water_wander" to WaterWanderTaskConfig::class.java,
"air_wander" to AirWanderTaskConfig::class.java,
"look_at_target" to LookAtTargetTaskConfig::class.java,
"follow_walk_target" to FollowWalkTargetTaskConfig::class.java,
"random" to RandomTaskConfig::class.java,
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
@@ -64,7 +65,6 @@ class WanderTaskConfig : SingleTaskConfig {
val wanderChance = wanderChance.resolveFloat()
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) return@Trigger false
// RandomStroll.stroll(speedMultiplier.resolveFloat(), horizontalRange.resolveInt(), verticalRange.resolveInt())
val targetVec = LandRandomPos.getPos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt(), { 0.0 }) ?: return@Trigger false
walkTarget.set(WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WANDER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.RandomStroll
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
class WaterWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
val wanderChance = numberVariable(WANDER, "water_wander_chance", 1/(20 * 3F)).asExpressible()
val speedMultiplier = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
override fun getVariables(entity: LivingEntity): List<MoLangConfigVariable> {
return listOf(condition, wanderChance, speedMultiplier).asVariables()
}
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean()) return null
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(MemoryModuleType.LOOK_TARGET)
).apply(it) { walkTarget, lookTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isUnderWater) {
return@Trigger false
}
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val wanderChance = wanderChance.resolveFloat()
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) return@Trigger false
val target = RandomStroll.getTargetSwimPos(entity)
if (target != null) {
walkTarget.set(WalkTarget(target, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(target))
return@Trigger true
}
return@Trigger false
}
}
}
}
}
@@ -95,8 +95,6 @@ class DropTable {
pokemon: Pokemon? = null,
) {
val drops = getDrops(amount, pokemon).toMutableList()
val heldItem = (entity as PokemonEntity?)?.pokemon?.heldItemNoCopy() ?: ItemStack.EMPTY
if (!heldItem.isEmpty) entity?.spawnAtLocation(heldItem.item)
LOOT_DROPPED.postThen(
event = LootDroppedEvent(this, player, entity, drops),
ifSucceeded = { it.drops.forEach { it.drop(entity, world, pos, player) } }
@@ -18,19 +18,17 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
data class PokemonScannedEvent(val player: ServerPlayer, val scannedPokemonEntityData: PokedexEntityData, val scannedEntity: ScannableEntity) {
val pokedexEntityData: PokedexEntityData
get() = scannedPokemonEntityData
val isOwned: Boolean
get() = scannedPokemonEntityData.ownerUUID == player.uuid
get() = scannedPokemonEntityData.pokemon.getOwnerUUID() == player.uuid
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to scannedPokemonEntityData.struct,
"pokemon" to scannedPokemonEntityData.pokemon.struct,
"disguise" to (scannedPokemonEntityData.disguise?.struct ?: DoubleValue.ZERO),
"entity" to {
when (scannedEntity) {
is LivingEntity -> scannedEntity.asMostSpecificMoLangValue()
else -> DoubleValue(0.0)
else -> DoubleValue.ZERO
}
},
)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -18,5 +19,11 @@ import com.cobblemon.mod.common.pokemon.Pokemon
*/
data class FriendshipUpdatedEvent(
val pokemon: Pokemon,
var newFriendship: Int
)
val newFriendshipInitial: Int
)
{
var newFriendship: Int = newFriendshipInitial
set(value) {
field = value.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
}
}
@@ -17,16 +17,14 @@ import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.FormDexRecord
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.mojang.datafixers.util.Either
import java.util.UUID
/**
* Event that fires when a Pokémon's information is gained or updated in the Pokédex.
*/
sealed interface PokedexDataChangedEvent {
val dataSource: Either<PokedexEntityData, Pokemon>
val dataSource: PokedexEntityData
val knowledge: PokedexEntryProgress
val playerUUID: UUID
val record: FormDexRecord
@@ -37,22 +35,22 @@ sealed interface PokedexDataChangedEvent {
fun getContext(): MutableMap<String, MoValue> {
return mutableMapOf(
"player" to (playerUUID.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to dataSource.map({ DoubleValue.ZERO }, { it.struct }),
"data" to dataSource.map({ it.struct }, { DoubleValue.ZERO }),
"pokemon" to dataSource.pokemon.struct,
"disguise" to (dataSource.disguise?.struct ?: DoubleValue.ZERO),
"knowledge" to StringValue(knowledge.name.lowercase()),
"pokedex" to pokedexManager.struct
)
}
class Pre(
override val dataSource: Either<PokedexEntityData, Pokemon>,
override val dataSource: PokedexEntityData,
override val knowledge: PokedexEntryProgress,
override val playerUUID: UUID,
override val record: FormDexRecord
) : PokedexDataChangedEvent, Cancelable()
class Post(
override val dataSource: Either<PokedexEntityData, Pokemon>,
override val dataSource: PokedexEntityData,
override val knowledge: PokedexEntryProgress,
override val playerUUID: UUID,
override val record: FormDexRecord
@@ -732,6 +732,8 @@ object MoLangFunctions {
map.put("max_hp") { DoubleValue(pokemon.maxHealth.toDouble()) }
map.put("current_hp") { DoubleValue(pokemon.currentHealth.toDouble()) }
map.put("friendship") { DoubleValue(pokemon.friendship.toDouble()) }
map.put("behaviour") { pokemon.form.behaviour.struct }
map.put("behavior") { pokemon.form.behaviour.struct } // Inferior
map.put("evs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
@@ -57,6 +57,7 @@ class NPCClass {
var isInvulnerable = false
var isLeashable = true
var allowProjectileHits = true
var hideNameTag = false
// If you're adding stuff here, add it to NPCPreset and NPCClassAdapter too
@@ -98,6 +99,7 @@ class NPCClass {
buffer.writeBoolean(isInvulnerable)
buffer.writeBoolean(isLeashable)
buffer.writeBoolean(allowProjectileHits)
buffer.writeBoolean(hideNameTag)
}
fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -150,5 +152,6 @@ class NPCClass {
isInvulnerable = buffer.readBoolean()
isLeashable = buffer.readBoolean()
allowProjectileHits = buffer.readBoolean()
hideNameTag = buffer.readBoolean()
}
}
@@ -37,6 +37,7 @@ class NPCPreset {
var isInvulnerable: Boolean? = null
var isLeashable: Boolean? = null
var allowProjectileHits: Boolean? = null
var hideNameTag: Boolean? = null
fun applyTo(npcClass: NPCClass) {
resourceIdentifier?.let { npcClass.resourceIdentifier = it }
@@ -63,5 +64,6 @@ class NPCPreset {
isInvulnerable?.let { npcClass.isInvulnerable = it }
isLeashable?.let { npcClass.isLeashable = it }
allowProjectileHits?.let { npcClass.allowProjectileHits = it }
hideNameTag?.let { npcClass.hideNameTag = it }
}
}
@@ -78,6 +78,7 @@ any number of presets and the properties of those presets will be merged into th
"skill": 5,
"autoHealParty": true,
"randomizePartyOrder": true,
"hideNameTag": false,
"party": {
"type": "simple",
"pokemon": [
@@ -411,4 +412,7 @@ Pokémon in the party.
### ai
The AI property is an array of brain configurations. This is used to configure the behaviours of the NPC. See
the documentation for brain configurations at [AI Configuration](../ai/config/README.md).
the documentation for brain configurations at [AI Configuration](../ai/config/README.md).
### hideNameTag
Whether the NPC's nametag should be hidden or not. Defaults to false.
@@ -202,6 +202,8 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
// Luxury ball effect, low priority as it must be triggered before soothe bell as of gen 4
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOW) { event ->
var increment = (event.newFriendship - event.pokemon.friendship).toFloat()
if (increment <= 0) //these affects are only meant to affect positive gains
return@subscribe
if (increment <= 1F) {
event.pokemon.caughtBall.effects.filterIsInstance<FriendshipEarningBoostEffect>()
.forEach { increment *= it.multiplier }
@@ -96,15 +96,21 @@ abstract class AbstractPokedexManager {
}
fun getNewInformation(pokedexEntityData: PokedexEntityData): PokedexLearnedInformation {
val speciesRecord = getSpeciesRecord(pokedexEntityData.species.resourceIdentifier)
val speciesRecord = getSpeciesRecord(pokedexEntityData.getApparentSpecies().resourceIdentifier)
if (speciesRecord == null || speciesRecord.getKnowledge() == PokedexEntryProgress.NONE) {
return PokedexLearnedInformation.SPECIES
}
val formRecord = speciesRecord.getFormRecord(pokedexEntityData.form.name)
val formRecord = speciesRecord.getFormRecord(pokedexEntityData.getApparentForm().name)
if (formRecord == null || formRecord.knowledge == PokedexEntryProgress.NONE) {
return PokedexLearnedInformation.FORM
}
if (pokedexEntityData.aspects.any{ !speciesRecord.hasAspect(it) } || pokedexEntityData.gender !in formRecord.getGenders() || !formRecord.hasSeenShinyState(pokedexEntityData.shiny)) {
// Can't update aspects on a disguise
if (pokedexEntityData.disguise != null) {
return PokedexLearnedInformation.NONE
}
if (pokedexEntityData.pokemon.aspects.any { !speciesRecord.hasAspect(it) } || pokedexEntityData.pokemon.gender !in formRecord.getGenders() || !formRecord.hasSeenShinyState(pokedexEntityData.pokemon.shiny)) {
return PokedexLearnedInformation.VARIATION
}
return PokedexLearnedInformation.NONE
@@ -89,21 +89,15 @@ class FormDexRecord {
it.knowledge = knowledge
}
fun encountered(pokemon: Pokemon) {
if (wouldBeDifferent(pokemon, PokedexEntryProgress.ENCOUNTERED)) {
addInformation(pokemon, PokedexEntryProgress.ENCOUNTERED)
}
}
fun encountered(pokedexEntityData: PokedexEntityData) {
if (wouldBeDifferent(pokedexEntityData, PokedexEntryProgress.ENCOUNTERED)) {
addInformation(pokedexEntityData, PokedexEntryProgress.ENCOUNTERED)
}
}
fun caught(pokemon: Pokemon) {
if (wouldBeDifferent(pokemon, PokedexEntryProgress.CAUGHT)) {
addInformation(pokemon, PokedexEntryProgress.CAUGHT)
fun caught(pokedexEntityData: PokedexEntityData) {
if (wouldBeDifferent(pokedexEntityData, PokedexEntryProgress.CAUGHT)) {
addInformation(pokedexEntityData, PokedexEntryProgress.CAUGHT)
}
}
@@ -125,47 +119,18 @@ class FormDexRecord {
speciesDexRecord.onFormRecordUpdated(this)
}
private fun addInformation(pokemon: Pokemon, knowledge: PokedexEntryProgress) {
(speciesDexRecord.pokedexManager as? PokedexManager)?.let { pokedexManager ->
CobblemonEvents.POKEDEX_DATA_CHANGED_PRE.postThen(
PokedexDataChangedEvent.Pre(
Either.right(pokemon),
knowledge,
pokedexManager.uuid,
this
),
ifSucceeded = {
genders.add(pokemon.gender)
seenShinyStates.add(if (pokemon.shiny) "shiny" else "normal")
if (knowledge.ordinal > this.knowledge.ordinal) {
this.knowledge = knowledge
}
speciesDexRecord.addInformation(pokemon, knowledge)
speciesDexRecord.onFormRecordUpdated(this)
CobblemonEvents.POKEDEX_DATA_CHANGED_POST.post(
PokedexDataChangedEvent.Post(
Either.right(pokemon),
knowledge,
pokedexManager.uuid,
this
)
)
})
}
}
private fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
(speciesDexRecord.pokedexManager as? PokedexManager)?.let { pokedexManager ->
CobblemonEvents.POKEDEX_DATA_CHANGED_PRE.postThen(
PokedexDataChangedEvent.Pre(
Either.left(pokedexEntityData),
knowledge,
pokedexManager.uuid,
this
pokedexEntityData,
knowledge,
pokedexManager.uuid,
this
),
ifSucceeded = {
genders.add(pokedexEntityData.gender)
seenShinyStates.add(if (pokedexEntityData.shiny) "shiny" else "normal")
genders.add(pokedexEntityData.pokemon.gender)
seenShinyStates.add(if (pokedexEntityData.pokemon.shiny) "shiny" else "normal")
if (knowledge.ordinal > this.knowledge.ordinal) {
this.knowledge = knowledge
}
@@ -173,10 +138,10 @@ class FormDexRecord {
speciesDexRecord.onFormRecordUpdated(this)
CobblemonEvents.POKEDEX_DATA_CHANGED_POST.post(
PokedexDataChangedEvent.Post(
Either.left(pokedexEntityData),
knowledge,
pokedexManager.uuid,
this
pokedexEntityData,
knowledge,
pokedexManager.uuid,
this
)
)
}
@@ -184,19 +149,10 @@ class FormDexRecord {
}
}
/** Returns whether the given [Pokemon] and [knowledge] would add new information to the Pokédex.*/
fun wouldBeDifferent(pokemon: Pokemon, knowledge: PokedexEntryProgress): Boolean {
return pokemon.gender !in genders
|| (pokemon.shiny && "shiny" !in seenShinyStates)
|| (!pokemon.shiny && "normal" !in seenShinyStates)
|| knowledge.ordinal > this.knowledge.ordinal
|| speciesDexRecord.wouldBeDifferent(pokemon)
}
fun wouldBeDifferent(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress): Boolean {
return pokedexEntityData.gender !in genders
|| (pokedexEntityData.shiny && "shiny" !in seenShinyStates)
|| (!pokedexEntityData.shiny && "normal" !in seenShinyStates)
return pokedexEntityData.pokemon.gender !in genders
|| (pokedexEntityData.pokemon.shiny && "shiny" !in seenShinyStates)
|| (!pokedexEntityData.pokemon.shiny && "normal" !in seenShinyStates)
|| knowledge.ordinal > this.knowledge.ordinal
|| speciesDexRecord.wouldBeDifferent(pokedexEntityData)
}
@@ -30,19 +30,19 @@ class PokedexManager(
fun encounter(pokemon: Pokemon) {
val speciesId = pokemon.species.resourceIdentifier
val formName = pokemon.form.name
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).encountered(pokemon)
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).encountered(PokedexEntityData(pokemon = pokemon, disguise = null))
}
fun encounter(pokedexEntityData: PokedexEntityData) {
val speciesId = pokedexEntityData.species.resourceIdentifier
val formName = pokedexEntityData.form.name
val speciesId = pokedexEntityData.getApparentSpecies().resourceIdentifier
val formName = pokedexEntityData.getApparentForm().name
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).encountered(pokedexEntityData)
}
fun catch(pokemon: Pokemon) {
val speciesId = pokemon.species.resourceIdentifier
val formName = pokemon.form.name
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).caught(pokemon)
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).caught(PokedexEntityData(pokemon = pokemon, disguise = null))
}
override fun markDirty() {
@@ -97,7 +97,7 @@ class SpeciesDexRecord {
}
fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
aspects.addAll(pokedexEntityData.aspects)
aspects.addAll(pokedexEntityData.pokemon.aspects)
}
fun addAspects(addedAspects: Set<String>) {
@@ -107,7 +107,7 @@ class SpeciesDexRecord {
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
internal fun wouldBeDifferent(pokemon: Pokemon) = pokemon.aspects.any { it !in aspects }
internal fun wouldBeDifferent(pokedexEntityData: PokedexEntityData) = pokedexEntityData.aspects.any { it !in aspects }
internal fun wouldBeDifferent(pokedexEntityData: PokedexEntityData) = pokedexEntityData.pokemon.aspects.any { it !in aspects }
fun getOrCreateFormRecord(formName: String): FormDexRecord {
return formRecords.getOrPut(formName.lowercase()) {
@@ -809,7 +809,7 @@ open class PokemonProperties {
private fun createAbility(id: String, form: FormData): Ability? {
val ability = Abilities.get(id) ?: return null
val potentialAbility = form.abilities.firstOrNull { potential -> potential.template == ability } ?: return ability.create(true)
return potentialAbility.template.create(false)
return potentialAbility.template.create(false, potentialAbility.priority)
}
}
@@ -105,9 +105,6 @@ interface Evolution : EvolutionLike {
* @param pokemon The [Pokemon] being evolved.
*/
fun evolve(pokemon: Pokemon): Boolean {
if (this.consumeHeldItem) {
pokemon.swapHeldItem(ItemStack.EMPTY)
}
if (this.optional) {
// All the networking is handled under the hood, see EvolutionController.
return pokemon.evolutionProxy.server().add(this)
@@ -223,6 +220,10 @@ interface Evolution : EvolutionLike {
this.shed(pokemon)
if (this.consumeHeldItem) {
pokemon.swapHeldItem(ItemStack.EMPTY)
}
val ownerPlayer = pokemon.getOwnerPlayer()
if (ownerPlayer != null && ownerPlayer.level().gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_LOOT)) {
drops.drop(
@@ -53,7 +53,7 @@ enum class Stats(override val identifier: ResourceLocation, override val display
fun getStat(statKey: String) = when(statKey.lowercase()) {
"hp" -> HP
"atk", "attack" -> ATTACK // Hyper Cutter states the full stat name "Attack"
"def", "defense" -> DEFENCE // Big Pecks states the full stat name "Defense"
"def", "defense", "defence" -> DEFENCE // Big Pecks states the full stat name "Defense"
"spa" -> SPECIAL_ATTACK
"spd" -> SPECIAL_DEFENCE
"spe", "speed" -> SPEED
@@ -10,15 +10,12 @@ package com.cobblemon.mod.common.api.spawning.fishing
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BobberSpawnPokemonEvent
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
@@ -27,7 +24,6 @@ import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.pokemon.Gender
@@ -129,8 +125,8 @@ class FishingSpawnCause(
}
fun alterFriendshipAttempt(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
if (pokemonEntity.pokemon.friendship + effect.value > 255)
pokemonEntity.pokemon.setFriendship(255)
if (pokemonEntity.pokemon.friendship + effect.value > Cobblemon.config.maxPokemonFriendship)
pokemonEntity.pokemon.setFriendship(Cobblemon.config.maxPokemonFriendship)
else
pokemonEntity.pokemon.setFriendship(pokemonEntity.pokemon.friendship + effect.value.toInt())
}
@@ -27,15 +27,15 @@ import net.minecraft.world.entity.player.Player
* @since February 21, 2024
*/
class PlayerInstancedDataStoreManager {
open class PlayerInstancedDataStoreManager {
val factories = mutableMapOf<PlayerInstancedDataStoreType, PlayerInstancedDataFactory<*>>()
val saveTasks = mutableMapOf<PlayerInstancedDataStoreType, ScheduledTask>()
fun setFactory(factory: PlayerInstancedDataFactory<*>, dataType: PlayerInstancedDataStoreType) {
open fun setFactory(factory: PlayerInstancedDataFactory<*>, dataType: PlayerInstancedDataStoreType) {
factories[dataType] = factory
}
fun setup(server: MinecraftServer) {
open fun setup(server: MinecraftServer) {
factories.values.forEach {
it.setup(server)
}
@@ -57,62 +57,62 @@ class PlayerInstancedDataStoreManager {
.build()
}
fun get(playerId: UUID, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
open fun get(playerId: UUID, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
if (!factories.contains(dataType)) {
throw UnsupportedOperationException("No factory registered for $dataType")
}
return factories[dataType]!!.getForPlayer(playerId)
}
fun get(player: Player, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
open fun get(player: Player, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
return get(player.uuid, dataType)
}
fun saveAllOfOneType(dataType: PlayerInstancedDataStoreType) {
open fun saveAllOfOneType(dataType: PlayerInstancedDataStoreType) {
if (!factories.contains(dataType)) {
throw UnsupportedOperationException("No factory registered for $dataType")
}
return factories[dataType]!!.saveAll()
}
fun saveSingle(playerData: InstancedPlayerData, dataType: PlayerInstancedDataStoreType) {
open fun saveSingle(playerData: InstancedPlayerData, dataType: PlayerInstancedDataStoreType) {
if (!factories.contains(dataType)) {
throw UnsupportedOperationException("No factory registered for $dataType")
}
return factories[dataType]!!.saveSingle(playerData.uuid)
}
fun onPlayerDisconnect(player: ServerPlayer) {
open fun onPlayerDisconnect(player: ServerPlayer) {
factories.values.forEach {
it.onPlayerDisconnect(player)
}
}
fun syncAllToPlayer(player: ServerPlayer) {
open fun syncAllToPlayer(player: ServerPlayer) {
factories.values.forEach {
it.sendToPlayer(player)
}
}
fun saveAllStores() {
open fun saveAllStores() {
factories.values.forEach {
it.saveAll()
}
}
fun getGenericData(player: ServerPlayer): GeneralPlayerData {
open fun getGenericData(player: ServerPlayer): GeneralPlayerData {
return getGenericData(player.uuid)
}
fun getGenericData(playerId: UUID): GeneralPlayerData {
open fun getGenericData(playerId: UUID): GeneralPlayerData {
return get(playerId, PlayerInstancedDataStoreTypes.GENERAL) as GeneralPlayerData
}
fun getPokedexData(player: ServerPlayer): PokedexManager {
open fun getPokedexData(player: ServerPlayer): PokedexManager {
return getPokedexData(player.uuid)
}
fun getPokedexData(playerId: UUID): PokedexManager {
open fun getPokedexData(playerId: UUID): PokedexManager {
return get(playerId, PlayerInstancedDataStoreTypes.POKEDEX) as PokedexManager
}
}
@@ -372,24 +372,24 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
override fun tick(entity: PokemonEntity) {
incrementAge(entity)
if(currentEntity.ownerUUID == null && currentEntity.pokemon.shiny && secondsSinceLastShinyParticle > SHINY_PARTICLE_COOLDOWN && !currentEntity.isBattling) {
//wild shiny not looked at
playShinyEffect("cobblemon:shiny_sparkle_ambient_wild")
lastShinyParticle = System.currentTimeMillis()
}
getClientShinyPokemon()
playWildShinySounds()
}
fun getClientShinyPokemon() {
fun playWildShinySounds() {
val player = Minecraft.getInstance().player ?: return
val isWithinRange = player.position().distanceTo(currentEntity.position()) <= Cobblemon.config.shinyNoticeParticlesDistance
if (currentEntity.pokemon.shiny && currentEntity.ownerUUID == null && !player.isSpectator ) {
if (isWithinRange && !shined) {
playShinyEffect("cobblemon:wild_shiny_ring")
shined = true
lastShinyParticle = System.currentTimeMillis()
} else if (!isWithinRange) {
if (currentEntity.pokemon.shiny && currentEntity.ownerUUID == null) {
if (isWithinRange) {
if (secondsSinceLastShinyParticle > SHINY_PARTICLE_COOLDOWN && !currentEntity.isBattling) {
playShinyEffect("cobblemon:shiny_sparkle_ambient_wild")
lastShinyParticle = System.currentTimeMillis()
}
if (!shined && !player.isSpectator) {
playShinyEffect("cobblemon:wild_shiny_ring")
shined = true
}
} else {
shined = false
}
}
@@ -191,6 +191,8 @@ class BattleTargetSelection(
matrices.pushPose()
matrices.translate(x + TARGET_WIDTH - (25 / 2.0) - 2, y + 5.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
// Grab aspects for right variant
state.currentAspects = battlePokemon.state.currentAspects
drawProfilePokemon(
species = battlePokemon.species.resourceIdentifier,
matrixStack = matrices,
@@ -18,7 +18,7 @@ class GrabbedStorageSlot(
) : StorageSlot(x, y, parent, {}) {
init {
isSelected = true
isSlotSelected = true
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -40,7 +40,8 @@ import net.minecraft.sounds.SoundEvent
class PCGUI(
val pc: ClientPC,
val party: ClientParty,
val configuration: PCGUIConfiguration
val configuration: PCGUIConfiguration,
val openOnBox: Int = 0
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
companion object {
@@ -110,6 +111,7 @@ class PCGUI(
pc = pc,
party = party
)
this.storageWidget.box = openOnBox
this.setPreviewPokemon(null)
this.addRenderableWidget(storageWidget)
@@ -46,7 +46,7 @@ open class StorageSlot(
private val slotOverlayMoveIconResource = cobblemonResource("textures/gui/pasture/pc_slot_icon_move.png")
}
protected var isSelected = false;
protected var isSlotSelected = false;
override fun playDownSound(soundManager: SoundManager) {
}
@@ -84,7 +84,7 @@ open class StorageSlot(
context.disableScissor()
if (!isSelected) {
if (!isSlotSelected) {
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 100.0)
@@ -129,7 +129,7 @@ open class StorageSlot(
matrices.translate(0.0, 0.0, 500.0)
val config = parent.pcGui.configuration
if (pokemon.tetheringId != null && !isSelected) {
if (pokemon.tetheringId != null && !isSlotSelected) {
if (isStationary()) {
blitk(
matrixStack = matrices,
@@ -163,8 +163,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
},
text = lang("ui.evolve"),
resource = evolveButtonResource,
renderRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() },
clickRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() }
renderRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() && CobblemonClient.battle == null },
clickRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() && CobblemonClient.battle == null }
)
)
@@ -19,6 +19,8 @@ object ServerSettingsPacketHandler : ClientNetworkPacketHandler<ServerSettingsPa
ServerSettings.displayEntityLevelLabel = packet.displayEntityLevelLabel
ServerSettings.displayEntityNameLabel = packet.displayEntityNameLabel
ServerSettings.maxPokemonLevel = packet.maxPokemonLevel
ServerSettings.maxPokemonFriendship = packet.maxPokemonFriendship
ServerSettings.maxDynamaxLevel = packet.maxDynamaxLevel
}
}
@@ -18,6 +18,6 @@ import net.minecraft.client.Minecraft
object OpenPCHandler : ClientNetworkPacketHandler<OpenPCPacket> {
override fun handle(packet: OpenPCPacket, client: Minecraft) {
val pc = CobblemonClient.storage.pcStores[packet.storeID] ?: return
Minecraft.getInstance().setScreen(PCGUI(pc, CobblemonClient.storage.myParty, PCGUIConfiguration()))
Minecraft.getInstance().setScreen(PCGUI(pc, CobblemonClient.storage.myParty, PCGUIConfiguration(), packet.box))
}
}
@@ -116,7 +116,7 @@ class PokedexScannerRenderer {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv.number", pokedexEntityData.level).bold(),
text = lang("ui.lv.number", pokedexEntityData.pokemon.level).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
@@ -126,7 +126,7 @@ class PokedexScannerRenderer {
if (infoDisplayedCounter == 2) {
val hasTrainer = (usageContext.scannableEntityInFocus?.resolveEntityScan() as? PokemonEntity)?.ownerUUID !== null
val speciesName = pokedexEntityData.species.translatedName.bold()
val speciesName = pokedexEntityData.getApparentSpecies().translatedName.bold()
var yOffsetName = if (hasTrainer) 2 else 0
if (hasTrainer) {
drawScaledText(
@@ -149,7 +149,7 @@ class PokedexScannerRenderer {
centered = true
)
val gender = pokedexEntityData.gender
val gender = pokedexEntityData.pokemon.gender
val speciesNameWidth = Minecraft.getInstance().font.width(speciesName.font(CobblemonResources.DEFAULT_LARGE))
if (gender != Gender.GENDERLESS) {
val isMale = gender == Gender.MALE
@@ -179,14 +179,15 @@ class PokedexScannerRenderer {
}
if (infoDisplayedCounter == 3) {
val typeText = lang("type.suffix", pokedexEntityData.form.types.map { it.displayName.copy() }.reduce { acc, next -> acc.plus("/").plus(next) }).bold()
val typeText = lang("type.suffix", pokedexEntityData.getApparentForm().types.map { it.displayName.copy() }.reduce { acc, next -> acc.plus("/").plus(next) }).bold()
val typeWidth = Minecraft.getInstance().font.width(typeText.font(CobblemonResources.DEFAULT_LARGE))
// Split into 2 lines if text width is too long
if (typeWidth > (OUTER_INFO_FRAME_WIDTH - 8) && pokedexEntityData.form.secondaryType !== null) {
if (typeWidth > (OUTER_INFO_FRAME_WIDTH - 8) && pokedexEntityData.getApparentForm().secondaryType !== null) {
val splitLines = typeText.string.split("/")
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("type.suffix", pokedexEntityData.form.primaryType.displayName).bold(),
text = text(splitLines[0], "/").bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - 4,
shadow = true,
@@ -196,7 +197,7 @@ class PokedexScannerRenderer {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("type.suffix", pokedexEntityData.form.secondaryType!!.displayName).bold(),
text = text(splitLines[1]).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + 3,
shadow = true,
@@ -204,8 +204,8 @@ class SnowstormParticle(
val spriteURange = sprite.u1 - sprite.u0
val spriteVRange = sprite.v1 - sprite.v0
val minU = uvs.startU * spriteURange + sprite.u0
val maxU = uvs.endU * spriteURange + sprite.u0
val maxU = uvs.startU * spriteURange + sprite.u0
val minU = uvs.endU * spriteURange + sprite.u0
val minV = uvs.startV * spriteVRange + sprite.v0
val maxV = uvs.endV * spriteVRange + sprite.v0
@@ -1,106 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ExeggutorAlolanModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("exeggutor")
override val head = getPart("head")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
val head2 = object : HeadedFrame {
override val rootPart = this@ExeggutorAlolanModel.rootPart
override val head: ModelPart = getPart("head2")
}
val head3 = object : HeadedFrame {
override val rootPart = this@ExeggutorAlolanModel.rootPart
override val head: ModelPart = getPart("head3")
}
override var portraitScale = 1.0F
override var portraitTranslation = Vec3(-1.4, 16.55, 0.0)
override var profileScale = 0.45F
override var profileTranslation = Vec3(0.0, 1.0, -30.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
lateinit var battleidle: Pose
override fun registerPoses() {
val blink1 = quirk { bedrockStateful("exeggutor_alolan", "blink") }
val blink2 = quirk { bedrockStateful("exeggutor_alolan", "blink2") }
val blink3 = quirk { bedrockStateful("exeggutor_alolan", "blink3") }
val blink4 = quirk { bedrockStateful("exeggutor_alolan", "blink4") }
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("exeggutor_alolan", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink1, blink2, blink3, blink4),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
SingleBoneLookAnimation(head2, false, false, disableX = false, disableY = false),
SingleBoneLookAnimation(head3, false, false, disableX = false, disableY = false),
bedrock("exeggutor_alolan", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink1, blink2, blink3, blink4),
animations = arrayOf(
singleBoneLook(),
SingleBoneLookAnimation(head2, false, false, disableX = false, disableY = false),
SingleBoneLookAnimation(head3, false, false, disableX = false, disableY = false),
bedrock("exeggutor_alolan", "ground_walk")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink1, blink2, blink3, blink4),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
SingleBoneLookAnimation(head2, false, false, disableX = false, disableY = false),
SingleBoneLookAnimation(head3, false, false, disableX = false, disableY = false),
bedrock("exeggutor_alolan", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("exeggutor_alolan", "faint") else null
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedW
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
@@ -40,6 +41,8 @@ class NidokingModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, B
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("nidoking", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("nidoking", "blink")}
standing = registerPose(
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedW
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
@@ -40,6 +41,8 @@ class NidoqueenModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("nidoqueen", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("nidoqueen", "blink")}
val spg = quirk(secondsBetweenOccurrences = 600F to 900F) { bedrockStateful("nidoqueen", "spgs_happy_place") }
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
@@ -37,6 +38,8 @@ class NidoranfModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, Q
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("nidoranf", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("nidoranf", "blink") }
standing = registerPose(
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
@@ -37,6 +38,8 @@ class NidoranmModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, Q
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("nidoranm", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("nidoranm", "blink") }
standing = registerPose(
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
@@ -40,6 +41,8 @@ class NidorinaModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, Q
lateinit var walk: Pose
lateinit var battleidle: Pose
override val cryAnimation = CryProvider { bedrockStateful("nidorina", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("nidorina", "blink")}
standing = registerPose(
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
@@ -36,6 +37,8 @@ class NidorinoModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, Q
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("nidorino", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("nidorino", "blink")}
standing = registerPose(
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
@@ -32,6 +33,8 @@ class PinsirModel(root: ModelPart) : PokemonPosableModel(root) {
lateinit var battleidle: CobblemonPose
lateinit var sleep: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("pinsir", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("pinsir", "blink")}
sleep = registerPose(
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
@@ -32,7 +33,7 @@ class HitmontopModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
lateinit var walk: Pose
lateinit var battleidle: Pose
// override val cryAnimation = CryProvider { bedrockStateful("hitmontop", "cry") }
override val cryAnimation = CryProvider { bedrockStateful("hitmontop", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("hitmontop", "blink") }
@@ -28,7 +28,7 @@ class ArctozoltModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
override var portraitTranslation = Vec3(-0.45, 2.15, 0.0)
override var portraitScale = 0.85F
override var profileTranslation = Vec3(0.02, 0.95, 0.0)
override var profileTranslation = Vec3(0.02, 1.14, 0.0)
override var profileScale = 0.33F
lateinit var sleep: CobblemonPose
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen8
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
@@ -24,12 +25,14 @@ class EiscueModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override var portraitTranslation = Vec3(-0.1, 0.31, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.3, 0.0)
override var profileTranslation = Vec3(0.0, 0.55, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("eiscue_ice", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("eiscue_ice", "blink")}
sleep = registerPose(
@@ -51,7 +51,9 @@ class CharcadetModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
val blink = quirk { bedrockStateful("charcadet", "blink") }
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("charcadet", "sleep"))
animations = arrayOf(bedrock("charcadet", "sleep"),
bedrock("charcadet", "eye_flame_overlay"),
bedrock("charcadet", "head_flame_overlay"))
)
standing = registerPose(
@@ -61,7 +63,9 @@ class CharcadetModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("charcadet", "ground_idle")
bedrock("charcadet", "ground_idle"),
bedrock("charcadet", "eye_flame_overlay"),
bedrock("charcadet", "head_flame_overlay")
)
)
@@ -72,7 +76,9 @@ class CharcadetModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("charcadet", "ground_walk")
bedrock("charcadet", "ground_walk"),
bedrock("charcadet", "eye_flame_overlay"),
bedrock("charcadet", "head_flame_overlay")
)
)
@@ -81,7 +87,9 @@ class CharcadetModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("charcadet", "shoulder_left")
bedrock("charcadet", "shoulder_left"),
bedrock("charcadet", "eye_flame_overlay"),
bedrock("charcadet", "head_flame_overlay")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.Y_AXIS, -0.1),
@@ -94,7 +102,9 @@ class CharcadetModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("charcadet", "shoulder_right")
bedrock("charcadet", "shoulder_right"),
bedrock("charcadet", "eye_flame_overlay"),
bedrock("charcadet", "head_flame_overlay")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.Y_AXIS, -0.1),
@@ -90,7 +90,6 @@ object PokemonModelRepository : VaryingModelRepository<PokemonPosableModel>() {
inbuilt("electabuzz", ::ElectabuzzModel)
inbuilt("exeggcute", ::ExeggcuteModel)
inbuilt("exeggutor", ::ExeggutorModel)
inbuilt("exeggutor_alolan", ::ExeggutorAlolanModel)
inbuilt("farfetchd", ::FarfetchdModel)
inbuilt("farfetchd_galarian", ::FarfetchdGalarianModel)
inbuilt("fearow", ::FearowModel)
@@ -60,4 +60,11 @@ class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEnt
// this.renderLabelIfPresent(entity, entity.displayName, poseMatrix, buffer, packedLight)
// }
}
override fun shouldShowName(entity: NPCEntity): Boolean {
if (entity.hideNameTag) {
return false
}
return super.shouldShowName(entity)
}
}
@@ -25,5 +25,7 @@ object ServerSettings {
var displayEntityLevelLabel = Cobblemon.config.displayEntityLevelLabel
var displayEntityNameLabel = Cobblemon.config.displayEntityNameLabel
var maxPokemonLevel = Cobblemon.config.maxPokemonLevel
var maxPokemonFriendship = Cobblemon.config.maxPokemonFriendship
var maxDynamaxLevel = Cobblemon.config.maxDynamaxLevel
}
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.command.argument.SpeciesArgumentType
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.permission
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
@@ -40,7 +40,7 @@ object ChangeWalkSpeed {
val pkm = SpeciesArgumentType.getPokemon(context, "pokemon")
val walkSpeed = FloatArgumentType.getFloat(context, "walkSpeed")
pkm.behaviour.moving.walk.walkSpeed = walkSpeed.toString().asExpressionLike()
pkm.behaviour.moving.walk.walkSpeed = walkSpeed.toString().asExpression()
pkm.forms.clear()
pkm.forms.add(FormData().also { it.initialize(pkm)})
return Command.SINGLE_SUCCESS
@@ -45,8 +45,8 @@ object CobblemonInfoCommand {
.append(this.INDENT)
.append(Component.literal("Is Snapshot:").withStyle { it.withColor(ChatFormatting.GRAY) })
.append(this.SPACE)
.append(Component.literal(if(CobblemonBuildDetails.SNAPSHOT) "Yes" else "No").withStyle {
it.withColor(if(CobblemonBuildDetails.SNAPSHOT) this.GREEN else this.RED)
.append(Component.literal(if (CobblemonBuildDetails.SNAPSHOT) "Yes" else "No").withStyle {
it.withColor(if (CobblemonBuildDetails.SNAPSHOT) this.GREEN else this.RED)
})
message.append(this.NEW_LINE)
@@ -17,10 +17,12 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
import com.cobblemon.mod.common.util.*
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.literal
import net.minecraft.commands.Commands.argument
object PcCommand {
@@ -30,25 +32,36 @@ object PcCommand {
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(literal(NAME)
.permission(CobblemonPermissions.PC)
.then(argument("box", IntegerArgumentType.integer(1))
.executes(this::execute)
)
.executes(this::execute)
)
}
private fun execute(context: CommandContext<CommandSourceStack>): Int {
val player = context.source.playerOrException
val box = try {
IntegerArgumentType.getInteger(context, "box")
} catch (e: IllegalArgumentException) {
1
}
val pc = player.pc()
if (player.isInBattle()) {
throw IN_BATTLE_EXCEPTION.create()
}
if (pc.boxes.size < box) {
throw SimpleCommandExceptionType(lang("command.pc.invalid-box", box, pc.boxes.size).red()).create()
}
PCLinkManager.addLink(PermissiblePcLink(pc, player, CobblemonPermissions.PC))
OpenPCPacket(pc.uuid).sendToPlayer(player)
OpenPCPacket(pc.uuid, box - 1).sendToPlayer(player)
context.source.level.playSoundServer(
position = context.source.player!!.position(),
sound = CobblemonSounds.PC_ON,
volume = 0.5F,
pitch = 1F
position = context.source.player!!.position(),
sound = CobblemonSounds.PC_ON,
volume = 0.5F,
pitch = 1F
)
return Command.SINGLE_SUCCESS
}
}
}
@@ -29,14 +29,14 @@ class FeatureFix(output: Schema) : PokemonFix(output) {
if (features.result().isPresent) {
return dynamic //already has Features nbt format... no work needed
}
var featureTag = emptyList<CompoundTag>()
var featureTag = mutableListOf<CompoundTag>()
if (dynamic.value is CompoundTag) {
var rootTag = dynamic.value as CompoundTag
var species = PokemonSpecies.getByIdentifier(rootTag.getString(DataKeys.POKEMON_SPECIES_IDENTIFIER).asResource())
if (species == null) {
return dynamic
}
SpeciesFeatures.getFeaturesFor(species!!).forEach {
SpeciesFeatures.getFeaturesFor(species).forEach {
val feature = it(rootTag) ?: return@forEach
var tag = CompoundTag()
tag.putString(FEATURE_ID, feature.name)
@@ -51,7 +51,10 @@ class FeatureFix(output: Schema) : PokemonFix(output) {
} else if (dynamic.value is JsonElement) {
var rootTag = (dynamic.value as JsonElement).asJsonObject
var species = PokemonSpecies.getByIdentifier(rootTag.get(DataKeys.POKEMON_SPECIES_IDENTIFIER).asString.asResource())
SpeciesFeatures.getFeaturesFor(species!!).forEach {
if (species == null) {
return dynamic
}
SpeciesFeatures.getFeaturesFor(species).forEach {
val feature = it(rootTag) ?: return@forEach
var tag = CompoundTag()
tag.putString(FEATURE_ID, feature.name)
@@ -8,18 +8,20 @@
package com.cobblemon.mod.common.datafixer.fix
import com.cobblemon.mod.common.util.DataKeys
import com.mojang.datafixers.schemas.Schema
import com.mojang.serialization.Dynamic
class ShoulderStateJsonFix(output: Schema) : PokemonFix(output) {
override fun fixPokemonData(dynamic: Dynamic<*>): Dynamic<*> {
val moveSetJson = dynamic.get("State").result()
val stateJson = dynamic.get("State").result()
//State not here
if (moveSetJson.isEmpty) {
if (stateJson.isEmpty) {
return dynamic
}
if (moveSetJson.get().mapValues.getOrThrow().size == 0) {
if (stateJson.get().mapValues.getOrThrow().isEmpty() ||
!stateJson.get().mapValues.getOrThrow().containsKey(dynamic.createString(DataKeys.POKEMON_STATE_TYPE))) {
return dynamic.remove("State")
}
@@ -35,6 +35,7 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.weightedSelection
import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.client.resources.sounds.EntityBoundSoundInstance
import net.minecraft.core.BlockPos
@@ -183,33 +184,19 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
}
fun chooseAdjustedSpawnBucket(buckets: List<SpawnBucket>, luckOfTheSeaLevel: Int): SpawnBucket {
val baseIncreases = listOf(2.5F, 0.5F, 0.2F) // Base increases for the first three buckets beyond the first
val baseValues = listOf(94.3F, 5.0F, 0.5F, 0.2F)
val adjustments = listOf(-4.1F, 2.5F, 1.0F, 0.6F)
val adjustedWeights = buckets.mapIndexed { index, bucket ->
if (index == 0) {
// Placeholder, will be recalculated
0.0F
} else {
val increase = if (index < baseIncreases.size) baseIncreases[index] else baseIncreases.last() + (index - baseIncreases.size + 1) * 0.15F
bucket.weight + increase * luckOfTheSeaLevel
if (index >= baseValues.size) {
return@mapIndexed bucket to bucket.weight
}
}.toMutableList()
val base = baseValues[index]
val adjustment = adjustments[index]
bucket to (base + adjustment * luckOfTheSeaLevel)
}.toMap()
// Recalculate the first bucket's weight to ensure the total is 100%
val totalAdjustedWeight = adjustedWeights.sum() - adjustedWeights[0] // Corrected to ensure the list contains Floats
adjustedWeights[0] = 100.0F - totalAdjustedWeight + buckets[0].weight
// Random selection based on adjusted weights
val weightSum = adjustedWeights.sum()
val chosenSum = kotlin.random.Random.nextDouble(weightSum.toDouble()).toFloat() // Ensure usage of Random from kotlin.random package
var sum = 0.0F
adjustedWeights.forEachIndexed { index, weight ->
sum += weight
if (sum >= chosenSum) {
return buckets[index]
}
}
return buckets.first() // Fallback
return buckets.weightedSelection { adjustedWeights[it]!! }!!
}
fun isOpenOrWaterAround(pos: BlockPos): Boolean {
@@ -335,7 +322,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
val buckets = Cobblemon.bestSpawner.config.buckets
// choose a spawn bucket according to weights no matter how many there are
// choose a spawn bucket according to weights and luck of the sea
chosenBucket = chooseAdjustedSpawnBucket(buckets, luckOfTheSeaLevel)
CobblemonEvents.BOBBER_BUCKET_CHOSEN.post(BobberBucketChosenEvent(chosenBucket, buckets, luckOfTheSeaLevel)) { event ->
chosenBucket = event.chosenBucket
@@ -698,10 +685,9 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
)
// This has a chance to fail, if the position has no suitability for a fishing context
// it could also just be a miss which
// means two attempts to spawn in the same location can have differing results (which is expected for
// randomness).
if (result == null) {
// it could also just be a miss which means two attempts to spawn in the same location
// can have differing results (which is expected for randomness).
if (result == null || result.isCompletedExceptionally) {
player.sendSystemMessage(lang("fishing.no_bite").red())
return false
}
@@ -110,6 +110,12 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
val level: Int
get() = entityData.get(LEVEL)
var hideNameTag: Boolean
get() = entityData.get(HIDE_NAME_TAG)
set(value) {
entityData.set(HIDE_NAME_TAG, value)
}
var skill: Int? = null // range from 0 - 5
var baseScale: Float? = null
@@ -208,7 +214,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
val BATTLE_IDS = SynchedEntityData.defineId(NPCEntity::class.java, UUIDSetDataSerializer)
val NPC_PLAYER_TEXTURE = SynchedEntityData.defineId(NPCEntity::class.java, NPCPlayerTextureSerializer)
val LEVEL = SynchedEntityData.defineId(NPCEntity::class.java, EntityDataSerializers.INT)
val HIDE_NAME_TAG = SynchedEntityData.defineId(NPCEntity::class.java, EntityDataSerializers.BOOLEAN)
// val BATTLING = Activity.register("npc_battling")
@@ -259,6 +265,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
builder.define(BATTLE_IDS, setOf())
builder.define(NPC_PLAYER_TEXTURE, NPCPlayerTexture(ByteArray(1), NPCPlayerModelType.NONE))
builder.define(LEVEL, 1)
builder.define(HIDE_NAME_TAG, false)
}
override fun getAddEntityPacket(serverEntity: ServerEntity) = ClientboundCustomPayloadPacket(
@@ -332,6 +339,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
super.saveWithoutId(nbt)
saveScriptingToNBT(nbt)
nbt.put(DataKeys.NPC_LEVEL, IntTag.valueOf(level))
nbt.putBoolean(DataKeys.NPC_HIDE_NAME_TAG, hideNameTag)
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)) } })
@@ -397,6 +405,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
override fun load(nbt: CompoundTag) {
npc = NPCClasses.getByIdentifier(ResourceLocation.parse(nbt.getString(DataKeys.NPC_CLASS))) ?: NPCClasses.classes.first()
entityData.set(LEVEL, nbt.getInt(DataKeys.NPC_LEVEL).takeIf { it != 0 } ?: 1)
entityData.set(HIDE_NAME_TAG, nbt.getBoolean(DataKeys.NPC_HIDE_NAME_TAG))
super.load(nbt)
loadScriptingFromNBT(nbt)
appliedAspects.addAll(nbt.getList(DataKeys.NPC_ASPECTS, Tag.TAG_STRING.toInt()).map { it.asString })
@@ -503,6 +512,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
fun initialize(level: Int) {
variationAspects.clear()
entityData.set(HIDE_NAME_TAG, npc.hideNameTag)
entityData.set(LEVEL, level)
remakeBrain()
npc.variations.values.forEach { this.variationAspects.addAll(it.provideAspects(this)) }
@@ -1696,17 +1696,15 @@ open class PokemonEntity(
private fun sidedCodec(): Codec<Pokemon> = if (this.level().isClientSide) Pokemon.CLIENT_CODEC else Pokemon.CODEC
override fun resolvePokemonScan(): PokedexEntityData? {
return PokemonSpecies.getByIdentifier(this.exposedSpecies.resourceIdentifier)?.let { species ->
PokedexEntityData(
species = species,
form = this.form,
gender = this.pokemon.gender,
aspects = this.aspects,
shiny = this.pokemon.shiny,
level = this.labelLevel(),
ownerUUID = this.ownerUUID
)
}
return PokedexEntityData(
pokemon = pokemon,
disguise = this.effects.mockEffect?.let {
PokedexEntityData.DisguiseData(
species = it.exposedSpecies ?: pokemon.species,
form = it.exposedForm ?: pokemon.form,
)
}
)
}
override fun resolveEntityScan(): LivingEntity {
@@ -34,6 +34,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.pathfinder.PathType
/** Handles purely server logic for a Pokémon */
@@ -260,6 +261,8 @@ class PokemonServerDelegate : PokemonSideDelegate {
if (entity.ownerUUID == null && entity.owner == null) {
entity.level().broadcastEntityEvent(entity, 60.toByte()) // Sends smoke effect
if(entity.level().gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_LOOT)) {
val heldItem = (entity as PokemonEntity?)?.pokemon?.heldItemNoCopy() ?: ItemStack.EMPTY
if (!heldItem.isEmpty) entity.spawnAtLocation(heldItem.item)
(entity.drops ?: entity.pokemon.form.drops).drop(entity, entity.level() as ServerLevel, entity.position(), entity.killer)
}
}
@@ -80,7 +80,7 @@ class PokemonNavigation(val world: Level, val pokemonEntity: PokemonEntity) : Gr
(isTouchingLava && moving.swim.canSwimInLava) ||
this.mob.isPassenger
if (pokemonEntity.behaviour.moving.swim.canSwimInWater) {
return isInLiquid || super.canUpdatePath()
return isInLiquid || ((this.mob.onGround() || this.pokemonEntity.isFlying()) || this.mob.isInLiquid() || this.mob.isPassenger())
} else {
return isAtValidPosition
}
@@ -1,56 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.entity.pokemon.ai.goals
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import net.minecraft.world.entity.ai.goal.Goal
import java.util.*
/**
* AI goal for sleeping in the wild.
*
* @author Hiroku
* @since July 18th, 2022
*/
class WildRestGoal(private val pokemonEntity: PokemonEntity) : Goal() {
override fun getFlags(): EnumSet<Flag> = EnumSet.allOf(Flag::class.java)
override fun canUse(): Boolean {
val rest = pokemonEntity.behaviour.resting
if (!pokemonEntity.pokemon.isWild() || pokemonEntity.random.nextFloat() < 1 - rest.sleepChance || !pokemonEntity.canSleep() || pokemonEntity.isBusy || !rest.depth.canSleep(pokemonEntity)) {
return false
}
return true
}
override fun isInterruptable(): Boolean {
return false
}
override fun canContinueToUse(): Boolean {
return if (pokemonEntity.canSleep() && !pokemonEntity.behaviour.resting.depth.shouldWake(pokemonEntity)) {
true
} else {
wake()
false
}
}
override fun start() {
pokemonEntity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
}
fun wake() {
if (pokemonEntity.battleId == null) {
pokemonEntity.pokemon.status = null
}
}
}
@@ -23,7 +23,9 @@ class ServerSettingsPacket internal constructor(
val preventCompletePartyDeposit: Boolean,
val displayEntityLevelLabel: Boolean,
val displayEntityNameLabel: Boolean,
val maxPokemonLevel: Int
val maxPokemonLevel: Int,
val maxPokemonFriendship: Int,
val maxDynamaxLevel: Int
) : NetworkPacket<ServerSettingsPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -31,9 +33,11 @@ class ServerSettingsPacket internal constructor(
buffer.writeBoolean(Cobblemon.config.displayEntityLevelLabel)
buffer.writeBoolean(Cobblemon.config.displayEntityNameLabel)
buffer.writeInt(Cobblemon.config.maxPokemonLevel)
buffer.writeInt(Cobblemon.config.maxPokemonFriendship)
buffer.writeInt(Cobblemon.config.maxDynamaxLevel)
}
companion object {
val ID = cobblemonResource("server_settings")
fun decode(buffer: RegistryFriendlyByteBuf) = ServerSettingsPacket(buffer.readBoolean(), buffer.readBoolean(), buffer.readBoolean(), buffer.readInt())
fun decode(buffer: RegistryFriendlyByteBuf) = ServerSettingsPacket(buffer.readBoolean(), buffer.readBoolean(), buffer.readBoolean(), buffer.readInt(), buffer.readInt(), buffer.readInt())
}
}
@@ -25,14 +25,14 @@ import net.minecraft.resources.ResourceLocation
* @author Licious
* @since December 29th, 2022
*/
internal class UnvalidatedPlaySoundS2CPacket(
val sound: ResourceLocation,
val category: SoundSource,
val x: Double,
val y: Double,
val z: Double,
val volume: Float,
val pitch: Float
class UnvalidatedPlaySoundS2CPacket(
var sound: ResourceLocation,
var category: SoundSource,
var x: Double,
var y: Double,
var z: Double,
var volume: Float,
var pitch: Float
) : NetworkPacket<UnvalidatedPlaySoundS2CPacket> {
override val id = ID
@@ -18,7 +18,7 @@ import net.minecraft.network.protocol.PacketUtils
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.world.phys.Vec3
abstract class SpawnExtraDataEntityPacket<T: NetworkPacket<T>, E : Entity>(private val vanillaSpawnPacket: ClientboundAddEntityPacket) : NetworkPacket<T> {
abstract class SpawnExtraDataEntityPacket<T: NetworkPacket<T>, E : Entity>(var vanillaSpawnPacket: ClientboundAddEntityPacket) : NetworkPacket<T> {
override fun encode(buffer: RegistryFriendlyByteBuf) {
this.encodeEntityData(buffer)
this.vanillaSpawnPacket.write(buffer)
@@ -30,13 +30,14 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
class SpawnNPCPacket(
private val npcClass: ResourceLocation,
private val aspects: Set<String>,
private val level: Int,
private val battleIds: Set<UUID>,
private val name: Component,
private val poseType: PoseType,
private val texture: NPCPlayerTexture,
var npcClass: ResourceLocation,
var aspects: Set<String>,
var level: Int,
var battleIds: Set<UUID>,
var name: Component,
var poseType: PoseType,
var texture: NPCPlayerTexture,
var hideNameTag: Boolean,
vanillaSpawnPacket: ClientboundAddEntityPacket
) : SpawnExtraDataEntityPacket<SpawnNPCPacket, NPCEntity>(vanillaSpawnPacket) {
@@ -50,6 +51,7 @@ class SpawnNPCPacket(
entity.name,
entity.entityData.get(NPCEntity.POSE_TYPE),
entity.entityData.get(NPCEntity.NPC_PLAYER_TEXTURE),
entity.hideNameTag,
vanillaSpawnPacket
)
@@ -64,6 +66,7 @@ class SpawnNPCPacket(
if (this.texture.model != NPCPlayerModelType.NONE) {
buffer.writeByteArray(this.texture.texture)
}
buffer.writeBoolean(this.hideNameTag)
}
override fun applyData(entity: NPCEntity) {
@@ -74,6 +77,7 @@ class SpawnNPCPacket(
entity.entityData.set(NPCEntity.ASPECTS, aspects)
entity.entityData.set(NPCEntity.POSE_TYPE, poseType)
entity.entityData.set(NPCEntity.NPC_PLAYER_TEXTURE, texture)
entity.entityData.set(NPCEntity.HIDE_NAME_TAG, this.hideNameTag)
}
override fun checkType(entity: Entity): Boolean = entity is NPCEntity
@@ -93,9 +97,10 @@ class SpawnNPCPacket(
} else {
NPCPlayerTexture(byteArrayOf(), model)
}
val hideNameTag = buffer.readBoolean()
val vanillaPacket = decodeVanillaPacket(buffer)
return SpawnNPCPacket(npc, aspects, level, battleIds, name, poseType, texture, vanillaPacket)
return SpawnNPCPacket(npc, aspects, level, battleIds, name, poseType, texture, hideNameTag, vanillaPacket)
}
}
@@ -24,26 +24,26 @@ import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.resources.ResourceLocation
class SpawnPokemonPacket(
private val ownerId: UUID?,
private val scaleModifier: Float,
private val speciesId: ResourceLocation,
private val gender: Gender,
private val shiny: Boolean,
private val formName: String,
private val aspects: Set<String>,
private val battleId: UUID?,
private val phasingTargetId: Int,
private val beamMode: Byte,
private val platform: PlatformType,
private val nickname: MutableComponent?,
private val labelLevel: Int,
private val poseType: PoseType,
private val unbattlable: Boolean,
private val hideLabel: Boolean,
private val caughtBall: ResourceLocation,
private val spawnYaw: Float,
private val friendship: Int,
private val freezeFrame: Float,
var ownerId: UUID?,
var scaleModifier: Float,
var speciesId: ResourceLocation,
var gender: Gender,
var shiny: Boolean,
var formName: String,
var aspects: Set<String>,
var battleId: UUID?,
var phasingTargetId: Int,
var beamMode: Byte,
var platform: PlatformType,
var nickname: MutableComponent?,
var labelLevel: Int,
var poseType: PoseType,
var unbattlable: Boolean,
var hideLabel: Boolean,
var caughtBall: ResourceLocation,
var spawnYaw: Float,
var friendship: Int,
var freezeFrame: Float,
vanillaSpawnPacket: ClientboundAddEntityPacket
) : SpawnExtraDataEntityPacket<SpawnPokemonPacket, PokemonEntity>(vanillaSpawnPacket) {
@@ -26,16 +26,17 @@ import net.minecraft.network.RegistryFriendlyByteBuf
* @author Hiroku
* @since June 20th, 2022
*/
class OpenPCPacket(val storeID: UUID) : NetworkPacket<OpenPCPacket> {
class OpenPCPacket(val storeID: UUID, val box: Int = 1) : NetworkPacket<OpenPCPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(storeID)
buffer.writeInt(box)
}
companion object {
val ID = cobblemonResource("open_pc")
fun decode(buffer: RegistryFriendlyByteBuf) = OpenPCPacket(buffer.readUUID())
fun decode(buffer: RegistryFriendlyByteBuf) = OpenPCPacket(buffer.readUUID(), buffer.readInt())
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.net.serverhandling.evolution
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.util.isInBattle
import com.cobblemon.mod.common.util.party
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
@@ -17,7 +18,7 @@ import net.minecraft.server.level.ServerPlayer
object AcceptEvolutionHandler : ServerNetworkPacketHandler<AcceptEvolutionPacket> {
override fun handle(packet: AcceptEvolutionPacket, server: MinecraftServer, player: ServerPlayer) {
val pokemon = player.party()[packet.pokemonUUID] ?: return
if (pokemon.entity?.isBusy == true) return
if (player.isInBattle() || pokemon.entity?.isBusy == true) return
val evolution = pokemon.evolutionProxy.server().firstOrNull { evolution -> evolution.id.equals(packet.evolutionId, true) } ?: return
pokemon.evolutionProxy.server().start(evolution)
}
@@ -43,7 +43,7 @@ object FinishScanningHandler : ServerNetworkPacketHandler<FinishScanningPacket>
val scannableEntity = targetEntity as? ScannableEntity ?: return
val dex = Cobblemon.playerDataManager.getPokedexData(player)
val pokedexEntityData = scannableEntity.resolvePokemonScan()
if(pokedexEntityData != null){
if (pokedexEntityData != null){
val newInformation = dex.getNewInformation(pokedexEntityData)
if ((scannableEntity as? PokemonEntity)?.owner === player) {
dex.catch(scannableEntity.pokemon)
@@ -52,7 +52,7 @@ object FinishScanningHandler : ServerNetworkPacketHandler<FinishScanningPacket>
}
POKEMON_SCANNED.post(PokemonScannedEvent(player, pokedexEntityData, scannableEntity))
ServerConfirmedRegisterPacket(pokedexEntityData.species.resourceIdentifier, newInformation).sendToPlayer(player)
ServerConfirmedRegisterPacket((pokedexEntityData.getApparentSpecies()).resourceIdentifier, newInformation).sendToPlayer(player)
}
}
}
@@ -9,32 +9,28 @@
package com.cobblemon.mod.common.pokedex.scanner
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.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.asArrayValue
import java.util.UUID
// Hiro note: I honestly want to move to not using this at all. There is always a Pokemon behind the data,
// it's just that sometimes it's harder to reach or there's a Zoroark or Ditto throwing a curveball. It makes
// in shittier in events to have to cater to two distinct classes of data that can be scanned.
data class PokedexEntityData(
val species: Species,
val form: FormData,
val gender: Gender,
val aspects: Set<String>,
val shiny: Boolean,
val level: Int,
val ownerUUID: UUID?
val pokemon: Pokemon,
val disguise: DisguiseData?
) {
val struct = QueryStruct(hashMapOf())
.addFunction("species") { species.struct }
.addFunction("gender") { StringValue(gender.name.lowercase()) }
.addFunction("aspects") { aspects.asArrayValue(::StringValue) }
.addFunction("shiny") { DoubleValue(shiny) }
.addFunction("level") { DoubleValue(level.toDouble()) }
.addFunction("has_owner") { DoubleValue(ownerUUID != null) }
class DisguiseData(
val species: Species,
val form: FormData,
) {
val struct = QueryStruct(hashMapOf())
.addFunction("species") { species.struct }
.addFunction("form") { StringValue(form.name) }
}
fun getApparentSpecies() = disguise?.species ?: pokemon.species
fun getApparentForm() = disguise?.form ?: pokemon.form
}
@@ -18,6 +18,9 @@ import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random
import net.minecraft.client.DeltaTracker
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
@@ -26,9 +29,6 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.util.Mth.clamp
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random
class PokedexUsageContext {
companion object {
@@ -85,7 +85,7 @@ class PokedexUsageContext {
if (transitionIntervals == TRANSITION_INTERVALS) playSound(CobblemonSounds.POKEDEX_SCAN_CLOSE)
transitionIntervals = max(transitionIntervals - updateInterval, 0F)
if (transitionIntervals <= 0) {
if (viewInfoTicks >= VIEW_INFO_BUFFER_TICKS) openPokedexGUI(type, scannableEntityInFocus!!.resolvePokemonScan()?.species?.resourceIdentifier)
if (viewInfoTicks >= VIEW_INFO_BUFFER_TICKS) openPokedexGUI(type, scannableEntityInFocus!!.resolvePokemonScan()?.getApparentSpecies()?.resourceIdentifier)
resetState()
}
}
@@ -155,7 +155,7 @@ class PokedexUsageContext {
}
// Check if Pokémon in focus is owned
isPokemonInFocusOwned = CobblemonClient.clientPokedexData.getHighestKnowledgeForSpecies(resolvedPokemon.species.resourceIdentifier) == PokedexEntryProgress.CAUGHT
isPokemonInFocusOwned = CobblemonClient.clientPokedexData.getHighestKnowledgeForSpecies(resolvedPokemon.getApparentSpecies().resourceIdentifier) == PokedexEntryProgress.CAUGHT
// Randomize info frames for render
if (focusIntervals == 0F) {
@@ -170,7 +170,7 @@ class PokedexUsageContext {
// Check if Pokémon in focus is new or has new data
newPokemonInfo = CobblemonClient.clientPokedexData.getNewInformation(resolvedPokemon)
if (newPokemonInfo == PokedexLearnedInformation.NONE) playSound(CobblemonSounds.POKEDEX_SCAN_DETAIL)
else scannedSpecies = resolvedPokemon.species.resourceIdentifier
else scannedSpecies = resolvedPokemon.getApparentSpecies().resourceIdentifier
}
} else {
resetFocusedPokemonState()
@@ -314,7 +314,6 @@ open class Pokemon : ShowdownIdentifiable {
*/
var friendship = this.form.baseFriendship
private set(value) {
// Don't check on client, that way we don't need to actually sync the config value it doesn't affect anything
if (!this.isPossibleFriendship(value)) {
return
}
@@ -129,10 +129,21 @@ class Species : ClientDataSynchronizer<Species>, ShowdownIdentifiable {
},
ApplyPresets().apply {
condition = Either.left("!q.entity.is_in_party".asExpression())
presets.add(cobblemonResource("wanders"))
presets.add(cobblemonResource("pokemon_sleeps"))
presets.add(cobblemonResource("pokemon_breeding"))
},
ApplyPresets().apply {
condition = Either.left("q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land".asExpression())
presets.add(cobblemonResource("wanders"))
},
ApplyPresets().apply {
condition = Either.left("q.entity.behaviour.moving.fly.can_fly && q.entity.behaviour.moving.walk.avoids_land".asExpression())
presets.add(cobblemonResource("wanders_air"))
},
ApplyPresets().apply {
condition = Either.left("q.entity.behaviour.moving.walk.avoids_land && q.entity.behaviour.moving.swim.can_swim_in_water && !q.entity.behaviour.moving.swim.avoids_water".asExpression())
presets.add(cobblemonResource("wanders_water"))
},
ApplyPresets().apply {
condition = Either.left("q.entity.is_wild".asExpression())
// presets like aggression to players
@@ -8,7 +8,16 @@
package com.cobblemon.mod.common.pokemon.ai
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
class CombatBehaviour {
var willDefendSelf = false
var willDefendOwner = true
@Transient
val struct = ObjectValue(this).also {
it.addFunction("will_defend_self") { DoubleValue(willDefendSelf) }
it.addFunction("will_defend_owner") { DoubleValue(willDefendOwner) }
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.pokemon.ai
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.util.party
import net.minecraft.server.level.ServerPlayer
@@ -21,13 +23,20 @@ class EntityBehaviour {
// val avoidedByPhantom = false
val avoidedBySkeleton = false
@Transient
val struct = ObjectValue(this).also {
it.addFunction("avoided_by_creeper") { DoubleValue(avoidedByCreeper) }
// it.addFunction("avoided_by_phantom") { DoubleValue(avoidedByPhantom) }
it.addFunction("avoided_by_skeleton") { DoubleValue(avoidedBySkeleton) }
}
companion object {
fun hasCreeperFearedShoulderMount(player: ServerPlayer) : Boolean {
return player.party().any { pokemon -> pokemon.state is ShoulderedState && pokemon.species.behaviour.entityInteract.avoidedByCreeper }
return player.party().any { pokemon -> pokemon.state is ShoulderedState && pokemon.form.behaviour.entityInteract.avoidedByCreeper }
}
fun hasSkeletonFearedShoulderMount(player: ServerPlayer) : Boolean {
return player.party().any { pokemon -> pokemon.state is ShoulderedState && pokemon.species.behaviour.entityInteract.avoidedBySkeleton }
return player.party().any { pokemon -> pokemon.state is ShoulderedState && pokemon.form.behaviour.entityInteract.avoidedBySkeleton }
}
}
}
@@ -8,9 +8,19 @@
package com.cobblemon.mod.common.pokemon.ai
import com.cobblemon.mod.common.util.asExpressionLike
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.createDuplicateRuntime
import com.cobblemon.mod.common.util.resolveFloat
class FlyBehaviour {
val canFly = false
val flySpeedHorizontal = "0.3".asExpressionLike()
val flySpeedHorizontal = "0.3".asExpression()
@Transient
val struct = ObjectValue(this).also {
it.addFunction("can_fly") { DoubleValue(canFly) }
it.addFunction("fly_speed_horizontal") { it.environment.createDuplicateRuntime().resolveFloat(flySpeedHorizontal) }
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.pokemon.ai
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.google.gson.annotations.SerializedName
/**
@@ -50,4 +51,13 @@ class FormPokemonBehaviour {
val combat: CombatBehaviour
get() = _combat ?: parent.combat
@Transient
val struct = ObjectValue(this).also {
it.addFunction("resting") { resting.struct }
it.addFunction("moving") { moving.struct }
it.addFunction("idle") { idle.struct }
it.addFunction("entity_interact") { entityInteract.struct }
it.addFunction("combat") { combat.struct }
}
}
@@ -8,6 +8,13 @@
package com.cobblemon.mod.common.pokemon.ai
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
class IdleBehaviour {
val pointsAtSpawn = false
@Transient
val struct = ObjectValue(this)
.addFunction("points_at_spawn") { DoubleValue(pointsAtSpawn) }
}
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.pokemon.ai
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
/**
* Behavioural properties relating to a Pokémon's ability to look and move.
*
@@ -23,4 +26,16 @@ class MoveBehaviour {
val wanderSpeed = 1.0
val canLook = true
val looksAtEntities = true
@Transient
val struct = ObjectValue<MoveBehaviour>(this).also {
it.addFunction("walk") { walk.struct }
it.addFunction("swim") { swim.struct }
it.addFunction("fly") { fly.struct }
it.addFunction("step_height") { DoubleValue(stepHeight) }
it.addFunction("wander_chance") { DoubleValue(wanderChance) }
it.addFunction("wander_speed") { DoubleValue(wanderSpeed) }
it.addFunction("can_look") { DoubleValue(canLook) }
it.addFunction("looks_at_entities") { DoubleValue(looksAtEntities) }
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.pokemon.ai
import com.cobblemon.mod.common.api.molang.ObjectValue
/**
* Collection of all AI properties definable at the species level of a Pokémon.
*
@@ -20,4 +22,13 @@ open class PokemonBehaviour {
val idle = IdleBehaviour()
val entityInteract = EntityBehaviour()
val combat = CombatBehaviour()
@Transient
val struct = ObjectValue<PokemonBehaviour>(this).also {
it.addFunction("resting") { resting.struct }
it.addFunction("moving") { moving.struct }
it.addFunction("idle") { idle.struct }
it.addFunction("entity_interact") { entityInteract.struct }
it.addFunction("combat") { combat.struct }
}
}
@@ -8,11 +8,13 @@
package com.cobblemon.mod.common.pokemon.ai
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.ai.SleepDepth
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.TimeRange
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
/**
* Behavioural properties relating to a Pokémon sleeping. This can be wild sleeping or sleeping on the player or both.
@@ -23,7 +25,6 @@ import net.minecraft.world.level.biome.Biome
class RestBehaviour {
val canSleep = false
val times = TimeRange.timeRanges["night"]!!
val sleepChance = 1 / 600F
val blocks = mutableListOf<RegistryLikeCondition<Block>>()
val biomes = mutableListOf<RegistryLikeCondition<Biome>>()
val light = IntRange(0, 15)
@@ -32,4 +33,12 @@ class RestBehaviour {
val skyLight: IntRange? = null
val willSleepOnBed = false
@Transient
val struct = ObjectValue<RestBehaviour>(this).also {
it.addFunction("can_sleep") { DoubleValue(canSleep) }
it.addFunction("min_light") { DoubleValue(light.first) }
it.addFunction("max_light") { DoubleValue(light.last) }
it.addFunction("will_sleep_on_bed") { DoubleValue(willSleepOnBed) }
}
}
@@ -8,7 +8,11 @@
package com.cobblemon.mod.common.pokemon.ai
import com.cobblemon.mod.common.util.asExpressionLike
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.createDuplicateRuntime
import com.cobblemon.mod.common.util.resolveFloat
import net.minecraft.tags.FluidTags
import net.minecraft.tags.TagKey
import net.minecraft.world.level.material.Fluid
@@ -18,7 +22,7 @@ class SwimBehaviour {
val hurtByLava = true
val canSwimInWater = true
val canSwimInLava = true
val swimSpeed = "0.3".asExpressionLike()
val swimSpeed = "0.3".asExpression()
val canBreatheUnderwater = false
val canBreatheUnderlava = false
val canWalkOnWater = false
@@ -27,4 +31,17 @@ class SwimBehaviour {
fun canWalkOnFluid(tag: TagKey<Fluid>) = if (tag == FluidTags.WATER) canWalkOnWater else if (tag == FluidTags.LAVA) canWalkOnLava else false
fun canBreatheUnderFluid(tag: TagKey<Fluid>) = if (tag == FluidTags.WATER) canBreatheUnderwater else if (tag == FluidTags.LAVA) canBreatheUnderlava else false
fun canSwimInFluid(tag: TagKey<Fluid>) = if (tag == FluidTags.WATER) canSwimInWater else if (tag == FluidTags.LAVA) canSwimInLava else false
@Transient
val struct = ObjectValue(this).also {
it.addFunction("avoids_water") { DoubleValue(avoidsWater) }
it.addFunction("hurt_by_lava") { DoubleValue(hurtByLava) }
it.addFunction("can_swim_in_water") { DoubleValue(canSwimInWater) }
it.addFunction("can_swim_in_lava") { DoubleValue(canSwimInLava) }
it.addFunction("swim_speed") { it.environment.createDuplicateRuntime().resolveFloat(swimSpeed) }
it.addFunction("can_breathe_underwater") { DoubleValue(canBreatheUnderwater) }
it.addFunction("can_breathe_underlava") { DoubleValue(canBreatheUnderlava) }
it.addFunction("can_walk_on_water") { DoubleValue(canWalkOnWater) }
it.addFunction("can_walk_on_lava") { DoubleValue(canWalkOnLava) }
}
}
@@ -8,11 +8,22 @@
package com.cobblemon.mod.common.pokemon.ai
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.createDuplicateRuntime
import com.cobblemon.mod.common.util.resolveFloat
class WalkBehaviour {
val canWalk = true
val avoidsLand = false
var walkSpeed: ExpressionLike = "0.35".asExpressionLike()
var walkSpeed: Expression = "0.35".asExpression()
@Transient
val struct = ObjectValue(this).also {
it.addFunction("can_walk") { DoubleValue(canWalk) }
it.addFunction("avoids_land") { DoubleValue(avoidsLand) }
it.addFunction("walk_speed") { it.environment.createDuplicateRuntime().resolveFloat(walkSpeed) }
}
}
@@ -124,6 +124,10 @@ object CobblemonHeldItemManager : BaseCobblemonHeldItemManager() {
if (battle.isPvP && !consumeHeldItems) {
return
}
// Block stealing from NPCs
if (battle.isPvN) {
return
}
// if items aren't consumed, then we don't want to give them to wild pokemon (dupe)
if (this.giveItemEffect.contains(effectId) && (pokemon.actor is PlayerBattleActor || consumeHeldItems)) {
this.give(pokemon, itemID)
@@ -147,6 +147,7 @@ internal object PropertiesCompletionProvider : DataRegistry {
this.inject(setOf("originaltrainer", "ot"), setOf(""))
this.inject(setOf("originaltrainertype", "ottype"), setOf("None", "Player", "NPC"))
this.inject(setOf("nickname", "nick"), setOf(""))
this.inject(setOf("helditem", "held_item"), setOf("cobblemon:exp_share"))
Stats.PERMANENT.forEach{ stat ->
val statName = stat.toString().lowercase()
@@ -233,6 +233,7 @@ object DataKeys {
const val NPC_HITBOX_WIDTH = "Width"
const val NPC_HITBOX_HEIGHT = "Height"
const val NPC_HITBOX_FIXED = "Fixed"
const val NPC_HIDE_NAME_TAG = "HideNPCNameTag"
const val SCRIPTED_BRAIN_IS_CUSTOM = "BehavioursAreCustom"
const val SCRIPTED_BRAIN_PRESETS = "BrainPresets"
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.util
import com.cobblemon.mod.common.api.ai.PathfindingMedium
import com.google.common.collect.ImmutableMap
import com.mojang.serialization.Dynamic
import net.minecraft.core.BlockPos
@@ -17,8 +18,10 @@ import net.minecraft.nbt.Tag
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.network.syncher.SynchedEntityData
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.pathfinder.PathType.WALKABLE
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.BooleanOp
@@ -197,6 +200,15 @@ fun Entity.isStandingOnSand(): Boolean {
return isStandingOn(setOf(Blocks.SAND))
}
fun PathfinderMob.getPathfindingMedium(): PathfindingMedium {
return when {
this.isUnderWater -> PathfindingMedium.WATER
this.isInLava -> PathfindingMedium.LAVA
navigation.nodeEvaluator.getPathType(this, blockPosition()) == WALKABLE -> PathfindingMedium.LAND
else -> PathfindingMedium.AIR
}
}
fun Entity.isStandingOnRedSand(): Boolean {
return isStandingOn(setOf(Blocks.RED_SAND))
}
@@ -223,4 +223,10 @@ fun Iterable<MoValue>.asArrayValue(): ArrayStruct {
val array = ArrayStruct()
forEachIndexed { index, value -> array.setDirectly("$index", value) }
return array
}
fun MoLangEnvironment.createDuplicateRuntime(): MoLangRuntime {
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(this)
return runtime
}
@@ -36,6 +36,7 @@ import net.minecraft.util.Mth
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
@@ -46,7 +47,10 @@ import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.HitResult
import java.util.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
// Stuff like getting their party
fun ServerPlayer.party() = Cobblemon.storage.getParty(this)
@@ -414,7 +418,7 @@ fun Player.giveOrDropItemStack(stack: ItemStack, playSound: Boolean = true) {
val inserted = this.inventory.add(stack)
if (inserted && stack.isEmpty) {
stack.count = 1
this.drop(stack, false)?.makeFakeItem()
this.dropFakeItem(stack)
if (playSound) {
this.level().playSound(null, this.x, this.y, this.z, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.2f, ((this.random.nextFloat() - this.random.nextFloat()) * 0.7f + 1.0f) * 2.0f)
}
@@ -428,6 +432,31 @@ fun Player.giveOrDropItemStack(stack: ItemStack, playSound: Boolean = true) {
}
}
/**
* Utility function to mimic the [Player.drop] method but as a fake item entity.
* Avoids cases where other mods listen to that drop method and expect a real item to be dropped.
*/
fun Player.dropFakeItem(stack: ItemStack): ItemEntity {
val d = this.eyeY - 0.3
val itemEntity = ItemEntity(this.level(), this.x, d, this.z, stack)
val f = 0.3F
val g = sin(this.xRot * (PI / 180.0)).toFloat()
val h = cos(this.xRot * (PI / 180.0)).toFloat()
val i = sin(this.yRot * (PI / 180.0)).toFloat()
val j = cos(this.yRot * (PI / 180.0)).toFloat()
val k = this.random.nextFloat() * (PI * 2)
val l = 0.02F * this.random.nextFloat()
itemEntity.deltaMovement = Vec3(
(-i * h * f).toDouble() + cos(k) * l.toDouble(),
(-g * f + 0.1f + (this.random.nextFloat() - this.random.nextFloat()) * 0.1f).toDouble(),
(j * h * f).toDouble() + sin(k) * l.toDouble()
)
itemEntity.makeFakeItem()
return itemEntity
}
/** Retrieves the battle theme associated with this player, or the default PVP theme if null. */
fun ServerPlayer.getBattleTheme() = Cobblemon.playerDataManager.getGenericData(this).battleTheme?.let { BuiltInRegistries.SOUND_EVENT.get(it) } ?: CobblemonSounds.PVP_BATTLE
@@ -441,4 +470,4 @@ fun Player.isPartyBusy() =
fun Player.isUsingPokedex() = isUsingItem &&
((mainHandItem.item is PokedexItem && usedItemHand == InteractionHand.MAIN_HAND) ||
(offhandItem.item is PokedexItem && usedItemHand == InteractionHand.OFF_HAND))
(offhandItem.item is PokedexItem && usedItemHand == InteractionHand.OFF_HAND))
@@ -83,6 +83,7 @@ object NPCClassAdapter : JsonDeserializer<NPCClass> {
obj.get("isInvulnerable")?.let { npcClass.isInvulnerable = it.asBoolean }
obj.get("isLeashable")?.let { npcClass.isLeashable = it.asBoolean }
obj.get("allowProjectileHits")?.let { npcClass.allowProjectileHits = it.asBoolean }
obj.get("hideNameTag")?.let { npcClass.hideNameTag = it.asBoolean }
return npcClass
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SynchronizedSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SynchronizedSpeciesFeatureProvider
import com.cobblemon.mod.common.client.settings.ServerSettings
import com.cobblemon.mod.common.pokemon.*
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.codec.CodecUtils
@@ -99,15 +100,18 @@ internal class ClientPokemonP1(
const val FEATURES = "Features"
const val FEATURE_ID = "${Cobblemon.MODID}:feature_id"
/**
* do not use cobblemon.config in here, as this is used by the client whose config is different to server, always use [ServerSettings]
*/
internal val CODEC: MapCodec<ClientPokemonP1> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
UUIDUtil.LENIENT_CODEC.fieldOf(DataKeys.POKEMON_UUID).forGetter(ClientPokemonP1::uuid),
Species.BY_IDENTIFIER_CODEC.fieldOf(DataKeys.POKEMON_SPECIES_IDENTIFIER).forGetter(ClientPokemonP1::species),
Codec.STRING.fieldOf(DataKeys.POKEMON_FORM_ID).forGetter { pokemon -> pokemon.form.formOnlyShowdownId() },
ComponentSerialization.CODEC.optionalFieldOf(DataKeys.POKEMON_NICKNAME).forGetter(ClientPokemonP1::nickname),
CodecUtils.dynamicIntRange(1) { Cobblemon.config.maxPokemonLevel }.fieldOf(DataKeys.POKEMON_LEVEL).forGetter(ClientPokemonP1::level),
CodecUtils.dynamicIntRange(1) { ServerSettings.maxPokemonLevel }.fieldOf(DataKeys.POKEMON_LEVEL).forGetter(ClientPokemonP1::level),
Codec.intRange(0, Int.MAX_VALUE).fieldOf(DataKeys.POKEMON_EXPERIENCE).forGetter(ClientPokemonP1::experience),
CodecUtils.dynamicIntRange(0) { Cobblemon.config.maxPokemonFriendship }.fieldOf(DataKeys.POKEMON_FRIENDSHIP).forGetter(ClientPokemonP1::friendship),
CodecUtils.dynamicIntRange(0) { ServerSettings.maxPokemonFriendship }.fieldOf(DataKeys.POKEMON_FRIENDSHIP).forGetter(ClientPokemonP1::friendship),
Codec.intRange(0, Int.MAX_VALUE).fieldOf(DataKeys.POKEMON_HEALTH).forGetter(ClientPokemonP1::currentHealth),
Gender.CODEC.fieldOf(DataKeys.POKEMON_GENDER).forGetter(ClientPokemonP1::gender),
IVs.CODEC.fieldOf(DataKeys.POKEMON_IVS).forGetter(ClientPokemonP1::ivs),
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.util.codec.internal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.types.tera.TeraType
import com.cobblemon.mod.common.client.settings.ServerSettings
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket.Companion.convertToDisplay
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.*
@@ -71,6 +72,9 @@ internal data class ClientPokemonP2(
}
companion object {
/**
* do not use cobblemon.config in here, as this is used by the client whose config is different to server, always use [ServerSettings]
*/
internal val CODEC: MapCodec<ClientPokemonP2> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
ShoulderedState.CODEC.optionalFieldOf(DataKeys.POKEMON_STATE).forGetter(ClientPokemonP2::state),
@@ -86,7 +90,7 @@ internal data class ClientPokemonP2(
CompoundTag.CODEC.fieldOf(DataKeys.POKEMON_PERSISTENT_DATA).forGetter(ClientPokemonP2::persistentData),
UUIDUtil.LENIENT_CODEC.optionalFieldOf(DataKeys.TETHERING_ID).forGetter(ClientPokemonP2::tetheringId),
TeraType.BY_IDENTIFIER_CODEC.fieldOf(DataKeys.POKEMON_TERA_TYPE).forGetter(ClientPokemonP2::teraType),
CodecUtils.dynamicIntRange(0) { Cobblemon.config.maxDynamaxLevel }.fieldOf(DataKeys.POKEMON_DMAX_LEVEL).forGetter(ClientPokemonP2::dmaxLevel),
CodecUtils.dynamicIntRange(0) { ServerSettings.maxDynamaxLevel }.fieldOf(DataKeys.POKEMON_DMAX_LEVEL).forGetter(ClientPokemonP2::dmaxLevel),
Codec.BOOL.fieldOf(DataKeys.POKEMON_GMAX_FACTOR).forGetter(ClientPokemonP2::gmaxFactor),
Codec.BOOL.fieldOf(DataKeys.POKEMON_TRADEABLE).forGetter(ClientPokemonP2::tradeable)
).apply(instance, ::ClientPokemonP2)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.util.codec.internal
import com.cobblemon.mod.common.client.settings.ServerSettings
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
@@ -32,6 +33,9 @@ internal data class ClientPokemonP3(
}
companion object {
/**
* do not use cobblemon.config in here, as this is used by the client whose config is different to server, always use [ServerSettings]
*/
internal val CODEC: MapCodec<ClientPokemonP3> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
OriginalTrainerType.CODEC.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, OriginalTrainerType.NONE).forGetter(ClientPokemonP3::originalTrainerType),
@@ -43,6 +43,14 @@
}
}
},
"animation.nidoranf.cry": {
"animation_length": 1.2,
"sound_effects": {
"0.04": {
"effect": "pokemon.nidoranf.cry"
}
}
},
"animation.nidoranf.blink": {
"animation_length": 0.16667,
"bones": {
@@ -354,7 +354,7 @@
}
},
"sound_effects": {
"0.0": {
"0.04": {
"effect": "pokemon.nidorina.cry"
}
}
@@ -105,8 +105,8 @@
}
},
"sound_effects": {
"0.0": {
"effect": "nidoqueen_ambient"
"0.04": {
"effect": "pokemon.nidoqueen.cry"
}
}
},
@@ -47,6 +47,14 @@
}
}
},
"animation.nidoranm.cry": {
"animation_length": 1.2,
"sound_effects": {
"0.04": {
"effect": "pokemon.nidoranm.cry"
}
}
},
"animation.nidoranm.blink": {
"animation_length": 0.16667,
"bones": {
@@ -46,6 +46,14 @@
}
}
},
"animation.nidorino.cry": {
"animation_length": 1.2,
"sound_effects": {
"0.04": {
"effect": "pokemon.nidorino.cry"
}
}
},
"animation.nidorino.blink": {
"animation_length": 1,
"bones": {
@@ -132,7 +132,7 @@
},
"sound_effects": {
"0.0": {
"effect": "nidoking_ambient"
"effect": "pokemon.nidoking.cry"
}
}
}

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