mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-21 06:36:25 -04:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17ca9a31f1 | ||
|
|
c851f57e36 | ||
|
|
2b3497e63d | ||
|
|
c014efa9a6 | ||
|
|
6dc4b6af24 | ||
|
|
071278caa2 | ||
|
|
12a29d1289 | ||
|
|
bc85fdb778 | ||
|
|
0d0d659ce5 | ||
|
|
535e0b6f87 | ||
|
|
dd8cb49295 | ||
|
|
2c5968986d | ||
|
|
7b29d31009 | ||
|
|
8820a546db | ||
|
|
c4eb422722 | ||
|
|
d8f4638806 | ||
|
|
f92aa1777c | ||
|
|
8d864a3ff8 | ||
|
|
17a1409619 | ||
|
|
51f92e1635 | ||
|
|
9c3f8c9e8f | ||
|
|
923f40ab68 | ||
|
|
cd7c3a0319 | ||
|
|
8311dd8785 | ||
|
|
f489cd096f | ||
|
|
0a4dc34895 | ||
|
|
b1bc867ef8 | ||
|
|
3f6af25afa | ||
|
|
9946c39de0 | ||
|
|
037ba953b7 | ||
|
|
5b7b9342e5 | ||
|
|
c52b99270b | ||
|
|
2e67e804a7 | ||
|
|
de58d11f9e | ||
|
|
f63970163a | ||
|
|
b34adc6b57 |
+6
-1
@@ -29,6 +29,7 @@
|
||||
- Added the `/pokemonrestart <reset_starters>` and the `/pokemonrestartother <player> <reset_starters>` command allowing command block/mcfunction users to reset a players Pokémon data.
|
||||
- Added support for "isBattle" and "isTouchingWater" properties on resource pack Pokémon poses. This allows your custom Pokémon to be posed differently when in battle.
|
||||
- Added support for "isVisible" on a transformed part on resource pack Pokémon poses. This allows your custom Pokémon to have bones disappear in specific poses, such as hiding Greninja's throwing star when not in a battle pose.
|
||||
- Added the `doPokemonLoot` gamerule to toggle Pokémon dropping items/exp on death.
|
||||
|
||||
### Pokémon Added
|
||||
#### Gen 2
|
||||
@@ -190,9 +191,11 @@
|
||||
- Clicking categories of the Stat sub-section or the party reorder button in the Summary screen will now produce a click sound.
|
||||
- Updated PC Recipe.
|
||||
- Improved Pokémon AI and movement.
|
||||
- Friendship will slowly increase when Pokémon are shoulder-mounted.
|
||||
|
||||
### Fixes
|
||||
- Fixed spawning moon phase dependent Pokémon only when the moon phase is wrong
|
||||
- Fixed large Pokémon spawning partially inside walls where they suffocate.
|
||||
- Fixed messages for entry hazards, screens, Tailwind, Perish Song, Destiny Bond, Shed Skin, Uproar, Forewarn, Disguise, Arena Trap, Yawn, Curse, Clamp, Whirlpool, Liquid Ooze, Miracle Eye and Safeguard.
|
||||
- Fixed Porygon not evolving with an Upgrade.
|
||||
- Fixed super sized Pumpkaboo not having any moves.
|
||||
@@ -220,7 +223,9 @@
|
||||
- Fixed Pokémon variants and layers not rendering correctly when shouldered and playing on a dedicated server, existing shoulders affected will need to be retrieved and shouldered again.
|
||||
- Shoulder effects will now stay applied through situations that remove potion effects such as drinking milk.
|
||||
- Fixed Shedninja evolving to use the consumed Pokeball and removed the held item.
|
||||
- Fixed non-existent species in spawn pool file causing random species to spawn
|
||||
- Fixed non-existent species in spawn pool file causing random species to spawn.
|
||||
- Fixed shearing Pokémon dropping 0-2 wool instead of 1-3.
|
||||
- Fixed some alignment issues in the stat hexagon of the summary menu.
|
||||
|
||||
### Developer
|
||||
- Added SpawnEvent
|
||||
|
||||
+5
-10
@@ -8,14 +8,12 @@
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.block.ApricornBlock;
|
||||
import com.cobblemon.mod.common.block.ShearableBlock;
|
||||
import kotlin.Unit;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.dispenser.ShearsDispenserBehavior;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvents;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.event.GameEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
@@ -24,15 +22,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(ShearsDispenserBehavior.class)
|
||||
public abstract class ShearsDispenserBehaviorMixin {
|
||||
|
||||
// This exists so we can execute our custom interaction only if no one redirected shears interactions to overwrite the vanilla ones.
|
||||
// This exists, so we can execute our custom interaction only if no one redirected shears interactions to overwrite the vanilla ones.
|
||||
@Inject(method = "tryShearBlock", at = @At("HEAD"), cancellable = true)
|
||||
private static void cobblemon$tryApricornHarvest(ServerWorld world, BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
||||
final BlockState state = world.getBlockState(pos);
|
||||
if (state.getBlock() instanceof ApricornBlock apricornBlock && state.get(ApricornBlock.Companion.getAGE()) == ApricornBlock.MAX_AGE) {
|
||||
world.playSound(null, pos, SoundEvents.ENTITY_SHEEP_SHEAR, SoundCategory.BLOCKS, 1F, 1F);
|
||||
apricornBlock.harvest(world, state, pos);
|
||||
world.emitGameEvent(null, GameEvent.SHEAR, pos);
|
||||
cir.setReturnValue(true);
|
||||
if (state.getBlock() instanceof ShearableBlock shearableBlock) {
|
||||
cir.setReturnValue(shearableBlock.attemptShear(world, state, pos, () -> Unit.INSTANCE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,4 +33,9 @@ public interface BlocksInvoker {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Invoker("createFlowerPotBlock")
|
||||
static FlowerPotBlock createFlowerPotBlock(Block flower, FeatureFlag... requiredFeatures) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,13 +54,19 @@ import com.cobblemon.mod.common.api.storage.pc.PCStore
|
||||
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerDataStoreManager
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.BattleSide
|
||||
import com.cobblemon.mod.common.battles.ShowdownThread
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.command.argument.*
|
||||
import com.cobblemon.mod.common.command.argument.MoveArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.PokemonArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.PokemonPropertiesArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.PokemonStoreArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.SpawnBucketArgumentType
|
||||
import com.cobblemon.mod.common.config.CobblemonConfig
|
||||
import com.cobblemon.mod.common.config.LastChangedVersion
|
||||
import com.cobblemon.mod.common.config.constraint.IntConstraint
|
||||
@@ -85,10 +91,25 @@ import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
|
||||
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
|
||||
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
|
||||
import com.cobblemon.mod.common.trade.TradeManager
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.ifDedicatedServer
|
||||
import com.cobblemon.mod.common.util.isLaterVersion
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import com.cobblemon.mod.common.util.removeAmountIf
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
|
||||
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.util.UUID
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.command.argument.serialize.ConstantArgumentSerializer
|
||||
import net.minecraft.entity.data.TrackedDataHandlerRegistry
|
||||
@@ -98,15 +119,6 @@ import net.minecraft.registry.RegistryKey
|
||||
import net.minecraft.util.WorldSavePath
|
||||
import net.minecraft.world.World
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
object Cobblemon {
|
||||
const val MODID = "cobblemon"
|
||||
@@ -284,9 +296,9 @@ object Cobblemon {
|
||||
var pokeball = Items.AIR
|
||||
player.inventory.combinedInventory.forEach {
|
||||
it.forEach {
|
||||
itemStack -> if (itemStack.item is PokeBallItem && pokeball == Items.AIR) {
|
||||
pokeball = itemStack.item as PokeBallItem
|
||||
}
|
||||
itemStack -> if (itemStack.item is PokeBallItem && pokeball == Items.AIR) {
|
||||
pokeball = itemStack.item as PokeBallItem
|
||||
}
|
||||
}
|
||||
}
|
||||
player.inventory.removeAmountIf(1) { it.item is PokeBallItem }
|
||||
@@ -301,7 +313,7 @@ object Cobblemon {
|
||||
}
|
||||
}
|
||||
|
||||
PokemonSpecies.observable.subscribe {
|
||||
BagItems.observable.subscribe {
|
||||
LOGGER.info("Starting dummy Showdown battle to force it to pre-load data.")
|
||||
battleRegistry.startBattle(
|
||||
BattleFormat.GEN_9_SINGLES,
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.mixin.invoker.*
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.block.piston.PistonBehavior
|
||||
import net.minecraft.entity.effect.StatusEffects
|
||||
import net.minecraft.registry.Registries
|
||||
import net.minecraft.registry.Registry
|
||||
import net.minecraft.registry.RegistryKey
|
||||
@@ -26,7 +27,7 @@ import net.minecraft.registry.RegistryKeys
|
||||
import net.minecraft.sound.BlockSoundGroup
|
||||
import net.minecraft.util.math.intprovider.UniformIntProvider
|
||||
|
||||
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate")
|
||||
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
|
||||
object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<Block>>, Block>() {
|
||||
|
||||
override val registry: Registry<Block> = Registries.BLOCK
|
||||
@@ -130,7 +131,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
|
||||
val YELLOW_APRICORN_SAPLING = this.create("yellow_apricorn_sapling", ApricornSaplingBlock(PLANT_PROPERTIES, Apricorn.YELLOW))
|
||||
|
||||
@JvmField
|
||||
val MEDICINAL_LEEK_CROP = this.create("medicinal_leek_crop", MedicinalLeekBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DULL_RED).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP)))
|
||||
val MEDICINAL_LEEK = this.create("medicinal_leek", MedicinalLeekBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DULL_RED).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP)))
|
||||
@JvmField
|
||||
val ENERGY_ROOT = this.create("energy_root", EnergyRootBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DIRT_BROWN).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.ROOTS)))
|
||||
@JvmField
|
||||
@@ -209,6 +210,10 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
|
||||
|
||||
@JvmField
|
||||
val VIVICHOKE_SEEDS = this.create("vivichoke_seeds", VivichokeBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DARK_GREEN).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP)))
|
||||
@JvmField
|
||||
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(StatusEffects.LEVITATION, 10, AbstractBlock.Settings.create().mapColor(MapColor.DARK_GREEN).noCollision().breakInstantly().sounds(BlockSoundGroup.GRASS).offset(AbstractBlock.OffsetType.XZ).pistonBehavior(PistonBehavior.DESTROY)))
|
||||
@JvmField
|
||||
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
|
||||
|
||||
/**
|
||||
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
|
||||
|
||||
@@ -8,24 +8,25 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.mulch.MulchVariant
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.api.pokemon.Natures
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.item.*
|
||||
import com.cobblemon.mod.common.item.group.CobblemonItemGroups
|
||||
import com.cobblemon.mod.common.item.interactive.CandyItem
|
||||
import com.cobblemon.mod.common.item.interactive.EnergyRoot
|
||||
import com.cobblemon.mod.common.item.interactive.LinkCableItem
|
||||
import com.cobblemon.mod.common.item.interactive.MintItem
|
||||
import com.cobblemon.mod.common.item.interactive.RemedyItem
|
||||
import com.cobblemon.mod.common.item.interactive.VitaminItem
|
||||
import com.cobblemon.mod.common.item.battle.DireHitItem
|
||||
import com.cobblemon.mod.common.item.battle.GuardSpecItem
|
||||
import com.cobblemon.mod.common.item.battle.XStatItem
|
||||
import com.cobblemon.mod.common.item.interactive.*
|
||||
import com.cobblemon.mod.common.item.interactive.PotionItem
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import com.cobblemon.mod.common.pokeball.PokeBall
|
||||
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.ComposterBlock
|
||||
import net.minecraft.entity.effect.StatusEffectInstance
|
||||
import net.minecraft.entity.effect.StatusEffects
|
||||
import net.minecraft.item.*
|
||||
import net.minecraft.registry.Registries
|
||||
import net.minecraft.registry.Registry
|
||||
@@ -248,17 +249,26 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
|
||||
@JvmField
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
|
||||
@JvmField
|
||||
val MEDICINAL_LEEK = create("medicinal_leek", MedicinalLeekItem(CobblemonBlocks.MEDICINAL_LEEK_CROP))
|
||||
val MEDICINAL_LEEK = create("medicinal_leek", MedicinalLeekItem(CobblemonBlocks.MEDICINAL_LEEK, Item.Settings().food(FoodComponent.Builder().snack().hunger(1).saturationModifier(0.2f).build())))
|
||||
@JvmField
|
||||
val ROASTED_LEEK = create("roasted_leek", Item(Item.Settings().food(FoodComponent.Builder().snack().hunger(3).saturationModifier(0.2f).build())))
|
||||
val ROASTED_LEEK = create("roasted_leek", Item(Item.Settings().food(FoodComponent.Builder().snack().hunger(3).saturationModifier(0.3f).build())))
|
||||
@JvmField
|
||||
val BRAISED_VIVICHOKE = create("braised_vivichoke", Item(Item.Settings().food(FoodComponent.Builder().hunger(6).saturationModifier(0.6f).build())))
|
||||
val BRAISED_VIVICHOKE = create("braised_vivichoke", Item(Item.Settings().maxCount(1).food(FoodComponent.Builder().hunger(6).saturationModifier(0.6f).build())))
|
||||
@JvmField
|
||||
val ENERGY_ROOT = create("energy_root", EnergyRoot(CobblemonBlocks.ENERGY_ROOT))
|
||||
val VIVICHOKE_DIP = create("vivichoke_dip", StewItem(Item.Settings().maxCount(1)
|
||||
.food(FoodComponent.Builder()
|
||||
.hunger(10)
|
||||
.saturationModifier(1.2F)
|
||||
.statusEffect(StatusEffectInstance(StatusEffects.ABSORPTION, 600, 0), 1F)
|
||||
.build()
|
||||
)
|
||||
))
|
||||
@JvmField
|
||||
val ENERGY_ROOT = create("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Item.Settings().food(FoodComponent.Builder().hunger(1).snack().saturationModifier(0.2f).build())))
|
||||
@JvmField
|
||||
val REVIVAL_HERB = create("revival_herb", RevivalHerbItem(CobblemonBlocks.REVIVAL_HERB))
|
||||
@JvmField
|
||||
val PEP_UP_FLOWER = create("pep_up_flower", PepUpFlowerItem(Item.Settings()))
|
||||
val PEP_UP_FLOWER = blockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = create("medicinal_brew", Item(Item.Settings()))
|
||||
@JvmField
|
||||
@@ -267,18 +277,30 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
|
||||
val FINE_REMEDY = create("fine_remedy", RemedyItem(RemedyItem.FINE))
|
||||
@JvmField
|
||||
val SUPERB_REMEDY = create("superb_remedy", RemedyItem(RemedyItem.SUPERB))
|
||||
|
||||
@JvmField
|
||||
val HEAL_POWDER = create("heal_powder", Item(Item.Settings()))
|
||||
val POTION = create("potion", PotionItem(PotionType.POTION))
|
||||
@JvmField
|
||||
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", StewItem(Item.Settings().food(FoodComponent.Builder().hunger(8).saturationModifier(0.6f).build())))
|
||||
val SUPER_POTION = create("super_potion", PotionItem(PotionType.SUPER_POTION))
|
||||
@JvmField
|
||||
val REVIVE = create("revive", Item(Item.Settings()))
|
||||
val HYPER_POTION = create("hyper_potion", PotionItem(PotionType.HYPER_POTION))
|
||||
@JvmField
|
||||
val MAX_REVIVE = create("max_revive", Item(Item.Settings()))
|
||||
val MAX_POTION = create("max_potion", PotionItem(PotionType.MAX_POTION))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", Item(Item.Settings()))
|
||||
val FULL_RESTORE = create("full_restore", PotionItem(PotionType.FULL_RESTORE))
|
||||
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", Item(Item.Settings()))
|
||||
val HEAL_POWDER = create("heal_powder", HealPowderItem())
|
||||
@JvmField
|
||||
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", StewItem(Item.Settings().food(FoodComponent.Builder().hunger(8).saturationModifier(0.6f).build()).maxCount(1)))
|
||||
@JvmField
|
||||
val REVIVE = create("revive", ReviveItem(max = false))
|
||||
@JvmField
|
||||
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", PPUpItem(1))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", PPUpItem(3))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT_SEEDS = blockItem("red_mint_seeds", MintType.RED.getCropBlock())
|
||||
@@ -348,12 +370,28 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
|
||||
@JvmField
|
||||
val SERIOUS_MINT = create("serious_mint", MintItem(Natures.SERIOUS))
|
||||
|
||||
@JvmField val X_ACCURACY = create("x_accuracy", CobblemonItem(Item.Settings()))
|
||||
@JvmField val X_ATTACK = create("x_attack", CobblemonItem(Item.Settings()))
|
||||
@JvmField val X_DEFENSE = create("x_defense", CobblemonItem(Item.Settings()))
|
||||
@JvmField val X_SP_ATK = create("x_sp_atk", CobblemonItem(Item.Settings()))
|
||||
@JvmField val X_SP_DEF = create("x_sp_def", CobblemonItem(Item.Settings()))
|
||||
@JvmField val X_SPEED = create("x_speed", CobblemonItem(Item.Settings()))
|
||||
@JvmField val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", XStatItem(Stats.ACCURACY))
|
||||
@JvmField val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", XStatItem(Stats.ATTACK))
|
||||
@JvmField val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", XStatItem(Stats.DEFENCE))
|
||||
@JvmField val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", XStatItem(Stats.SPECIAL_ATTACK))
|
||||
@JvmField val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", XStatItem(Stats.SPECIAL_DEFENCE))
|
||||
@JvmField val X_SPEED = create("x_${Stats.SPEED.identifier.path}", XStatItem(Stats.SPEED))
|
||||
|
||||
@JvmField val DIRE_HIT = create("dire_hit", DireHitItem())
|
||||
@JvmField val GUARD_SPEC = create("guard_spec", GuardSpecItem())
|
||||
|
||||
@JvmField val BURN_HEAL = create("burn_heal", StatusCureItem("item.cobblemon.burn_heal", Statuses.BURN))
|
||||
@JvmField val PARALYZE_HEAL = create("paralyze_heal", StatusCureItem("item.cobblemon.paralyze_heal", Statuses.PARALYSIS))
|
||||
@JvmField val ICE_HEAL = create("ice_heal", StatusCureItem("item.cobblemon.ice_heal", Statuses.FROZEN))
|
||||
@JvmField val ANTIDOTE = create("antidote", StatusCureItem("item.cobblemon.antidote", Statuses.POISON, Statuses.POISON_BADLY))
|
||||
@JvmField val AWAKENING = create("awakening", StatusCureItem("item.cobblemon.awakening", Statuses.SLEEP))
|
||||
|
||||
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal"))
|
||||
|
||||
@JvmField val ETHER = create("ether", EtherItem(max = false))
|
||||
@JvmField val MAX_ETHER = create("max_ether", EtherItem(max = true))
|
||||
@JvmField val ELIXIR = create("elixir", ElixirItem(max = false))
|
||||
@JvmField val MAX_ELIXIR = create("max_elixir", ElixirItem(max = true))
|
||||
|
||||
/**
|
||||
* Evolution Ores and Stones
|
||||
@@ -425,7 +463,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
|
||||
@JvmField
|
||||
val ASSAULT_VEST = heldItem("assault_vest")
|
||||
@JvmField
|
||||
val BIG_ROOT = create("big_root", BigRoot(CobblemonBlocks.BIG_ROOT))
|
||||
val BIG_ROOT = blockItem("big_root", CobblemonBlocks.BIG_ROOT)
|
||||
@JvmField
|
||||
val BLACK_BELT = heldItem("black_belt")
|
||||
@JvmField
|
||||
@@ -488,26 +526,44 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
|
||||
val TWISTED_SPOON = heldItem("twisted_spoon")
|
||||
@JvmField
|
||||
val WISE_GLASSES = heldItem("wise_glasses")
|
||||
@JvmField
|
||||
val MENTAL_HERB = heldItem("mental_herb")
|
||||
@JvmField
|
||||
val MIRROR_HERB = heldItem("mirror_herb")
|
||||
@JvmField
|
||||
val POWER_HERB = heldItem("power_herb")
|
||||
@JvmField
|
||||
val WHITE_HERB = heldItem("white_herb")
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = heldItem("bright_powder")
|
||||
@JvmField
|
||||
val METAL_POWDER = heldItem("metal_powder")
|
||||
@JvmField
|
||||
val QUICK_POWDER = heldItem("quick_powder")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = heldItem("destiny_knot")
|
||||
@JvmField
|
||||
val EVERSTONE = heldItem("everstone")
|
||||
|
||||
// Mulch
|
||||
@JvmField
|
||||
val MULCH_BASE = noSettingsItem("mulch_base")
|
||||
@JvmField
|
||||
val COARSE_MULCH = noSettingsItem("coarse_mulch")
|
||||
val COARSE_MULCH = mulchItem("coarse_mulch", MulchVariant.COARSE)
|
||||
@JvmField
|
||||
val GROWTH_MULCH = noSettingsItem("growth_mulch")
|
||||
val GROWTH_MULCH = mulchItem("growth_mulch", MulchVariant.GROWTH)
|
||||
@JvmField
|
||||
val HUMID_MULCH = noSettingsItem("humid_mulch")
|
||||
val HUMID_MULCH = mulchItem("humid_mulch", MulchVariant.HUMID)
|
||||
@JvmField
|
||||
val LOAMY_MULCH = noSettingsItem("loamy_mulch")
|
||||
val LOAMY_MULCH = mulchItem("loamy_mulch", MulchVariant.LOAMY)
|
||||
@JvmField
|
||||
val PEAT_MULCH = noSettingsItem("peat_mulch")
|
||||
val PEAT_MULCH = mulchItem("peat_mulch", MulchVariant.PEAT)
|
||||
@JvmField
|
||||
val RICH_MULCH = noSettingsItem("rich_mulch")
|
||||
val RICH_MULCH = mulchItem("rich_mulch", MulchVariant.RICH)
|
||||
@JvmField
|
||||
val SANDY_MULCH = noSettingsItem("sandy_mulch")
|
||||
val SANDY_MULCH = mulchItem("sandy_mulch", MulchVariant.SANDY)
|
||||
@JvmField
|
||||
val SURPRISE_MULCH = noSettingsItem("surprise_mulch")
|
||||
val SURPRISE_MULCH = mulchItem("surprise_mulch", MulchVariant.SURPRISE)
|
||||
|
||||
// Misc
|
||||
@JvmField
|
||||
@@ -517,6 +573,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
|
||||
|
||||
private fun noSettingsItem(name: String): CobblemonItem = this.create(name, CobblemonItem(Item.Settings()))
|
||||
|
||||
private fun mulchItem(name: String, mulchVariant: MulchVariant): MulchItem = this.create(name, MulchItem(mulchVariant))
|
||||
|
||||
private fun pokeBallItem(pokeBall: PokeBall): PokeBallItem {
|
||||
val item = create(pokeBall.name.path, PokeBallItem(pokeBall))
|
||||
pokeBall.item = item
|
||||
|
||||
@@ -18,6 +18,9 @@ import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.callback.move.OpenMoveCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
|
||||
@@ -49,6 +52,9 @@ import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.PropertiesCompletionRegistrySyncPacket
|
||||
@@ -98,6 +104,12 @@ import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
|
||||
@@ -122,6 +134,12 @@ import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartySelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyMoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyPokemonMoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
|
||||
@@ -236,7 +254,7 @@ object CobblemonNetwork : NetworkManager {
|
||||
this.createClientBound(BattleCaptureStartPacket.ID, BattleCaptureStartPacket::decode, BattleCaptureStartHandler)
|
||||
this.createClientBound(BattleCaptureEndPacket.ID, BattleCaptureEndPacket::decode, BattleCaptureEndHandler)
|
||||
this.createClientBound(BattleCaptureShakePacket.ID, BattleCaptureShakePacket::decode, BattleCaptureShakeHandler)
|
||||
this.createClientBound(BattleApplyCaptureResponsePacket.ID, BattleApplyCaptureResponsePacket::decode, BattleApplyCaptureResponseHandler)
|
||||
this.createClientBound(BattleApplyPassResponsePacket.ID, BattleApplyPassResponsePacket::decode, BattleApplyPassResponseHandler)
|
||||
this.createClientBound(BattleChallengeNotificationPacket.ID, BattleChallengeNotificationPacket::decode, BattleChallengeNotificationHandler)
|
||||
this.createClientBound(BattleUpdateTeamPokemonPacket.ID, BattleUpdateTeamPokemonPacket::decode, BattleUpdateTeamPokemonHandler)
|
||||
this.createClientBound(BattlePersistentStatusPacket.ID, BattlePersistentStatusPacket::decode, BattlePersistentStatusHandler)
|
||||
@@ -282,6 +300,15 @@ object CobblemonNetwork : NetworkManager {
|
||||
|
||||
// Behaviours
|
||||
this.createClientBound(PokemonCryPacket.ID, PokemonCryPacket::decode, PokemonCryHandler)
|
||||
|
||||
// Move select packets
|
||||
this.createClientBound(OpenMoveCallbackPacket.ID, OpenMoveCallbackPacket::decode, OpenMoveCallbackHandler)
|
||||
|
||||
// Party select packets
|
||||
this.createClientBound(OpenPartyCallbackPacket.ID, OpenPartyCallbackPacket::decode, OpenPartyCallbackHandler)
|
||||
|
||||
// Party move select packets
|
||||
this.createClientBound(OpenPartyMoveCallbackPacket.ID, OpenPartyMoveCallbackPacket::decode, OpenPartyMoveCallbackHandler)
|
||||
}
|
||||
|
||||
override fun registerServerBound() {
|
||||
@@ -332,6 +359,18 @@ object CobblemonNetwork : NetworkManager {
|
||||
this.createServerBound(PasturePokemonPacket.ID, PasturePokemonPacket::decode, PasturePokemonHandler)
|
||||
this.createServerBound(UnpasturePokemonPacket.ID, UnpasturePokemonPacket::decode, UnpasturePokemonHandler)
|
||||
this.createServerBound(UnpastureAllPokemonPacket.ID, UnpastureAllPokemonPacket::decode, UnpastureAllPokemonHandler)
|
||||
|
||||
// Move select packets
|
||||
this.createServerBound(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler)
|
||||
this.createServerBound(MoveSelectCancelledPacket.ID, MoveSelectCancelledPacket::decode, MoveSelectCancelledHandler)
|
||||
|
||||
// Party select packets
|
||||
this.createServerBound(PartyPokemonSelectedPacket.ID, PartyPokemonSelectedPacket::decode, PartyPokemonSelectedHandler)
|
||||
this.createServerBound(PartySelectCancelledPacket.ID, PartySelectCancelledPacket::decode, PartySelectCancelledHandler)
|
||||
|
||||
// Party move select packets
|
||||
this.createServerBound(PartyPokemonMoveSelectedPacket.ID, PartyPokemonMoveSelectedPacket::decode, PartyPokemonMoveSelectedHandler)
|
||||
this.createServerBound(PartyMoveSelectCancelledPacket.ID, PartyMoveSelectCancelledPacket::decode, PartyMoveSelectCancelledHandler)
|
||||
}
|
||||
|
||||
private inline fun <reified T : NetworkPacket<T>> createClientBound(identifier: Identifier, noinline decoder: (PacketByteBuf) -> T, handler: ClientNetworkPacketHandler<T>) {
|
||||
|
||||
@@ -34,6 +34,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
|
||||
val PC_DROP = this.create("pc.drop")
|
||||
@JvmField
|
||||
val PC_RELEASE = this.create("pc.release")
|
||||
@JvmField
|
||||
val PC_CLICK = this.create("pc.click")
|
||||
|
||||
@JvmField
|
||||
val HEALING_MACHINE_ACTIVE = this.create("healing_machine.active")
|
||||
@@ -68,7 +70,13 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
|
||||
val PVW_BATTLE = this.create("battle.pvw.default")
|
||||
|
||||
@JvmField
|
||||
val MINT_USE = this.create("mint.use")
|
||||
val MEDICINE_HERB_USE = this.create("medicine_herb.use")
|
||||
@JvmField
|
||||
val MEDICINE_LIQUID_USE = this.create("medicine_liquid.use")
|
||||
@JvmField
|
||||
val MEDICINE_PILLS_USE = this.create("medicine_pills.use")
|
||||
@JvmField
|
||||
val MEDICINE_SPRAY_USE = this.create("medicine_spray.use")
|
||||
|
||||
|
||||
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.of(cobblemonResource(name)))
|
||||
|
||||
@@ -37,7 +37,7 @@ object Abilities : DataRegistry {
|
||||
PotentialAbility.types.add(CommonAbilityType)
|
||||
PotentialAbility.types.add(HiddenAbilityType)
|
||||
this.abilityMap.clear()
|
||||
val abilitiesJson = ShowdownService.get().getAbilityIds()
|
||||
val abilitiesJson = ShowdownService.service.getAbilityIds()
|
||||
for (i in 0 until abilitiesJson.size()) {
|
||||
val id = abilitiesJson[i].asString
|
||||
val ability = AbilityTemplate(id)
|
||||
|
||||
+18
-9
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* A class responsible for parsing a raw simulator protocol message received from Showdown.
|
||||
@@ -25,7 +26,6 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
* @since December 31st, 2022
|
||||
*/
|
||||
class BattleMessage(rawMessage: String) {
|
||||
|
||||
/**
|
||||
* The ID of the action received in the message.
|
||||
*/
|
||||
@@ -53,11 +53,6 @@ class BattleMessage(rawMessage: String) {
|
||||
*/
|
||||
private val optionalArgumentMatcher = Regex("^\\$OPTIONAL_ARG_START([^]]+)$OPTIONAL_ARG_END")
|
||||
|
||||
/**
|
||||
* Pattern to match a Showdown position.
|
||||
*/
|
||||
private val pnxMatcher = Regex("p\\d[a-c]")
|
||||
|
||||
init {
|
||||
this.parse(rawMessage)
|
||||
}
|
||||
@@ -122,6 +117,10 @@ class BattleMessage(rawMessage: String) {
|
||||
return this
|
||||
}
|
||||
|
||||
fun pokemonByUuid(index: Int, battle: PokemonBattle): BattlePokemon? {
|
||||
return this.argumentAt(index)?.let { UUID.fromString(it) }?.let { uuid -> battle.actors.flatMap { it.pokemonList }.find { it.uuid == uuid } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries an argument at the given [index] for a 'pnx' that will be parsed into a [BattleActor] and [ActiveBattlePokemon].
|
||||
*
|
||||
@@ -165,12 +164,14 @@ class BattleMessage(rawMessage: String) {
|
||||
* @return A 'pnx' String representing position and a 'uuid' String representing the unique Pokemon if parsed correctly, otherwise null.
|
||||
*/
|
||||
fun pnxAndUuid(index: Int): Pair<String, String>? {
|
||||
val argument = this.argumentAt(index)?.takeIf { it.length >= 3 }?.split(":")?.takeIf { it.size == 2 } ?: return null
|
||||
val pnx = argument[0].takeIf { it.matches(pnxMatcher) } ?: return null
|
||||
val argument = this.argumentAt(index)?.takeIf { it.length >= 2 }?.split(":")?.takeIf { it.size == 2 } ?: return null
|
||||
val pnx = argument[0].takeIf { it.matches(PNX_MATCHER) } ?: return null
|
||||
val uuid = argument[1].trim()
|
||||
return pnx to uuid
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to parse an [Effect] from an argument at the given [index].
|
||||
*
|
||||
@@ -238,6 +239,14 @@ class BattleMessage(rawMessage: String) {
|
||||
private const val OPTIONAL_ARG_START = "["
|
||||
private const val OPTIONAL_ARG_END = "]"
|
||||
|
||||
}
|
||||
/**
|
||||
* Pattern to match a Showdown position e.g. p2a, p1b
|
||||
*/
|
||||
val PNX_MATCHER = Regex("p\\d[a-c]")
|
||||
|
||||
/**
|
||||
* Pattern to match a Showdown side position e.g. p2, p1
|
||||
*/
|
||||
val PN_MATCHER = Regex("p\\d")
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ interface Effect {
|
||||
ABILITY("ability:"),
|
||||
ITEM("item:"),
|
||||
MOVE("move:"),
|
||||
BAGITEM("bagitem:"),
|
||||
PURE(""),
|
||||
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ import com.cobblemon.mod.common.battles.dispatch.GO
|
||||
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
|
||||
import com.cobblemon.mod.common.battles.interpreter.ContextManager
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
@@ -49,6 +49,7 @@ import net.minecraft.text.Text
|
||||
import java.io.File
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeLines
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
|
||||
/**
|
||||
* Individual battle instance
|
||||
@@ -149,6 +150,11 @@ open class PokemonBattle(
|
||||
return actors.find { actor -> actor.uuid == actorId }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first battle actor whom the given player controls, or null if there is no such actor.
|
||||
*/
|
||||
fun getActor(player: ServerPlayerEntity) = actors.firstOrNull { it.isForPlayer(player) }
|
||||
|
||||
/**
|
||||
* Gets a BattleActor and an [ActiveBattlePokemon] from a pnx key, e.g. p2a
|
||||
*
|
||||
@@ -181,7 +187,7 @@ open class PokemonBattle(
|
||||
|
||||
fun writeShowdownAction(vararg messages: String) {
|
||||
log(messages.joinToString("\n"))
|
||||
ShowdownService.get().send(battleId, messages.toList().toTypedArray())
|
||||
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
|
||||
}
|
||||
|
||||
fun turn(newTurnNumber: Int) {
|
||||
@@ -443,4 +449,4 @@ open class PokemonBattle(
|
||||
LOGGER.error("Missing interpretation on '{}' action: \nPublic » {}\nPrivate » {}", publicMessage.id, publicMessage.rawMessage, privateMessage.rawMessage)
|
||||
return Text.literal("Missing interpretation on '${publicMessage.id}' action please report to the developers").red()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-11
@@ -11,12 +11,13 @@ package com.cobblemon.mod.common.api.battles.model.actor
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.BallActionResponse
|
||||
import com.cobblemon.mod.common.battles.ForcePassActionResponse
|
||||
import com.cobblemon.mod.common.battles.ShowdownActionRequest
|
||||
import com.cobblemon.mod.common.battles.ShowdownActionResponse
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyPassResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import java.util.UUID
|
||||
@@ -40,7 +41,7 @@ abstract class BattleActor(
|
||||
|
||||
var request: ShowdownActionRequest? = null
|
||||
var responses = mutableListOf<ShowdownActionResponse>()
|
||||
var expectingCaptureActions = 0
|
||||
val expectingPassActions = mutableListOf<ShowdownActionResponse>()
|
||||
var mustChoose = false
|
||||
|
||||
/** For when battles start, it's the number of Pokémon that are still in the process of being sent out (animation wise) */
|
||||
@@ -48,6 +49,16 @@ abstract class BattleActor(
|
||||
|
||||
abstract val type: ActorType
|
||||
|
||||
fun canFitForcedAction() = mustChoose && request?.let { request ->
|
||||
val countMovable = (request.active?.count() ?: 0) - request.forceSwitch.count { it }
|
||||
return@let countMovable > expectingPassActions.size && !battle.ended
|
||||
} ?: false
|
||||
|
||||
fun forceChoose(response: ShowdownActionResponse) {
|
||||
expectingPassActions.add(response)
|
||||
sendUpdate(BattleApplyPassResponsePacket())
|
||||
}
|
||||
|
||||
fun getSide() = if (this in battle.side1.actors) battle.side1 else battle.side2
|
||||
open fun getPlayerUUIDs(): Iterable<UUID> = emptyList()
|
||||
|
||||
@@ -63,7 +74,7 @@ abstract class BattleActor(
|
||||
val requestActive = request.active
|
||||
if (requestActive == null || requestActive.isEmpty() || request.wait) {
|
||||
this.request = null
|
||||
expectingCaptureActions = 0
|
||||
expectingPassActions.clear()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -81,23 +92,24 @@ abstract class BattleActor(
|
||||
|
||||
fun setActionResponses(responses: List<ShowdownActionResponse>) {
|
||||
val request = request ?: return
|
||||
val originalCaptureActions = expectingCaptureActions
|
||||
val originalPassActions = expectingPassActions.toList()
|
||||
responses.forEachIndexed { index, response ->
|
||||
val activeBattlePokemon = activePokemon.let { if (it.size > index) it[index] else return }
|
||||
val showdownMoveSet = request.active?.let { if (it.size > index) it[index] else null }
|
||||
val forceSwitch = request.forceSwitch.let { if (it.size > index) it[index] else false }
|
||||
if (!response.isValid(activeBattlePokemon, showdownMoveSet, forceSwitch)) {
|
||||
expectingCaptureActions = originalCaptureActions
|
||||
expectingPassActions.clear()
|
||||
expectingPassActions.addAll(originalPassActions)
|
||||
throw IllegalActionChoiceException(this, "Invalid action choice for ${activeBattlePokemon.battlePokemon!!.getName().string}: $response")
|
||||
} else if (response is BallActionResponse) {
|
||||
expectingCaptureActions--
|
||||
} else if (response is ForcePassActionResponse) {
|
||||
this.responses.add(expectingPassActions.removeAt(0))
|
||||
} else {
|
||||
this.responses.add(response)
|
||||
}
|
||||
this.responses.add(response)
|
||||
}
|
||||
if (expectingCaptureActions > 0) {
|
||||
if (expectingPassActions.size > 0) {
|
||||
throw IllegalActionChoiceException(this, "Invalid action choice: a capture was expected. Are you hacking me?")
|
||||
}
|
||||
expectingCaptureActions = originalCaptureActions
|
||||
mustChoose = false
|
||||
battle.checkForInputDispatch()
|
||||
}
|
||||
@@ -113,7 +125,7 @@ abstract class BattleActor(
|
||||
}
|
||||
responses.clear()
|
||||
request = null
|
||||
expectingCaptureActions = 0
|
||||
expectingPassActions.clear()
|
||||
battle.writeShowdownAction(">$showdownId ${showdownMessages.joinToString()}")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.callback
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.InBattleMove
|
||||
import com.cobblemon.mod.common.net.IntSize
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.util.readSizedInt
|
||||
import com.cobblemon.mod.common.util.writeSizedInt
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
|
||||
/**
|
||||
* Used for opening move selection screens for players and handling their choice. Currently
|
||||
* only supports up to 4 moves to select from.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 1st, 2023
|
||||
*/
|
||||
object MoveSelectCallbacks {
|
||||
val callbacks = mutableMapOf<UUID, MoveSelectCallback>()
|
||||
|
||||
@JvmOverloads
|
||||
fun create(
|
||||
player: ServerPlayerEntity,
|
||||
title: Text = "".text(),
|
||||
possibleMoves: List<MoveSelectDTO>,
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (ServerPlayerEntity, index: Int, MoveSelectDTO) -> Unit
|
||||
) {
|
||||
val callback = MoveSelectCallback(
|
||||
shownMoves = possibleMoves,
|
||||
cancel = cancel,
|
||||
handler = handler
|
||||
)
|
||||
|
||||
callbacks[player.uuid] = callback
|
||||
|
||||
player.sendPacket(OpenMoveCallbackPacket(callback.uuid, title.copy(), possibleMoves))
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun create(
|
||||
player: ServerPlayerEntity,
|
||||
moves: List<Move>,
|
||||
canSelect: (Move) -> Boolean = { true },
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (Move) -> Unit
|
||||
) = create(
|
||||
player = player,
|
||||
possibleMoves = moves.map { battleMove -> MoveSelectDTO(battleMove).also { it.enabled = canSelect(battleMove) } },
|
||||
cancel = cancel,
|
||||
handler = { _, index, _ -> handler(moves[index]) }
|
||||
)
|
||||
|
||||
@JvmOverloads
|
||||
fun createBattleSelect(
|
||||
player: ServerPlayerEntity,
|
||||
moves: List<InBattleMove>,
|
||||
canSelect: (InBattleMove) -> Boolean = { true },
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (InBattleMove) -> Unit
|
||||
) = create(
|
||||
player = player,
|
||||
possibleMoves = moves.map { battleMove -> MoveSelectDTO(battleMove).also { it.enabled = canSelect(battleMove) } },
|
||||
cancel = cancel,
|
||||
handler = { _, index, _ -> handler(moves[index]) }
|
||||
)
|
||||
|
||||
fun handleCancelled(player: ServerPlayerEntity, uuid: UUID) {
|
||||
val callback = callbacks[player.uuid] ?: return
|
||||
if (callback.uuid != uuid) {
|
||||
return
|
||||
}
|
||||
callbacks.remove(player.uuid)
|
||||
callback.cancel(player)
|
||||
}
|
||||
|
||||
fun handleCallback(player: ServerPlayerEntity, uuid: UUID, index: Int) {
|
||||
val callback = callbacks[player.uuid] ?: return
|
||||
callbacks.remove(player.uuid)
|
||||
if (callback.uuid != uuid) {
|
||||
Cobblemon.LOGGER.warn("A move select callback ran but with a mismatching UUID from ${player.gameProfile.name}. Hacking attempts?")
|
||||
} else if (index >= callback.shownMoves.size) {
|
||||
Cobblemon.LOGGER.warn("${player.gameProfile.name} used move select callback with an index that was out of bounds. Hacking attempts? Tried $index, possible size was ${callback.shownMoves.size}")
|
||||
} else if (!callback.shownMoves[index].enabled) {
|
||||
Cobblemon.LOGGER.warn("${player.gameProfile.name} used move select callback with a move that is not enabled. Hacking attempts?")
|
||||
} else {
|
||||
callback.handler(player, index, callback.shownMoves[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MoveSelectCallback(
|
||||
val uuid: UUID = UUID.randomUUID(),
|
||||
val shownMoves: List<MoveSelectDTO>,
|
||||
val cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
val handler: (ServerPlayerEntity, Int, MoveSelectDTO) -> Unit
|
||||
)
|
||||
|
||||
class MoveSelectDTO(val moveTemplate: MoveTemplate, var enabled: Boolean, val pp: Int = -1, val ppMax: Int = -1) {
|
||||
@JvmOverloads
|
||||
constructor(move: Move, enabled: Boolean = true): this(moveTemplate = move.template, enabled = enabled, pp = move.currentPp, ppMax = move.maxPp)
|
||||
@JvmOverloads
|
||||
constructor(move: InBattleMove, enabled: Boolean = true): this(moveTemplate = Moves.getByNameOrDummy(move.move), enabled = enabled, pp = move.pp, ppMax = move.maxpp)
|
||||
constructor(buffer: PacketByteBuf): this(
|
||||
moveTemplate = Moves.getByNameOrDummy(buffer.readString()),
|
||||
enabled = buffer.readBoolean(),
|
||||
pp = buffer.readSizedInt(IntSize.BYTE),
|
||||
ppMax = buffer.readSizedInt(IntSize.BYTE)
|
||||
)
|
||||
|
||||
fun writeToBuffer(buffer: PacketByteBuf) {
|
||||
buffer.writeString(moveTemplate.name)
|
||||
buffer.writeBoolean(enabled)
|
||||
buffer.writeSizedInt(IntSize.BYTE, pp)
|
||||
buffer.writeSizedInt(IntSize.BYTE, ppMax)
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.callback
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import java.util.UUID
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
|
||||
/**
|
||||
* Used for opening a party select screen for players which routes to move selection when they choose a Pokémon.
|
||||
* You can do the same sort of thing with combined [MoveSelectCallbacks] and [PartySelectCallbacks] but it isn't
|
||||
* as smooth an experience.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 29th, 2023
|
||||
*/
|
||||
object PartyMoveSelectCallbacks {
|
||||
val callbacks = mutableMapOf<UUID, PartyMoveSelectCallback>()
|
||||
|
||||
@JvmOverloads
|
||||
fun create(
|
||||
player: ServerPlayerEntity,
|
||||
pokemon: List<Pair<PartySelectPokemonDTO, List<MoveSelectDTO>>>,
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (ServerPlayerEntity, pokemonIndex: Int, PartySelectPokemonDTO, moveIndex: Int, MoveSelectDTO) -> Unit
|
||||
) {
|
||||
val callback = PartyMoveSelectCallback(
|
||||
pokemon = pokemon,
|
||||
cancel = cancel,
|
||||
handler = handler
|
||||
)
|
||||
callbacks[player.uuid] = callback
|
||||
player.sendPacket(OpenPartyMoveCallbackPacket(callback.uuid, callback.pokemon))
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun createFromPokemon(
|
||||
player: ServerPlayerEntity,
|
||||
pokemon: List<Pokemon>,
|
||||
moves: (Pokemon) -> List<Move> = { it.moveSet.getMoves() },
|
||||
canSelectPokemon: (Pokemon) -> Boolean = { true },
|
||||
canSelectMove: (Pokemon, Move) -> Boolean = { _, _ -> true },
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (Pokemon, Move) -> Unit
|
||||
) {
|
||||
val pokemonList = mutableListOf<Pair<PartySelectPokemonDTO, List<MoveSelectDTO>>>()
|
||||
for (pk in pokemon) {
|
||||
val enabled = canSelectPokemon(pk)
|
||||
val moveList = moves(pk).map { MoveSelectDTO(it, enabled = canSelectMove(pk, it)) }
|
||||
pokemonList.add(PartySelectPokemonDTO(pk, enabled) to moveList)
|
||||
}
|
||||
|
||||
create(
|
||||
player = player,
|
||||
pokemon = pokemonList,
|
||||
cancel = cancel,
|
||||
handler = { _, pkIndex, _, moveIndex, _ ->
|
||||
handler(pokemon[pkIndex], pokemon[pkIndex].moveSet[moveIndex] ?: return@create)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun handleCancelled(player: ServerPlayerEntity, uuid: UUID) {
|
||||
val callback = callbacks[player.uuid] ?: return
|
||||
if (callback.uuid != uuid) {
|
||||
return
|
||||
}
|
||||
callbacks.remove(player.uuid)
|
||||
callback.cancel(player)
|
||||
}
|
||||
|
||||
fun handleCallback(player: ServerPlayerEntity, uuid: UUID, pokemonIndex: Int, moveIndex: Int) {
|
||||
val callback = callbacks[player.uuid] ?: return
|
||||
callbacks.remove(player.uuid)
|
||||
if (callback.uuid != uuid) {
|
||||
return Cobblemon.LOGGER.warn("A party move select callback ran but with a mismatching UUID from ${player.gameProfile.name}. Hacking attempts?")
|
||||
} else if (pokemonIndex >= callback.pokemon.size) {
|
||||
return Cobblemon.LOGGER.warn("${player.gameProfile.name} used party move select callback with a Pokémon index that was out of bounds. Hacking attempts? Tried $pokemonIndex, possible size was ${callback.pokemon.size}")
|
||||
}
|
||||
|
||||
val (pokemon, moves) = callback.pokemon[pokemonIndex]
|
||||
|
||||
if (!pokemon.enabled) {
|
||||
return Cobblemon.LOGGER.warn("${player.gameProfile.name} used party move select callback with a Pokémon that is not enabled. Hacking attempts?")
|
||||
} else if (moveIndex >= moves.size) {
|
||||
return Cobblemon.LOGGER.warn("${player.gameProfile.name} used party move select callback with a move index that was out of bounds. Hacking attempts? Tried $pokemonIndex-$moveIndex, possible size was ${moves.size}")
|
||||
}
|
||||
|
||||
val move = moves[moveIndex]
|
||||
|
||||
if (!move.enabled) {
|
||||
Cobblemon.LOGGER.warn("${player.gameProfile.name} used party move select callback with a move that is not enabled. Hacking attempts?")
|
||||
} else {
|
||||
callback.handler(player, pokemonIndex, pokemon, moveIndex, move)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PartyMoveSelectCallback(
|
||||
val uuid: UUID = UUID.randomUUID(),
|
||||
val pokemon: List<Pair<PartySelectPokemonDTO, List<MoveSelectDTO>>>,
|
||||
val cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
val handler: (ServerPlayerEntity, pokemonIndex: Int, PartySelectPokemonDTO, moveIndex: Int, MoveSelectDTO) -> Unit
|
||||
)
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.callback
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonPropertyExtractor
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import java.util.UUID
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
|
||||
/**
|
||||
* Used for opening party Pokémon selection screens for players and handling their choice. Currently
|
||||
* only supports up to 6 Pokémon to select from.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 1st, 2023
|
||||
*/
|
||||
object PartySelectCallbacks {
|
||||
val callbacks = mutableMapOf<UUID, PartySelectCallback>()
|
||||
|
||||
@JvmOverloads
|
||||
fun create(
|
||||
player: ServerPlayerEntity,
|
||||
title: Text = "".text(),
|
||||
pokemon: List<PartySelectPokemonDTO>,
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (ServerPlayerEntity, index: Int) -> Unit
|
||||
) {
|
||||
val callback = PartySelectCallback(
|
||||
shownPokemon = pokemon,
|
||||
cancel = cancel,
|
||||
handler = handler
|
||||
)
|
||||
|
||||
callbacks[player.uuid] = callback
|
||||
|
||||
player.sendPacket(
|
||||
OpenPartyCallbackPacket(
|
||||
uuid = callback.uuid,
|
||||
title = title.copy(),
|
||||
pokemon = pokemon
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun createBattleSelect(
|
||||
player: ServerPlayerEntity,
|
||||
pokemon: List<BattlePokemon>,
|
||||
canSelect: (BattlePokemon) -> Boolean,
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (BattlePokemon) -> Unit
|
||||
) = create(
|
||||
player = player,
|
||||
cancel = cancel,
|
||||
pokemon = pokemon.map { pk -> PartySelectPokemonDTO(pk.effectedPokemon).also { it.enabled = canSelect(pk) } }
|
||||
) { _, index -> handler(pokemon[index]) }
|
||||
|
||||
@JvmOverloads
|
||||
fun createFromPokemon(
|
||||
player: ServerPlayerEntity,
|
||||
pokemon: List<Pokemon>,
|
||||
canSelect: (Pokemon) -> Boolean,
|
||||
cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
handler: (Pokemon) -> Unit
|
||||
) = create(
|
||||
player = player,
|
||||
cancel = cancel,
|
||||
pokemon = pokemon.map { pk -> PartySelectPokemonDTO(pk).also { it.enabled = canSelect(pk) } }
|
||||
) { _, index -> handler(pokemon[index]) }
|
||||
|
||||
fun handleCancelled(player: ServerPlayerEntity, uuid: UUID) {
|
||||
val callback = callbacks[player.uuid] ?: return
|
||||
if (callback.uuid != uuid) {
|
||||
return
|
||||
}
|
||||
callbacks.remove(player.uuid)
|
||||
callback.cancel(player)
|
||||
}
|
||||
|
||||
fun handleCallback(player: ServerPlayerEntity, uuid: UUID, index: Int) {
|
||||
val callback = callbacks[player.uuid] ?: return
|
||||
callbacks.remove(player.uuid)
|
||||
if (callback.uuid != uuid) {
|
||||
Cobblemon.LOGGER.warn("A party select callback ran but with a mismatching UUID from ${player.gameProfile.name}. Hacking attempts?")
|
||||
} else if (index >= callback.shownPokemon.size) {
|
||||
Cobblemon.LOGGER.warn("${player.gameProfile.name} used party select callback with an out of bounds index. Hacking attempts? Tried $index, Pokémon list size was ${callback.shownPokemon.size}")
|
||||
} else if (!callback.shownPokemon[index].enabled) {
|
||||
Cobblemon.LOGGER.warn("${player.gameProfile.name} used party select callback with a Pokémon that is not enabled. Hacking attempts?")
|
||||
} else {
|
||||
callback.handler(player, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PartySelectCallback(
|
||||
val uuid: UUID = UUID.randomUUID(),
|
||||
val shownPokemon: List<PartySelectPokemonDTO>,
|
||||
val cancel: (ServerPlayerEntity) -> Unit = {},
|
||||
val handler: (ServerPlayerEntity, index: Int) -> Unit
|
||||
)
|
||||
|
||||
open class PartySelectPokemonDTO(
|
||||
val pokemonProperties: PokemonProperties,
|
||||
val heldItem: ItemStack = ItemStack.EMPTY,
|
||||
var hpRatio: Float = 1F,
|
||||
var enabled: Boolean
|
||||
) {
|
||||
@JvmOverloads
|
||||
constructor(pokemon: Pokemon, enabled: Boolean = true): this(
|
||||
pokemonProperties = pokemon.createPokemonProperties(
|
||||
PokemonPropertyExtractor.ASPECTS,
|
||||
PokemonPropertyExtractor.SPECIES,
|
||||
PokemonPropertyExtractor.LEVEL,
|
||||
PokemonPropertyExtractor.NICKNAME,
|
||||
PokemonPropertyExtractor.POKEBALL,
|
||||
PokemonPropertyExtractor.STATUS
|
||||
),
|
||||
heldItem = pokemon.heldItemNoCopy(),
|
||||
hpRatio = pokemon.currentHealth / pokemon.hp.toFloat(),
|
||||
enabled = enabled
|
||||
)
|
||||
|
||||
constructor(buffer: PacketByteBuf): this(
|
||||
pokemonProperties = PokemonProperties().loadFromNBT(buffer.readNbt() as NbtCompound),
|
||||
heldItem = buffer.readItemStack(),
|
||||
hpRatio = buffer.readFloat(),
|
||||
enabled = buffer.readBoolean()
|
||||
)
|
||||
|
||||
fun writeToBuffer(buffer: PacketByteBuf) {
|
||||
buffer.writeNbt(pokemonProperties.saveToNBT())
|
||||
buffer.writeItemStack(heldItem)
|
||||
buffer.writeFloat(hpRatio)
|
||||
buffer.writeBoolean(enabled)
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -11,12 +11,13 @@ package com.cobblemon.mod.common.api.events.world
|
||||
import com.cobblemon.mod.common.api.events.Cancelable
|
||||
import com.cobblemon.mod.common.block.BigRootBlock
|
||||
import com.cobblemon.mod.common.block.EnergyRootBlock
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
/**
|
||||
* Event fired when a [BigRootBlock] attempts to spread. The event includes the original root position
|
||||
* as well as the position of the new root. [isEnergyRoot] controls whether the new root will be an
|
||||
* as well as the position of the new root. [resultingSpread] controls whether the new root will be an
|
||||
* [EnergyRootBlock]. The new root position and whether it is an energy root is mutable.
|
||||
*
|
||||
* Cancelling this event will prevent propagation from occurring.
|
||||
@@ -28,5 +29,5 @@ class BigRootPropagatedEvent(
|
||||
val world: ServerWorld,
|
||||
val pos: BlockPos,
|
||||
var newRootPosition: BlockPos,
|
||||
var isEnergyRoot: Boolean
|
||||
var resultingSpread: BlockState
|
||||
) : Cancelable()
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.item
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.callback.MoveSelectCallbacks
|
||||
import com.cobblemon.mod.common.api.callback.PartyMoveSelectCallbacks
|
||||
import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.battles.BagItemActionResponse
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getBattleState
|
||||
import com.cobblemon.mod.common.util.isHeld
|
||||
import com.cobblemon.mod.common.util.isLookingAt
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.util.TypedActionResult
|
||||
import net.minecraft.util.math.Box
|
||||
|
||||
/**
|
||||
* Interface to make it easier to define items that will, upon use, prompt you to
|
||||
* select a Pokémon and move for the item to be used on. This can be by interacting
|
||||
* with the Pokémon directly (which will skip the Pokémon selection screen) or by
|
||||
* interacting with air which will first show a Pokémon selection screen.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 29th, 2023
|
||||
*/
|
||||
interface PokemonAndMoveSelectingItem {
|
||||
fun use(player: ServerPlayerEntity, stack: ItemStack): TypedActionResult<ItemStack>? {
|
||||
val entity = player.world
|
||||
.getOtherEntities(player, Box.of(player.pos, 16.0, 16.0, 16.0))
|
||||
.filter { player.isLookingAt(it, stepDistance = 0.1F) }
|
||||
.minByOrNull { it.distanceTo(player) } as? PokemonEntity?
|
||||
|
||||
player.getBattleState()?.let { (_, actor) ->
|
||||
if (bagItem == null) {
|
||||
return TypedActionResult.fail(stack)
|
||||
}
|
||||
if (!actor.canFitForcedAction()) {
|
||||
player.sendMessage(battleLang("bagitem.cannot").red())
|
||||
return TypedActionResult.fail(stack)
|
||||
}
|
||||
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
|
||||
if (entity == null) {
|
||||
return interactGeneralBattle(player, stack, actor)
|
||||
} else if (battlePokemon != null) {
|
||||
return interactWithSpecificBattle(player, stack, battlePokemon)
|
||||
}
|
||||
} ?: run {
|
||||
if (entity != null) {
|
||||
val pokemon = entity.pokemon
|
||||
if (entity.ownerUuid == player.uuid) {
|
||||
return interactWithSpecific(player, stack, pokemon)
|
||||
} else {
|
||||
return TypedActionResult.fail(stack)
|
||||
}
|
||||
} else {
|
||||
return interactGeneral(player, stack)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
val bagItem: BagItem?
|
||||
fun applyToPokemon(player: ServerPlayerEntity, stack: ItemStack, pokemon: Pokemon, move: Move)
|
||||
|
||||
fun applyToBattlePokemon(player: ServerPlayerEntity, stack: ItemStack, battlePokemon: BattlePokemon, move: Move) {
|
||||
val battle = battlePokemon.actor.battle
|
||||
val bagItem = bagItem
|
||||
if (!battlePokemon.actor.canFitForcedAction()) {
|
||||
player.sendMessage(battleLang("bagitem.cannot").red())
|
||||
} else if (!bagItem!!.canUse(battle, battlePokemon)) {
|
||||
player.sendMessage(battleLang("bagitem.invalid").red())
|
||||
} else {
|
||||
battlePokemon.actor.forceChoose(BagItemActionResponse(bagItem, battlePokemon, move.template.name))
|
||||
if (!player.isCreative) {
|
||||
stack.decrement(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun canUseOnPokemon(pokemon: Pokemon): Boolean
|
||||
fun canUseOnBattlePokemon(battlePokemon: BattlePokemon): Boolean = bagItem!!.canUse(battlePokemon.actor.battle, battlePokemon)
|
||||
fun canUseOnMove(pokemon: Pokemon, move: Move): Boolean = canUseOnMove(move)
|
||||
fun canUseOnMove(move: Move): Boolean
|
||||
|
||||
fun interactWithSpecific(player: ServerPlayerEntity, stack: ItemStack, pokemon: Pokemon): TypedActionResult<ItemStack>? {
|
||||
MoveSelectCallbacks.create(
|
||||
player = player,
|
||||
moves = pokemon.moveSet.toList(),
|
||||
canSelect = ::canUseOnMove,
|
||||
handler = { move -> if (stack.isHeld(player)) applyToPokemon(player, stack, pokemon, move) }
|
||||
)
|
||||
return TypedActionResult.success(stack)
|
||||
}
|
||||
|
||||
fun interactWithSpecificBattle(player: ServerPlayerEntity, stack: ItemStack, battlePokemon: BattlePokemon): TypedActionResult<ItemStack>? {
|
||||
return if (canUseOnBattlePokemon(battlePokemon)) {
|
||||
MoveSelectCallbacks.create(
|
||||
player = player,
|
||||
moves = battlePokemon.moveSet.getMoves(),
|
||||
canSelect = ::canUseOnMove,
|
||||
handler = { applyToBattlePokemon(player, stack, battlePokemon, it) }
|
||||
)
|
||||
TypedActionResult.success(stack)
|
||||
} else {
|
||||
player.sendMessage(battleLang("bagitem.invalid").red())
|
||||
TypedActionResult.fail(stack)
|
||||
}
|
||||
}
|
||||
|
||||
fun interactGeneral(player: ServerPlayerEntity, stack: ItemStack): TypedActionResult<ItemStack>? {
|
||||
PartyMoveSelectCallbacks.createFromPokemon(
|
||||
player = player,
|
||||
pokemon = player.party().toList(),
|
||||
canSelectPokemon = ::canUseOnPokemon,
|
||||
canSelectMove = ::canUseOnMove,
|
||||
handler = { pk, mv -> if (stack.isHeld(player)) applyToPokemon(player, stack, pk, mv) }
|
||||
)
|
||||
|
||||
return TypedActionResult.success(stack)
|
||||
}
|
||||
|
||||
fun interactGeneralBattle(player: ServerPlayerEntity, stack: ItemStack, actor: BattleActor): TypedActionResult<ItemStack>? {
|
||||
PartyMoveSelectCallbacks.createFromPokemon(
|
||||
player = player,
|
||||
pokemon = actor.pokemonList.map { it.effectedPokemon },
|
||||
moves = { pk -> actor.pokemonList.find { it.effectedPokemon == pk }!!.moveSet.getMoves() },
|
||||
canSelectPokemon = { pk -> canUseOnBattlePokemon(actor.pokemonList.find { it.effectedPokemon == pk }!!) },
|
||||
canSelectMove = ::canUseOnMove,
|
||||
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk }!!, mv) }
|
||||
)
|
||||
|
||||
return TypedActionResult.success(stack)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.item
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.callback.PartySelectCallbacks
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.battles.BagItemActionResponse
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getBattleState
|
||||
import com.cobblemon.mod.common.util.isHeld
|
||||
import com.cobblemon.mod.common.util.isLookingAt
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.util.TypedActionResult
|
||||
import net.minecraft.util.math.Box
|
||||
|
||||
/**
|
||||
* Interface to make it easier to define items that will, upon use, prompt you to
|
||||
* select a Pokémon for it to be used on. This can be by interacting with the Pokémon
|
||||
* directly (which will skip the Pokémon selection screen) or by interacting with air
|
||||
* which will first show a Pokémon selection screen.
|
||||
*
|
||||
* Works for battle items too.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 29th, 2023
|
||||
*/
|
||||
interface PokemonSelectingItem {
|
||||
fun use(player: ServerPlayerEntity, stack: ItemStack): TypedActionResult<ItemStack> {
|
||||
val entity = player.world
|
||||
.getOtherEntities(player, Box.of(player.pos, 16.0, 16.0, 16.0))
|
||||
.filter { player.isLookingAt(it, stepDistance = 0.1F) }
|
||||
.minByOrNull { it.distanceTo(player) } as? PokemonEntity?
|
||||
|
||||
player.getBattleState()?.let { (_, actor) ->
|
||||
if (bagItem == null) return TypedActionResult.fail(stack)
|
||||
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
|
||||
|
||||
if (!actor.canFitForcedAction()) {
|
||||
player.sendMessage(battleLang("bagitem.cannot").red())
|
||||
return TypedActionResult.fail(stack)
|
||||
}
|
||||
|
||||
if (entity == null) {
|
||||
return interactGeneralBattle(player, stack, actor)
|
||||
} else if (battlePokemon != null) {
|
||||
return interactWithSpecificBattle(player, stack, battlePokemon)
|
||||
}
|
||||
} ?: run {
|
||||
if (entity != null) {
|
||||
val pokemon = entity.pokemon
|
||||
if (entity.ownerUuid == player.uuid) {
|
||||
return applyToPokemon(player, stack, pokemon) ?: TypedActionResult.pass(stack)
|
||||
} else {
|
||||
return TypedActionResult.fail(stack)
|
||||
}
|
||||
} else {
|
||||
return interactGeneral(player, stack)
|
||||
}
|
||||
}
|
||||
|
||||
return TypedActionResult.pass(stack)
|
||||
}
|
||||
|
||||
val bagItem: BagItem?
|
||||
fun applyToPokemon(player: ServerPlayerEntity, stack: ItemStack, pokemon: Pokemon): TypedActionResult<ItemStack>?
|
||||
|
||||
fun applyToBattlePokemon(player: ServerPlayerEntity, stack: ItemStack, battlePokemon: BattlePokemon) {
|
||||
val battle = battlePokemon.actor.battle
|
||||
val bagItem = bagItem
|
||||
if (!battlePokemon.actor.canFitForcedAction()) {
|
||||
player.sendMessage(battleLang("bagitem.cannot").red())
|
||||
} else if (!bagItem!!.canUse(battle, battlePokemon)) {
|
||||
player.sendMessage(battleLang("bagitem.invalid").red())
|
||||
} else {
|
||||
battlePokemon.actor.forceChoose(BagItemActionResponse(bagItem, battlePokemon))
|
||||
if (!player.isCreative) {
|
||||
stack.decrement(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun canUseOnPokemon(pokemon: Pokemon): Boolean
|
||||
fun canUseOnBattlePokemon(battlePokemon: BattlePokemon): Boolean = bagItem!!.canUse(battlePokemon.actor.battle, battlePokemon)
|
||||
|
||||
fun interactWithSpecificBattle(player: ServerPlayerEntity, stack: ItemStack, battlePokemon: BattlePokemon): TypedActionResult<ItemStack> {
|
||||
return if (canUseOnBattlePokemon(battlePokemon)) {
|
||||
applyToBattlePokemon(player, stack, battlePokemon)
|
||||
TypedActionResult.success(stack)
|
||||
} else {
|
||||
player.sendMessage(battleLang("bagitem.invalid").red())
|
||||
TypedActionResult.fail(stack)
|
||||
}
|
||||
}
|
||||
|
||||
fun interactGeneral(player: ServerPlayerEntity, stack: ItemStack): TypedActionResult<ItemStack> {
|
||||
PartySelectCallbacks.createFromPokemon(
|
||||
player = player,
|
||||
pokemon = player.party().toList(),
|
||||
canSelect = ::canUseOnPokemon,
|
||||
handler = { pk ->
|
||||
if (stack.isHeld(player)) {
|
||||
applyToPokemon(player, stack, pk)
|
||||
if (!player.isCreative) {
|
||||
stack.decrement(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return TypedActionResult.success(stack)
|
||||
}
|
||||
|
||||
fun interactGeneralBattle(player: ServerPlayerEntity, stack: ItemStack, actor: BattleActor): TypedActionResult<ItemStack> {
|
||||
PartySelectCallbacks.createBattleSelect(
|
||||
player = player,
|
||||
pokemon = actor.pokemonList,
|
||||
canSelect = { pk -> canUseOnBattlePokemon(actor.pokemonList.find { it.effectedPokemon == pk.effectedPokemon }!!) },
|
||||
handler = { pk -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk.effectedPokemon }!!) }
|
||||
)
|
||||
|
||||
return TypedActionResult.success(stack)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.readSizedInt
|
||||
import com.cobblemon.mod.common.util.writeSizedInt
|
||||
import com.google.gson.JsonObject
|
||||
import kotlin.math.ceil
|
||||
import kotlin.properties.Delegates
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
@@ -29,17 +30,31 @@ open class Move(
|
||||
currentPp: Int,
|
||||
raisedPpStages: Int = 0
|
||||
) {
|
||||
|
||||
private var emit = true
|
||||
val observable = SimpleObservable<Move>()
|
||||
|
||||
var currentPp: Int by Delegates.observable(currentPp) { _, oldValue, newValue ->
|
||||
if (oldValue != newValue) {
|
||||
observable.emit(this)
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
var raisedPpStages: Int by Delegates.observable(raisedPpStages) { _, oldValue, newValue ->
|
||||
if (oldValue != newValue) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
fun doThenUpdate(action: () -> Unit) {
|
||||
val oldEmit = emit
|
||||
emit = false
|
||||
action()
|
||||
emit = oldEmit
|
||||
update()
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (emit) {
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -71,6 +86,20 @@ open class Move(
|
||||
val maxPp: Int
|
||||
get() = template.pp + raisedPpStages * template.pp / 5
|
||||
|
||||
/** Raises the max PP and rescales the current PP value. Returns false if it was already maxed out. */
|
||||
fun raiseMaxPP(amount: Int): Boolean {
|
||||
val oldPp = maxPp
|
||||
doThenUpdate {
|
||||
val ppRatio = currentPp / maxPp.toFloat()
|
||||
raisedPpStages += amount
|
||||
if (raisedPpStages > 3) {
|
||||
raisedPpStages = 3
|
||||
}
|
||||
currentPp = ceil(ppRatio * maxPp).toInt()
|
||||
}
|
||||
return oldPp != maxPp
|
||||
}
|
||||
|
||||
fun saveToNBT(nbt: NbtCompound): NbtCompound {
|
||||
nbt.putString(DataKeys.POKEMON_MOVESET_MOVENAME, name)
|
||||
nbt.putInt(DataKeys.POKEMON_MOVESET_MOVEPP, currentPp)
|
||||
|
||||
@@ -128,6 +128,7 @@ class MoveSet : Iterable<Move> {
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun doWithoutEmitting(action: () -> Unit) {
|
||||
val previousEmit = emit
|
||||
emit = false
|
||||
|
||||
@@ -38,7 +38,7 @@ object Moves : DataRegistry {
|
||||
override fun reload(manager: ResourceManager) {
|
||||
this.allMoves.clear()
|
||||
this.idMapping.clear()
|
||||
val movesJson = ShowdownService.get().getMoves()
|
||||
val movesJson = ShowdownService.service.getMoves()
|
||||
for (i in 0 until movesJson.size()) {
|
||||
val jsMove = movesJson[i].asJsonObject
|
||||
val id = jsMove.get("id").asString
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.mulch
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.item.MulchItem
|
||||
|
||||
/**
|
||||
* Represents the different types of Mulch implemented in the mod.
|
||||
*
|
||||
*/
|
||||
enum class MulchVariant {
|
||||
|
||||
COARSE,
|
||||
GROWTH,
|
||||
HUMID,
|
||||
LOAMY,
|
||||
PEAT,
|
||||
RICH,
|
||||
SANDY,
|
||||
SURPRISE;
|
||||
|
||||
/**
|
||||
* Resolves the item version of the Mulch.
|
||||
*
|
||||
* @return The [MulchItem] equivalent.
|
||||
*/
|
||||
fun item(): MulchItem = when (this) {
|
||||
COARSE -> CobblemonItems.COARSE_MULCH
|
||||
GROWTH -> CobblemonItems.GROWTH_MULCH
|
||||
HUMID -> CobblemonItems.HUMID_MULCH
|
||||
LOAMY -> CobblemonItems.LOAMY_MULCH
|
||||
PEAT -> CobblemonItems.PEAT_MULCH
|
||||
RICH -> CobblemonItems.RICH_MULCH
|
||||
SANDY -> CobblemonItems.SANDY_MULCH
|
||||
SURPRISE -> CobblemonItems.SURPRISE_MULCH
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.mulch
|
||||
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.random.Random
|
||||
|
||||
/**
|
||||
* Represents something that can have mulch applied to it.
|
||||
* Typically, this will be a block implementation.
|
||||
*/
|
||||
interface Mulchable {
|
||||
|
||||
/**
|
||||
* Checks if mulch can be applied to a specific spot in the world.
|
||||
*
|
||||
* @param world The affected [ServerWorld].
|
||||
* @param pos The [BlockPos] being checked.
|
||||
* @param state The [BlockState] of the block at the [pos] in the [world].
|
||||
* @param variant The [MulchVariant] being applied.
|
||||
* @return If the application is possible.
|
||||
*/
|
||||
fun canHaveMulchApplied(world: ServerWorld, pos: BlockPos, state: BlockState, variant: MulchVariant): Boolean
|
||||
|
||||
/**
|
||||
* Applies the mulch to the specified spot.
|
||||
* This is expected to be invoked if [canHaveMulchApplied] is true.
|
||||
*
|
||||
* @param world The affected [ServerWorld].
|
||||
* @param random The [Random] instance being used in the interaction.
|
||||
* @param pos The [BlockPos] being checked.
|
||||
* @param state The [BlockState] of the block at the [pos] in the [world].
|
||||
* @param variant The [MulchVariant] being applied.
|
||||
*/
|
||||
fun applyMulch(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState, variant: MulchVariant)
|
||||
|
||||
}
|
||||
@@ -13,9 +13,12 @@ import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.*
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.isInt
|
||||
@@ -81,12 +84,13 @@ open class PokemonProperties {
|
||||
props.pokeball = parseIdentifierOfRegistry(keyPairs, listOf("pokeball")) { identifier -> PokeBalls.getPokeBall(identifier)?.name?.toString() }
|
||||
props.nature = parseIdentifierOfRegistry(keyPairs, listOf("nature")) { identifier -> Natures.getNature(identifier)?.name?.toString() }
|
||||
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
|
||||
props.status = parseStringOfRegistry(keyPairs, listOf("status")) { (Statuses.getStatus(it) ?: Statuses.getStatus(it.asIdentifierDefaultingNamespace()))?.showdownName }
|
||||
props.nickname = parseText(keyPairs, listOf("nickname", "nick"))
|
||||
|
||||
val maybeIVs = IVs()
|
||||
val maybeEVs = EVs()
|
||||
Stats.PERMANENT.forEach{ stat ->
|
||||
val statName = stat.displayName.string.lowercase()
|
||||
val statName = stat.toString().lowercase()
|
||||
parseIntProperty(keyPairs, listOf("${statName}_iv"))?.coerceIn(0, IVs.MAX_VALUE)?.let { maybeIVs[stat] = it }
|
||||
parseIntProperty(keyPairs, listOf("${statName}_ev"))?.coerceIn(0, EVs.MAX_STAT_VALUE)?.let { maybeEVs[stat] = it }
|
||||
}
|
||||
@@ -241,6 +245,7 @@ open class PokemonProperties {
|
||||
var nature: String? = null
|
||||
var ability: String? = null
|
||||
var aspects: Set<String> = emptySet()
|
||||
var status: String? = null
|
||||
|
||||
var ivs: IVs? = null
|
||||
var evs: EVs? = null
|
||||
@@ -278,6 +283,7 @@ open class PokemonProperties {
|
||||
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
|
||||
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
|
||||
ability?.let { Abilities.getOrException(it).create(true).let { ability -> pokemon.ability = ability } }
|
||||
status?.let { Statuses.getStatus(it)?.let { pokemon.applyStatus(it as PersistentStatus) } }
|
||||
customProperties.forEach { it.apply(pokemon) }
|
||||
ivs?.let { ivs ->
|
||||
ivs.forEach { stat ->
|
||||
@@ -312,6 +318,7 @@ open class PokemonProperties {
|
||||
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemonEntity.pokemon.caughtBall = pokeball } }
|
||||
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemonEntity.pokemon.nature = nature } }
|
||||
ability?.let { Abilities.getOrException(it).create(true).let { ability -> pokemonEntity.pokemon.ability = ability } }
|
||||
status?.let { Statuses.getStatus(it)?.let { pokemonEntity.pokemon.applyStatus(it as PersistentStatus) } }
|
||||
customProperties.forEach { it.apply(pokemonEntity) }
|
||||
ivs?.let { ivs ->
|
||||
ivs.forEach { stat ->
|
||||
@@ -348,7 +355,8 @@ open class PokemonProperties {
|
||||
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
|
||||
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
|
||||
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
|
||||
ability?.takeIf { it != pokemon.ability.name }?. let { return false }
|
||||
ability?.takeIf { it != pokemon.ability.name }?.let { return false }
|
||||
status?.takeIf { it != pokemon.status?.status?.showdownName }?.let { return false }
|
||||
ivs?.forEach{ stat ->
|
||||
if (stat.value != pokemon.ivs[stat.key]) { return false }
|
||||
}
|
||||
@@ -379,7 +387,8 @@ open class PokemonProperties {
|
||||
friendship?.takeIf { it != pokemonEntity.pokemon.friendship }?.let { return false }
|
||||
pokeball?.takeIf { it != pokemonEntity.pokemon.caughtBall.name.toString() }?.let { return false }
|
||||
nature?.takeIf { it != pokemonEntity.pokemon.nature.name.toString() }?.let { return false }
|
||||
ability?.takeIf { it != pokemonEntity.pokemon.ability.name }?. let { return false }
|
||||
ability?.takeIf { it != pokemonEntity.pokemon.ability.name }?.let { return false }
|
||||
status?.takeIf { it != pokemonEntity.pokemon.status?.status?.showdownName }?.let { return false }
|
||||
ivs?.forEach{ stat ->
|
||||
if (stat.value != pokemonEntity.pokemon.ivs[stat.key]) { return false }
|
||||
}
|
||||
@@ -410,7 +419,8 @@ open class PokemonProperties {
|
||||
friendship?.takeIf { it != properties.friendship }?.let { return false }
|
||||
pokeball?.takeIf { it != properties.pokeball }?.let { return false }
|
||||
nature?.takeIf { it != properties.nature }?.let { return false }
|
||||
ability?.takeIf { it != properties.ability }?. let { return false }
|
||||
ability?.takeIf { it != properties.ability }?.let { return false }
|
||||
status?.takeIf { it != properties.status }?.let { return false }
|
||||
ivs?.let{ ivs ->
|
||||
ivs.forEach{ stat ->
|
||||
//If the potential subset has IV and the main set does not then it cant be a subset
|
||||
@@ -460,6 +470,7 @@ open class PokemonProperties {
|
||||
pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) }
|
||||
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
|
||||
ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) }
|
||||
status?.let { nbt.putString(DataKeys.POKEMON_STATUS_NAME, it) }
|
||||
ivs?.let { nbt.put(DataKeys.POKEMON_IVS, it.saveToNBT(NbtCompound())) }
|
||||
evs?.let { nbt.put(DataKeys.POKEMON_EVS, it.saveToNBT(NbtCompound())) }
|
||||
val custom = NbtList()
|
||||
@@ -480,6 +491,7 @@ open class PokemonProperties {
|
||||
pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null
|
||||
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
|
||||
ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null
|
||||
status = if (tag.contains(DataKeys.POKEMON_STATUS_NAME)) tag.getString(DataKeys.POKEMON_STATUS_NAME) else null
|
||||
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) ivs?.loadFromNBT(tag.getCompound(DataKeys.POKEMON_IVS)) as IVs? else null
|
||||
evs = if (tag.contains(DataKeys.POKEMON_EVS)) evs?.loadFromNBT(tag.getCompound(DataKeys.POKEMON_EVS)) as EVs? else null
|
||||
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, NbtElement.STRING_TYPE.toInt())
|
||||
@@ -502,6 +514,7 @@ open class PokemonProperties {
|
||||
pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) }
|
||||
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
|
||||
ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) }
|
||||
status?.let { json.addProperty(DataKeys.POKEMON_STATUS_NAME, it) }
|
||||
ivs?.let { json.add(DataKeys.POKEMON_IVS, it.saveToJSON(JsonObject())) }
|
||||
evs?.let { json.add(DataKeys.POKEMON_EVS, it.saveToJSON(JsonObject())) }
|
||||
val custom = JsonArray()
|
||||
@@ -523,6 +536,7 @@ open class PokemonProperties {
|
||||
pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString
|
||||
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
|
||||
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
|
||||
status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString
|
||||
ivs?.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_IVS))
|
||||
evs?.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_EVS))
|
||||
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
|
||||
@@ -544,6 +558,7 @@ open class PokemonProperties {
|
||||
pokeball?.let { pieces.add("pokeball=$it") }
|
||||
nature?.let { pieces.add("nature=$it") }
|
||||
ability?.let { pieces.add("ability=$it") }
|
||||
status?.let { pieces.add("status=$it") }
|
||||
ivs?.forEach{ stat ->
|
||||
pieces.add("${stat.key}_iv=${stat.value}")
|
||||
}
|
||||
|
||||
+29
-20
@@ -21,28 +21,37 @@ import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
fun interface PokemonPropertyExtractor {
|
||||
companion object {
|
||||
@JvmField
|
||||
val SPECIES = PokemonPropertyExtractor { pokemon, properties -> properties.species = pokemon.species.resourceIdentifier.toString() }
|
||||
@JvmField
|
||||
val FORM = PokemonPropertyExtractor { pokemon, properties -> properties.form = pokemon.form.formOnlyShowdownId() }
|
||||
@JvmField
|
||||
val SHINY = PokemonPropertyExtractor { pokemon, properties -> properties.shiny = pokemon.shiny }
|
||||
@JvmField
|
||||
val ASPECTS = PokemonPropertyExtractor { pokemon, properties -> properties.aspects = pokemon.aspects }
|
||||
@JvmField
|
||||
val LEVEL = PokemonPropertyExtractor { pokemon, properties -> properties.level = pokemon.level }
|
||||
@JvmField
|
||||
val GENDER = PokemonPropertyExtractor { pokemon, properties -> properties.gender = pokemon.gender }
|
||||
@JvmField
|
||||
val FRIENDSHIP = PokemonPropertyExtractor { pokemon, properties -> properties.friendship = pokemon.friendship }
|
||||
@JvmField
|
||||
val POKEBALL = PokemonPropertyExtractor { pokemon, properties -> properties.pokeball = pokemon.caughtBall.name.toString() }
|
||||
@JvmField
|
||||
val NATURE = PokemonPropertyExtractor { pokemon, properties -> properties.nature = pokemon.nature.name.toString() }
|
||||
@JvmField
|
||||
val ABILITY = PokemonPropertyExtractor { pokemon, properties -> properties.ability = pokemon.ability.name }
|
||||
val ALL = mutableListOf<PokemonPropertyExtractor>()
|
||||
|
||||
@JvmField
|
||||
val ALL = arrayOf(SPECIES, FORM, SHINY, ASPECTS, LEVEL, GENDER, FRIENDSHIP, POKEBALL, NATURE, ABILITY)
|
||||
val SPECIES = add { pokemon, properties -> properties.species = pokemon.species.resourceIdentifier.toString() }
|
||||
@JvmField
|
||||
val FORM = add { pokemon, properties -> properties.form = pokemon.form.formOnlyShowdownId() }
|
||||
@JvmField
|
||||
val SHINY = add { pokemon, properties -> properties.shiny = pokemon.shiny }
|
||||
@JvmField
|
||||
val ASPECTS = add { pokemon, properties -> properties.aspects = pokemon.aspects }
|
||||
@JvmField
|
||||
val LEVEL = add { pokemon, properties -> properties.level = pokemon.level }
|
||||
@JvmField
|
||||
val GENDER = add { pokemon, properties -> properties.gender = pokemon.gender }
|
||||
@JvmField
|
||||
val FRIENDSHIP = add { pokemon, properties -> properties.friendship = pokemon.friendship }
|
||||
@JvmField
|
||||
val POKEBALL = add { pokemon, properties -> properties.pokeball = pokemon.caughtBall.name.toString() }
|
||||
@JvmField
|
||||
val NATURE = add { pokemon, properties -> properties.nature = pokemon.nature.name.toString() }
|
||||
@JvmField
|
||||
val ABILITY = add { pokemon, properties -> properties.ability = pokemon.ability.name }
|
||||
@JvmField
|
||||
val NICKNAME = add { pokemon, properties -> properties.nickname = pokemon.nickname }
|
||||
@JvmField
|
||||
val STATUS = add { pokemon, properties -> properties.status = pokemon.status?.status?.showdownName }
|
||||
|
||||
fun add(extractor: PokemonPropertyExtractor): PokemonPropertyExtractor {
|
||||
ALL.add(extractor)
|
||||
return extractor
|
||||
}
|
||||
}
|
||||
|
||||
operator fun invoke(pokemon: Pokemon, properties: PokemonProperties)
|
||||
|
||||
@@ -115,12 +115,14 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
SpeciesAdditions.observable.subscribe {
|
||||
this.species.forEach(Species::initialize)
|
||||
this.species.forEach(Species::resolveEvolutionMoves)
|
||||
Cobblemon.showdownThread.queue { ShowdownService.get().registerSpecies() }
|
||||
// Reload this with the mod
|
||||
CobblemonEmptyHeldItemManager.load()
|
||||
CobblemonHeldItemManager.load()
|
||||
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
|
||||
this.observable.emit(this)
|
||||
Cobblemon.showdownThread.queue {
|
||||
it.registerSpecies()
|
||||
it.indicateSpeciesInitialized()
|
||||
// Reload this with the mod
|
||||
CobblemonHeldItemManager.load()
|
||||
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
|
||||
this.observable.emit(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ interface Stat {
|
||||
*/
|
||||
val type: Type
|
||||
|
||||
/**
|
||||
* The name used by Showdown to store the stats (attack is 'atk', for example)
|
||||
*/
|
||||
val showdownId: String
|
||||
|
||||
/**
|
||||
* Represents the type of this stat.
|
||||
*/
|
||||
|
||||
@@ -18,16 +18,16 @@ import net.minecraft.util.Identifier
|
||||
* An enumeration of the default implemented [Stat]s.
|
||||
* Contains all the traditional stats in official Pokémon games.
|
||||
*/
|
||||
enum class Stats(override val identifier: Identifier, override val displayName: Text, override val type: Stat.Type) : Stat {
|
||||
enum class Stats(override val identifier: Identifier, override val displayName: Text, override val type: Stat.Type, override val showdownId: String) : Stat {
|
||||
|
||||
HP(cobblemonResource("hp"), lang("stat.hp.name"), Stat.Type.PERMANENT),
|
||||
ATTACK(cobblemonResource("attack"), lang("stat.attack.name"), Stat.Type.PERMANENT),
|
||||
DEFENCE(cobblemonResource("defence"), lang("stat.defence.name"), Stat.Type.PERMANENT),
|
||||
SPECIAL_ATTACK(cobblemonResource("special_attack"), lang("stat.special_attack.name"), Stat.Type.PERMANENT),
|
||||
SPECIAL_DEFENCE(cobblemonResource("special_defence"), lang("stat.special_defence.name"), Stat.Type.PERMANENT),
|
||||
SPEED(cobblemonResource("speed"), lang("stat.speed.name"), Stat.Type.PERMANENT),
|
||||
EVASION(cobblemonResource("evasion"), lang("stat.evasion.name"), Stat.Type.BATTLE_ONLY),
|
||||
ACCURACY(cobblemonResource("accuracy"), lang("stat.accuracy.name"), Stat.Type.BATTLE_ONLY);
|
||||
HP(cobblemonResource("hp"), lang("stat.hp.name"), Stat.Type.PERMANENT, "hp"),
|
||||
ATTACK(cobblemonResource("attack"), lang("stat.attack.name"), Stat.Type.PERMANENT, "atk"),
|
||||
DEFENCE(cobblemonResource("defence"), lang("stat.defence.name"), Stat.Type.PERMANENT, "def"),
|
||||
SPECIAL_ATTACK(cobblemonResource("special_attack"), lang("stat.special_attack.name"), Stat.Type.PERMANENT, "spa"),
|
||||
SPECIAL_DEFENCE(cobblemonResource("special_defence"), lang("stat.special_defence.name"), Stat.Type.PERMANENT, "spd"),
|
||||
SPEED(cobblemonResource("speed"), lang("stat.speed.name"), Stat.Type.PERMANENT, "spe"),
|
||||
EVASION(cobblemonResource("evasion"), lang("stat.evasion.name"), Stat.Type.BATTLE_ONLY, "evasion"),
|
||||
ACCURACY(cobblemonResource("accuracy"), lang("stat.accuracy.name"), Stat.Type.BATTLE_ONLY, "accuracy");
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
+2
-3
@@ -14,9 +14,9 @@ import com.cobblemon.mod.common.api.spawning.mixins.CachedOnlyChunkAccessor
|
||||
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.registry.tag.BlockTags
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Box
|
||||
import net.minecraft.util.math.ChunkSectionPos.getSectionCoord
|
||||
@@ -97,8 +97,7 @@ object CobblemonSpawningProspector : SpawningProspector {
|
||||
skyLevel[x - area.baseX][z - area.baseZ] = y
|
||||
}
|
||||
|
||||
// TODO don't just check solid, have some property somewhere modifiable that excludes some blocks from occluding
|
||||
if (state.isSolid && !state.isIn(BlockTags.LEAVES)) {
|
||||
if (state.fluidState.isEmpty && !state.isIn(CobblemonBlockTags.SEES_SKY)) {
|
||||
canSeeSky = false
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -22,8 +22,6 @@ import net.minecraft.block.Block
|
||||
* @since February 7th, 2022
|
||||
*/
|
||||
abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCondition<T>() {
|
||||
var minWidth: Int? = null
|
||||
var maxWidth: Int? = null
|
||||
var minHeight: Int? = null
|
||||
var maxHeight: Int? = null
|
||||
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
|
||||
@@ -31,10 +29,6 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCond
|
||||
override fun fits(ctx: T, detail: SpawnDetail): Boolean {
|
||||
if (!super.fits(ctx, detail)) {
|
||||
return false
|
||||
} else if (minWidth != null && ctx.width < minWidth!!) {
|
||||
return false
|
||||
} else if (maxWidth != null && ctx.width > maxWidth!!) {
|
||||
return false
|
||||
} else if (minHeight != null && ctx.height < minHeight!!) {
|
||||
return false
|
||||
} else if (maxHeight != null && ctx.height > maxHeight!!) {
|
||||
@@ -49,8 +43,6 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCond
|
||||
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
|
||||
super.copyFrom(other, merger)
|
||||
if (other is AreaTypeSpawningCondition) {
|
||||
merger.mergeSingle(minWidth, other.minWidth)
|
||||
merger.mergeSingle(maxWidth, other.maxWidth)
|
||||
merger.mergeSingle(minHeight, other.minHeight)
|
||||
merger.mergeSingle(maxHeight, other.maxHeight)
|
||||
neededNearbyBlocks = merger.merge(neededNearbyBlocks, other.neededNearbyBlocks)?.toMutableList()
|
||||
|
||||
+4
-2
@@ -12,9 +12,7 @@ import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
|
||||
import com.cobblemon.mod.common.util.Merger
|
||||
import com.cobblemon.mod.common.util.asResource
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
/**
|
||||
* Base type for a spawning condition that applies to some kind of [GroundedSpawningContext]. This
|
||||
@@ -29,6 +27,10 @@ abstract class GroundedTypeSpawningCondition<T : GroundedSpawningContext> : Area
|
||||
override fun fits(ctx: T, detail: SpawnDetail): Boolean {
|
||||
return if (!super.fits(ctx, detail)) {
|
||||
false
|
||||
} else if (minHeight != null && ctx.height < minHeight!!) {
|
||||
return false
|
||||
} else if (maxHeight != null && ctx.height > maxHeight!!) {
|
||||
return false
|
||||
} else !(neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlock.block, ctx.blockRegistry) })
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -30,6 +30,10 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
|
||||
override fun fits(ctx: T, detail: SpawnDetail): Boolean {
|
||||
return if (!super.fits(ctx, detail)) {
|
||||
false
|
||||
} else if (minHeight != null && ctx.height < minHeight!!) {
|
||||
return false
|
||||
} else if (maxHeight != null && ctx.height > maxHeight!!) {
|
||||
return false
|
||||
} else if (minDepth != null && ctx.depth < minDepth!!) {
|
||||
false
|
||||
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
|
||||
|
||||
+43
-4
@@ -10,13 +10,14 @@ package com.cobblemon.mod.common.api.spawning.context
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.SpawnCause
|
||||
import com.cobblemon.mod.common.api.spawning.WorldSlice
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.ChunkPos
|
||||
import net.minecraft.world.World
|
||||
|
||||
/**
|
||||
* A [SpawningContext] that is for a particular area, and therefore has spatial properties.
|
||||
@@ -31,8 +32,6 @@ open class AreaSpawningContext(
|
||||
override val light: Int,
|
||||
override val canSeeSky: Boolean,
|
||||
override val influences: MutableList<SpawningInfluence>,
|
||||
/** Space horizontally (diameter) */
|
||||
val width: Int,
|
||||
/** Space above. */
|
||||
val height: Int,
|
||||
val nearbyBlocks: List<BlockState>,
|
||||
@@ -43,4 +42,44 @@ open class AreaSpawningContext(
|
||||
override fun getStructureCache(pos: BlockPos): StructureChunkCache {
|
||||
return slice.getStructureCache(pos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given block can be occupied by a spawn in this context.
|
||||
* This is not considering the provided state as what the entity would be on top of,
|
||||
* but rather the space its hitbox would fill.
|
||||
*/
|
||||
open fun isSafeSpace(world: ServerWorld, pos: BlockPos, state: BlockState): Boolean = !state.isFullCube(world, pos)
|
||||
|
||||
override fun postFilter(detail: SpawnDetail): Boolean {
|
||||
if (!super.postFilter(detail)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (detail.width > 1 || detail.height > 1) {
|
||||
val sizeX = detail.width.takeIf { it > 0 } ?: 1
|
||||
val sizeY = detail.height.takeIf { it > 0 } ?: 1
|
||||
|
||||
val minX = floor(position.x + 0.5 - (sizeX - 1) / 2F).toInt() - 1
|
||||
val maxX = ceil(position.x + 0.5 + (sizeX + 1) / 2F).toInt() + 1
|
||||
|
||||
val maxY = ceil(position.y + (sizeY + 1) / 2F).toInt() + 1
|
||||
|
||||
val minZ = floor(position.z + 0.5 - (sizeX - 1) / 2F).toInt() - 1
|
||||
val maxZ = ceil(position.z + 0.5 + (sizeX + 1) / 2F).toInt() + 1
|
||||
|
||||
val mutable = BlockPos.Mutable()
|
||||
for (x in minX until maxX) {
|
||||
for (y in (position.y + 1)..maxY) {
|
||||
for (z in minZ until maxZ) {
|
||||
val state = world.getBlockState(mutable.set(x, y, z))
|
||||
if (!isSafeSpace(world, mutable, state)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+10
-11
@@ -12,9 +12,9 @@ import com.cobblemon.mod.common.api.spawning.SpawnCause
|
||||
import com.cobblemon.mod.common.api.spawning.WorldSlice
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.registry.tag.FluidTags
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
|
||||
/**
|
||||
* A type of area based spawning context with a floor.
|
||||
@@ -29,11 +29,10 @@ abstract class FlooredSpawningContext(
|
||||
light: Int,
|
||||
canSeeSky: Boolean,
|
||||
influences: MutableList<SpawningInfluence>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : AreaSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice) {
|
||||
) : AreaSpawningContext(cause, world, position, light, canSeeSky, influences, height, nearbyBlocks, slice) {
|
||||
/** The block that the spawning is occurring on. */
|
||||
val baseBlock = slice.getBlockState(position.x, position.y, position.z)
|
||||
}
|
||||
@@ -51,11 +50,10 @@ open class GroundedSpawningContext(
|
||||
light: Int,
|
||||
canSeeSky: Boolean,
|
||||
influences: MutableList<SpawningInfluence>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice)
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, height, nearbyBlocks, slice)
|
||||
|
||||
/**
|
||||
* A spawning context that occurs at the bottom of a body of water.
|
||||
@@ -70,11 +68,12 @@ open class SeafloorSpawningContext(
|
||||
light: Int,
|
||||
canSeeSky: Boolean,
|
||||
influences: MutableList<SpawningInfluence>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice)
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, height, nearbyBlocks, slice) {
|
||||
override fun isSafeSpace(world: ServerWorld, pos: BlockPos, state: BlockState) = state.fluidState.isIn(FluidTags.WATER)
|
||||
}
|
||||
|
||||
/**
|
||||
* A spawning context that occurs at the bottom of bodies of lava.
|
||||
@@ -89,11 +88,12 @@ open class LavafloorSpawningContext(
|
||||
light: Int,
|
||||
canSeeSky: Boolean,
|
||||
influences: MutableList<SpawningInfluence>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice)
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, height, nearbyBlocks, slice) {
|
||||
override fun isSafeSpace(world: ServerWorld, pos: BlockPos, state: BlockState) = state.fluidState.isIn(FluidTags.LAVA)
|
||||
}
|
||||
|
||||
open class SurfaceSpawningContext(
|
||||
cause: SpawnCause,
|
||||
@@ -102,9 +102,8 @@ open class SurfaceSpawningContext(
|
||||
light: Int,
|
||||
canSeeSky: Boolean,
|
||||
influences: MutableList<SpawningInfluence>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
val depth: Int,
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice)
|
||||
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, height, nearbyBlocks, slice)
|
||||
+7
-1
@@ -24,7 +24,6 @@ import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.ChunkPos
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.biome.Biome
|
||||
import net.minecraft.world.gen.StructureAccessor
|
||||
import net.minecraft.world.gen.structure.Structure
|
||||
@@ -142,6 +141,13 @@ abstract class SpawningContext {
|
||||
/** Returns true if none of the influences.affectSpawnable return false */
|
||||
return influences.none { !it.affectSpawnable(detail, this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a spawning detail by some extra, more expensive condition defined by the context itself.
|
||||
* @return true if the [SpawnDetail] is acceptable by the context's own logic.
|
||||
*/
|
||||
open fun postFilter(detail: SpawnDetail): Boolean = true
|
||||
|
||||
open fun afterSpawn(entity: Entity) {
|
||||
influences.forEach { it.affectSpawn(entity) }
|
||||
}
|
||||
|
||||
+3
-3
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
|
||||
/**
|
||||
* A type of area based spawning context with a fluid base block.
|
||||
@@ -29,11 +28,12 @@ open class SubmergedSpawningContext(
|
||||
light: Int,
|
||||
canSeeSky: Boolean,
|
||||
influences: MutableList<SpawningInfluence>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
val depth: Int,
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : AreaSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice) {
|
||||
) : AreaSpawningContext(cause, world, position, light, canSeeSky, influences, height, nearbyBlocks, slice) {
|
||||
val fluid = slice.getBlockState(position.x, position.y, position.z).fluidState
|
||||
|
||||
override fun isSafeSpace(world: ServerWorld, pos: BlockPos, state: BlockState) = state.fluidState.fluid == fluid.fluid
|
||||
}
|
||||
-4
@@ -61,7 +61,6 @@ object GroundedSpawningContextCalculator : FlooredSpawningContextCalculator<Grou
|
||||
light = getLight(input),
|
||||
canSeeSky = getCanSeeSky(input),
|
||||
influences = input.spawner.copyInfluences(),
|
||||
width = getHorizontalSpace(input, surroundingCondition, config.maxHorizontalSpace, offsetY = 1),
|
||||
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
|
||||
slice = input.slice,
|
||||
nearbyBlocks = getNearbyBlocks(input)
|
||||
@@ -88,7 +87,6 @@ object SeafloorSpawningContextCalculator : FlooredSpawningContextCalculator<Seaf
|
||||
light = getLight(input),
|
||||
canSeeSky = getCanSeeSky(input),
|
||||
influences = input.spawner.copyInfluences(),
|
||||
width = getHorizontalSpace(input, surroundingCondition, config.maxHorizontalSpace, offsetY = 1),
|
||||
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
|
||||
slice = input.slice,
|
||||
nearbyBlocks = getNearbyBlocks(input)
|
||||
@@ -115,7 +113,6 @@ object LavafloorSpawningContextCalculator : FlooredSpawningContextCalculator<Lav
|
||||
light = getLight(input),
|
||||
canSeeSky = getCanSeeSky(input),
|
||||
influences = input.spawner.copyInfluences(),
|
||||
width = getHorizontalSpace(input, surroundingCondition, config.maxHorizontalSpace, offsetY = 1),
|
||||
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
|
||||
slice = input.slice,
|
||||
nearbyBlocks = getNearbyBlocks(input)
|
||||
@@ -143,7 +140,6 @@ object SurfaceSpawningContextCalculator : FlooredSpawningContextCalculator<Surfa
|
||||
light = getLight(input),
|
||||
canSeeSky = getCanSeeSky(input),
|
||||
influences = input.spawner.copyInfluences(),
|
||||
width = getHorizontalSpace(input, surroundingCondition, config.maxHorizontalSpace, offsetY = 1),
|
||||
height = getHeight(input, surroundingCondition, config.maxVerticalSpace / 2, offsetY = 1),
|
||||
depth = getDepth(input, baseCondition, config.maxVerticalSpace / 2),
|
||||
slice = input.slice,
|
||||
|
||||
-1
@@ -47,7 +47,6 @@ object SubmergedSpawningContextCalculator : AreaSpawningContextCalculator<Submer
|
||||
light = getLight(input),
|
||||
canSeeSky = getCanSeeSky(input),
|
||||
influences = input.spawner.copyInfluences(),
|
||||
width = getHorizontalSpace(input, fluidCondition, config.maxHorizontalSpace),
|
||||
height = getHeight(input, fluidCondition, ceil(config.maxVerticalSpace / 2F)),
|
||||
depth = getDepth(input, fluidCondition, ceil(config.maxVerticalSpace / 2F)),
|
||||
slice = input.slice,
|
||||
|
||||
+14
@@ -15,9 +15,11 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlin.math.ceil
|
||||
import net.minecraft.text.MutableText
|
||||
|
||||
/**
|
||||
@@ -38,6 +40,10 @@ class PokemonSpawnDetail : SpawnDetail() {
|
||||
val drops: DropTable? = null
|
||||
val heldItems: MutableList<PossibleHeldItem>? = null
|
||||
|
||||
private val pokemonExample: Pokemon by lazy { pokemon.create() }
|
||||
|
||||
// Calculate the size based off the hitbox unless it's been explicitly set
|
||||
|
||||
/* todo breadcrumbing, ai */
|
||||
|
||||
|
||||
@@ -68,6 +74,14 @@ class PokemonSpawnDetail : SpawnDetail() {
|
||||
species.secondaryType?.let { listOf(species.primaryType.name.lowercase(), it.name.lowercase()) }
|
||||
?: listOf(species.primaryType.name.lowercase())
|
||||
)
|
||||
|
||||
if (height == -1) {
|
||||
height = ceil(pokemonExample.form.hitbox.height * pokemonExample.form.baseScale).toInt()
|
||||
}
|
||||
|
||||
if (width == -1) {
|
||||
width = ceil(pokemonExample.form.hitbox.width * pokemonExample.form.baseScale).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ abstract class SpawnDetail : ModDependant {
|
||||
var anticonditions = mutableListOf<SpawningCondition<*>>()
|
||||
var compositeCondition: CompositeSpawningCondition? = null
|
||||
var weightMultipliers = mutableListOf<WeightMultiplier>()
|
||||
var width = -1
|
||||
var height = -1
|
||||
|
||||
var weight = -1F
|
||||
var percentage = -1F
|
||||
@@ -63,7 +65,10 @@ abstract class SpawnDetail : ModDependant {
|
||||
return false
|
||||
} else if (compositeCondition?.satisfiedBy(ctx, this) == false) {
|
||||
return false
|
||||
} else if (!ctx.postFilter(this)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -21,7 +21,7 @@ import kotlin.reflect.full.createInstance
|
||||
* A spawning selector that compiles a distinct list of all spawn details that
|
||||
* are possible across the context list, chooses which context type to spawn based
|
||||
* on a weighted selection of the number of spawns possible in it, performs a weighted
|
||||
* selection of them of spawns in that context type, and then chooses which of its contexts
|
||||
* selection of spawns in that context type, and then chooses which of its contexts
|
||||
* to spawn them on based on their context-adjusted weights.
|
||||
*
|
||||
* The goal of this algorithm is to be kinder to spawns that are only possible
|
||||
@@ -78,11 +78,12 @@ open class FlatContextWeightedSelector : SpawningSelector {
|
||||
|
||||
val possible = spawner.getMatchingSpawns(ctx)
|
||||
if (possible.isNotEmpty()) {
|
||||
|
||||
val contextSelectionData = contextTypesToSpawns.getOrPut(contextType) { ContextSelectionData(mutableMapOf(), 0F) }
|
||||
|
||||
possible.forEach {
|
||||
if (it.percentage > 0) {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this context
|
||||
// type, otherwise the percentage will get amplified for every context the thing was possible,
|
||||
// completely ruining the point of this pre-selection percentage.
|
||||
if (it.percentage > 0 && !contextSelectionData.spawnsToContexts.containsKey(it)) {
|
||||
contextSelectionData.percentSum += it.percentage
|
||||
}
|
||||
|
||||
@@ -107,8 +108,8 @@ open class FlatContextWeightedSelector : SpawningSelector {
|
||||
if (selectionData.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Which context should we use?
|
||||
|
||||
// Which context type should we use?
|
||||
val contextSelectionData = selectionData.entries.toList().weightedSelection { getWeight(it.key) * it.value.size }!!.value
|
||||
|
||||
val spawnsToContexts = contextSelectionData.spawnsToContexts
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.spawning.selection
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
|
||||
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A spawning selector that compiles a distinct list of all spawn details that
|
||||
* are possible across any context type and performs a weighted selection of spawns.
|
||||
*
|
||||
* The goal of this algorithm is to be kinder to spawns that are only possible
|
||||
* in very specific locational conditions or context types by not letting the scarcity
|
||||
* of suitable locations hurt its chances of spawning. It differs from the
|
||||
* [FlatContextWeightedSelector] because here a spawning context type only appearing once
|
||||
* will not make it any less likely to be selected.
|
||||
*
|
||||
* When choosing which context a specific spawn detail will spawn at once that spawn detail
|
||||
* has been chosen, it does a flat random across the possible contexts.
|
||||
*
|
||||
* At a glance:
|
||||
* - Spawn detail selection is flat across context quantity
|
||||
* - The popularity of specific context types has no bearing on the spawn chance.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 10th, 2022
|
||||
*/
|
||||
open class FlatSelector : SpawningSelector {
|
||||
open fun getWeight(contextType: RegisteredSpawningContext<*>) = contextType.getWeight()
|
||||
|
||||
protected class ContextSelectionData(
|
||||
val spawnsToContexts: MutableMap<SpawnDetail, MutableList<SpawningContext>>,
|
||||
var percentSum: Float
|
||||
) {
|
||||
val size: Int
|
||||
get() = spawnsToContexts.size
|
||||
}
|
||||
|
||||
protected fun getSelectionData(
|
||||
spawner: Spawner,
|
||||
contexts: List<SpawningContext>
|
||||
): ContextSelectionData {
|
||||
val spawnsToContexts: MutableMap<SpawnDetail, MutableList<SpawningContext>> = mutableMapOf()
|
||||
var percentSum = 0F
|
||||
|
||||
contexts.forEach { ctx ->
|
||||
spawner.getMatchingSpawns(ctx).forEach {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
|
||||
// the percentage will get amplified for every context the thing was possible, completely
|
||||
// ruining the point of this pre-selection percentage.
|
||||
if (it.percentage > 0 && !spawnsToContexts.containsKey(it)) {
|
||||
percentSum += it.percentage
|
||||
}
|
||||
|
||||
val selectingSpawnInformation = spawnsToContexts.getOrPut(
|
||||
it,
|
||||
::mutableListOf
|
||||
)
|
||||
selectingSpawnInformation.add(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return ContextSelectionData(spawnsToContexts, percentSum)
|
||||
}
|
||||
|
||||
override fun select(
|
||||
spawner: Spawner,
|
||||
contexts: List<SpawningContext>
|
||||
): Pair<SpawningContext, SpawnDetail>? {
|
||||
val selectionData = getSelectionData(spawner, contexts)
|
||||
|
||||
if (selectionData.size == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
val spawnsToContexts = selectionData.spawnsToContexts
|
||||
var percentSum = selectionData.percentSum
|
||||
|
||||
|
||||
// First pass is doing percentage checks.
|
||||
if (percentSum > 0) {
|
||||
|
||||
if (percentSum > 100) {
|
||||
Cobblemon.LOGGER.warn(
|
||||
"""
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
|
||||
This means you don't understand how this option works.
|
||||
""".trimIndent()
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
/*
|
||||
* It's [0, 1) and I want (0, 1]
|
||||
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
|
||||
*/
|
||||
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
|
||||
percentSum = 0F
|
||||
for ((spawnDetail, info) in spawnsToContexts) {
|
||||
if (spawnDetail.percentage > 0) {
|
||||
percentSum += spawnDetail.percentage
|
||||
if (percentSum >= selectedPercentage) {
|
||||
return info.random() to spawnDetail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val selectedSpawn = spawnsToContexts.entries.toList().weightedSelection { it.key.weight }!!
|
||||
|
||||
return selectedSpawn.value.random() to selectedSpawn.key
|
||||
}
|
||||
|
||||
override fun getTotalWeights(
|
||||
spawner: Spawner,
|
||||
contexts: List<SpawningContext>
|
||||
): Map<SpawnDetail, Float> {
|
||||
val selectionData = getSelectionData(spawner, contexts)
|
||||
|
||||
if (selectionData.size == 0) {
|
||||
return mapOf()
|
||||
}
|
||||
|
||||
val totalWeights = mutableMapOf<SpawnDetail, Float>()
|
||||
|
||||
for (spawnDetail in selectionData.spawnsToContexts.keys) {
|
||||
totalWeights[spawnDetail] = spawnDetail.weight
|
||||
}
|
||||
|
||||
return totalWeights
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
|
||||
|
||||
/**
|
||||
* Interface responsible for taking all of the potential spawns across many contexts, and applying some kind of
|
||||
* Interface responsible for taking all the potential spawns across many contexts, and applying some kind of
|
||||
* selection process to choose one. It is also responsible for generating a name to percentage probability for the given
|
||||
* spawn information for checking spawns under specific conditions.
|
||||
*
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.selection.FlatContextWeightedSelector
|
||||
import com.cobblemon.mod.common.api.spawning.selection.FlatSelector
|
||||
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
|
||||
import net.minecraft.entity.Entity
|
||||
|
||||
@@ -32,7 +32,7 @@ abstract class TickingSpawner(
|
||||
var spawns: SpawnPool,
|
||||
val manager: SpawnerManager
|
||||
) : Spawner {
|
||||
private var selector: SpawningSelector = FlatContextWeightedSelector()
|
||||
private var selector: SpawningSelector = FlatSelector()
|
||||
override val influences = mutableListOf<SpawningInfluence>()
|
||||
|
||||
override fun canSpawn() = active
|
||||
|
||||
@@ -247,9 +247,6 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
|
||||
|
||||
fun toBattleTeam(clone: Boolean = false, checkHealth: Boolean = true, leadingPokemon: UUID? = null) = mapNotNull {
|
||||
// TODO Other 'able to battle' checks
|
||||
if (checkHealth && it.currentHealth <= 0) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
return@mapNotNull if (clone) {
|
||||
BattlePokemon.safeCopyOf(it)
|
||||
} else {
|
||||
|
||||
+8
-3
@@ -24,10 +24,15 @@ class JsonPlayerDataStoreFactory : PlayerDataStoreFactory {
|
||||
fun setup(server: MinecraftServer) {
|
||||
adapter.setup(server);
|
||||
}
|
||||
|
||||
override fun load(uuid: UUID): PlayerData {
|
||||
if(cache.contains(uuid))
|
||||
return cache[uuid]!!;
|
||||
return adapter.load(uuid);
|
||||
return if(cache.contains(uuid))
|
||||
cache[uuid]!!;
|
||||
else {
|
||||
val data = adapter.load(uuid);
|
||||
cache[uuid] = data
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(data: PlayerData) {
|
||||
|
||||
@@ -9,74 +9,32 @@
|
||||
package com.cobblemon.mod.common.api.tags
|
||||
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.registry.Registry
|
||||
import net.minecraft.registry.Registries
|
||||
import net.minecraft.registry.RegistryKeys
|
||||
import net.minecraft.registry.tag.TagKey
|
||||
|
||||
/**
|
||||
* A collection of the Cobblemon [TagKey]s related to the [Registry.BLOCK].
|
||||
* A collection of the Cobblemon [TagKey]s related to the [Registries.BLOCK].
|
||||
*
|
||||
* @author Licious
|
||||
* @since October 29th, 2022
|
||||
*/
|
||||
@Suppress("HasPlatformType", "unused")
|
||||
object CobblemonBlockTags {
|
||||
|
||||
@JvmField
|
||||
val APRICORN_LEAVES = createTag("apricorn_leaves")
|
||||
@JvmField
|
||||
val APRICORN_LOGS = createTag("apricorn_logs")
|
||||
@JvmField
|
||||
val APRICORN_SAPLINGS = createTag("apricorn_saplings")
|
||||
@JvmField
|
||||
val APRICORNS = createTag("apricorns")
|
||||
@JvmField
|
||||
val DRIPSTONE_GROWABLE = createTag("dripstone_growable")
|
||||
@JvmField
|
||||
val DRIPSTONE_REPLACEABLES = createTag("dripstone_replaceables")
|
||||
|
||||
// ToDo remove in 1.3
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val FENCE_GATES = createTag("fence_gates")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val FENCES = createTag("fences")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val LEAVES = createTag("leaves")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val LOGS = createTag("logs")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val LOGS_THAT_BURN = createTag("logs_that_burn")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val PLANKS = createTag("planks")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val SAPLINGS = createTag("saplings")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val STANDING_SIGNS = createTag("standing_signs")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val WALL_SIGNS = createTag("wall_signs")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val WOODEN_BUTTONS = createTag("wooden_buttons")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val WOODEN_FENCES = createTag("wooden_fences")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val WOODEN_PRESSURE_PLATES = createTag("wooden_pressure_plates")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val WOODEN_SLABS = createTag("wooden_slabs")
|
||||
@Deprecated(message = "This tag no longer exists", level = DeprecationLevel.ERROR)
|
||||
@JvmField
|
||||
val WOODEN_STAIRS = createTag("wooden_stairs")
|
||||
@JvmField val APRICORN_LEAVES = createTag("apricorn_leaves")
|
||||
@JvmField val APRICORN_LOGS = createTag("apricorn_logs")
|
||||
@JvmField val APRICORN_SAPLINGS = createTag("apricorn_saplings")
|
||||
@JvmField val APRICORNS = createTag("apricorns")
|
||||
@JvmField val CROPS = createTag("crops")
|
||||
@JvmField val DRIPSTONE_GROWABLE = createTag("dripstone_growable")
|
||||
@JvmField val DRIPSTONE_REPLACEABLES = createTag("dripstone_replaceables")
|
||||
@JvmField val FLOWERS = createTag("flowers")
|
||||
@JvmField val MEDICINAL_LEEK_PLANTABLE = createTag("medicinal_leek_plantable")
|
||||
@JvmField val MINTS = createTag("mints")
|
||||
@JvmField val ROOTS_SPREADABLE = createTag("roots_spreadable")
|
||||
@JvmField val SMALL_FLOWERS = createTag("small_flowers")
|
||||
@JvmField val SEES_SKY = createTag("sees_sky")
|
||||
|
||||
private fun createTag(name: String) = TagKey.of(RegistryKeys.BLOCK, cobblemonResource(name))
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ object CobblemonItemTags {
|
||||
@JvmField
|
||||
val LUCKY_EGG = create("held/lucky_egg")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = create("held/destiny_knot")
|
||||
@JvmField
|
||||
val EVERSTONE = create("held/everstone")
|
||||
@JvmField
|
||||
val EVOLUTION_STONES = create("evolution_stones")
|
||||
@JvmField
|
||||
val EVOLUTION_ITEMS = create("evolution_items")
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.PrioritizedList
|
||||
import com.cobblemon.mod.common.api.data.DataRegistry
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.item.battle.BagItemConvertible
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.io.File
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.resource.ResourceType
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
/**
|
||||
* A registry for [BagItem]s that could be parsed from [ItemStack]s. This registry is used as the resource loading
|
||||
* mechanism for bag item scripts, but add to [bagItems] to map from stacks to [BagItem]s.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since June 26th, 2023
|
||||
*/
|
||||
object BagItems : DataRegistry {
|
||||
override val id = cobblemonResource("bag_items")
|
||||
override val type = ResourceType.SERVER_DATA
|
||||
override val observable = SimpleObservable<BagItems>()
|
||||
override fun sync(player: ServerPlayerEntity) {}
|
||||
|
||||
val bagItems = PrioritizedList<BagItemConvertible>()
|
||||
internal val bagItemsScripts = mutableMapOf<String, String>() // itemId to JavaScript
|
||||
|
||||
init {
|
||||
observable.subscribe {
|
||||
Cobblemon.showdownThread.queue(ShowdownService::registerBagItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun getConvertibleForStack(stack: ItemStack): BagItemConvertible? {
|
||||
return bagItems.firstOrNull { it.getBagItem(stack) != null }
|
||||
}
|
||||
|
||||
override fun reload(manager: ResourceManager) {
|
||||
manager.findResources("bag_items") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
|
||||
resource.inputStream.use { stream ->
|
||||
stream.bufferedReader().use { reader ->
|
||||
val resolvedIdentifier = Identifier(identifier.namespace, File(identifier.path).nameWithoutExtension)
|
||||
val js = reader.readText()
|
||||
bagItemsScripts[resolvedIdentifier.path] = js
|
||||
}
|
||||
}
|
||||
}
|
||||
this.observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleChallengeExpiredPacket
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.google.gson.GsonBuilder
|
||||
import java.time.Instant
|
||||
import java.util.Optional
|
||||
@@ -199,7 +199,7 @@ object BattleRegistry {
|
||||
}
|
||||
|
||||
// Compiles the request and sends it off
|
||||
ShowdownService.get().startBattle(battle, messages.toTypedArray())
|
||||
ShowdownService.service.startBattle(battle, messages.toTypedArray())
|
||||
CobblemonEvents.BATTLE_STARTED_POST.post(BattleStartedPostEvent(battle))
|
||||
return battle
|
||||
}
|
||||
@@ -213,7 +213,7 @@ object BattleRegistry {
|
||||
}
|
||||
|
||||
fun getBattleByParticipatingPlayer(serverPlayerEntity: ServerPlayerEntity) : PokemonBattle? {
|
||||
return battleMap.values.find { it.actors.any { it.isForPlayer(serverPlayerEntity) } }
|
||||
return battleMap.values.find { it.getActor(serverPlayerEntity) != null }
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.net.IntSize
|
||||
import com.cobblemon.mod.common.util.readSizedInt
|
||||
import com.cobblemon.mod.common.util.writeSizedInt
|
||||
@@ -67,8 +69,9 @@ enum class ShowdownActionResponseType(val loader: (PacketByteBuf) -> ShowdownAct
|
||||
SWITCH({ SwitchActionResponse(UUID.randomUUID()) }),
|
||||
MOVE({ MoveActionResponse("", null) }),
|
||||
DEFAULT({ DefaultActionResponse() }),
|
||||
BALL({ BallActionResponse() }),
|
||||
PASS({ PassActionResponse });
|
||||
FORCE_PASS({ ForcePassActionResponse() }),
|
||||
PASS({ PassActionResponse }),
|
||||
HEAL_ITEM({ HealItemActionResponse("potion") });
|
||||
}
|
||||
|
||||
abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
|
||||
@@ -141,6 +144,27 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
|
||||
}
|
||||
}
|
||||
|
||||
data class HealItemActionResponse(var item: String) : ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
|
||||
override fun saveToBuffer(buffer: PacketByteBuf) {
|
||||
super.saveToBuffer(buffer)
|
||||
buffer.writeString(item)
|
||||
}
|
||||
|
||||
override fun loadFromBuffer(buffer: PacketByteBuf): ShowdownActionResponse {
|
||||
super.loadFromBuffer(buffer)
|
||||
item = buffer.readString()
|
||||
return this
|
||||
}
|
||||
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
|
||||
return !forceSwitch
|
||||
}
|
||||
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
|
||||
return "healitem ${activeBattlePokemon.getPNX()} $item"
|
||||
}
|
||||
}
|
||||
|
||||
data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse(ShowdownActionResponseType.SWITCH) {
|
||||
override fun saveToBuffer(buffer: PacketByteBuf) {
|
||||
super.saveToBuffer(buffer)
|
||||
@@ -168,6 +192,7 @@ data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse
|
||||
return "switch ${activeBattlePokemon.actor.pokemonList.indexOfFirst { it.uuid == newPokemonId } + 1}"
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultActionResponse: ShowdownActionResponse(ShowdownActionResponseType.DEFAULT) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "default"
|
||||
@@ -177,7 +202,11 @@ object PassActionResponse : ShowdownActionResponse(ShowdownActionResponseType.PA
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "pass"
|
||||
}
|
||||
class BallActionResponse() : ShowdownActionResponse(ShowdownActionResponseType.BALL) {
|
||||
|
||||
/**
|
||||
* Only meant to be used when the player is capturing or using an item - they are forced to pass
|
||||
*/
|
||||
class ForcePassActionResponse : ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
|
||||
if (forceSwitch) {
|
||||
return false
|
||||
@@ -185,11 +214,32 @@ class BallActionResponse() : ShowdownActionResponse(ShowdownActionResponseType.B
|
||||
return false
|
||||
}
|
||||
|
||||
return activeBattlePokemon.actor.expectingCaptureActions-- > 0
|
||||
return activeBattlePokemon.actor.expectingPassActions.size > 0
|
||||
}
|
||||
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "pass"
|
||||
}
|
||||
|
||||
class BagItemActionResponse(val bagItem: BagItem, val target: BattlePokemon, val data: String? = null): ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
|
||||
override fun isValid(
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
showdownMoveSet: ShowdownMoveset?,
|
||||
forceSwitch: Boolean
|
||||
): Boolean {
|
||||
if (forceSwitch) {
|
||||
return false
|
||||
} else if (showdownMoveSet == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return activeBattlePokemon.actor.expectingPassActions.size > 0
|
||||
}
|
||||
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
|
||||
return "useitem ${target.uuid} ${bagItem.itemName} ${bagItem.getShowdownInput(target.actor, target, data)}"
|
||||
}
|
||||
}
|
||||
|
||||
class ShowdownMoveset {
|
||||
lateinit var moves: List<InBattleMove>
|
||||
var trapped = false
|
||||
@@ -240,6 +290,7 @@ class ShowdownPokemon {
|
||||
lateinit var baseAbility: String
|
||||
lateinit var pokeball: String
|
||||
lateinit var ability: String
|
||||
var reviving: Boolean = false
|
||||
|
||||
val uuid: UUID by lazy { UUID.fromString(details.split(",")[1].trim()) }
|
||||
fun saveToBuffer(buffer: PacketByteBuf) {
|
||||
@@ -247,6 +298,7 @@ class ShowdownPokemon {
|
||||
buffer.writeString(details)
|
||||
buffer.writeString(condition)
|
||||
buffer.writeBoolean(active)
|
||||
buffer.writeBoolean(reviving)
|
||||
buffer.writeSizedInt(IntSize.U_BYTE, moves.size)
|
||||
moves.forEach(buffer::writeString)
|
||||
buffer.writeString(baseAbility)
|
||||
@@ -259,6 +311,7 @@ class ShowdownPokemon {
|
||||
details = buffer.readString()
|
||||
condition = buffer.readString()
|
||||
active = buffer.readBoolean()
|
||||
reviving = buffer.readBoolean()
|
||||
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
|
||||
moves.add(buffer.readString())
|
||||
}
|
||||
|
||||
@@ -34,12 +34,7 @@ import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionP
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.runOnServer
|
||||
import com.cobblemon.mod.common.util.swap
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.math.roundToInt
|
||||
@@ -74,6 +69,7 @@ object ShowdownInterpreter {
|
||||
updateInstructions["|win|"] = this::handleWinInstruction
|
||||
updateInstructions["|move|"] = this::handleMoveInstruction
|
||||
updateInstructions["|cant|"] = this::handleCantInstruction
|
||||
updateInstructions["|bagitem|"] = this::handleBagItemInstruction
|
||||
updateInstructions["|-supereffective|"] = this::handleSuperEffectiveInstruction
|
||||
updateInstructions["|-resisted|"] = this::handleResistInstruction
|
||||
updateInstructions["|-crit"] = this::handleCritInstruction
|
||||
@@ -155,7 +151,7 @@ object ShowdownInterpreter {
|
||||
val boostBucket = if (isBoost) BattleContext.Type.BOOST else BattleContext.Type.UNBOOST
|
||||
val context = getContextFromAction(message, boostBucket, battle)
|
||||
// TODO: replace with context that tracks detailed information such as # of stages
|
||||
repeat(stages) {pokemon.contextManager.add(context)}
|
||||
repeat(stages) { pokemon.contextManager.add(context) }
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
@@ -533,6 +529,24 @@ object ShowdownInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |bagitem|POKEMON|ITEMNAME
|
||||
*
|
||||
* POKEMON had ITEMNAME used on it from the 'bag'.
|
||||
*/
|
||||
private fun handleBagItemInstruction(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
|
||||
battle.dispatchGo {
|
||||
val pokemon = message.pokemonByUuid(0, battle)!!
|
||||
val item = message.argumentAt(1)!!
|
||||
|
||||
val ownerName = pokemon.actor.getName()
|
||||
val itemName = item.asTranslated()
|
||||
|
||||
battle.broadcastChatMessage(battleLang("bagitem.use", ownerName, itemName, pokemon.getName()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |win|USER
|
||||
@@ -664,7 +678,7 @@ object ShowdownInterpreter {
|
||||
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
|
||||
val name = pokemon.getName()
|
||||
// Move may be null as it's not always given
|
||||
val moveName = message.argumentAt(2)?.let { Moves.getByName(it)?.displayName } ?: Text.EMPTY
|
||||
val moveName = message.argumentAt(2)?.let { Moves.getByName(it.replace(" ",""))?.displayName } ?: "".text()
|
||||
|
||||
val lang = when (effectID) {
|
||||
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
|
||||
@@ -935,7 +949,7 @@ object ShowdownInterpreter {
|
||||
}
|
||||
// Includes revealed move
|
||||
"forewarn" -> {
|
||||
val moveName = message.argumentAt(2)?.let { Moves.getByName(it)?.displayName } ?: Text.EMPTY
|
||||
val moveName = message.argumentAt(2)?.let { Moves.getByName(it.replace(" ",""))?.displayName } ?: "".text()
|
||||
battleLang("activate.forewarn", sourceName, moveName)
|
||||
}
|
||||
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, message.effectAt(1)!!.typelessData)
|
||||
@@ -1336,8 +1350,7 @@ object ShowdownInterpreter {
|
||||
* The specified Pokémon POKEMON has taken damage, and is now at HP STATUS
|
||||
*/
|
||||
fun handleDamageInstruction(battle: PokemonBattle, actor: BattleActor, publicMessage: BattleMessage, privateMessage: BattleMessage) {
|
||||
val (pnx, _) = publicMessage.pnxAndUuid(0) ?: return
|
||||
val battlePokemon = privateMessage.getBattlePokemon(0, battle) ?: return
|
||||
val battlePokemon = publicMessage.getBattlePokemon(0, battle) ?: return
|
||||
if (privateMessage.optionalArgument("from")?.equals("recoil", true) == true) {
|
||||
battlePokemon.effectedPokemon.let { pokemon ->
|
||||
val recoilProgress = RecoilEvolutionProgress()
|
||||
@@ -1403,7 +1416,8 @@ object ShowdownInterpreter {
|
||||
GO
|
||||
}
|
||||
}
|
||||
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, remainingHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio))
|
||||
privateMessage.pnxAndUuid(0)?.let { (pnx, _) -> battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, remainingHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio)) }
|
||||
|
||||
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
|
||||
WaitDispatch(1F)
|
||||
}
|
||||
@@ -1509,7 +1523,7 @@ object ShowdownInterpreter {
|
||||
* The specified Pokémon POKEMON has healed damage, and is now at HP STATUS.
|
||||
*/
|
||||
private fun handleHealInstruction(battle: PokemonBattle, actor: BattleActor, publicMessage: BattleMessage, privateMessage: BattleMessage) {
|
||||
val (pnx, _) = privateMessage.pnxAndUuid(0) ?: return
|
||||
val pnx = privateMessage.pnxAndUuid(0)?.first
|
||||
val battlePokemon = privateMessage.getBattlePokemon(0, battle) ?: return
|
||||
val rawHpAndStatus = privateMessage.argumentAt(1)?.split(" ") ?: return
|
||||
val rawHpRatio = rawHpAndStatus.getOrNull(0) ?: return
|
||||
@@ -1520,7 +1534,9 @@ object ShowdownInterpreter {
|
||||
broadcastOptionalAbility(battle, effect, pokemonName)
|
||||
|
||||
battle.dispatchWaiting {
|
||||
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, newHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio))
|
||||
if (pnx != null) {
|
||||
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, newHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio))
|
||||
}
|
||||
val silent = privateMessage.hasOptionalArgument("silent")
|
||||
if (!silent) {
|
||||
val lang = when {
|
||||
@@ -1547,7 +1563,9 @@ object ShowdownInterpreter {
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> battleLang("heal.generic", battlePokemon.getName())
|
||||
else -> {
|
||||
battleLang("heal.generic", battlePokemon.getName())
|
||||
}
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
}
|
||||
@@ -1559,7 +1577,9 @@ object ShowdownInterpreter {
|
||||
val status = Statuses.getStatus(rawStatus) ?: return@dispatchWaiting
|
||||
if (status is PersistentStatus) {
|
||||
battlePokemon.effectedPokemon.applyStatus(status)
|
||||
battle.sendUpdate(BattlePersistentStatusPacket(pnx, status))
|
||||
if (pnx != null) {
|
||||
battle.sendUpdate(BattlePersistentStatusPacket(pnx, status))
|
||||
}
|
||||
if (!silent) {
|
||||
status.applyMessage.let { battle.broadcastChatMessage(it.asTranslated(battlePokemon.getName())) }
|
||||
}
|
||||
|
||||
@@ -17,19 +17,19 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
|
||||
private val latch = CountDownLatch(1)
|
||||
|
||||
private val whenReady : Queue<Runnable> = LinkedList()
|
||||
private val whenReady : Queue<(ShowdownService) -> Unit> = LinkedList()
|
||||
|
||||
fun launch() {
|
||||
this.start()
|
||||
this.latch.await()
|
||||
for (action in whenReady) {
|
||||
action.run()
|
||||
action(ShowdownService.service)
|
||||
}
|
||||
}
|
||||
|
||||
fun queue(action: Runnable) {
|
||||
fun queue(action: (ShowdownService) -> Unit) {
|
||||
if (this.latch.count == 0L) {
|
||||
action.run()
|
||||
action(ShowdownService.service)
|
||||
} else {
|
||||
this.whenReady.add(action)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
|
||||
override fun run() {
|
||||
LOGGER.info("Starting showdown service...")
|
||||
ShowdownService.get().openConnection()
|
||||
ShowdownService.service.openConnection()
|
||||
LOGGER.info("Showdown has been started!")
|
||||
this.latch.countDown()
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ package com.cobblemon.mod.common.battles.runner
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
|
||||
import com.cobblemon.mod.common.battles.runner.socket.SocketShowdownService
|
||||
import com.google.gson.JsonArray
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service.
|
||||
@@ -32,13 +31,11 @@ interface ShowdownService {
|
||||
fun getMoves(): JsonArray
|
||||
fun getItemIds(): JsonArray
|
||||
fun registerSpecies()
|
||||
fun registerBagItems()
|
||||
fun indicateSpeciesInitialized() {}
|
||||
|
||||
|
||||
companion object {
|
||||
private var service: ShowdownService? = null
|
||||
|
||||
fun get(): ShowdownService {
|
||||
if (service == null) service = GraalShowdownService()
|
||||
return service!!
|
||||
}
|
||||
val service: ShowdownService by lazy { GraalShowdownService() }
|
||||
}
|
||||
}
|
||||
+55
-4
@@ -11,16 +11,28 @@ package com.cobblemon.mod.common.battles.runner.graal
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.nio.file.AccessMode
|
||||
import java.nio.file.DirectoryStream
|
||||
import java.nio.file.LinkOption
|
||||
import java.nio.file.OpenOption
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.util.UUID
|
||||
import org.graalvm.polyglot.Context
|
||||
import org.graalvm.polyglot.HostAccess
|
||||
import org.graalvm.polyglot.HostAccess.Export
|
||||
import org.graalvm.polyglot.PolyglotAccess
|
||||
import org.graalvm.polyglot.Value
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import org.graalvm.polyglot.io.FileSystem
|
||||
|
||||
/**
|
||||
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
|
||||
@@ -49,13 +61,38 @@ class GraalShowdownService : ShowdownService {
|
||||
}
|
||||
|
||||
private fun createContext() {
|
||||
val access = HostAccess.newBuilder(HostAccess.ALL).build()
|
||||
val wd = Paths.get("./showdown")
|
||||
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
|
||||
.allowIterableAccess(true)
|
||||
.allowArrayAccess(true)
|
||||
.allowListAccess(true)
|
||||
.allowMapAccess(true)
|
||||
.build()
|
||||
context = Context.newBuilder("js")
|
||||
.allowIO(true)
|
||||
.fileSystem(object : FileSystem
|
||||
{
|
||||
val default = FileSystem.newDefaultFileSystem()
|
||||
override fun parsePath(uri: URI) = default.parsePath(uri)
|
||||
override fun parsePath(path: String) = default.parsePath(path)
|
||||
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) = default.createDirectory(dir, *attrs)
|
||||
override fun delete(path: Path) = default.delete(path)
|
||||
override fun newByteChannel(path: Path, options: MutableSet<out OpenOption>, vararg attrs: FileAttribute<*>) = default.newByteChannel(path, options, *attrs)
|
||||
override fun newDirectoryStream(dir: Path, filter: DirectoryStream.Filter<in Path>) = default.newDirectoryStream(dir, filter)
|
||||
override fun toAbsolutePath(path: Path) = default.toAbsolutePath(path)
|
||||
override fun toRealPath(path: Path, vararg linkOptions: LinkOption) = default.toRealPath(path, *linkOptions)
|
||||
override fun readAttributes(path: Path, attributes: String, vararg options: LinkOption) = default.readAttributes(path, attributes, *options)
|
||||
override fun checkAccess(path: Path, modes: MutableSet<out AccessMode>, vararg linkOptions: LinkOption) {
|
||||
if (!path.toRealPath(LinkOption.NOFOLLOW_LINKS).startsWith(wd.toRealPath(LinkOption.NOFOLLOW_LINKS))) {
|
||||
Cobblemon.LOGGER.error("Hacked JS files in datapacks or some weird file system setup that Hiroku failed to anticipate.")
|
||||
throw IOException("Someone has put hacked JS files into datapacks because file access is being attempted outside of controlled folders.")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.allowExperimentalOptions(true)
|
||||
.allowPolyglotAccess(PolyglotAccess.ALL)
|
||||
.allowHostAccess(access)
|
||||
.allowAllAccess(true)
|
||||
.allowCreateThread(true)
|
||||
.logHandler(GraalLogger)
|
||||
.option("engine.WarnInterpreterOnly", "false")
|
||||
@@ -130,14 +167,28 @@ class GraalShowdownService : ShowdownService {
|
||||
receiveSpeciesDataFn.execute(jsArray)
|
||||
}
|
||||
|
||||
override fun indicateSpeciesInitialized() {
|
||||
val afterCobbledSpeciesInitFn = this.context.getBindings("js").getMember("afterCobbledSpeciesInit")
|
||||
afterCobbledSpeciesInitFn.execute()
|
||||
}
|
||||
|
||||
override fun registerBagItems() {
|
||||
val receiveBagItemDataFn = this.context.getBindings("js").getMember("receiveBagItemData")
|
||||
for ((itemId, js) in BagItems.bagItemsScripts) {
|
||||
receiveBagItemDataFn.execute(itemId, js.replace("\n", " "))
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
|
||||
sendBattleMessageFunction.execute(battleId.toString(), messages)
|
||||
}
|
||||
|
||||
@Export
|
||||
fun sendFromShowdown(battleId: String, message: String) {
|
||||
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), message)
|
||||
}
|
||||
|
||||
@Export
|
||||
fun log(message: String) {
|
||||
Cobblemon.LOGGER.info(message)
|
||||
}
|
||||
|
||||
+33
-8
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.runner.socket
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
@@ -23,7 +24,7 @@ import java.io.OutputStreamWriter
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
|
||||
@@ -60,7 +61,7 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
|
||||
|
||||
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
|
||||
writer.write(">startbattle ${battle.battleId}\n")
|
||||
writer.flush()
|
||||
acknowledge { Cobblemon.LOGGER.error("Failed to start battle!") }
|
||||
send(battle.battleId, messages)
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
|
||||
}
|
||||
|
||||
override fun getAbilityIds(): JsonArray {
|
||||
writer.write(">getCobbledAbilityIds\n")
|
||||
writer.write(">getCobbledAbilityIds")
|
||||
writer.flush()
|
||||
val response = readMessage()
|
||||
return gson.fromJson(response, JsonArray::class.java)
|
||||
@@ -116,7 +117,7 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
|
||||
}
|
||||
|
||||
override fun getItemIds(): JsonArray {
|
||||
writer.write(">getCobbledItemIds\n")
|
||||
writer.write(">getCobbledItemIds")
|
||||
writer.flush()
|
||||
val response = readMessage()
|
||||
return gson.fromJson(response, JsonArray::class.java)
|
||||
@@ -124,14 +125,17 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
|
||||
|
||||
private fun sendSpeciesData(species: Species, form: FormData?) {
|
||||
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
|
||||
writer.flush()
|
||||
val ack = CharArray(3)
|
||||
reader.read(ack) // Wait for showdown to acknowledge.
|
||||
assert(String(ack) == "ACK")
|
||||
acknowledge()
|
||||
}
|
||||
|
||||
private fun sendBagItem(itemId: String, js: String) {
|
||||
writer.write(">receiveBagItemData $itemId $js")
|
||||
acknowledge { Cobblemon.LOGGER.error("Failed to send bag item to Showdown: $itemId") }
|
||||
}
|
||||
|
||||
override fun registerSpecies() {
|
||||
writer.write(">resetSpeciesData\n")
|
||||
acknowledge()
|
||||
PokemonSpecies.species.forEach { species ->
|
||||
sendSpeciesData(species, null)
|
||||
species.forms.forEach { form ->
|
||||
@@ -141,4 +145,25 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun acknowledge(ifFails: () -> Unit = {}) {
|
||||
writer.flush()
|
||||
val ack = CharArray(3)
|
||||
reader.read(ack)
|
||||
if (String(ack) != "ACK") {
|
||||
ifFails()
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerBagItems() {
|
||||
for ((itemId, js) in BagItems.bagItemsScripts) {
|
||||
sendBagItem(itemId, js.replace("\n", " "))
|
||||
}
|
||||
}
|
||||
|
||||
override fun indicateSpeciesInitialized() {
|
||||
writer.write(">afterCobbledSpeciesInit")
|
||||
acknowledge()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.item.ItemPlacementContext
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.sound.SoundCategory
|
||||
import net.minecraft.sound.SoundEvents
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.IntProperty
|
||||
@@ -42,7 +43,8 @@ import net.minecraft.world.WorldView
|
||||
import net.minecraft.world.event.GameEvent
|
||||
|
||||
// Note we cannot make this inherit from CocoaBlock since our age properties differ, it is however safe to copy most of the logic from it
|
||||
class ApricornBlock(settings: Settings, val apricorn: Apricorn) : HorizontalFacingBlock(settings), Fertilizable {
|
||||
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
class ApricornBlock(settings: Settings, val apricorn: Apricorn) : HorizontalFacingBlock(settings), Fertilizable, ShearableBlock {
|
||||
|
||||
init {
|
||||
this.defaultState = this.stateManager.defaultState
|
||||
@@ -104,7 +106,6 @@ class ApricornBlock(settings: Settings, val apricorn: Apricorn) : HorizontalFaci
|
||||
return null
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun getStateForNeighborUpdate(state: BlockState, direction: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState? {
|
||||
return if (direction == state.get(FACING) && !state.canPlaceAt(world, pos)) Blocks.AIR.defaultState
|
||||
else super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
|
||||
@@ -122,10 +123,8 @@ class ApricornBlock(settings: Settings, val apricorn: Apricorn) : HorizontalFaci
|
||||
builder.add(FACING, AGE)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun canPathfindThrough(state: BlockState, world: BlockView, pos: BlockPos, type: NavigationType) = false
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult {
|
||||
if (state.get(AGE) != MAX_AGE) {
|
||||
return super.onUse(state, world, pos, player, hand, hit)
|
||||
@@ -166,6 +165,17 @@ class ApricornBlock(settings: Settings, val apricorn: Apricorn) : HorizontalFaci
|
||||
return resetState
|
||||
}
|
||||
|
||||
override fun attemptShear(world: World, state: BlockState, pos: BlockPos, successCallback: () -> Unit): Boolean {
|
||||
if (state.get(AGE) == MAX_AGE) {
|
||||
return false
|
||||
}
|
||||
world.playSound(null, pos, SoundEvents.ENTITY_SHEEP_SHEAR, SoundCategory.BLOCKS, 1F, 1F)
|
||||
this.harvest(world, state, pos)
|
||||
successCallback()
|
||||
world.emitGameEvent(null, GameEvent.SHEAR, pos)
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val AGE: IntProperty = Properties.AGE_3
|
||||
|
||||
@@ -8,110 +8,42 @@
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockRenderType
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.block.Fertilizable
|
||||
import net.minecraft.block.ShapeContext
|
||||
import net.minecraft.registry.tag.BlockTags
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.Items
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.hit.BlockHitResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldAccess
|
||||
import net.minecraft.world.WorldView
|
||||
|
||||
class BigRootBlock(settings: Settings) : Block(settings), Fertilizable {
|
||||
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
|
||||
class BigRootBlock(settings: Settings) : RootBlock(settings) {
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AABB
|
||||
|
||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult {
|
||||
val stack = player.getStackInHand(hand)
|
||||
if (stack.isOf(Items.SHEARS) && this.attemptShear(world, state, pos) { stack.damage(1, player) { affected -> affected.sendToolBreakStatus(hand) } }) {
|
||||
return ActionResult.success(world.isClient)
|
||||
}
|
||||
return super.onUse(state, world, pos, player, hand, hit)
|
||||
}
|
||||
|
||||
override fun shearedResultingState(): BlockState = Blocks.HANGING_ROOTS.defaultState
|
||||
|
||||
override fun shearedDrop(): ItemStack = Items.STRING.defaultStack
|
||||
|
||||
companion object {
|
||||
const val MAX_PROPAGATING_LIGHT_LEVEL = 11
|
||||
|
||||
private val AABB = VoxelShapes.cuboid(0.2, 0.3, 0.2, 0.8, 1.0, 0.8)
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
this.defaultState = stateManager.defaultState
|
||||
}
|
||||
|
||||
override fun getOutlineShape(
|
||||
state: BlockState,
|
||||
world: BlockView,
|
||||
pos: BlockPos,
|
||||
context: ShapeContext
|
||||
) = AABB
|
||||
|
||||
override fun hasRandomTicks(state: BlockState) = true
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
// Check for propagation
|
||||
if (random.nextDouble() < Cobblemon.config.bigRootPropagationChance && world.getLightLevel(pos) < MAX_PROPAGATING_LIGHT_LEVEL) {
|
||||
spreadFrom(world, pos, random)
|
||||
}
|
||||
}
|
||||
|
||||
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
|
||||
return world.getBlockState(pos.up()).isIn(BlockTags.DIRT) && world.isAir(pos)
|
||||
}
|
||||
|
||||
override fun getStateForNeighborUpdate(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
world: WorldAccess,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
return if (!world.getBlockState(pos.up()).isIn(BlockTags.DIRT)) {
|
||||
Blocks.AIR.defaultState
|
||||
} else {
|
||||
super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
}
|
||||
|
||||
fun spreadFrom(world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
for (xDiff in -1..1) {
|
||||
for (zDiff in -1..1) {
|
||||
if (xDiff == 0 && zDiff == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
val adjacent = pos.add(xDiff, 0, zDiff)
|
||||
if (canPlaceAt(world.getBlockState(adjacent), world, adjacent)) {
|
||||
val isEnergyRoot = random.nextFloat() < Cobblemon.config.energyRootChance
|
||||
val event = BigRootPropagatedEvent(
|
||||
world = world,
|
||||
pos = pos,
|
||||
newRootPosition = adjacent,
|
||||
isEnergyRoot = isEnergyRoot
|
||||
)
|
||||
|
||||
CobblemonEvents.BIG_ROOT_PROPAGATED.postThen(
|
||||
event = event,
|
||||
ifCanceled = {},
|
||||
ifSucceeded = { ev ->
|
||||
world.setBlockState(
|
||||
ev.newRootPosition,
|
||||
if (ev.isEnergyRoot) CobblemonBlocks.ENERGY_ROOT.defaultState else defaultState
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = true
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = true
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
|
||||
spreadFrom(world, pos, random)
|
||||
}
|
||||
|
||||
override fun getRenderType(state: BlockState) = BlockRenderType.MODEL
|
||||
}
|
||||
@@ -8,62 +8,29 @@
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockRenderType
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.block.Fertilizable
|
||||
import net.minecraft.block.ShapeContext
|
||||
import net.minecraft.registry.tag.BlockTags
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldAccess
|
||||
import net.minecraft.world.WorldView
|
||||
|
||||
class EnergyRootBlock(settings: Settings) : Block(settings), Fertilizable {
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
class EnergyRootBlock(settings: Settings) : RootBlock(settings) {
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AABB
|
||||
|
||||
override fun shearedResultingState(): BlockState = CobblemonBlocks.BIG_ROOT.defaultState
|
||||
|
||||
override fun shearedDrop(): ItemStack = CobblemonItems.ENERGY_ROOT.defaultStack
|
||||
|
||||
companion object {
|
||||
|
||||
private val AABB = VoxelShapes.cuboid(0.2, 0.1, 0.2, 0.8, 1.0, 0.8)
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
this.defaultState = stateManager.defaultState
|
||||
}
|
||||
|
||||
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
|
||||
return world.getBlockState(pos.up()).isIn(BlockTags.DIRT) && world.isAir(pos)
|
||||
}
|
||||
|
||||
override fun getStateForNeighborUpdate(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
world: WorldAccess,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
return if (!world.getBlockState(pos.up()).isIn(BlockTags.DIRT)) {
|
||||
Blocks.AIR.defaultState
|
||||
} else {
|
||||
super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOutlineShape(
|
||||
state: BlockState,
|
||||
world: BlockView,
|
||||
pos: BlockPos,
|
||||
context: ShapeContext
|
||||
) = AABB
|
||||
|
||||
// Maybe turn into Giant Root? idk
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = false
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = true
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {}
|
||||
|
||||
override fun getRenderType(state: BlockState) = BlockRenderType.MODEL
|
||||
}
|
||||
@@ -9,61 +9,69 @@
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.fluid.Fluids
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.ItemConvertible
|
||||
import net.minecraft.registry.tag.FluidTags
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.IntProperty
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldView
|
||||
import kotlin.math.min
|
||||
|
||||
class MedicinalLeekBlock(settings: Settings) : PlantBlock(settings), Fertilizable {
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
class MedicinalLeekBlock(settings: Settings) : CropBlock(settings) {
|
||||
|
||||
init {
|
||||
defaultState = stateManager.defaultState
|
||||
.with(AGE, 0)
|
||||
}
|
||||
override fun getAgeProperty(): IntProperty = AGE
|
||||
|
||||
override fun getPickStack(world: BlockView, pos: BlockPos, state: BlockState) = ItemStack(CobblemonItems.MEDICINAL_LEEK)
|
||||
private fun isMature(state: BlockState) = state.get(AGE) == MATURE_AGE
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = !isMature(state)
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = true
|
||||
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
|
||||
world.setBlockState(pos, state.with(AGE, min(state.get(AGE) + 1, MATURE_AGE)), NOTIFY_LISTENERS)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext) = AGE_TO_SHAPE[state.get(AGE)]
|
||||
override fun hasRandomTicks(state: BlockState) = !isMature(state)
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
if (world.getBaseLightLevel(pos, 0) >= 0) {
|
||||
if (isMature(state)) return
|
||||
if (random.nextInt(50) != 0) return
|
||||
|
||||
val currentAge = state.get(AGE)
|
||||
world.setBlockState(pos, defaultState.with(AGE, currentAge + 1), NOTIFY_LISTENERS)
|
||||
}
|
||||
}
|
||||
override fun getMaxAge(): Int = MATURE_AGE
|
||||
|
||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||
builder.add(AGE)
|
||||
builder.add(this.ageProperty)
|
||||
}
|
||||
|
||||
override fun canPlantOnTop(floor: BlockState, world: BlockView, pos: BlockPos): Boolean {
|
||||
val fluidState = world.getFluidState(pos)
|
||||
val fluidState2 = world.getFluidState(pos.up())
|
||||
return (fluidState.fluid === Fluids.WATER) && fluidState2.fluid === Fluids.EMPTY
|
||||
override fun getSeedsItem(): ItemConvertible = CobblemonItems.MEDICINAL_LEEK
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AGE_TO_SHAPE[this.getAge(state)]
|
||||
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
// This is specified as growing fast like sugar cane
|
||||
// They have 15 age stages until they grow upwards, this is an attempt at a chance based but likely event
|
||||
if (this.isMature(state) || random.nextInt(4) != 0) {
|
||||
return
|
||||
}
|
||||
this.applyGrowth(world, pos, state)
|
||||
}
|
||||
|
||||
// These 3 are still around for the sake of compatibility, vanilla won't trigger it but some mods might
|
||||
// We implement applyGrowth & getGrowthAmount for them
|
||||
override fun isFertilizable(world: WorldView?, pos: BlockPos?, state: BlockState?, isClient: Boolean): Boolean = false
|
||||
|
||||
override fun applyGrowth(world: World, pos: BlockPos, state: BlockState) {
|
||||
world.setBlockState(pos, state.with(this.ageProperty, (this.getAge(state) + 1).coerceAtMost(this.maxAge)), NOTIFY_LISTENERS)
|
||||
}
|
||||
|
||||
override fun getGrowthAmount(world: World): Int = 1
|
||||
|
||||
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
|
||||
// We don't care about the sky & light level, sugar cane doesn't either
|
||||
return this.canPlantOnTop(state, world, pos)
|
||||
}
|
||||
|
||||
override fun canPlantOnTop(state: BlockState, world: BlockView, pos: BlockPos): Boolean {
|
||||
val down = pos.down()
|
||||
val floor = world.getBlockState(down)
|
||||
val fluidState = world.getFluidState(down)
|
||||
return floor.isIn(CobblemonBlockTags.MEDICINAL_LEEK_PLANTABLE) && fluidState.level == 8 && fluidState.isIn(FluidTags.WATER)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val MATURE_AGE = 3
|
||||
val AGE: IntProperty = Properties.AGE_3
|
||||
val AGE_TO_SHAPE = arrayOf(
|
||||
@@ -72,5 +80,6 @@ class MedicinalLeekBlock(settings: Settings) : PlantBlock(settings), Fertilizabl
|
||||
createCuboidShape(0.0, 0.0, 0.0, 16.0, 8.0, 16.0),
|
||||
createCuboidShape(0.0, 0.0, 0.0, 16.0, 11.0, 16.0)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,53 +10,43 @@ package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import kotlin.math.min
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.block.Fertilizable
|
||||
import net.minecraft.block.PlantBlock
|
||||
import net.minecraft.block.ShapeContext
|
||||
import com.cobblemon.mod.common.item.MintLeafItem
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.item.Item
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.ItemConvertible
|
||||
import net.minecraft.registry.tag.BlockTags
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.BooleanProperty
|
||||
import net.minecraft.state.property.IntProperty
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldView
|
||||
|
||||
class MintBlock(private val mintType: MintType, settings: Settings) : PlantBlock(settings), Fertilizable {
|
||||
@Suppress("OVERRIDE_DEPRECATION", "MemberVisibilityCanBePrivate")
|
||||
class MintBlock(private val mintType: MintType, settings: Settings) : CropBlock(settings), Fertilizable {
|
||||
|
||||
init {
|
||||
defaultState = stateManager.defaultState
|
||||
.with(AGE, 0)
|
||||
.with(IS_WILD, false)
|
||||
}
|
||||
|
||||
override fun getPickStack(world: BlockView, pos: BlockPos, state: BlockState) = ItemStack(mintType.getSeed())
|
||||
private fun isMature(state: BlockState) = state.get(AGE) == MATURE_AGE
|
||||
private fun isWild(state: BlockState) = state.get(IS_WILD)
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = !isMature(state)
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = true
|
||||
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
|
||||
world.setBlockState(pos, state.with(AGE, min(state.get(AGE) + 1, MATURE_AGE)), NOTIFY_LISTENERS)
|
||||
// DO NOT use withAge
|
||||
// Explanation for these 2 beautiful copy pasta are basically that we need to keep the blockstate and that's not possible with the default impl :(
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
if (world.getBaseLightLevel(pos, 0) < 9 || this.isMature(state) || random.nextInt(50) != 0) {
|
||||
return
|
||||
}
|
||||
this.applyGrowth(world, pos, state, false)
|
||||
}
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext) = AGE_TO_SHAPE[state.get(AGE)]
|
||||
override fun hasRandomTicks(state: BlockState) = !isMature(state)
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
if (world.getBaseLightLevel(pos, 0) >= 0) {
|
||||
if (isMature(state)) return
|
||||
if (random.nextInt(50) != 0) return
|
||||
|
||||
val currentAge = state.get(AGE)
|
||||
world.setBlockState(pos, defaultState.with(AGE, currentAge + 1), NOTIFY_LISTENERS)
|
||||
}
|
||||
override fun applyGrowth(world: World, pos: BlockPos, state: BlockState) {
|
||||
this.applyGrowth(world, pos, state, true)
|
||||
}
|
||||
|
||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||
@@ -64,15 +54,29 @@ class MintBlock(private val mintType: MintType, settings: Settings) : PlantBlock
|
||||
builder.add(IS_WILD)
|
||||
}
|
||||
|
||||
override fun canPlantOnTop(floor: BlockState, world: BlockView, pos: BlockPos): Boolean {
|
||||
val topState = world.getBlockState(pos.up())
|
||||
if (topState.block is MintBlock && isWild(topState)) {
|
||||
return true
|
||||
}
|
||||
return floor.isOf(Blocks.FARMLAND)
|
||||
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
|
||||
val floor = world.getBlockState(pos.down())
|
||||
// A bit of a copy pasta but we don't have access to the BlockState being attempted to be placed above on the canPlantOnTop
|
||||
return (world.getBaseLightLevel(pos, 0) >= 8 || world.isSkyVisible(pos)) && ((this.isWild(state) && floor.isIn(BlockTags.DIRT)) || this.canPlantOnTop(floor, world, pos))
|
||||
}
|
||||
|
||||
override fun getSeedsItem(): ItemConvertible = this.mintType.getSeed()
|
||||
|
||||
override fun getGrowthAmount(world: World): Int = 1
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AGE_TO_SHAPE[this.getAge(state)]
|
||||
|
||||
fun isWild(state: BlockState): Boolean = state.get(IS_WILD)
|
||||
|
||||
private fun applyGrowth(world: World, pos: BlockPos, state: BlockState, useRandomGrowthAmount: Boolean) {
|
||||
val growthAmount = if (useRandomGrowthAmount) this.getGrowthAmount(world) else 1
|
||||
val newAge = (this.getAge(state) + growthAmount).coerceAtMost(MATURE_AGE)
|
||||
world.setBlockState(pos, state.with(AGE, newAge), NOTIFY_LISTENERS)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
enum class MintType {
|
||||
|
||||
RED,
|
||||
BLUE,
|
||||
CYAN,
|
||||
@@ -80,49 +84,59 @@ class MintBlock(private val mintType: MintType, settings: Settings) : PlantBlock
|
||||
GREEN,
|
||||
WHITE;
|
||||
|
||||
fun getSeed(): Item {
|
||||
return when (this) {
|
||||
RED -> CobblemonItems.RED_MINT_SEEDS
|
||||
BLUE -> CobblemonItems.BLUE_MINT_SEEDS
|
||||
CYAN -> CobblemonItems.CYAN_MINT_SEEDS
|
||||
PINK -> CobblemonItems.PINK_MINT_SEEDS
|
||||
GREEN -> CobblemonItems.GREEN_MINT_SEEDS
|
||||
WHITE -> CobblemonItems.WHITE_MINT_SEEDS
|
||||
}
|
||||
fun getSeed(): Item = when (this) {
|
||||
RED -> CobblemonItems.RED_MINT_SEEDS
|
||||
BLUE -> CobblemonItems.BLUE_MINT_SEEDS
|
||||
CYAN -> CobblemonItems.CYAN_MINT_SEEDS
|
||||
PINK -> CobblemonItems.PINK_MINT_SEEDS
|
||||
GREEN -> CobblemonItems.GREEN_MINT_SEEDS
|
||||
WHITE -> CobblemonItems.WHITE_MINT_SEEDS
|
||||
}
|
||||
|
||||
fun getLeaf(): Item {
|
||||
return when (this) {
|
||||
RED -> CobblemonItems.RED_MINT_LEAF
|
||||
BLUE -> CobblemonItems.BLUE_MINT_LEAF
|
||||
CYAN -> CobblemonItems.CYAN_MINT_LEAF
|
||||
PINK -> CobblemonItems.PINK_MINT_LEAF
|
||||
GREEN -> CobblemonItems.GREEN_MINT_LEAF
|
||||
WHITE -> CobblemonItems.WHITE_MINT_LEAF
|
||||
}
|
||||
fun getLeaf(): MintLeafItem = when (this) {
|
||||
RED -> CobblemonItems.RED_MINT_LEAF
|
||||
BLUE -> CobblemonItems.BLUE_MINT_LEAF
|
||||
CYAN -> CobblemonItems.CYAN_MINT_LEAF
|
||||
PINK -> CobblemonItems.PINK_MINT_LEAF
|
||||
GREEN -> CobblemonItems.GREEN_MINT_LEAF
|
||||
WHITE -> CobblemonItems.WHITE_MINT_LEAF
|
||||
}
|
||||
|
||||
fun getCropBlock(): Block {
|
||||
return when (this) {
|
||||
RED -> CobblemonBlocks.RED_MINT
|
||||
BLUE -> CobblemonBlocks.BLUE_MINT
|
||||
CYAN -> CobblemonBlocks.CYAN_MINT
|
||||
PINK -> CobblemonBlocks.PINK_MINT
|
||||
GREEN -> CobblemonBlocks.GREEN_MINT
|
||||
WHITE -> CobblemonBlocks.WHITE_MINT
|
||||
}
|
||||
fun getCropBlock(): MintBlock = when (this) {
|
||||
RED -> CobblemonBlocks.RED_MINT
|
||||
BLUE -> CobblemonBlocks.BLUE_MINT
|
||||
CYAN -> CobblemonBlocks.CYAN_MINT
|
||||
PINK -> CobblemonBlocks.PINK_MINT
|
||||
GREEN -> CobblemonBlocks.GREEN_MINT
|
||||
WHITE -> CobblemonBlocks.WHITE_MINT
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MATURE_AGE = 3
|
||||
val AGE: IntProperty = Properties.AGE_3
|
||||
|
||||
val AGE: IntProperty = CropBlock.AGE
|
||||
const val MATURE_AGE = MAX_AGE
|
||||
val IS_WILD: BooleanProperty = BooleanProperty.of("is_wild")
|
||||
|
||||
private val AGE_0_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.1, 1.0)
|
||||
private val AGE_1_TO_2_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.2, 1.0)
|
||||
private val AGE_3_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.3, 1.0)
|
||||
private val AGE_4_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.4, 1.0)
|
||||
private val AGE_5_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.5, 1.0)
|
||||
private val AGE_6_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.6, 1.0)
|
||||
private val AGE_7_SHAPE = VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.7, 1.0)
|
||||
|
||||
val AGE_TO_SHAPE = arrayOf(
|
||||
createCuboidShape(5.0, -1.0, 5.0, 11.0, 1.0, 11.0),
|
||||
createCuboidShape(3.0, -1.0, 3.0, 13.0, 2.0, 13.0),
|
||||
createCuboidShape(3.0, -1.0, 3.0, 13.0, 4.0, 13.0),
|
||||
createCuboidShape(3.0, -1.0, 3.0, 13.0, 8.0, 13.0)
|
||||
AGE_0_SHAPE,
|
||||
AGE_1_TO_2_SHAPE,
|
||||
AGE_1_TO_2_SHAPE,
|
||||
AGE_3_SHAPE,
|
||||
AGE_4_SHAPE,
|
||||
AGE_5_SHAPE,
|
||||
AGE_6_SHAPE,
|
||||
AGE_7_SHAPE
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,80 +8,148 @@
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import kotlin.math.min
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.mulch.MulchVariant
|
||||
import com.cobblemon.mod.common.api.mulch.Mulchable
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.block.Fertilizable
|
||||
import net.minecraft.block.PlantBlock
|
||||
import net.minecraft.block.CropBlock
|
||||
import net.minecraft.block.ShapeContext
|
||||
import net.minecraft.item.ItemConvertible
|
||||
import net.minecraft.registry.tag.BlockTags
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.BooleanProperty
|
||||
import net.minecraft.state.property.EnumProperty
|
||||
import net.minecraft.state.property.IntProperty
|
||||
import net.minecraft.util.StringIdentifiable
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldView
|
||||
|
||||
class RevivalHerbBlock(settings: Settings) : PlantBlock(settings), Fertilizable {
|
||||
companion object {
|
||||
const val MIN_AGE = 0
|
||||
const val MAX_AGE = 8
|
||||
val AGE = IntProperty.of("age", MIN_AGE, MAX_AGE)
|
||||
val IS_WILD = BooleanProperty.of("is_wild")
|
||||
|
||||
val STAGE_0_AABB = VoxelShapes.cuboid(0.0, 0.0, 0.0, 1.0, 0.2, 1.0)
|
||||
val STAGE_1_AABB = VoxelShapes.cuboid(0.0, 0.0, 0.0, 1.0, 0.4, 1.0)
|
||||
val STAGE_2_AABB = VoxelShapes.cuboid(0.0, 0.0, 0.0, 1.0, 0.6, 1.0)
|
||||
val STAGE_3_AABB = VoxelShapes.cuboid(0.0, 0.0, 0.0, 1.0, 0.8, 1.0)
|
||||
}
|
||||
@Suppress("OVERRIDE_DEPRECATION", "MemberVisibilityCanBePrivate", "unused")
|
||||
class RevivalHerbBlock(settings: Settings) : CropBlock(settings), Mulchable {
|
||||
|
||||
init {
|
||||
defaultState = this.stateManager.defaultState.with(AGE, MIN_AGE).with(IS_WILD, false)
|
||||
defaultState = this.stateManager.defaultState.with(AGE, MIN_AGE)
|
||||
.with(IS_WILD, false)
|
||||
.with(MUTATION, Mutation.NONE)
|
||||
}
|
||||
|
||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||
builder.add(AGE)
|
||||
builder.add(IS_WILD)
|
||||
builder.add(MUTATION)
|
||||
}
|
||||
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState): Boolean {
|
||||
return state.get(AGE) < MAX_AGE
|
||||
}
|
||||
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
|
||||
world.setBlockState(pos, state.with(AGE, min(state.get(AGE) + 1, MAX_AGE)), NOTIFY_LISTENERS)
|
||||
}
|
||||
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean): Boolean {
|
||||
return state.get(AGE) < MAX_AGE
|
||||
}
|
||||
override fun getAgeProperty(): IntProperty = AGE
|
||||
|
||||
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
|
||||
val down = world.getBlockState(pos.down())
|
||||
val existing = world.getBlockState(pos)
|
||||
return if (existing.isAir || existing.block == Blocks.MOSS_CARPET) {
|
||||
(down.isIn(BlockTags.DIRT) && state.get(IS_WILD)) || down.block == Blocks.FARMLAND
|
||||
} else {
|
||||
false
|
||||
val floor = world.getBlockState(pos.down())
|
||||
// A bit of a copy pasta but we don't have access to the BlockState being attempted to be placed above on the canPlantOnTop
|
||||
return (world.getBaseLightLevel(pos, 0) >= 8 || world.isSkyVisible(pos)) && ((state.get(IS_WILD) && floor.isIn(BlockTags.DIRT)) || this.canPlantOnTop(floor, world, pos))
|
||||
}
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AGE_SHAPES.getOrNull(this.getAge(state)) ?: VoxelShapes.fullCube()
|
||||
|
||||
override fun getSeedsItem(): ItemConvertible = CobblemonItems.REVIVAL_HERB
|
||||
|
||||
override fun canHaveMulchApplied(world: ServerWorld, pos: BlockPos, state: BlockState, variant: MulchVariant): Boolean =
|
||||
variant == MulchVariant.SURPRISE && this.getAge(state) <= MUTABLE_MAX_AGE && !this.isMutated(state)
|
||||
|
||||
override fun applyMulch(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState, variant: MulchVariant) {
|
||||
val picked = Mutation.values().filterNot { it == Mutation.NONE }.random()
|
||||
world.setBlockState(pos, state.with(MUTATION, picked).with(AGE, MUTABLE_MAX_AGE + 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current [Mutation] of the given [state].
|
||||
*
|
||||
* @param state The [BlockState] being queried.
|
||||
* @return The current [Mutation].
|
||||
*/
|
||||
fun mutationOf(state: BlockState): Mutation = state.get(MUTATION)
|
||||
|
||||
/**
|
||||
* Checks if the given [state] has a [Mutation] different from [Mutation.NONE].
|
||||
*
|
||||
* @param state The [BlockState] being queried.
|
||||
* @return If a mutation has occurred.
|
||||
*/
|
||||
fun isMutated(state: BlockState): Boolean = this.mutationOf(state) != Mutation.NONE
|
||||
|
||||
override fun getMaxAge(): Int = MAX_AGE
|
||||
|
||||
// DO NOT use withAge
|
||||
// Explanation for these 2 beautiful copy pasta are basically that we need to keep the blockstate and that's not possible with the default impl :(
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
if (world.getBaseLightLevel(pos, 0) < 9 || this.isMature(state)) {
|
||||
return
|
||||
}
|
||||
val currentMoisture = getAvailableMoisture(this, world, pos)
|
||||
if (random.nextInt((25F / currentMoisture).toInt() + 1) == 0) {
|
||||
this.applyGrowth(world, pos, state, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasRandomTicks(state: BlockState) = state.get(AGE) < MAX_AGE
|
||||
|
||||
override fun getOutlineShape(
|
||||
state: BlockState,
|
||||
world: BlockView,
|
||||
pos: BlockPos,
|
||||
context: ShapeContext
|
||||
) = when (state.get(AGE)) {
|
||||
1, 2 -> STAGE_0_AABB
|
||||
3, 4, 5 -> STAGE_1_AABB
|
||||
6, 7, 8 -> STAGE_2_AABB
|
||||
else -> STAGE_3_AABB
|
||||
override fun applyGrowth(world: World, pos: BlockPos, state: BlockState) {
|
||||
this.applyGrowth(world, pos, state, true)
|
||||
}
|
||||
|
||||
private fun applyGrowth(world: World, pos: BlockPos, state: BlockState, useRandomGrowthAmount: Boolean) {
|
||||
val growthAmount = if (useRandomGrowthAmount) this.getGrowthAmount(world) else 1
|
||||
val newAge = (this.getAge(state) + growthAmount).coerceAtMost(this.maxAge)
|
||||
world.setBlockState(pos, state.with(AGE, newAge), NOTIFY_LISTENERS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the possible mutation states of this plant.
|
||||
*/
|
||||
enum class Mutation : StringIdentifiable {
|
||||
|
||||
NONE,
|
||||
MENTAL,
|
||||
POWER,
|
||||
WHITE,
|
||||
MIRROR;
|
||||
|
||||
override fun asString(): String = this.name.lowercase()
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val MIN_AGE = 0
|
||||
const val MAX_AGE = 8
|
||||
|
||||
/**
|
||||
* This represents the max age the plant can be at before no longer being able to mutate.
|
||||
*/
|
||||
const val MUTABLE_MAX_AGE = 6
|
||||
val AGE = IntProperty.of("age", MIN_AGE, MAX_AGE)
|
||||
val IS_WILD = BooleanProperty.of("is_wild")
|
||||
val MUTATION = EnumProperty.of("mutation", Mutation::class.java)
|
||||
|
||||
/**
|
||||
* The [VoxelShape] equivalent to a certain age.
|
||||
* Highest index is [MAX_AGE].
|
||||
*/
|
||||
val AGE_SHAPES = arrayOf(
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.1, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.2, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.3, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.4, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.5, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.7, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.7, 1.0),
|
||||
VoxelShapes.cuboid(0.0, -0.9, 0.0, 1.0, 0.9, 1.0),
|
||||
VoxelShapes.fullCube()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.block
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.sound.SoundCategory
|
||||
import net.minecraft.sound.SoundEvents
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldAccess
|
||||
import net.minecraft.world.WorldView
|
||||
import net.minecraft.world.event.GameEvent
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION", "UNUSED_PARAMETER", "MemberVisibilityCanBePrivate", "DEPRECATION")
|
||||
abstract class RootBlock(settings: Settings) : Block(settings), Fertilizable, ShearableBlock {
|
||||
|
||||
private val possibleDirections = setOf(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST)
|
||||
|
||||
override fun hasRandomTicks(state: BlockState) = true
|
||||
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
// Check for propagation
|
||||
if (random.nextDouble() < Cobblemon.config.bigRootPropagationChance && world.getLightLevel(pos) < MAX_PROPAGATING_LIGHT_LEVEL) {
|
||||
this.spreadFrom(world, pos, random)
|
||||
}
|
||||
}
|
||||
|
||||
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean = this.canGoOn(state, world, pos) { true }
|
||||
|
||||
override fun getStateForNeighborUpdate(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
world: WorldAccess,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
return if (direction == Direction.UP && !this.canPlaceAt(state, world, pos)) Blocks.AIR.defaultState else super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = this.canSpread(world, pos, state)
|
||||
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = this.canSpread(world, pos, state)
|
||||
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
|
||||
this.spreadFrom(world, pos, random)
|
||||
}
|
||||
|
||||
override fun getRenderType(state: BlockState) = BlockRenderType.MODEL
|
||||
|
||||
fun spreadFrom(world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
val possibleDirections = this.possibleDirections.toMutableSet()
|
||||
while (possibleDirections.isNotEmpty()) {
|
||||
val picked = possibleDirections.random()
|
||||
possibleDirections.remove(picked)
|
||||
val adjacent = pos.offset(picked)
|
||||
if (this.canSpreadTo(this.defaultState, world, adjacent)) {
|
||||
val resultingSpread = this.spreadingRoot(random)
|
||||
val event = BigRootPropagatedEvent(
|
||||
world = world,
|
||||
pos = pos,
|
||||
newRootPosition = adjacent,
|
||||
resultingSpread = resultingSpread
|
||||
)
|
||||
CobblemonEvents.BIG_ROOT_PROPAGATED.postThen(
|
||||
event = event,
|
||||
ifSucceeded = { ev ->
|
||||
world.setBlockState(
|
||||
ev.newRootPosition,
|
||||
ev.resultingSpread
|
||||
)
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun attemptShear(world: World, state: BlockState, pos: BlockPos, successCallback: () -> Unit): Boolean {
|
||||
// We always allow the shearing at the moment but hey if it ever changes at least it's easy to do so.
|
||||
world.playSound(null, pos, SoundEvents.ENTITY_SHEEP_SHEAR, SoundCategory.BLOCKS, 1F, 1F)
|
||||
world.setBlockState(pos, this.shearedResultingState())
|
||||
val shearedDrop = this.shearedDrop()
|
||||
if (!shearedDrop.isEmpty) {
|
||||
dropStack(world, pos, shearedDrop)
|
||||
}
|
||||
world.emitGameEvent(null, GameEvent.SHEAR, pos)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this block can spread to any neighbouring blocks.
|
||||
*
|
||||
* @param world The [WorldView] being queried.
|
||||
* @param pos The [BlockPos] the block is currently on.
|
||||
* @param state The [BlockState] of the block.
|
||||
* @return If any direction supports spreading.
|
||||
*/
|
||||
protected fun canSpread(world: WorldView, pos: BlockPos, state: BlockState): Boolean = this.possibleDirections.any { direction ->
|
||||
val adjacent = pos.offset(direction)
|
||||
this.canSpreadTo(this.defaultState, world, adjacent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given coordinates allow for a root to spread to.
|
||||
*
|
||||
* @param state The base root [BlockState].
|
||||
* @param world The [WorldView] being queried.
|
||||
* @param pos The [BlockPos] being queried.
|
||||
* @return If the given coordinates allow for a root to spread onto.
|
||||
*/
|
||||
protected fun canSpreadTo(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
|
||||
val existingState = world.getBlockState(pos)
|
||||
// Isn't this useless since it's always air or replaceable if this is happening, not! See the spread implementation for the need of a valid block in the target pos as well.
|
||||
return (existingState.isAir || existingState.isReplaceable) && this.canGoOn(state, world, pos) { ceiling -> ceiling.isIn(CobblemonBlockTags.ROOTS_SPREADABLE) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks the [BlockState] that results from a spread of this block.
|
||||
*
|
||||
* @param random The [Random] instance used during a spread attempt.
|
||||
* @return The [BlockState] that will represent the spread root.
|
||||
*/
|
||||
protected fun spreadingRoot(random: Random): BlockState = if (random.nextFloat() < Cobblemon.config.energyRootChance) CobblemonBlocks.ENERGY_ROOT.defaultState else this.defaultState
|
||||
|
||||
/**
|
||||
* Checks if the given coordinates allow for a root to be placed with some context.
|
||||
*
|
||||
* @param state The base root [BlockState].
|
||||
* @param world The [WorldView] being queried.
|
||||
* @param pos The [BlockPos] being queried.
|
||||
* @param ceilingValidator An extra condition to validate if the block it will be placed on allows for it based on context.
|
||||
* @return If the given coordinates allow for a root to be set.
|
||||
*/
|
||||
protected fun canGoOn(state: BlockState, world: WorldView, pos: BlockPos, ceilingValidator: (ceiling: BlockState) -> Boolean): Boolean {
|
||||
val up = pos.up()
|
||||
val upState = world.getBlockState(up)
|
||||
return upState.isSideSolidFullSquare(world, up, Direction.DOWN) && ceilingValidator(upState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the [BlockState] when sheared.
|
||||
*
|
||||
* @return The resulting [BlockState] when this [ShearableBlock] is sheared.
|
||||
*/
|
||||
protected abstract fun shearedResultingState(): BlockState
|
||||
|
||||
/**
|
||||
* Resolves the [ItemStack] drop when sheared.
|
||||
*
|
||||
* @return The resulting [ItemStack] to be dropped when this [ShearableBlock] is sheared.
|
||||
*/
|
||||
protected abstract fun shearedDrop(): ItemStack
|
||||
|
||||
companion object {
|
||||
|
||||
const val MAX_PROPAGATING_LIGHT_LEVEL = 11
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.block
|
||||
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
|
||||
/**
|
||||
* Represents a block that can be sheared.
|
||||
* This is used to compliment our Mixin implementation for Dispenser compatibility.
|
||||
*/
|
||||
interface ShearableBlock {
|
||||
|
||||
/**
|
||||
* Attempts to shear this block.
|
||||
*
|
||||
* @param world The [World] where the shear is being attempted.
|
||||
* @param state The current [BlockState] of this block.
|
||||
* @param pos The [BlockPos] of this block.
|
||||
* @param successCallback The callback invoked if the shear attempt was successful.
|
||||
* @return If the shearing was successful.
|
||||
*/
|
||||
fun attemptShear(world: World, state: BlockState, pos: BlockPos, successCallback: () -> Unit = {}): Boolean
|
||||
|
||||
}
|
||||
@@ -9,77 +9,53 @@
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.block.VivichokeBlock
|
||||
import kotlin.math.min
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.block.Fertilizable
|
||||
import net.minecraft.block.PlantBlock
|
||||
import net.minecraft.block.CropBlock
|
||||
import net.minecraft.block.ShapeContext
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.BooleanProperty
|
||||
import net.minecraft.state.property.IntProperty
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.item.ItemConvertible
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.random.Random
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldView
|
||||
|
||||
class VivichokeBlock(settings: Settings) : PlantBlock(settings), Fertilizable {
|
||||
init {
|
||||
defaultState = stateManager.defaultState
|
||||
.with(AGE, 0)
|
||||
}
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
class VivichokeBlock(settings: Settings) : CropBlock(settings) {
|
||||
|
||||
override fun getPickStack(world: BlockView, pos: BlockPos, state: BlockState) = ItemStack(CobblemonItems.VIVICHOKE)
|
||||
private fun isMature(state: BlockState) = state.get(AGE) == MATURE_AGE
|
||||
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = !isMature(state)
|
||||
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = true
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AGE_TO_SHAPE.getOrElse(state.get(this.ageProperty)) { VoxelShapes.fullCube() }
|
||||
|
||||
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
|
||||
world.setBlockState(pos, state.with(AGE, min(state.get(AGE) + 1, MATURE_AGE)), NOTIFY_LISTENERS)
|
||||
}
|
||||
// This is a design choice, they shouldn't grow more than a single stage at a time.
|
||||
override fun getGrowthAmount(world: World): Int = 1
|
||||
|
||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext) = AGE_TO_SHAPE[state.get(AGE)]
|
||||
override fun hasRandomTicks(state: BlockState) = !isMature(state)
|
||||
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
|
||||
if (world.getBaseLightLevel(pos, 0) >= 0) {
|
||||
if (isMature(state)) return
|
||||
if (random.nextInt(50) != 0) return
|
||||
|
||||
val currentAge = state.get(AGE)
|
||||
world.setBlockState(pos, defaultState.with(AGE, currentAge + 1), NOTIFY_LISTENERS)
|
||||
}
|
||||
}
|
||||
|
||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||
builder.add(AGE)
|
||||
}
|
||||
|
||||
override fun canPlantOnTop(floor: BlockState, world: BlockView, pos: BlockPos): Boolean {
|
||||
return floor.isOf(Blocks.FARMLAND)
|
||||
}
|
||||
override fun getSeedsItem(): ItemConvertible = CobblemonItems.VIVICHOKE_SEEDS
|
||||
|
||||
companion object {
|
||||
const val MATURE_AGE = 7
|
||||
val AGE: IntProperty = Properties.AGE_7
|
||||
val STAGE_0_SHAPE = createCuboidShape(5.0, -1.0, 5.0, 11.0, 1.0, 11.0)
|
||||
val STAGE_1_SHAPE = createCuboidShape(3.0, -1.0, 3.0, 13.0, 2.0, 13.0)
|
||||
val STAGE_2_SHAPE = createCuboidShape(3.0, -1.0, 3.0, 13.0, 4.0, 13.0)
|
||||
val STAGE_3_SHAPE = createCuboidShape(3.0, -1.0, 3.0, 13.0, 8.0, 13.0)
|
||||
|
||||
private val STAGE_0_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 2.0, 10.0)
|
||||
private val STAGE_1_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 5.0, 10.0)
|
||||
private val STAGE_2_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 7.0, 10.0)
|
||||
private val STAGE_3_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 9.0, 10.0)
|
||||
private val STAGE_4_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 7.0, 10.0)
|
||||
private val STAGE_5_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 8.0, 10.0)
|
||||
private val STAGE_6_SHAPE = createCuboidShape(6.0, -1.0, 6.0, 10.0, 9.0, 10.0)
|
||||
private val STAGE_7_SHAPE = VoxelShapes.union(
|
||||
createCuboidShape(6.0, -1.0, 6.0, 10.0, 10.0, 10.0),
|
||||
createCuboidShape(5.5, 10.0, 5.5, 10.5, 14.0, 10.5)
|
||||
)
|
||||
|
||||
/**
|
||||
* An array with the equivalent [VoxelShape] of a growth stage as the index.
|
||||
*/
|
||||
val AGE_TO_SHAPE = arrayOf(
|
||||
STAGE_0_SHAPE,
|
||||
STAGE_1_SHAPE,
|
||||
STAGE_1_SHAPE,
|
||||
STAGE_1_SHAPE,
|
||||
STAGE_2_SHAPE,
|
||||
STAGE_2_SHAPE,
|
||||
STAGE_2_SHAPE,
|
||||
STAGE_3_SHAPE,
|
||||
STAGE_4_SHAPE,
|
||||
STAGE_5_SHAPE,
|
||||
STAGE_6_SHAPE,
|
||||
STAGE_7_SHAPE
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -92,7 +92,7 @@ class HealingMachineBlockEntity(
|
||||
}
|
||||
}
|
||||
this.currentUser = user
|
||||
this.healTimeLeft = 60
|
||||
this.healTimeLeft = 24
|
||||
|
||||
markUpdated()
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ import net.minecraft.client.render.entity.EntityRenderer
|
||||
import net.minecraft.client.render.entity.EntityRendererFactory
|
||||
import net.minecraft.client.render.entity.LivingEntityRenderer
|
||||
import net.minecraft.client.render.entity.model.PlayerEntityModel
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.resource.ResourceManager
|
||||
@@ -99,6 +98,7 @@ object CobblemonClient {
|
||||
PlatformEvents.CLIENT_ITEM_TOOLTIP.subscribe { event ->
|
||||
val stack = event.stack
|
||||
val lines = event.lines
|
||||
@Suppress("DEPRECATION")
|
||||
if (stack.item.registryEntry.key.isPresent && stack.item.registryEntry.key.get().value.namespace == Cobblemon.MODID) {
|
||||
if (stack.nbt?.getBoolean(DataKeys.HIDE_TOOLTIP) == true) {
|
||||
return@subscribe
|
||||
@@ -150,7 +150,7 @@ object CobblemonClient {
|
||||
CobblemonBlocks.WHITE_APRICORN,
|
||||
CobblemonBlocks.YELLOW_APRICORN,
|
||||
CobblemonBlocks.HEALING_MACHINE,
|
||||
CobblemonBlocks.MEDICINAL_LEEK_CROP,
|
||||
CobblemonBlocks.MEDICINAL_LEEK,
|
||||
CobblemonBlocks.HEALING_MACHINE,
|
||||
CobblemonBlocks.RED_MINT,
|
||||
CobblemonBlocks.BLUE_MINT,
|
||||
@@ -163,6 +163,8 @@ object CobblemonClient {
|
||||
CobblemonBlocks.BIG_ROOT,
|
||||
CobblemonBlocks.REVIVAL_HERB,
|
||||
CobblemonBlocks.VIVICHOKE_SEEDS,
|
||||
CobblemonBlocks.PEP_UP_FLOWER,
|
||||
CobblemonBlocks.POTTED_PEP_UP_FLOWER
|
||||
)
|
||||
}
|
||||
|
||||
@@ -174,10 +176,11 @@ object CobblemonClient {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun onAddLayer(skinMap: Map<String, EntityRenderer<out PlayerEntity>>?) {
|
||||
var renderer: LivingEntityRenderer<PlayerEntity, PlayerEntityModel<PlayerEntity>>? = skinMap?.get("default") as LivingEntityRenderer<PlayerEntity, PlayerEntityModel<PlayerEntity>>
|
||||
renderer?.addFeature(PokemonOnShoulderRenderer(renderer))
|
||||
renderer = skinMap.get("slim") as LivingEntityRenderer<PlayerEntity, PlayerEntityModel<PlayerEntity>>?
|
||||
renderer = skinMap["slim"] as LivingEntityRenderer<PlayerEntity, PlayerEntityModel<PlayerEntity>>?
|
||||
renderer?.addFeature(PokemonOnShoulderRenderer(renderer))
|
||||
}
|
||||
|
||||
|
||||
+31
-20
@@ -56,25 +56,36 @@ class BattleSwitchPokemonSelection(
|
||||
) {
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SWITCH_TILE_WIDTH) && mouseY in (y..(y + SWITCH_TILE_HEIGHT))
|
||||
fun render(context: DrawContext, mouseX: Double, mouseY: Double, deltaTicks: Float) {
|
||||
CobblemonClient.battleOverlay.drawBattleTile(
|
||||
context = context,
|
||||
x = x,
|
||||
y = y,
|
||||
reversed = false,
|
||||
species = pokemon.species,
|
||||
level = pokemon.level,
|
||||
aspects = pokemon.aspects,
|
||||
displayName = pokemon.getDisplayName(),
|
||||
gender = pokemon.gender,
|
||||
status = pokemon.status?.status,
|
||||
maxHealth = pokemon.hp,
|
||||
health = pokemon.currentHealth.toFloat(),
|
||||
isFlatHealth = true,
|
||||
state = null,
|
||||
colour = null,
|
||||
opacity = selection.opacity,
|
||||
partialTicks = deltaTicks
|
||||
)
|
||||
val healthRatioSplits = showdownPokemon.condition.split(" ")[0].split("/")
|
||||
try {
|
||||
val (hp, maxHp) = if (healthRatioSplits.size == 1) {
|
||||
0 to 0
|
||||
} else {
|
||||
healthRatioSplits[0].toInt() to pokemon.hp
|
||||
}
|
||||
CobblemonClient.battleOverlay.drawBattleTile(
|
||||
context = context,
|
||||
x = x,
|
||||
y = y,
|
||||
reversed = false,
|
||||
species = pokemon.species,
|
||||
level = pokemon.level,
|
||||
aspects = pokemon.aspects,
|
||||
displayName = pokemon.getDisplayName(),
|
||||
gender = pokemon.gender,
|
||||
status = pokemon.status?.status,
|
||||
maxHealth = maxHp,
|
||||
health = hp.toFloat(),
|
||||
isFlatHealth = true,
|
||||
state = null,
|
||||
colour = null,
|
||||
opacity = selection.opacity,
|
||||
partialTicks = deltaTicks
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
println(showdownPokemon.condition)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +100,7 @@ class BattleSwitchPokemonSelection(
|
||||
}
|
||||
.filter { it.second.uuid !in battleGUI.actor!!.activePokemon.map { it.battlePokemon?.uuid } }
|
||||
.filter { it.second.uuid !in switchingInPokemon }
|
||||
.filter { it.second.currentHealth > 0 }
|
||||
.filter { "fnt" !in it.first.condition || it.first.reviving }
|
||||
|
||||
showdownPokemonToPokemon.forEachIndexed { index, (showdownPokemon, pokemon) ->
|
||||
val row = index / 2
|
||||
|
||||
+58
-13
@@ -8,22 +8,33 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.interact.moveselect
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.callback.MoveSelectDTO
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.client.gui.ExitButton
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.util.UUID
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.sound.SoundEvent
|
||||
import net.minecraft.text.MutableText
|
||||
import net.minecraft.text.Text
|
||||
|
||||
class MoveSelectConfiguration(
|
||||
val title: MutableText,
|
||||
val moves: List<MoveSelectDTO>,
|
||||
val onCancel: (MoveSelectGUI) -> Unit,
|
||||
val onBack: (MoveSelectGUI) -> Unit,
|
||||
val onSelect: (MoveSelectGUI, MoveSelectDTO) -> Unit,
|
||||
)
|
||||
|
||||
class MoveSelectGUI(
|
||||
pokemon: Pokemon
|
||||
val config: MoveSelectConfiguration
|
||||
) : Screen(Text.translatable("cobblemon.ui.interact.moveselect")) {
|
||||
companion object {
|
||||
const val WIDTH = 122
|
||||
@@ -32,19 +43,43 @@ class MoveSelectGUI(
|
||||
private val baseBackgroundResource = cobblemonResource("textures/gui/interact/move_select.png")
|
||||
}
|
||||
|
||||
private val moves = pokemon.moveSet.getMoves()
|
||||
var closed = false
|
||||
|
||||
constructor(
|
||||
title: MutableText,
|
||||
moves: List<MoveSelectDTO>,
|
||||
uuid: UUID
|
||||
): this(
|
||||
MoveSelectConfiguration(
|
||||
title = title,
|
||||
moves = moves,
|
||||
onSelect = { gui, it ->
|
||||
CobblemonNetwork.sendToServer(MoveSelectedPacket(uuid = uuid, moves.indexOf(it)))
|
||||
gui.closeProperly()
|
||||
},
|
||||
onCancel = { CobblemonNetwork.sendToServer(MoveSelectCancelledPacket(uuid = uuid)) },
|
||||
onBack = MoveSelectGUI::close
|
||||
)
|
||||
)
|
||||
|
||||
fun closeProperly() {
|
||||
closed = true
|
||||
close()
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
val x = (width - WIDTH) / 2
|
||||
val y = (height - HEIGHT) / 2
|
||||
|
||||
moves.forEachIndexed { index, move ->
|
||||
config.moves.forEachIndexed { index, move ->
|
||||
addDrawableChild(
|
||||
MoveSlotButton(
|
||||
x = x + 7,
|
||||
y = y + ((MoveSlotButton.HEIGHT + 3) * index),
|
||||
move = move,
|
||||
enabled = shouldBeEnabled(move)
|
||||
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
|
||||
move = move.moveTemplate,
|
||||
pp = move.pp,
|
||||
ppMax = move.ppMax,
|
||||
enabled = move.enabled
|
||||
) { onPress(move) }
|
||||
)
|
||||
}
|
||||
@@ -56,7 +91,7 @@ class MoveSelectGUI(
|
||||
pY = y + 115
|
||||
) {
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
MinecraftClient.getInstance().setScreen(null)
|
||||
config.onBack(this)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -80,13 +115,23 @@ class MoveSelectGUI(
|
||||
super.render(context, mouseX, mouseY, partialTicks)
|
||||
}
|
||||
|
||||
private fun onPress(move: Move) {
|
||||
private fun onPress(move: MoveSelectDTO) {
|
||||
if (!move.enabled) {
|
||||
return
|
||||
}
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
config.onSelect(this, move)
|
||||
}
|
||||
|
||||
private fun shouldBeEnabled(move: Move): Boolean {
|
||||
return true
|
||||
override fun close() {
|
||||
if (!closed) {
|
||||
config.onCancel(this)
|
||||
}
|
||||
super.close()
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc() = true
|
||||
|
||||
fun playSound(soundEvent: SoundEvent) {
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
|
||||
}
|
||||
|
||||
+31
-18
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.interact.moveselect
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.gold
|
||||
@@ -23,29 +24,35 @@ import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.math.toRGB
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.widget.ButtonWidget
|
||||
import net.minecraft.client.sound.SoundManager
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.math.MathHelper
|
||||
|
||||
class MoveSlotButton(
|
||||
x: Int, y: Int,
|
||||
val move: Move,
|
||||
val move: MoveTemplate,
|
||||
val pp: Int,
|
||||
val ppMax: Int,
|
||||
val enabled: Boolean = true,
|
||||
onPress: PressAction
|
||||
) : ButtonWidget(x, y, WIDTH, HEIGHT, Text.literal("Move"), onPress, NarrationSupplier { "".text() }) {
|
||||
|
||||
companion object {
|
||||
private val moveResource = cobblemonResource("ui/summary/summary_move.png")
|
||||
private val moveOverlayResource = cobblemonResource("ui/summary/summary_move_overlay.png")
|
||||
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
|
||||
private val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
|
||||
|
||||
const val WIDTH = 108
|
||||
const val HEIGHT = 22
|
||||
}
|
||||
|
||||
override fun render(context: DrawContext, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
hovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
|
||||
hovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
|
||||
|
||||
val moveTemplate = Moves.getByNameOrDummy(move.name)
|
||||
val rgb = moveTemplate.elementalType.hue.toRGB()
|
||||
|
||||
val alpha = if (enabled) 1.0 else 0.5
|
||||
|
||||
val matrices = context.matrices
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
@@ -58,7 +65,8 @@ class MoveSlotButton(
|
||||
textureHeight = HEIGHT * 2,
|
||||
red = rgb.first,
|
||||
green = rgb.second,
|
||||
blue = rgb.third
|
||||
blue = rgb.third,
|
||||
alpha = alpha
|
||||
)
|
||||
|
||||
blitk(
|
||||
@@ -67,24 +75,27 @@ class MoveSlotButton(
|
||||
x = x,
|
||||
y = y,
|
||||
width = WIDTH,
|
||||
height = HEIGHT
|
||||
height = HEIGHT,
|
||||
alpha = alpha
|
||||
)
|
||||
|
||||
var movePPText = Text.literal("${move.currentPp}/${move.maxPp}").bold()
|
||||
if (pp != -1 && ppMax != -1) {
|
||||
var movePPText = Text.literal("$pp/$ppMax").bold()
|
||||
|
||||
if (move.currentPp <= MathHelper.floor(move.maxPp / 2F)) {
|
||||
movePPText = if (move.currentPp == 0) movePPText.red() else movePPText.gold()
|
||||
if (pp <= MathHelper.floor(ppMax / 2F)) {
|
||||
movePPText = if (pp == 0) movePPText.red() else movePPText.gold()
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = movePPText,
|
||||
x = x + 93,
|
||||
y = y + 13,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = movePPText,
|
||||
x = x + 93,
|
||||
y = y + 13,
|
||||
centered = true
|
||||
)
|
||||
|
||||
// Type Icon
|
||||
TypeIcon(
|
||||
x = x + 2,
|
||||
@@ -109,4 +120,6 @@ class MoveSlotButton(
|
||||
shadow = true
|
||||
)
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {}
|
||||
}
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.interact.partyselect
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.callback.MoveSelectDTO
|
||||
import com.cobblemon.mod.common.api.callback.PartySelectPokemonDTO
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.client.gui.ExitButton
|
||||
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectGUI
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.util.UUID
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.sound.SoundEvent
|
||||
import net.minecraft.text.MutableText
|
||||
import net.minecraft.text.Text
|
||||
|
||||
class PartySelectConfiguration(
|
||||
val title: MutableText,
|
||||
val pokemon: List<PartySelectPokemonDTO>,
|
||||
val onCancel: (PartySelectGUI) -> Unit,
|
||||
val onBack: (PartySelectGUI) -> Unit,
|
||||
val onSelect: (PartySelectGUI, PartySelectPokemonDTO) -> Unit,
|
||||
)
|
||||
class PartySelectGUI(
|
||||
val config: PartySelectConfiguration
|
||||
) : Screen(Text.translatable("cobblemon.ui.interact.moveselect")) {
|
||||
companion object {
|
||||
const val WIDTH = 102
|
||||
const val HEIGHT = 115
|
||||
|
||||
private val baseBackgroundResource = cobblemonResource("textures/gui/interact/party_select.png")
|
||||
}
|
||||
|
||||
var closed = false
|
||||
|
||||
constructor(
|
||||
title: MutableText,
|
||||
pokemon: List<PartySelectPokemonDTO>,
|
||||
uuid: UUID
|
||||
): this(
|
||||
PartySelectConfiguration(
|
||||
title = title,
|
||||
pokemon = pokemon,
|
||||
onSelect = { gui, it ->
|
||||
CobblemonNetwork.sendToServer(PartyPokemonSelectedPacket(uuid = uuid, pokemon.indexOf(it)))
|
||||
gui.closeProperly()
|
||||
},
|
||||
onCancel = { CobblemonNetwork.sendToServer(PartySelectCancelledPacket(uuid = uuid)) },
|
||||
onBack = PartySelectGUI::close
|
||||
)
|
||||
)
|
||||
|
||||
fun closeProperly() {
|
||||
closed = true
|
||||
close()
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
val x = (width - WIDTH) / 2
|
||||
val y = (height - HEIGHT) / 2
|
||||
|
||||
config.pokemon.forEachIndexed { index, pokemon ->
|
||||
val slotX = x + 5 + (index % 2) * (PartySlotButton.WIDTH + 2)
|
||||
val slotY = y + 5 + (index / 2) * (PartySlotButton.HEIGHT + 2)
|
||||
addDrawableChild(
|
||||
PartySlotButton(
|
||||
parent = this,
|
||||
x = slotX,
|
||||
y = slotY,
|
||||
pokemon = pokemon.pokemonProperties,
|
||||
heldItem = pokemon.heldItem,
|
||||
hpRatio = pokemon.hpRatio,
|
||||
enabled = pokemon.enabled
|
||||
) { onPress(pokemon) }
|
||||
)
|
||||
}
|
||||
|
||||
// Add Exit Button
|
||||
addDrawableChild(
|
||||
ExitButton(
|
||||
pX = x + 72,
|
||||
pY = y + 96
|
||||
) {
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
config.onBack(this)
|
||||
}
|
||||
)
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, partialTicks: Float) {
|
||||
val x = (width - WIDTH) / 2
|
||||
val y = (height - HEIGHT) / 2
|
||||
|
||||
blitk(
|
||||
matrixStack = context.matrices,
|
||||
texture = baseBackgroundResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = WIDTH,
|
||||
height = HEIGHT
|
||||
)
|
||||
|
||||
// Render all added Widgets
|
||||
super.render(context, mouseX, mouseY, partialTicks)
|
||||
}
|
||||
|
||||
private fun onPress(pokemon: PartySelectPokemonDTO) {
|
||||
if (!pokemon.enabled) {
|
||||
return
|
||||
}
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
config.onSelect(this, pokemon)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!closed) {
|
||||
config.onCancel(this)
|
||||
}
|
||||
super.close()
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc() = true
|
||||
|
||||
fun playSound(soundEvent: SoundEvent) {
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.interact.partyselect
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
|
||||
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
|
||||
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.math.fromEulerXYZDegrees
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.widget.ButtonWidget
|
||||
import net.minecraft.client.gui.widget.ButtonWidget.NarrationSupplier
|
||||
import net.minecraft.client.sound.SoundManager
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.text.Text
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
class PartySlotButton(
|
||||
x: Int, y: Int,
|
||||
val pokemon: PokemonProperties,
|
||||
val hpRatio: Float,
|
||||
val heldItem: ItemStack,
|
||||
val enabled: Boolean = true,
|
||||
val parent: PartySelectGUI,
|
||||
onPress: PressAction
|
||||
) : ButtonWidget(x, y, WIDTH, HEIGHT, Text.literal("Pokemon"), onPress, NarrationSupplier { "".text() }) {
|
||||
|
||||
companion object {
|
||||
private val slotResource = cobblemonResource("textures/gui/summary/summary_party_slot.png")
|
||||
private val slotFaintedResource = cobblemonResource("textures/gui/summary/summary_party_slot_fainted.png")
|
||||
private val slotEmptyResource = cobblemonResource("textures/gui/summary/summary_party_slot_empty.png")
|
||||
|
||||
const val WIDTH = 46
|
||||
const val HEIGHT = 27
|
||||
}
|
||||
|
||||
var animationSeconds = 0F
|
||||
var lastTime = System.currentTimeMillis()
|
||||
val state = PokemonFloatingState()
|
||||
|
||||
private val renderablePokemon = pokemon.asRenderablePokemon()
|
||||
|
||||
override fun render(context: DrawContext, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
hovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
|
||||
val alpha = if (enabled) 1.0 else 0.5
|
||||
val matrices = context.matrices
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if (hpRatio <= 0F) slotFaintedResource else slotResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height,
|
||||
vOffset = if (hovered) height else 0,
|
||||
textureHeight = height * 2,
|
||||
alpha = alpha
|
||||
)
|
||||
|
||||
context.matrices.push()
|
||||
context.matrices.translate(x.toDouble() + 13, y.toDouble() - 2, 0.0)
|
||||
|
||||
// if (!hovered) {
|
||||
// state.reset()
|
||||
// }
|
||||
|
||||
drawProfilePokemon(
|
||||
renderablePokemon = renderablePokemon,
|
||||
matrixStack = context.matrices,
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
|
||||
state = state,
|
||||
scale = 10F,
|
||||
partialTicks = if (!hovered) 0F else pPartialTicks
|
||||
)
|
||||
context.matrices.pop()
|
||||
|
||||
val halfScale = 0.5F
|
||||
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.pokeball!!.asIdentifierDefaultingNamespace().path + ".png")
|
||||
val ballHeight = 22
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = ballIcon,
|
||||
x = (x - 2) / halfScale,
|
||||
y = (y - 3) / halfScale,
|
||||
height = ballHeight,
|
||||
width = 18,
|
||||
textureHeight = ballHeight * 2,
|
||||
scale = halfScale
|
||||
)
|
||||
|
||||
val status = pokemon.status
|
||||
if (hpRatio > 0F && status != null) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = cobblemonResource("textures/gui/party/status_$status.png"),
|
||||
x = x + 42,
|
||||
y = y + 5,
|
||||
height = 14,
|
||||
width = 4
|
||||
)
|
||||
}
|
||||
|
||||
val barWidthMax = 37
|
||||
val barWidth = hpRatio * barWidthMax
|
||||
val (red, green) = getDepletableRedGreen(hpRatio)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = CobblemonResources.WHITE,
|
||||
x = x + 4,
|
||||
y = y + 25,
|
||||
width = barWidth,
|
||||
height = 1,
|
||||
textureWidth = barWidth / hpRatio,
|
||||
uOffset = barWidthMax - barWidth,
|
||||
red = red * 0.8F,
|
||||
green = green * 0.8F,
|
||||
blue = 0.27F
|
||||
)
|
||||
|
||||
if ("male" in pokemon.aspects || "female" in pokemon.aspects) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if ("male" in pokemon.aspects) PartySlotWidget.genderIconMale else PartySlotWidget.genderIconFemale,
|
||||
x = (x + 40) / halfScale,
|
||||
y = (y + 20) / halfScale,
|
||||
height = 7,
|
||||
width = 5,
|
||||
scale = halfScale
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.lv.number", pokemon.level!!),
|
||||
x = x + 31,
|
||||
y = y + 13,
|
||||
centered = true,
|
||||
shadow = true,
|
||||
scale = halfScale
|
||||
)
|
||||
|
||||
// Held Item
|
||||
if (!heldItem.isEmpty) {
|
||||
renderScaledGuiItemIcon(
|
||||
itemStack = heldItem,
|
||||
x = x + 14.0,
|
||||
y = y + 9.5,
|
||||
scale = 0.5,
|
||||
matrixStack = matrices
|
||||
)
|
||||
}
|
||||
|
||||
// Pokémon Name
|
||||
val displayName = pokemon.nickname ?: renderablePokemon.species.translatedName
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = displayName.copy(),
|
||||
x = x + 4,
|
||||
y = y + HEIGHT - 7,
|
||||
scale = halfScale
|
||||
)
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class NavigationButton(
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.PC_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (HEIGHT * SCALE)))
|
||||
|
||||
@@ -98,7 +98,7 @@ class StorageWidget(
|
||||
onPress = {
|
||||
if (!displayConfirmRelease) {
|
||||
displayConfirmRelease = true
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
playSound(CobblemonSounds.PC_CLICK)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+5
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.startselection
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.ColourLibrary
|
||||
import com.cobblemon.mod.common.api.gui.MultiLineLabelK
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
@@ -35,6 +36,7 @@ import com.cobblemon.mod.common.util.math.toRGB
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
|
||||
@@ -184,6 +186,7 @@ class StarterSelectionScreen private constructor(): Screen("cobblemon.ui.starter
|
||||
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0
|
||||
) {
|
||||
MinecraftClient.getInstance().setScreen(null)
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -281,6 +284,7 @@ class StarterSelectionScreen private constructor(): Screen("cobblemon.ui.starter
|
||||
}
|
||||
|
||||
private fun right() {
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
currentSelection = rightOfCurrentSelection()
|
||||
updateSelection()
|
||||
}
|
||||
@@ -288,6 +292,7 @@ class StarterSelectionScreen private constructor(): Screen("cobblemon.ui.starter
|
||||
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= currentCategory.pokemon.size - 1) currentSelection + 1 else 0
|
||||
|
||||
private fun left() {
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
currentSelection = leftOfCurrentSelection()
|
||||
updateSelection()
|
||||
}
|
||||
|
||||
+3
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.startselection.widgets
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
@@ -16,6 +17,7 @@ import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
|
||||
@@ -131,6 +133,7 @@ class CategoryList(
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
starterSelectionScreen.changeCategory(category = category)
|
||||
minecraft.soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.widget.TexturedButtonWidget
|
||||
import net.minecraft.client.sound.SoundManager
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
class ExitButton(
|
||||
pX: Int, pY: Int,
|
||||
@@ -38,4 +39,6 @@ class ExitButton(
|
||||
}
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) { }
|
||||
|
||||
}
|
||||
+8
@@ -8,10 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.widget.TexturedButtonWidget
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.client.sound.SoundManager
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.util.Identifier
|
||||
class ArrowButton(
|
||||
@@ -32,6 +36,10 @@ class ArrowButton(
|
||||
private const val ARROW_BUTTON_HEIGHT = 14f
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager?) {
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun renderButton(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
hovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height
|
||||
if (isHovered) {
|
||||
|
||||
+8
@@ -8,13 +8,17 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.ColourLibrary
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.widget.ButtonWidget
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.client.sound.SoundManager
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
|
||||
@@ -31,6 +35,10 @@ class SelectionButton(
|
||||
private const val SCALE = 0.7f
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager?) {
|
||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun renderButton(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val matrices = context.matrices
|
||||
if (isHovered)
|
||||
|
||||
+26
-19
@@ -21,6 +21,8 @@ import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.render.BufferRenderer
|
||||
@@ -97,6 +99,7 @@ class StatWidget(
|
||||
bufferBuilder.vertex(v2.x.toDouble(), v2.y.toDouble(), 10.0).next()
|
||||
bufferBuilder.vertex(v3.x.toDouble(), v3.y.toDouble(), 10.0).next()
|
||||
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end())
|
||||
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
|
||||
}
|
||||
|
||||
private fun drawStatHexagon(stats: Map<Stat, Int>, colour: Vector3f, maximum: Int) {
|
||||
@@ -108,45 +111,49 @@ class StatWidget(
|
||||
val hexRightX = x + 108.5
|
||||
val hexCenterX = (hexLeftX + hexRightX) / 2
|
||||
val hexCenterY = (hexTopY + hexBottomY) / 2
|
||||
val minTriangleSize = 8F
|
||||
val minXTriangleLen = sin(Math.toRadians(61.0)).toFloat() * minTriangleSize * 0.95F
|
||||
val minYTriangleLen = cos(Math.toRadians(60.0)).toFloat() * minTriangleSize
|
||||
|
||||
val triangleWidth = (hexCenterX - hexLeftX).toFloat()
|
||||
val triangleHeight = ((hexBottomY - hexTopY) / 2).toFloat()
|
||||
val triangleLongEdge = (hexCenterY - hexTopY - minTriangleSize).toFloat()
|
||||
val triangleMediumEdge = (triangleLongEdge * sin(Math.toRadians(61.0))).toFloat()
|
||||
val triangleShortEdge = (triangleLongEdge * cos(Math.toRadians(61.0))).toFloat()
|
||||
|
||||
val hpRatio = (stats.getOrDefault(Stats.HP, 0).toFloat() / maximum).coerceIn(0.075F, 1F)
|
||||
val atkRatio = (stats.getOrDefault(Stats.ATTACK, 0).toFloat() / maximum).coerceIn(0.075F, 1F)
|
||||
val defRatio = (stats.getOrDefault(Stats.DEFENCE, 0).toFloat() / maximum).coerceIn(0.075F, 1F)
|
||||
val spAtkRatio = (stats.getOrDefault(Stats.SPECIAL_ATTACK, 0).toFloat() / maximum).coerceIn(0.075F, 1F)
|
||||
val spDefRatio = (stats.getOrDefault(Stats.SPECIAL_DEFENCE, 0).toFloat() / maximum).coerceIn(0.075F, 1F)
|
||||
val spdRatio = (stats.getOrDefault(Stats.SPEED, 0).toFloat() / maximum).coerceIn(0.075F, 1F)
|
||||
val hpRatio = (stats.getOrDefault(Stats.HP, 0).toFloat() / maximum).coerceIn(0F, 1F)
|
||||
val atkRatio = (stats.getOrDefault(Stats.ATTACK, 0).toFloat() / maximum).coerceIn(0F, 1F)
|
||||
val defRatio = (stats.getOrDefault(Stats.DEFENCE, 0).toFloat() / maximum).coerceIn(0F, 1F)
|
||||
val spAtkRatio = (stats.getOrDefault(Stats.SPECIAL_ATTACK, 0).toFloat() / maximum).coerceIn(0F, 1F)
|
||||
val spDefRatio = (stats.getOrDefault(Stats.SPECIAL_DEFENCE, 0).toFloat() / maximum).coerceIn(0F, 1F)
|
||||
val spdRatio = (stats.getOrDefault(Stats.SPEED, 0).toFloat() / maximum).coerceIn(0F, 1F)
|
||||
|
||||
val hpPoint = Vec2f(
|
||||
hexCenterX.toFloat(),
|
||||
hexCenterY.toFloat() - hpRatio * triangleHeight
|
||||
hexCenterY.toFloat() - minTriangleSize - hpRatio * triangleLongEdge
|
||||
)
|
||||
|
||||
val attackPoint = Vec2f(
|
||||
hexCenterX.toFloat() + atkRatio * triangleWidth,
|
||||
hexCenterY.toFloat() - atkRatio * triangleHeight / 2
|
||||
hexCenterX.toFloat() + minXTriangleLen + atkRatio * triangleMediumEdge,
|
||||
hexCenterY.toFloat() - minYTriangleLen - atkRatio * triangleShortEdge
|
||||
)
|
||||
|
||||
val defencePoint = Vec2f(
|
||||
hexCenterX.toFloat() + defRatio * triangleWidth,
|
||||
hexCenterY.toFloat() + defRatio * triangleHeight / 2
|
||||
hexCenterX.toFloat() + minXTriangleLen + defRatio * triangleMediumEdge,
|
||||
hexCenterY.toFloat() + minYTriangleLen + defRatio * triangleShortEdge
|
||||
)
|
||||
|
||||
val specialAttackPoint = Vec2f(
|
||||
hexCenterX.toFloat() - spAtkRatio * triangleWidth,
|
||||
hexCenterY.toFloat() - spAtkRatio * triangleHeight / 2
|
||||
hexCenterX.toFloat() - minXTriangleLen - spAtkRatio * triangleMediumEdge,
|
||||
hexCenterY.toFloat() - minYTriangleLen - spAtkRatio * triangleShortEdge
|
||||
)
|
||||
|
||||
val specialDefencePoint = Vec2f(
|
||||
hexCenterX.toFloat() - spDefRatio * triangleWidth,
|
||||
hexCenterY.toFloat() + spDefRatio * triangleHeight / 2
|
||||
hexCenterX.toFloat() - minXTriangleLen - spDefRatio * triangleMediumEdge,
|
||||
hexCenterY.toFloat() + minYTriangleLen + spDefRatio * triangleShortEdge
|
||||
)
|
||||
|
||||
val speedPoint = Vec2f(
|
||||
hexCenterX.toFloat(),
|
||||
hexCenterY.toFloat() + spdRatio * triangleHeight
|
||||
hexCenterY.toFloat() + minTriangleSize + spdRatio * triangleLongEdge
|
||||
)
|
||||
|
||||
val centerPoint = Vec2f(
|
||||
@@ -521,4 +528,4 @@ class StatWidget(
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -9,17 +9,17 @@
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.battles.BallActionResponse
|
||||
import com.cobblemon.mod.common.battles.ForcePassActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyCaptureResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyPassResponsePacket
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object BattleApplyCaptureResponseHandler : ClientNetworkPacketHandler<BattleApplyCaptureResponsePacket> {
|
||||
override fun handle(packet: BattleApplyCaptureResponsePacket, client: MinecraftClient) {
|
||||
object BattleApplyPassResponseHandler : ClientNetworkPacketHandler<BattleApplyPassResponsePacket> {
|
||||
override fun handle(packet: BattleApplyPassResponsePacket, client: MinecraftClient) {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
val req = battle.pendingActionRequests.firstOrNull { it.response == null } ?: return
|
||||
val res = BallActionResponse()
|
||||
val res = ForcePassActionResponse()
|
||||
val gui = MinecraftClient.getInstance().currentScreen
|
||||
if (gui is BattleGUI) {
|
||||
gui.selectAction(req, res)
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.net.callback.move
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object OpenMoveCallbackHandler : ClientNetworkPacketHandler<OpenMoveCallbackPacket> {
|
||||
override fun handle(packet: OpenMoveCallbackPacket, client: MinecraftClient) {
|
||||
client.setScreen(MoveSelectGUI(packet.title, packet.moves, packet.uuid))
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.net.callback.party
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.gui.interact.partyselect.PartySelectGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object OpenPartyCallbackHandler : ClientNetworkPacketHandler<OpenPartyCallbackPacket> {
|
||||
override fun handle(packet: OpenPartyCallbackPacket, client: MinecraftClient) {
|
||||
client.setScreen(
|
||||
PartySelectGUI(
|
||||
title = packet.title,
|
||||
pokemon = packet.pokemon,
|
||||
// usePortraits = packet.usePortraits,
|
||||
// animate = packet.animate,
|
||||
uuid = packet.uuid
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.callback.partymove
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.callback.PartySelectPokemonDTO
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectConfiguration
|
||||
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectGUI
|
||||
import com.cobblemon.mod.common.client.gui.interact.partyselect.PartySelectConfiguration
|
||||
import com.cobblemon.mod.common.client.gui.interact.partyselect.PartySelectGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object OpenPartyMoveCallbackHandler : ClientNetworkPacketHandler<OpenPartyMoveCallbackPacket> {
|
||||
override fun handle(packet: OpenPartyMoveCallbackPacket, client: MinecraftClient) {
|
||||
val pokemonToMoves = packet.pokemonList.toMap()
|
||||
val cancel: (Any) -> Unit = {
|
||||
CobblemonNetwork.sendToServer(PartyMoveSelectCancelledPacket(uuid = packet.uuid))
|
||||
if (it is MoveSelectGUI) {
|
||||
it.closeProperly()
|
||||
} else if (it is PartySelectGUI) {
|
||||
it.closeProperly()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lateinit var partySelectConfiguration: PartySelectConfiguration
|
||||
|
||||
fun makeMoveSelectConfiguration(pokemonSelectDTO: PartySelectPokemonDTO): MoveSelectConfiguration {
|
||||
return MoveSelectConfiguration(
|
||||
title = "".text(),
|
||||
moves = pokemonToMoves[pokemonSelectDTO]!!,
|
||||
onCancel = cancel,
|
||||
onBack = { MinecraftClient.getInstance().setScreen(PartySelectGUI(partySelectConfiguration)) },
|
||||
onSelect = { gui, moveSelectDTO ->
|
||||
val pokemonIndex = packet.pokemonList.indexOfFirst { it.first == pokemonSelectDTO }
|
||||
val moveIndex = pokemonToMoves[pokemonSelectDTO]!!.indexOf(moveSelectDTO)
|
||||
CobblemonNetwork.sendToServer(PartyPokemonMoveSelectedPacket(packet.uuid, pokemonIndex, moveIndex))
|
||||
gui.closeProperly()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
partySelectConfiguration = PartySelectConfiguration(
|
||||
title = "".text(),
|
||||
pokemon = pokemonToMoves.keys.toList(),
|
||||
onCancel = cancel,
|
||||
onBack = cancel,
|
||||
onSelect = { _, it -> MinecraftClient.getInstance().setScreen(MoveSelectGUI(makeMoveSelectConfiguration(it))) }
|
||||
)
|
||||
|
||||
MinecraftClient.getInstance().setScreen(PartySelectGUI(partySelectConfiguration))
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ object ReloadShowdownCommand {
|
||||
|
||||
private fun execute(context: CommandContext<ServerCommandSource>): Int {
|
||||
try {
|
||||
ShowdownService.get().closeConnection()
|
||||
ShowdownService.get().openConnection()
|
||||
ShowdownService.service.closeConnection()
|
||||
ShowdownService.service.openConnection()
|
||||
context.source.sendMessage(Text.of("Reloaded showdown"))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -45,7 +45,7 @@ object TestPartySlotCommand {
|
||||
val player = context.player(PLAYER)
|
||||
val slot = IntegerArgumentType.getInteger(context, SLOT)
|
||||
val properties = PokemonPropertiesArgumentType.getPokemonProperties(context, PROPERTIES)
|
||||
return if (player.party().get(slot - 1)?.createPokemonProperties(*PokemonPropertyExtractor.ALL)
|
||||
return if (player.party().get(slot - 1)?.createPokemonProperties(PokemonPropertyExtractor.ALL)
|
||||
?.let { properties.isSubSetOf(it) } == true) Command.SINGLE_SUCCESS else NO_SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ object TestPcSlotCommand {
|
||||
val boxNumber = IntegerArgumentType.getInteger(context, BOX)
|
||||
val slot = IntegerArgumentType.getInteger(context, SLOT)
|
||||
val properties = PokemonPropertiesArgumentType.getPokemonProperties(context, PROPERTIES)
|
||||
return if (player.pc().boxes[boxNumber - 1][slot - 1]?.createPokemonProperties(*PokemonPropertyExtractor.ALL)
|
||||
return if (player.pc().boxes[boxNumber - 1][slot - 1]?.createPokemonProperties(PokemonPropertyExtractor.ALL)
|
||||
?.let { properties.isSubSetOf(it) } == true) Command.SINGLE_SUCCESS else NO_SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatureAssignments
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
|
||||
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.cobblemon.mod.common.pokemon.SpeciesAdditions
|
||||
@@ -59,6 +60,7 @@ object CobblemonDataProvider : DataProvider {
|
||||
this.register(PropertiesCompletionProvider)
|
||||
this.register(SpawnDetailPresets)
|
||||
this.register(CobblemonMechanics)
|
||||
this.register(BagItems)
|
||||
|
||||
CobblemonSpawnPools.load()
|
||||
|
||||
|
||||
+15
-20
@@ -29,12 +29,13 @@ import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.BattleCaptureAction
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.BattleTypes
|
||||
import com.cobblemon.mod.common.battles.ForcePassActionResponse
|
||||
import com.cobblemon.mod.common.client.entity.EmptyPokeBallClientDelegate
|
||||
import com.cobblemon.mod.common.entity.EntityProperty
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.Poseable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyCaptureResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyPassResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleCaptureStartPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
|
||||
import com.cobblemon.mod.common.pokeball.PokeBall
|
||||
@@ -192,29 +193,23 @@ class EmptyPokeBallEntity : ThrownItemEntity, Poseable, WaterDragModifier {
|
||||
return drop()
|
||||
}
|
||||
|
||||
val request = throwerActor.request?.takeIf { throwerActor.mustChoose } ?: run {
|
||||
val canFitForcedAction = throwerActor.canFitForcedAction()
|
||||
if (!canFitForcedAction) {
|
||||
owner.sendMessage(lang("capture.not_your_turn").red())
|
||||
return drop()
|
||||
}
|
||||
|
||||
val countMovable = (request.active?.count() ?: 0) - request.forceSwitch.count { it }
|
||||
if (countMovable > throwerActor.expectingCaptureActions) {
|
||||
throwerActor.expectingCaptureActions++
|
||||
battle.captureActions.add(BattleCaptureAction(battle, hitBattlePokemon, this).also { it.attach() })
|
||||
battle.broadcastChatMessage(
|
||||
lang(
|
||||
"capture.attempted_capture",
|
||||
throwerActor.getName(),
|
||||
pokeBall.item().name,
|
||||
pokemonEntity.pokemon.species.translatedName
|
||||
).yellow()
|
||||
)
|
||||
battle.sendUpdate(BattleCaptureStartPacket(pokeBall.name, aspects.get(), hitBattlePokemon.getPNX()))
|
||||
throwerActor.sendUpdate(BattleApplyCaptureResponsePacket())
|
||||
} else {
|
||||
owner.sendMessage(lang("capture.not_your_turn").red())
|
||||
return drop()
|
||||
}
|
||||
battle.captureActions.add(BattleCaptureAction(battle, hitBattlePokemon, this).also { it.attach() })
|
||||
battle.broadcastChatMessage(
|
||||
lang(
|
||||
"capture.attempted_capture",
|
||||
throwerActor.getName(),
|
||||
pokeBall.item().name,
|
||||
pokemonEntity.pokemon.species.translatedName
|
||||
).yellow()
|
||||
)
|
||||
battle.sendUpdate(BattleCaptureStartPacket(pokeBall.name, aspects.get(), hitBattlePokemon.getPNX()))
|
||||
throwerActor.forceChoose(ForcePassActionResponse())
|
||||
} else if (pokemonEntity.isBusy) {
|
||||
owner?.sendMessage(lang("capture.busy", pokemonEntity.pokemon.species.translatedName).red())
|
||||
return drop()
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnMain
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes.FIRE
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
@@ -620,36 +621,53 @@ class PokemonEntity(
|
||||
override fun getMinAmbientSoundDelay() = Cobblemon.config.ambientPokemonCryTicks
|
||||
|
||||
private fun attemptItemInteraction(player: PlayerEntity, stack: ItemStack): Boolean {
|
||||
if (stack.isEmpty) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (player is ServerPlayerEntity && isBattling) {
|
||||
val battle = battleId.get().orElse(null)?.let(BattleRegistry::getBattle) ?: return false
|
||||
|
||||
val bagItemConvertible = BagItems.getConvertibleForStack(stack) ?: return false
|
||||
|
||||
val battlePokemon = battle.actors.flatMap { it.pokemonList }.find { it.effectedPokemon.uuid == pokemon.uuid } ?: return false // Shouldn't be possible but anyway
|
||||
if (battlePokemon.actor.getSide().actors.none { it.isForPlayer(player)}) {
|
||||
return true
|
||||
}
|
||||
|
||||
return bagItemConvertible.handleInteraction(player, battlePokemon, stack)
|
||||
}
|
||||
if (player !is ServerPlayerEntity || this.isBusy) {
|
||||
return false
|
||||
}
|
||||
if (!stack.isEmpty) {
|
||||
if (pokemon.getOwnerPlayer() == player) {
|
||||
val context = ItemInteractionEvolution.ItemInteractionContext(stack.item, player.world)
|
||||
pokemon.evolutions
|
||||
.filterIsInstance<ItemInteractionEvolution>()
|
||||
.forEach { evolution ->
|
||||
if (evolution.attemptEvolution(pokemon, context)) {
|
||||
if (!player.isCreative) {
|
||||
stack.decrement(1)
|
||||
}
|
||||
this.world.playSoundServer(position = this.pos, sound = CobblemonSounds.ITEM_USE, volume = 1F, pitch = 1F)
|
||||
return true
|
||||
|
||||
// Check evolution item interaction
|
||||
if (pokemon.getOwnerPlayer() == player) {
|
||||
val context = ItemInteractionEvolution.ItemInteractionContext(stack.item, player.world)
|
||||
pokemon.evolutions
|
||||
.filterIsInstance<ItemInteractionEvolution>()
|
||||
.forEach { evolution ->
|
||||
if (evolution.attemptEvolution(pokemon, context)) {
|
||||
if (!player.isCreative) {
|
||||
stack.decrement(1)
|
||||
}
|
||||
this.world.playSoundServer(position = this.pos, sound = CobblemonSounds.ITEM_USE, volume = 1F, pitch = 1F)
|
||||
return true
|
||||
}
|
||||
}
|
||||
(stack.item as? PokemonInteractiveItem)?.let {
|
||||
if (it.onInteraction(player, this, stack)) {
|
||||
it.getSound()?.let {
|
||||
this.world.playSoundServer(
|
||||
position = this.pos,
|
||||
sound = it,
|
||||
volume = 1F,
|
||||
pitch = 1F
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
(stack.item as? PokemonInteractiveItem)?.let {
|
||||
if (it.onInteraction(player, this, stack)) {
|
||||
it.getSound()?.let {
|
||||
this.world.playSoundServer(
|
||||
position = this.pos,
|
||||
sound = it,
|
||||
volume = 1F,
|
||||
pitch = 1F
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -900,7 +918,7 @@ class PokemonEntity(
|
||||
feature.enabled = true
|
||||
this.pokemon.markFeatureDirty(feature)
|
||||
this.pokemon.updateAspects()
|
||||
val i = this.random.nextInt(3)
|
||||
val i = this.random.nextInt(3) + 1
|
||||
for (j in 0 until i) {
|
||||
val itemEntity = this.dropItem(Items.WHITE_WOOL, 1) ?: return
|
||||
itemEntity.velocity = itemEntity.velocity.add(
|
||||
|
||||
+4
-1
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
|
||||
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
|
||||
import com.cobblemon.mod.common.util.playSoundServer
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
|
||||
import java.util.Optional
|
||||
import net.minecraft.entity.Entity
|
||||
import net.minecraft.entity.ai.pathing.PathNodeType
|
||||
@@ -202,7 +203,9 @@ class PokemonServerDelegate : PokemonSideDelegate {
|
||||
if (entity.deathTime == 60) {
|
||||
if (entity.owner == null) {
|
||||
entity.world.sendEntityStatus(entity, 60.toByte()) // Sends smoke effect
|
||||
(entity.drops ?: entity.pokemon.form.drops).drop(entity, entity.world as ServerWorld, entity.pos, entity.killer)
|
||||
if(entity.world.gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_LOOT)) {
|
||||
(entity.drops ?: entity.pokemon.form.drops).drop(entity, entity.world as ServerWorld, entity.pos, entity.killer)
|
||||
}
|
||||
}
|
||||
|
||||
entity.remove(Entity.RemovalReason.KILLED)
|
||||
|
||||
@@ -9,32 +9,34 @@
|
||||
package com.cobblemon.mod.common.item
|
||||
|
||||
import com.cobblemon.mod.common.block.MedicinalLeekBlock
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.block.ComposterBlock
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.AliasedBlockItem
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.ItemUsageContext
|
||||
import net.minecraft.item.PlaceableOnWaterItem
|
||||
import net.minecraft.item.*
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.TypedActionResult
|
||||
import net.minecraft.world.RaycastContext
|
||||
import net.minecraft.world.World
|
||||
|
||||
class MedicinalLeekItem(block: MedicinalLeekBlock) : AliasedBlockItem(block, Settings()) {
|
||||
class MedicinalLeekItem(block: MedicinalLeekBlock, settings: Settings) : AliasedBlockItem(block, settings) {
|
||||
|
||||
init {
|
||||
ComposterBlock.ITEM_TO_LEVEL_INCREASE_CHANCE[this] = .65F
|
||||
}
|
||||
|
||||
override fun use(world: World?, user: PlayerEntity, hand: Hand?): TypedActionResult<ItemStack?>? {
|
||||
override fun useOnBlock(context: ItemUsageContext): ActionResult = ActionResult.PASS
|
||||
|
||||
override fun use(world: World, user: PlayerEntity, hand: Hand): TypedActionResult<ItemStack> {
|
||||
val blockHitResult = PlaceableOnWaterItem.raycast(world, user, RaycastContext.FluidHandling.SOURCE_ONLY)
|
||||
val blockHitResult2 = blockHitResult.withBlockPos(blockHitResult.blockPos.up())
|
||||
val actionResult: ActionResult = super.useOnBlock(ItemUsageContext(user, hand, blockHitResult2))
|
||||
return TypedActionResult(actionResult, user.getStackInHand(hand))
|
||||
val placeResult = this.place(ItemPlacementContext(ItemUsageContext(user, hand, blockHitResult2)))
|
||||
val stack = user.getStackInHand(hand)
|
||||
// This will always be true but just in case we change it down the line.
|
||||
if (!placeResult.isAccepted && this.isFood && user.canConsume(this.foodComponent?.isAlwaysEdible == true)) {
|
||||
user.setCurrentHand(hand)
|
||||
return TypedActionResult.consume(stack)
|
||||
}
|
||||
return TypedActionResult(placeResult, stack)
|
||||
}
|
||||
|
||||
override fun useOnBlock(context: ItemUsageContext?): ActionResult? {
|
||||
return ActionResult.PASS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.item
|
||||
|
||||
import com.cobblemon.mod.common.api.mulch.Mulchable
|
||||
import com.cobblemon.mod.common.api.mulch.MulchVariant
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.ItemUsageContext
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldEvents
|
||||
|
||||
class MulchItem(val variant: MulchVariant) : CobblemonItem(Settings()) {
|
||||
|
||||
override fun useOnBlock(context: ItemUsageContext): ActionResult {
|
||||
val world = context.world
|
||||
val pos = context.blockPos
|
||||
if (this.useOnMulchAble(context.stack, world, pos)) {
|
||||
// Plays bone meal effect
|
||||
if (!world.isClient) {
|
||||
world.syncWorldEvent(WorldEvents.BONE_MEAL_USED, pos, 0)
|
||||
}
|
||||
return ActionResult.success(world.isClient)
|
||||
}
|
||||
return ActionResult.PASS
|
||||
}
|
||||
|
||||
private fun useOnMulchAble(stack: ItemStack, world: World, pos: BlockPos): Boolean {
|
||||
val state = world.getBlockState(pos)
|
||||
if (state.block is Mulchable) {
|
||||
val mulchAble = state.block as? Mulchable ?: return false
|
||||
if (world is ServerWorld && mulchAble.canHaveMulchApplied(world, pos, state, this.variant)) {
|
||||
mulchAble.applyMulch(world, world.random, pos, state, this.variant)
|
||||
stack.decrement(1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,17 +8,76 @@
|
||||
|
||||
package com.cobblemon.mod.common.item
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.CobblemonMechanics
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.block.RevivalHerbBlock
|
||||
import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import kotlin.math.ceil
|
||||
import net.minecraft.block.ComposterBlock
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.AliasedBlockItem
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.sound.SoundCategory
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.TypedActionResult
|
||||
import net.minecraft.world.World
|
||||
|
||||
class RevivalHerbItem(block: RevivalHerbBlock) : AliasedBlockItem(block, Settings()) {
|
||||
class RevivalHerbItem(block: RevivalHerbBlock) : AliasedBlockItem(block, Settings()), PokemonSelectingItem {
|
||||
|
||||
init {
|
||||
// 65% to raise composter level
|
||||
ComposterBlock.ITEM_TO_LEVEL_INCREASE_CHANCE[this] = .65F
|
||||
}
|
||||
|
||||
private val runtime = MoLangRuntime()
|
||||
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.revival_herb"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health <= 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.decrementFriendship(CobblemonMechanics.remedies.getFriendshipDrop(runtime))
|
||||
return "revive 0.25"
|
||||
}
|
||||
}
|
||||
|
||||
override fun use(world: World, user: PlayerEntity, hand: Hand): TypedActionResult<ItemStack> {
|
||||
return super.use(world, user, hand)
|
||||
if (user is ServerPlayerEntity) {
|
||||
val result = use(user, user.getStackInHand(hand))
|
||||
if (result.result != ActionResult.PASS) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return super<AliasedBlockItem>.use(world, user, hand)
|
||||
}
|
||||
|
||||
override fun canUseOnPokemon(pokemon: Pokemon) = pokemon.currentHealth <= 0
|
||||
override fun applyToPokemon(
|
||||
player: ServerPlayerEntity,
|
||||
stack: ItemStack,
|
||||
pokemon: Pokemon
|
||||
): TypedActionResult<ItemStack>? {
|
||||
if (pokemon.currentHealth <= 0) {
|
||||
player.playSound(CobblemonSounds.MEDICINE_HERB_USE, SoundCategory.PLAYERS, 1F, 1F)
|
||||
pokemon.currentHealth = ceil(pokemon.hp / 4F).toInt()
|
||||
if (!player.isCreative) {
|
||||
stack.decrement(1)
|
||||
}
|
||||
return TypedActionResult.success(stack)
|
||||
} else {
|
||||
return TypedActionResult.pass(stack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun applyToBattlePokemon(player: ServerPlayerEntity, stack: ItemStack, battlePokemon: BattlePokemon) {
|
||||
super.applyToBattlePokemon(player, stack, battlePokemon)
|
||||
player.playSound(CobblemonSounds.MEDICINE_HERB_USE, SoundCategory.PLAYERS, 1F, 1F)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.item.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
|
||||
/**
|
||||
* A bag item effect that links to a script in Showdown. Implementations must be added to
|
||||
* [BagItems.bagItems].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since June 26th, 2023
|
||||
*/
|
||||
interface BagItem {
|
||||
companion object {
|
||||
val EMPTY: BagItem = object : BagItem {
|
||||
override val itemName = "name"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = true
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "none"
|
||||
}
|
||||
}
|
||||
|
||||
/** The name provided to Showdown so that battle messages include the name of the effect for lang. */
|
||||
val itemName: String
|
||||
/** Whether or not the item can probably be used right now, based on the mod-side version of battle state. */
|
||||
fun canUse(battle: PokemonBattle, target: BattlePokemon): Boolean
|
||||
/** Gets the itemId and data for inputting to Showdown. Hyper potion is `potion 200` for example. */
|
||||
fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String
|
||||
|
||||
/**
|
||||
* Used for bag items that required prompts before selecting. It checks that the given stack is in the player's
|
||||
* hands, is non-zero on size, that the battle is still tolerant of a forced action for bag use, and that this
|
||||
* bag item can still be used.
|
||||
*/
|
||||
fun canStillUse(player: ServerPlayerEntity, battle: PokemonBattle, actor: BattleActor, target: BattlePokemon, stack: ItemStack): Boolean {
|
||||
return stack in player.handItems && stack.count > 0 && canUse(battle, target) && actor.canFitForcedAction()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.item.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.battles.BagItemActionResponse
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
|
||||
/**
|
||||
* Something that can be a bag item. This needs to be registered in [BagItems]
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since June 26th, 2023
|
||||
*/
|
||||
interface BagItemConvertible {
|
||||
/**
|
||||
* Returns a [BagItem] if the given stack matches this convertible.
|
||||
*
|
||||
* If you're implementing this from an item subclass then you really
|
||||
* need to remember to check that the supplied stack is of the correct
|
||||
* item type because it doesn't necessarily have to be.
|
||||
*
|
||||
* [BagItemConvertible] can be implemented by non-Items so using it on
|
||||
* items is actually a bit weird, just convenient.
|
||||
*/
|
||||
fun getBagItem(stack: ItemStack): BagItem?
|
||||
|
||||
fun handleInteraction(player: ServerPlayerEntity, battlePokemon: BattlePokemon, stack: ItemStack): Boolean {
|
||||
val battle = battlePokemon.actor.battle
|
||||
val bagItem = getBagItem(stack) ?: return false
|
||||
if (!battlePokemon.actor.canFitForcedAction()) {
|
||||
player.sendMessage(battleLang("bagitem.cannot").red())
|
||||
return false
|
||||
}
|
||||
|
||||
if (!bagItem.canUse(battle, battlePokemon)) {
|
||||
player.sendMessage(battleLang("bagitem.invalid").red())
|
||||
return false
|
||||
}
|
||||
|
||||
battlePokemon.actor.forceChoose(BagItemActionResponse(bagItem, battlePokemon))
|
||||
stack.decrement(1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.item.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.item.CobblemonItem
|
||||
|
||||
class DireHitItem : CobblemonItem(Settings()), SimpleBagItemConvertible {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.dire_hit"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.incrementFriendship(1)
|
||||
return "dire_hit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.item.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.item.CobblemonItem
|
||||
|
||||
class GuardSpecItem : CobblemonItem(Settings()), SimpleBagItemConvertible {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.guard_spec"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.incrementFriendship(1)
|
||||
return "guard_spec"
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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.item.battle
|
||||
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
/**
|
||||
* A type of [BagItemConvertible] that just checks the item type of the stack.
|
||||
*
|
||||
* Used for internal Item subclasses.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 1st, 2023
|
||||
*/
|
||||
interface SimpleBagItemConvertible : BagItemConvertible {
|
||||
val bagItem: BagItem
|
||||
override fun getBagItem(stack: ItemStack) = stack.item.takeIf { it == this }?.let { bagItem }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user