forked from mirrors/cobblemon
Compare commits
148
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
767fc05387 | ||
|
|
1941636113 | ||
|
|
1a3b655d3f | ||
|
|
1f5f278970 | ||
|
|
0b4eb5239a | ||
|
|
118e8f6205 | ||
|
|
cf126c5906 | ||
|
|
645cf61911 | ||
|
|
87d5ede3dd | ||
|
|
203e5d89cd | ||
|
|
f6ad7ff865 | ||
|
|
54e8763470 | ||
|
|
db34687df0 | ||
|
|
06ebf51078 | ||
|
|
39c5bcd73c | ||
|
|
8f5f158b60 | ||
|
|
3bc1e35125 | ||
|
|
460bedddae | ||
|
|
8b64c315f3 | ||
|
|
7c824a2b72 | ||
|
|
f197ac5077 | ||
|
|
e0be669482 | ||
|
|
08ea21fff4 | ||
|
|
9dac604a93 | ||
|
|
28c0f8ab7d | ||
|
|
fcbe0db568 | ||
|
|
83cefe476e | ||
|
|
690ccbd260 | ||
|
|
63846d53fb | ||
|
|
86cb865742 | ||
|
|
1a0c9b6a82 | ||
|
|
985eb103fe | ||
|
|
aec76a78da | ||
|
|
17f3edf2be | ||
|
|
9c5bfda20f | ||
|
|
6d545fc1ca | ||
|
|
9a8ad27122 | ||
|
|
9f1dbc9ff0 | ||
|
|
99942748b0 | ||
|
|
64c34230bf | ||
|
|
36881b70be | ||
|
|
9b052478f6 | ||
|
|
339f8339f0 | ||
|
|
db9a802c3f | ||
|
|
e5666e7a82 | ||
|
|
a588213323 | ||
|
|
56b2ba08ad | ||
|
|
7e6ca7be5e | ||
|
|
25ce784a60 | ||
|
|
5358cccb62 | ||
|
|
c43b3bbb4d | ||
|
|
d826aeeb07 | ||
|
|
f90c6967ce | ||
|
|
b9cf18adbd | ||
|
|
30650e1f9f | ||
|
|
eaa558a7c8 | ||
|
|
0dc725e513 | ||
|
|
a575c6bc3d | ||
|
|
9c102a7850 | ||
|
|
22a1eaab19 | ||
|
|
0118ace5fc | ||
|
|
d4a93abbf7 | ||
|
|
d0ee3f3e9d | ||
|
|
698fa9dff9 | ||
|
|
46ae53f565 | ||
|
|
7d4e5892ba | ||
|
|
f9de415020 | ||
|
|
4d123b6e4b | ||
|
|
54dc2e7f6f | ||
|
|
1d60ba093f | ||
|
|
0fef74dce7 | ||
|
|
b08647bb32 | ||
|
|
de9e89f040 | ||
|
|
84a29ebc2a | ||
|
|
37b13ae959 | ||
|
|
1cacbe8306 | ||
|
|
12d224d15a | ||
|
|
a57eee3afc | ||
|
|
57595c999c | ||
|
|
83615b7eb3 | ||
|
|
c53c544a8b | ||
|
|
c7c272c79c | ||
|
|
c759161367 | ||
|
|
19b7b4804f | ||
|
|
3a005cc813 | ||
|
|
a8e16efa48 | ||
|
|
c3ddfce0a7 | ||
|
|
9500f7a1f3 | ||
|
|
83a8d92e3f | ||
|
|
e105c5d4d7 | ||
|
|
3d816f57e2 | ||
|
|
1e6dcfba9b | ||
|
|
e59d755d32 | ||
|
|
c155b5575d | ||
|
|
9c6b8b231a | ||
|
|
92b404dac4 | ||
|
|
8a1c536d68 | ||
|
|
04719d22a5 | ||
|
|
dacc312756 | ||
|
|
ee4a3d46ba | ||
|
|
80b11f1c3d | ||
|
|
ddcf4eea7b | ||
|
|
6a0681e4c6 | ||
|
|
8ebb378da7 | ||
|
|
e92a878074 | ||
|
|
0b97b41c08 | ||
|
|
20fe732cfc | ||
|
|
0575aa29d9 | ||
|
|
29b560d244 | ||
|
|
dcc98b4a54 | ||
|
|
d3ce455391 | ||
|
|
85c6b633d8 | ||
|
|
956b599060 | ||
|
|
f88552f99b | ||
|
|
e2b559f3eb | ||
|
|
39816d33b6 | ||
|
|
0cf2a23fd4 | ||
|
|
c4d8eae47e | ||
|
|
2a5a292386 | ||
|
|
e8a09f5c48 | ||
|
|
e82193cdfd | ||
|
|
87e7cdebf6 | ||
|
|
9ca84d852f | ||
|
|
8194f93d13 | ||
|
|
c158ec33fa | ||
|
|
1e27c7494e | ||
|
|
54a1f8d932 | ||
|
|
d2af0c9332 | ||
|
|
379a566d71 | ||
|
|
f30ff739ed | ||
|
|
a8949cf1f9 | ||
|
|
61db6334f9 | ||
|
|
c45e34648c | ||
|
|
d56586ecac | ||
|
|
9df8be33e9 | ||
|
|
ff2afd546e | ||
|
|
b8b7756a45 | ||
|
|
3d6146f551 | ||
|
|
31d81db243 | ||
|
|
4c36db8685 | ||
|
|
7cedfb9047 | ||
|
|
a7ab2e37c2 | ||
|
|
4723f91500 | ||
|
|
e23aa6b066 | ||
|
|
46d278ca55 | ||
|
|
339038956d | ||
|
|
116a93d8cc | ||
|
|
acebc35d3f |
+37
-37
@@ -1,54 +1,54 @@
|
||||
# Changelog
|
||||
## [1.7.1 (MONTH xth, 2025)](#1-7-1)
|
||||
## [1.7.2 (MONTH Xth, YEAR)](#1-7-2)
|
||||
|
||||
### Additions
|
||||
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
|
||||
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
|
||||
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
|
||||
- Added brewing recipe for Throat Spray.
|
||||
- Added new effects
|
||||
- Mental Restoration: Reduces "Time Since Last Rest" statistic, which controls Insomnia, by half a minute per second per effect level
|
||||
- Negative Effect Cleanse: Removes all negative effects
|
||||
- Effect Cleanse: Removes all effects
|
||||
- Added more seasonings for sinister tea
|
||||
- Mental Herb - Mental Restoration I for 10 seconds
|
||||
- White Herb - Negative Effect Cleanse
|
||||
- Milk - Effect Cleanse
|
||||
- Moomoo Milk - Effect Cleanse
|
||||
- Added `label` as an alternate key to the `tag` property.
|
||||
|
||||
### Changes
|
||||
- Converted the riding freelook button to a configurable keybinding.
|
||||
- Reduced the cost of the Vivichoke Seed trade with the Wandering Trader.
|
||||
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
|
||||
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air.
|
||||
|
||||
### Fixes
|
||||
- Prevent displaying ride controls overlay to passengers.
|
||||
- Fixed player suffocation on vanilla mounts experienced on some worlds.
|
||||
- Fixed Poké Snack spawning sounds not coming from the block.
|
||||
- Fix player suffocation on vanilla mounts experienced on some worlds.
|
||||
- Stop passengers from hearing the shiny noise of a ridden shiny Pokémon.
|
||||
- Fixed incorrect camera pivot on Bird, Jet, and Dolphin mounts.
|
||||
- Fixed Poké Snack spawning sound positioning.
|
||||
- Ponigiri can no longer be eaten at full hunger.
|
||||
- Medicinal Brew's Campfire Pot recipe now correctly displays that it can be made using an empty glass bottle or a filled bottle.
|
||||
- Fixed a crash when you place a block in the way of hearty grains 2nd block
|
||||
- Fixed a graphics crash that could occur on some machines when campfires were nearby.
|
||||
- Fixed glass bottle not being returned when honey-ing a saccharine leaf block
|
||||
- Fixed Combees not depositing honey upon leaving a hive if they entered with nectar.
|
||||
- Fixed pastured Combees not moving in night or rain.
|
||||
- Fixed a case in which a Combee might try to path to a flower that no longer exists.
|
||||
- Fixed Combees attempting to enter a full hive.
|
||||
- Fixed a crash involving Combees and hives.
|
||||
- Fixed crashing and rendering issues when opening a PC box with certain wallpapers.
|
||||
- Fixed a crash when opening a PC box with certain wallpapers while using *VulkanMod*.
|
||||
- Fixed a glitch where friendship would reset to the default value when evolving a Pokémon.
|
||||
- Fixed a crashed related to NPC navigation.
|
||||
- Fix player suffocation on vanilla mounts experienced on some worlds.
|
||||
- Stop passengers from hearing the shiny noise of a ridden shiny Pokémon.
|
||||
- Fixed friendship resetting to the default value when evolving a Pokémon.
|
||||
- Fixed a bunch of incompatibilities with NeoForge mods
|
||||
- Fixed a bug where wind charges hitting pasture blocks would recall all pokémon in it.
|
||||
- Fixed an issue in the OmniPathNodeMaker/Navigation that prevented Combees from pathing into and out of Saccharine leaves.
|
||||
- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon.
|
||||
- Fixed Furfrou not being trimmable on NeoForge.
|
||||
- Fixed shiny Pokémon not respecting the silent flag on spawn.
|
||||
- Fixed honey from Saccharine Leaves being harvestable at less than the max age.
|
||||
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
|
||||
- Fix all Pokémon being saved to chunks and never despawning.
|
||||
- Fixed PokeSnacks crashing if there was no available spawn.
|
||||
- Fixed Pokémon spawn influences not being specific to regional forms
|
||||
- Fixed crashing when viewing another player in spectator mode
|
||||
- Fixed missing apricorn textures for Adorn blocks
|
||||
- Fixed crashing when riding due to malformed json file, instead resetting it
|
||||
- Fixed mochi items consuming two items at a time
|
||||
- Fix item interaction sometimes not working properly when playing on servers.
|
||||
- Fixed Soothe Bell not being properly tagged as a held item.
|
||||
- Fixed Mint Leaves not being usable for filling the Resurrection Machine.
|
||||
- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags.
|
||||
- Fixed the `run_script` Molang function to not fail if the environment's context is null.
|
||||
|
||||
### Developer
|
||||
- Changed the `owner` parameter in the `OwnerQueryRequirement` interface from `ServerPlayer` to `Player`. This method is now also called on the client to verify whether a Pokémon interaction succeeded, so make sure to update your implementations to handle both server and client contexts.
|
||||
|
||||
### Molang & Datapacks
|
||||
- Removed automatic generation of PC wallpaper screen glow if no asset is provided; it is now optional.
|
||||
- Ride sounds can now be set as exclusive to passengers
|
||||
- Ride sounds no longer play when submerged unless specified with a new setting
|
||||
|
||||
### Particles
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- French
|
||||
- Japanese
|
||||
- Korean
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Traditional Chinese
|
||||
- Updated translations for:
|
||||
@@ -0,0 +1,61 @@
|
||||
# Changelog
|
||||
## [1.7.1 (November 29th, 2025)](#1-7-1)
|
||||
|
||||
### Additions
|
||||
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
|
||||
- Added brewing recipe for Throat Spray.
|
||||
- Added vanilla sprinting's field of view change to Pokémon land mounts.
|
||||
- Added a customizable keybind for the riding freelook button.
|
||||
- Added double tap to sprint on land mounts.
|
||||
|
||||
### Changes
|
||||
- Made Jet pitch turning 1.5x faster across all Pokémon.
|
||||
- Party Pokémon now attack only mobs that are attacking their owner, instead of randomly targeting unrelated mobs and getting themselves hurt.
|
||||
- Reduced the cost of the Vivichoke Seed trade with the Wandering Trader.
|
||||
|
||||
### Fixes
|
||||
- Fixed incorrect camera pivot on Bird, Jet, and Dolphin mounts, leading to some disorienting riding.
|
||||
- Fixed players suffocating while on vanilla mounts.
|
||||
- Fixed a graphics crash that could occur on some machines when campfire pots were nearby.
|
||||
- Fixed ride controls overlay being displayed to passengers. The passengers probably don't need to know about those.
|
||||
- Fixed mount jumping so that it doesn't switch back to ground animations.
|
||||
- Fixed ridden Pokémon land collision and some issues around stepping up blocks. Land mounts are smoother overall now.
|
||||
- Fixed Furfrou's pink and magenta trims being swapped. Only a man could make that mistake.
|
||||
- Fixed friendship being reset to the default value when evolving a Pokémon.
|
||||
- Fixed Combees not depositing honey upon leaving a hive if they entered it with nectar.
|
||||
- Fixed pastured Combees not moving during the night or in rain.
|
||||
- Fixed a case in which a Combee might try to path to a flower that no longer exists.
|
||||
- Fixed Combees attempting to enter a full hive.
|
||||
- Fixed a crash involving Combees and hives. 500 bee bugs. Bugged bees? We can be bees. Mark, this is good news.
|
||||
- Fixed a crash related to NPC navigation.
|
||||
- Fixed a crash when opening a PC box with certain wallpapers while using *VulkanMod*.
|
||||
- Fixed a crash when you place a block in the way of hearty grains 2nd block.
|
||||
- Fixed passengers hearing the shiny noise of a ridden shiny Pokémon.
|
||||
- Ponigiri can no longer be eaten at full hunger.
|
||||
- Fixed Poké Snack spawning sounds not coming from the block.
|
||||
- Medicinal Brew's Campfire Pot recipe now correctly displays that it can be made using an empty glass bottle or a filled bottle.
|
||||
- Fixed glass bottle not being returned when slathering honey on a saccharine leaf block.
|
||||
- Fixed saccharine hanging signs sometimes dropping their oak wood counterpart when being broken.
|
||||
- Fixed a bunch of incompatibilities with NeoForge mods.
|
||||
- Fixed a bug where wind charges hitting pasture blocks would recall all Pokémon in it.
|
||||
- Fixed the Home on the Range advancement triggering when opening the Pokémon interaction wheel.
|
||||
- Fixed recipes that use concrete not working on NeoForge.
|
||||
- Fixed Boltund's model.
|
||||
- Fixed Cyclizar's textures.
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
- Removed automatic generation of PC wallpaper screen glow if no asset is provided; it is now optional.
|
||||
- This is because there's no practical way to do this without blowing up things like *VulkanMod*.
|
||||
|
||||
### Particles
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- French
|
||||
- Japanese
|
||||
- Korean
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Traditional Chinese
|
||||
@@ -11,11 +11,15 @@ package com.cobblemon.mod.common.mixin;
|
||||
import com.cobblemon.mod.common.Cobblemon;
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.PlayerSpawnerAccessor;
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle;
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
|
||||
import com.cobblemon.mod.common.api.stats.CobblemonStats;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
@@ -89,4 +93,32 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
|
||||
private boolean cobblemon$cancelSavingPokemonMounts(boolean original, @Local(ordinal=0) Entity entity) {
|
||||
return original && !(entity instanceof PokemonEntity);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "checkRidingStatistics", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getVehicle()Lnet/minecraft/world/entity/Entity;"))
|
||||
public Entity cobblemon$checkRidingStatistics(ServerPlayer player, Operation<Entity> original, @Local int distance) {
|
||||
var entity = original.call(player);
|
||||
if (!(entity instanceof PokemonEntity pokemonEntity)) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
var ridingController = pokemonEntity.getRidingController();
|
||||
if (ridingController == null) return entity;
|
||||
|
||||
var activeContext = ridingController.getContext();
|
||||
if (activeContext == null) return entity;
|
||||
|
||||
var ridingStyle = activeContext.getStyle();
|
||||
CobblemonStats.CobblemonStat stat = switch (ridingStyle) {
|
||||
case RidingStyle.LAND -> CobblemonStats.RIDING_LAND;
|
||||
case RidingStyle.AIR -> CobblemonStats.RIDING_AIR;
|
||||
case RidingStyle.LIQUID -> CobblemonStats.RIDING_LIQUID;
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (stat != null) {
|
||||
player.awardStat(CobblemonStats.getStat(stat), distance);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ public class AbstractClientPlayerMixin {
|
||||
cir.setReturnValue(usageContext.getFovMultiplier());
|
||||
} else {
|
||||
// Only modify fov through riding if the pokedex is not open
|
||||
LocalPlayer player = (LocalPlayer) (Object) this;
|
||||
AbstractClientPlayer player = (AbstractClientPlayer) (Object) this;
|
||||
// Only modify fov for the driver and not the passengers
|
||||
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity && player.getVehicle().getControllingPassenger() == player) {
|
||||
PokemonEntity ride = (PokemonEntity) player.getVehicle();
|
||||
|
||||
@@ -50,16 +50,8 @@ public abstract class EntityMixin {
|
||||
)
|
||||
)
|
||||
public boolean cobblemon$forceOnGroundForStepUp(Entity entity, Operation<Boolean> original) {
|
||||
if (entity instanceof PokemonEntity && entity.hasControllingPassenger()) {
|
||||
BlockPos below = entity.blockPosition().below();
|
||||
Level level = entity.level();
|
||||
var blockStateBelow = level.getBlockState(below);
|
||||
boolean isAirOrLiquid = blockStateBelow.isAir() || !blockStateBelow.getFluidState().isEmpty();
|
||||
boolean canSupportEntity = blockStateBelow.isFaceSturdy(level, below, Direction.UP);
|
||||
boolean standingOnSolid = canSupportEntity && !isAirOrLiquid;
|
||||
if (standingOnSolid) {
|
||||
return true;
|
||||
}
|
||||
if (entity instanceof PokemonEntity vehicle && entity.hasControllingPassenger()) {
|
||||
return true;
|
||||
}
|
||||
return original.call(entity);
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
|
||||
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
|
||||
import com.cobblemon.mod.common.api.spawning.BestSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningZoneGenerator
|
||||
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
|
||||
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
|
||||
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
|
||||
import com.cobblemon.mod.common.api.starter.StarterHandler
|
||||
import com.cobblemon.mod.common.api.stats.CobblemonStats
|
||||
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
|
||||
@@ -76,12 +76,7 @@ import com.cobblemon.mod.common.config.LastChangedVersion
|
||||
import com.cobblemon.mod.common.config.constraint.IntConstraint
|
||||
import com.cobblemon.mod.common.config.starter.StarterConfig
|
||||
import com.cobblemon.mod.common.data.CobblemonDataProvider
|
||||
import com.cobblemon.mod.common.events.AdvancementHandler
|
||||
import com.cobblemon.mod.common.events.EntityCallbackHandler
|
||||
import com.cobblemon.mod.common.events.CallbackHandler
|
||||
import com.cobblemon.mod.common.events.PokedexHandler
|
||||
import com.cobblemon.mod.common.events.ServerTickHandler
|
||||
import com.cobblemon.mod.common.events.StatHandler
|
||||
import com.cobblemon.mod.common.events.*
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
|
||||
import com.cobblemon.mod.common.permission.LaxPermissionValidator
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
@@ -96,7 +91,7 @@ import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatu
|
||||
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
|
||||
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
|
||||
import com.cobblemon.mod.common.pokemon.properties.*
|
||||
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.LabelProperty
|
||||
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
|
||||
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
|
||||
import com.cobblemon.mod.common.util.*
|
||||
@@ -206,6 +201,7 @@ object Cobblemon {
|
||||
implementation.registerEntityDataSerializers()
|
||||
implementation.registerCriteria()
|
||||
implementation.registerEntitySubPredicates()
|
||||
implementation.registerMobEffects()
|
||||
DispenserBehaviorRegistry.registerDispenserBehaviors()
|
||||
|
||||
DropEntry.register("command", CommandDropEntry::class.java)
|
||||
@@ -319,7 +315,7 @@ object Cobblemon {
|
||||
|
||||
CustomPokemonProperty.register(UncatchableProperty)
|
||||
CustomPokemonProperty.register(BattleCloneProperty)
|
||||
CustomPokemonProperty.register(PokemonFlagProperty)
|
||||
CustomPokemonProperty.register(LabelProperty)
|
||||
CustomPokemonProperty.register(HiddenAbilityPropertyType)
|
||||
CustomPokemonProperty.register(AspectPropertyType)
|
||||
CustomPokemonProperty.register(UnaspectPropertyType)
|
||||
|
||||
@@ -215,7 +215,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", CobblemonHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", CobblemonHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
val SACCHARINE_WALL_HANGING_SIGN = this.create("saccharine_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
|
||||
@@ -117,6 +117,11 @@ interface CobblemonImplementation {
|
||||
|
||||
fun registerEntitySubPredicates()
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
fun registerMobEffects()
|
||||
|
||||
|
||||
/**
|
||||
* Add a feature to the current platform implementation.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.cooking.effects.CleanseAllEffect
|
||||
import com.cobblemon.mod.common.api.cooking.effects.CleanseNegativeEffect
|
||||
import com.cobblemon.mod.common.api.cooking.effects.MentalRestorationEffect
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.world.effect.MobEffect
|
||||
|
||||
object CobblemonMobEffects : PlatformRegistry<Registry<MobEffect>, ResourceKey<Registry<MobEffect>>, MobEffect>() {
|
||||
override val registry: Registry<MobEffect> = BuiltInRegistries.MOB_EFFECT
|
||||
override val resourceKey: ResourceKey<Registry<MobEffect>> = Registries.MOB_EFFECT
|
||||
|
||||
@JvmField
|
||||
val CLEANSE_NEGATIVE = create("cleanse_negative", CleanseNegativeEffect())
|
||||
|
||||
@JvmField
|
||||
val MENTAL_RESTORATION = create("mental_restoration", MentalRestorationEffect())
|
||||
|
||||
@JvmField
|
||||
val CLEANSE_ALL = create("cleanse_all", CleanseAllEffect())
|
||||
}
|
||||
@@ -21,15 +21,12 @@ import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallb
|
||||
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
|
||||
import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.PokemonInteractionsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.*
|
||||
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
|
||||
@@ -37,11 +34,7 @@ import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
|
||||
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
|
||||
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.*
|
||||
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
|
||||
@@ -59,14 +52,7 @@ import com.cobblemon.mod.common.client.net.storage.party.SetPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.client.net.storage.party.SetPartyReferenceHandler
|
||||
import com.cobblemon.mod.common.client.net.storage.pc.*
|
||||
import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeAcceptanceChangedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeCancelledHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeCompletedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeOfferExpiredHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.*
|
||||
import com.cobblemon.mod.common.net.PacketRegisterInfo
|
||||
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
|
||||
@@ -83,20 +69,12 @@ import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.*
|
||||
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.*
|
||||
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
|
||||
@@ -116,43 +94,19 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePart
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.MoveClientPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.RenamePCBoxPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeCompletedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.*
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
|
||||
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.battle.BattleTeamLeavePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
@@ -177,11 +131,7 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPack
|
||||
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
|
||||
@@ -192,21 +142,11 @@ import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokem
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptancePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
@@ -232,11 +172,7 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
|
||||
@@ -250,12 +186,7 @@ import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemo
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.*
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
@@ -401,6 +332,7 @@ object CobblemonNetwork {
|
||||
// Data registries
|
||||
list.add(PacketRegisterInfo(AbilityRegistrySyncPacket.ID, AbilityRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(CobblemonMechanicsSyncPacket.ID, CobblemonMechanicsSyncPacket::decode, CobblemonMechanicsSyncHandler))
|
||||
list.add(PacketRegisterInfo(PokemonInteractionsSyncPacket.ID, PokemonInteractionsSyncPacket::decode, PokemonInteractionsSyncHandler))
|
||||
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.DefendOwnerTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class DefendOwnerTaskConfig : SingleTaskConfig {
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
MemoryModuleType.ATTACK_TARGET,
|
||||
CobblemonMemories.NEAREST_VISIBLE_ATTACKER,
|
||||
)
|
||||
|
||||
return WrapperLivingEntityTask(
|
||||
DefendOwnerTask.create(),
|
||||
PokemonEntity::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ interface TaskConfig {
|
||||
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
|
||||
cobblemonResource("manage_flight_in_battle") to ManageFlightInBattleTaskConfig::class.java,
|
||||
cobblemonResource("attack_hostile_mobs") to AttackHostileMobsTaskConfig::class.java,
|
||||
cobblemonResource("defend_owner") to DefendOwnerTaskConfig::class.java,
|
||||
cobblemonResource("move_to_sweet_berry_bush") to MoveToSweetBerryBushTaskConfig::class.java,
|
||||
cobblemonResource("stop_moving_to_sweet_berry_bush") to StopTryingToReachSweetBerryBushTaskConfig::class.java,
|
||||
cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBushTaskConfig::class.java,
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.cooking.effects
|
||||
|
||||
import net.minecraft.world.effect.MobEffect
|
||||
import net.minecraft.world.effect.MobEffectCategory
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class CleanseAllEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
|
||||
override fun isInstantenous() = true
|
||||
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
|
||||
target.removeAllEffects()
|
||||
}
|
||||
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.cooking.effects
|
||||
|
||||
import net.minecraft.world.effect.MobEffect
|
||||
import net.minecraft.world.effect.MobEffectCategory
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class CleanseNegativeEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
|
||||
override fun isInstantenous() = true
|
||||
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
|
||||
val snapshot = target.activeEffects.toList()
|
||||
for (ei in snapshot) {
|
||||
if (ei.effect.value().category == MobEffectCategory.HARMFUL) {
|
||||
target.removeEffect(ei.effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.cooking.effects
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.stats.Stats
|
||||
import net.minecraft.world.effect.MobEffect
|
||||
import net.minecraft.world.effect.MobEffectCategory
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class MentalRestorationEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xAA66FF) {
|
||||
override fun applyEffectTick(entity: LivingEntity, amplifier: Int): Boolean {
|
||||
if (entity is ServerPlayer) {
|
||||
val stats = entity.stats
|
||||
val statType = Stats.CUSTOM.get(Stats.TIME_SINCE_REST)
|
||||
val current = stats.getValue(statType)
|
||||
val reduction = 31 * (amplifier + 1)// reduce by half a minute every second per level (30 ticks removed per tick + 1 to account for the tick passing)
|
||||
val newValue = Math.max(current - reduction, 0)
|
||||
stats.setValue(entity, statType, newValue)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -12,10 +12,10 @@ import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.data.PokemonInteractionsSyncPacket
|
||||
import com.cobblemon.mod.common.pokemon.adapters.CobblemonRequirementAdapter
|
||||
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
|
||||
import com.cobblemon.mod.common.pokemon.requirements.PokemonPropertiesRequirement
|
||||
@@ -51,7 +51,12 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
val speciesInteractions = mutableListOf<PokemonInteractionSet>()
|
||||
val generalInteractions = mutableListOf<PokemonInteractionSet>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {}
|
||||
override fun sync(player: ServerPlayer) {
|
||||
PokemonInteractionsSyncPacket(
|
||||
speciesInteractions,
|
||||
generalInteractions,
|
||||
).sendToPlayer(player)
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
|
||||
speciesInteractions.clear()
|
||||
|
||||
+88
-19
@@ -15,11 +15,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
import com.cobblemon.mod.common.util.withPlayerValue
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.cobblemon.mod.common.pokemon.adapters.CobblemonRequirementAdapter
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.sounds.SoundSource
|
||||
@@ -28,30 +27,83 @@ import net.minecraft.world.entity.EquipmentSlot
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
/**
|
||||
* A
|
||||
*/
|
||||
data class PokemonInteractionSet(
|
||||
val requirements: List<Requirement> = listOf(),
|
||||
val interactions: List<PokemonInteraction> = listOf()
|
||||
)
|
||||
val interactions: List<PokemonInteraction> = listOf(),
|
||||
) {
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeCollection(requirements) { it, requirement ->
|
||||
CobblemonRequirementAdapter.encode(requirement, it)
|
||||
}
|
||||
buffer.writeCollection(interactions) { it, interaction ->
|
||||
interaction.encode(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): PokemonInteractionSet {
|
||||
val requirements = buffer.readList { it ->
|
||||
CobblemonRequirementAdapter.decode(it)
|
||||
}
|
||||
val interactions = buffer.readList { it ->
|
||||
PokemonInteraction.decode(it)
|
||||
}
|
||||
|
||||
return PokemonInteractionSet(
|
||||
requirements,
|
||||
interactions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class PokemonInteraction(
|
||||
val grouping: ResourceLocation,
|
||||
val requirements: List<Requirement> = listOf(),
|
||||
val effects: List<InteractionEffect> = listOf(),
|
||||
val cooldown: ExpressionLike = "0".asExpressionLike()
|
||||
)
|
||||
) {
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(grouping)
|
||||
buffer.writeCollection(requirements) { it, requirement ->
|
||||
CobblemonRequirementAdapter.encode(requirement, it)
|
||||
}
|
||||
buffer.writeExpressionLike(cooldown)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): PokemonInteraction {
|
||||
val grouping = buffer.readResourceLocation()
|
||||
val requirements = buffer.readList { it ->
|
||||
CobblemonRequirementAdapter.decode(it)
|
||||
}
|
||||
|
||||
// No need to sync these for now
|
||||
val effects = emptyList<InteractionEffect>()
|
||||
val cooldown = buffer.readExpressionLike()
|
||||
|
||||
return PokemonInteraction(
|
||||
grouping,
|
||||
requirements,
|
||||
effects,
|
||||
cooldown,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface InteractionEffect {
|
||||
fun applyEffect(pokemon: PokemonEntity, player: ServerPlayer)
|
||||
}
|
||||
|
||||
class DropItemEffect(val item: ResourceLocation, val amount: IntRange?): InteractionEffect {
|
||||
class DropItemEffect(val item: ResourceLocation, val amount: IntRange?) : InteractionEffect {
|
||||
override fun applyEffect(
|
||||
pokemon: PokemonEntity,
|
||||
player: ServerPlayer
|
||||
) {
|
||||
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException("Cannot load item with id: $item")
|
||||
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException(
|
||||
"Cannot load item with id: $item"
|
||||
)
|
||||
val stack = ItemStack(item)
|
||||
stack.count = amount?.randomOrNull() ?: 1
|
||||
if (stack.isEmpty)
|
||||
@@ -68,12 +120,14 @@ class DropItemEffect(val item: ResourceLocation, val amount: IntRange?): Interac
|
||||
}
|
||||
}
|
||||
|
||||
class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?): InteractionEffect {
|
||||
class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?) : InteractionEffect {
|
||||
override fun applyEffect(
|
||||
pokemon: PokemonEntity,
|
||||
player: ServerPlayer
|
||||
) {
|
||||
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException("Cannot load item with id: $item")
|
||||
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException(
|
||||
"Cannot load item with id: $item"
|
||||
)
|
||||
val stack = ItemStack(item)
|
||||
stack.count = amount?.randomOrNull() ?: 1
|
||||
if (stack.isEmpty)
|
||||
@@ -86,14 +140,29 @@ class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?): Interac
|
||||
}
|
||||
}
|
||||
|
||||
class PlaySoundEffect(val sound: ResourceLocation, val soundSource: SoundSource?, val playAround: Boolean = true, val distance: Double = 64.0, val volume: Float = 1.0F, val pitch: Float = 1.0F): InteractionEffect {
|
||||
class PlaySoundEffect(
|
||||
val sound: ResourceLocation,
|
||||
val soundSource: SoundSource?,
|
||||
val playAround: Boolean = true,
|
||||
val distance: Double = 64.0,
|
||||
val volume: Float = 1.0F,
|
||||
val pitch: Float = 1.0F
|
||||
) : InteractionEffect {
|
||||
override fun applyEffect(
|
||||
pokemon: PokemonEntity,
|
||||
player: ServerPlayer
|
||||
) {
|
||||
val packet = UnvalidatedPlaySoundS2CPacket(sound, soundSource ?: SoundSource.NEUTRAL, pokemon.x, pokemon.y, pokemon.z, volume, pitch)
|
||||
val packet = UnvalidatedPlaySoundS2CPacket(
|
||||
sound,
|
||||
soundSource ?: SoundSource.NEUTRAL,
|
||||
pokemon.x,
|
||||
pokemon.y,
|
||||
pokemon.z,
|
||||
volume,
|
||||
pitch
|
||||
)
|
||||
if (playAround) {
|
||||
packet.sendToPlayersAround( pokemon.x, pokemon.y, pokemon.z, distance, pokemon.level().dimension())
|
||||
packet.sendToPlayersAround(pokemon.x, pokemon.y, pokemon.z, distance, pokemon.level().dimension())
|
||||
} else {
|
||||
packet.sendToPlayer(player)
|
||||
}
|
||||
@@ -104,7 +173,7 @@ class PlaySoundEffect(val sound: ResourceLocation, val soundSource: SoundSource?
|
||||
}
|
||||
}
|
||||
|
||||
class ShrinkItemEffect(val amount: Int = 1): InteractionEffect {
|
||||
class ShrinkItemEffect(val amount: Int = 1) : InteractionEffect {
|
||||
override fun applyEffect(
|
||||
pokemon: PokemonEntity,
|
||||
player: ServerPlayer
|
||||
@@ -120,7 +189,7 @@ class ShrinkItemEffect(val amount: Int = 1): InteractionEffect {
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptEffect(val script: ExpressionLike): InteractionEffect {
|
||||
class ScriptEffect(val script: ExpressionLike) : InteractionEffect {
|
||||
override fun applyEffect(
|
||||
pokemon: PokemonEntity,
|
||||
player: ServerPlayer
|
||||
|
||||
@@ -311,7 +311,7 @@ object MoLangFunctions {
|
||||
runtime.environment.variable = params.environment.variable
|
||||
val args = params.params.subList(1, params.params.size)
|
||||
runtime.environment.context = ContextStruct(
|
||||
params.environment.context.map + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap()
|
||||
(params.environment.context?.map ?: emptyMap()) + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap()
|
||||
)
|
||||
val script = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
// store the args in the
|
||||
@@ -779,6 +779,20 @@ object MoLangFunctions {
|
||||
battleStartResult.ifSuccessful { returnValue = it.struct }
|
||||
return@put returnValue
|
||||
}
|
||||
map.put("get_custom_stat") { params ->
|
||||
val statName = params.getString(0)
|
||||
|
||||
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@put DoubleValue.ZERO
|
||||
val statResourceLocation = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@put DoubleValue.ZERO
|
||||
|
||||
val exists = net.minecraft.stats.Stats.CUSTOM.contains(statResourceLocation)
|
||||
if (!exists) return@put DoubleValue.ZERO
|
||||
|
||||
val stat = net.minecraft.stats.Stats.CUSTOM.get(statResourceLocation)
|
||||
val value = player.stats.getValue(stat)
|
||||
|
||||
DoubleValue(value)
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
@@ -1739,22 +1753,35 @@ object MoLangFunctions {
|
||||
pokemon.removeCosmeticItem()
|
||||
}
|
||||
map.put("add_marks") { params ->
|
||||
var appliedMark = false
|
||||
for (param in params.params) {
|
||||
val identifier = param.asString().asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) pokemon.exchangeMark(mark, true)
|
||||
if (mark != null) {
|
||||
pokemon.exchangeMark(mark, true)
|
||||
appliedMark = true
|
||||
}
|
||||
}
|
||||
|
||||
DoubleValue(appliedMark)
|
||||
}
|
||||
map.put("add_marks_with_chance") { params ->
|
||||
var appliedMark = false
|
||||
for (param in params.params) {
|
||||
val identifier = param.asString().asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
|
||||
mark?.let {
|
||||
val probability = it.chance.coerceIn(0F, 1F) * 100
|
||||
val randomValue = Random.nextDouble(0.0, 100.0)
|
||||
if (randomValue < probability) pokemon.exchangeMark(it, true)
|
||||
if (randomValue < probability) {
|
||||
pokemon.exchangeMark(it, true)
|
||||
appliedMark = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoubleValue(appliedMark)
|
||||
}
|
||||
map.put("add_potential_marks") { params ->
|
||||
for (param in params.params) {
|
||||
|
||||
+10
-8
@@ -80,7 +80,9 @@ open class ChoiceSpeciesFeatureProvider(
|
||||
|
||||
internal constructor(): this(emptyList())
|
||||
|
||||
override fun get(pokemon: Pokemon) = pokemon.getFeature<StringSpeciesFeature>(keys.first())
|
||||
override fun get(pokemon: Pokemon): StringSpeciesFeature? {
|
||||
return pokemon.features.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
|
||||
}
|
||||
|
||||
override fun invoke(pokemon: Pokemon): StringSpeciesFeature? {
|
||||
val existing = get(pokemon)
|
||||
@@ -101,15 +103,15 @@ open class ChoiceSpeciesFeatureProvider(
|
||||
}
|
||||
|
||||
override fun invoke(nbt: CompoundTag): StringSpeciesFeature? {
|
||||
return if (nbt.contains(keys.first())) {
|
||||
StringSpeciesFeature(keys.first(), "").also { it.loadFromNBT(nbt) }
|
||||
} else null
|
||||
val key = keys.find { nbt.contains(it) }
|
||||
if (key == null) return null
|
||||
return StringSpeciesFeature(key, "").also { it.loadFromNBT(nbt) }
|
||||
}
|
||||
|
||||
override fun invoke(json: JsonObject): StringSpeciesFeature? {
|
||||
return if (json.has(keys.first())) {
|
||||
StringSpeciesFeature(keys.first(), "").also { it.loadFromJSON(json) }
|
||||
} else null
|
||||
val key = keys.find { json.has(it) }
|
||||
if (key == null) return null
|
||||
return StringSpeciesFeature(key, "").also { it.loadFromJSON(json) }
|
||||
}
|
||||
|
||||
override fun fromString(value: String?): StringSpeciesFeature? {
|
||||
@@ -131,7 +133,7 @@ open class ChoiceSpeciesFeatureProvider(
|
||||
|
||||
override fun provide(properties: PokemonProperties): Set<String> {
|
||||
return if (isAspect) {
|
||||
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name == keys.first() }
|
||||
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
|
||||
if (feature != null) {
|
||||
setOf(getAspect(feature))
|
||||
} else {
|
||||
|
||||
+23
-11
@@ -132,7 +132,9 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
|
||||
|
||||
constructor(vararg keys: String) : this(keys.toList())
|
||||
|
||||
override fun get(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(keys.first())
|
||||
override fun get(pokemon: Pokemon): FlagSpeciesFeature? {
|
||||
return pokemon.features.filterIsInstance<FlagSpeciesFeature>().find { it.name in keys }
|
||||
}
|
||||
|
||||
override fun invoke(pokemon: Pokemon): FlagSpeciesFeature? {
|
||||
return get(pokemon)
|
||||
@@ -144,15 +146,15 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
|
||||
}
|
||||
|
||||
override fun invoke(nbt: CompoundTag): FlagSpeciesFeature? {
|
||||
return if (nbt.contains(keys.first())) {
|
||||
FlagSpeciesFeature(keys.first(), false).also { it.loadFromNBT(nbt) }
|
||||
} else null
|
||||
val key = keys.find { nbt.contains(it) }
|
||||
if (key == null) return null
|
||||
return FlagSpeciesFeature(key, false).also { it.loadFromNBT(nbt) }
|
||||
}
|
||||
|
||||
override fun invoke(json: JsonObject): FlagSpeciesFeature? {
|
||||
return if (json.has(keys.first())) {
|
||||
FlagSpeciesFeature(keys.first(), false).also { it.loadFromJSON(json) }
|
||||
} else null
|
||||
val key = keys.find { json.has(it) }
|
||||
if (key == null) return null
|
||||
return FlagSpeciesFeature(key, false).also { it.loadFromJSON(json) }
|
||||
}
|
||||
|
||||
override fun fromString(value: String?): FlagSpeciesFeature? {
|
||||
@@ -170,16 +172,26 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
|
||||
}
|
||||
|
||||
override fun provide(pokemon: Pokemon): Set<String> {
|
||||
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
|
||||
setOf(keys.first())
|
||||
return if (isAspect) {
|
||||
val feature = pokemon.features.filterIsInstance<FlagSpeciesFeature>().find { it.name in keys }
|
||||
if (feature != null && feature.enabled) {
|
||||
setOf(keys.first())
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(properties: PokemonProperties): Set<String> {
|
||||
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
|
||||
setOf(keys.first())
|
||||
return if (isAspect) {
|
||||
val feature = properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name in keys }
|
||||
if (feature != null && feature.enabled) {
|
||||
setOf(keys.first())
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
|
||||
+6
-10
@@ -142,19 +142,15 @@ class IntSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<IntSpeciesF
|
||||
}
|
||||
|
||||
override fun invoke(nbt: CompoundTag): IntSpeciesFeature? {
|
||||
return if (nbt.contains(keys.first())) {
|
||||
IntSpeciesFeature(keys.first(), nbt.getInt(keys.first()))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val key = keys.find { nbt.contains(it) }
|
||||
if (key == null) return null
|
||||
return IntSpeciesFeature(keys.first(), nbt.getInt(key));
|
||||
}
|
||||
|
||||
override fun invoke(json: JsonObject): IntSpeciesFeature? {
|
||||
return if (json.has(keys.first())) {
|
||||
IntSpeciesFeature(keys.first(), json.get(keys.first()).asInt)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val key = keys.find { json.has(it) }
|
||||
if (key == null) return null
|
||||
return IntSpeciesFeature(keys.first(), json.get(key).asInt)
|
||||
}
|
||||
|
||||
override fun get(pokemon: Pokemon) = pokemon.features.filterIsInstance<IntSpeciesFeature>().find { it.name in keys }
|
||||
|
||||
+5
-4
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
/**
|
||||
* A [Requirement] that expects a [LivingEntity] to be attached to the [Pokemon].
|
||||
@@ -26,7 +27,7 @@ interface OwnerQueryRequirement : Requirement {
|
||||
override fun check(pokemon: Pokemon): Boolean {
|
||||
val owner = pokemon.getOwnerEntity() ?: return false
|
||||
return when (owner) {
|
||||
is ServerPlayer -> {
|
||||
is Player -> {
|
||||
this.checkPlayer(pokemon, owner)
|
||||
}
|
||||
is NPCEntity -> {
|
||||
@@ -37,13 +38,13 @@ interface OwnerQueryRequirement : Requirement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given [Pokemon] & [ServerPlayer] satisfies the requirement.
|
||||
* Checks if the given [Pokemon] & [Player] satisfies the requirement.
|
||||
*
|
||||
* @param pokemon The [Pokemon] being queried.
|
||||
* @param owner The [ServerPlayer] that owns this [Pokemon].
|
||||
* @param owner The [Player] that owns this [Pokemon].
|
||||
* @return If the requirement was satisfied.
|
||||
*/
|
||||
fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean
|
||||
fun checkPlayer(pokemon: Pokemon, owner: Player): Boolean
|
||||
|
||||
/**
|
||||
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
|
||||
|
||||
+1
-1
@@ -322,7 +322,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
|
||||
val pitchRot = handling * state.currMouseYForce.get()
|
||||
|
||||
// Roll
|
||||
val rollRot = handling * 1.5 * state.currMouseXForce.get()
|
||||
val rollRot = handling * state.currMouseXForce.get()
|
||||
|
||||
val mouseRotation = Vec3(0.0, pitchRot, rollRot)
|
||||
|
||||
|
||||
+49
-19
@@ -114,10 +114,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
input: Vec3
|
||||
) {
|
||||
if (vehicle.level().isClientSide) {
|
||||
handleSprinting(state)
|
||||
handleSprinting(state, driver)
|
||||
inAirCheck(state, vehicle)
|
||||
tickStamina(settings, state, vehicle)
|
||||
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.01)
|
||||
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.01 || driver.zza != 0.0f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,9 +139,24 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
}
|
||||
|
||||
fun handleSprinting(
|
||||
state: HorseState
|
||||
state: HorseState,
|
||||
driver: Player
|
||||
) {
|
||||
val tryingToSprint = Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()
|
||||
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
|
||||
var hasDoubleTapped = false
|
||||
|
||||
// If you are not sprinting and you just pressed down and theres no
|
||||
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
|
||||
state.sprintTickTimer.set(sprintTimerTickLength)
|
||||
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
|
||||
hasDoubleTapped = true
|
||||
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
|
||||
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
|
||||
} else {
|
||||
state.sprintTickTimer.set(0)
|
||||
}
|
||||
|
||||
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
|
||||
|
||||
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
|
||||
// If stamina runs out or the player is not holding forward then stop sprinting
|
||||
@@ -156,6 +171,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
// If you are trying to sprint and the toggle allows it then start sprinting and disable toggle
|
||||
state.sprinting.set(true)
|
||||
}
|
||||
|
||||
// Take not of whether forward was held last tick
|
||||
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
|
||||
}
|
||||
|
||||
fun tickStamina(
|
||||
@@ -227,7 +245,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
|
||||
// Normalize the current rotation diff
|
||||
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
|
||||
val rotDiffNorm = rotDiff / maxYawDiff
|
||||
val rotDiffNorm = rotDiff / maxYawDiff//(rotDiff / maxYawDiff).coerceIn(-0.2f, 0.2f)
|
||||
|
||||
// Take the square root so that the ride levels out quicker when at lower differences between entity
|
||||
// y and driver y
|
||||
@@ -240,7 +258,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
|
||||
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
|
||||
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
|
||||
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * sqrt(abs(invRelSpeed)), 1.0)).toFloat()
|
||||
|
||||
// Ensure you only ever rotate as much difference as there is between the angles.
|
||||
val turnSpeed = turnRate * rotDiffMod
|
||||
@@ -273,30 +291,24 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
|
||||
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0)
|
||||
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0) else 0.5 * 20.0
|
||||
val accel = topSpeed / accelTime
|
||||
var activeInput = false
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
val dmSpeed = vehicle.deltaMovement.length()
|
||||
val rvSpeed = state.rideVelocity.get().length()
|
||||
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
// if (dmSpeed > rvSpeed) {
|
||||
// // align the velocity vector to be in local vehicle space
|
||||
// newVelocity = vehicle.deltaMovement
|
||||
// val yawAligned = Matrix3f().rotateY(-vehicle.yRot.toRadians())
|
||||
// newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
|
||||
// }
|
||||
|
||||
if (vehicle.horizontalCollision) {
|
||||
newVelocity = newVelocity.normalize().scale(vehicle.deltaMovement.length())
|
||||
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
|
||||
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
@@ -372,7 +384,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
}
|
||||
|
||||
//Zero out lateral velocity possibly picked up from a controller transition
|
||||
// Zero out lateral velocity possibly picked up from a controller transition
|
||||
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
|
||||
|
||||
|
||||
@@ -461,7 +473,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return 1.0f
|
||||
return if (state.sprinting.get()) 1.15f else 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
@@ -520,6 +532,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun maxUpStep(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
vehicle: PokemonEntity
|
||||
): Float? {
|
||||
return if (state.inAir.get()) 0.0f else 1.2f
|
||||
}
|
||||
|
||||
override fun getRideSounds(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
@@ -618,6 +638,8 @@ class HorseState : RidingBehaviourState() {
|
||||
var sprintToggleable = ridingState(false, Side.CLIENT)
|
||||
var inAir = ridingState(false, Side.CLIENT)
|
||||
var jumpTicks = ridingState(0, Side.CLIENT)
|
||||
var sprintTickTimer = ridingState(0, Side.CLIENT)
|
||||
var forwardHeldLastTick = ridingState(false, Side.CLIENT)
|
||||
|
||||
override fun encode(buffer: FriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
@@ -626,6 +648,8 @@ class HorseState : RidingBehaviourState() {
|
||||
buffer.writeBoolean(sprintToggleable.get())
|
||||
buffer.writeBoolean(inAir.get())
|
||||
buffer.writeInt(jumpTicks.get())
|
||||
buffer.writeInt(sprintTickTimer.get())
|
||||
buffer.writeBoolean(forwardHeldLastTick.get())
|
||||
}
|
||||
|
||||
override fun decode(buffer: FriendlyByteBuf) {
|
||||
@@ -635,6 +659,8 @@ class HorseState : RidingBehaviourState() {
|
||||
sprintToggleable.set(buffer.readBoolean(), forced = true)
|
||||
inAir.set(buffer.readBoolean(), forced = true)
|
||||
jumpTicks.set(buffer.readInt(), forced = true)
|
||||
sprintTickTimer.set(buffer.readInt(), forced = true)
|
||||
forwardHeldLastTick.set(buffer.readBoolean(), forced = true)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
@@ -644,6 +670,8 @@ class HorseState : RidingBehaviourState() {
|
||||
sprintToggleable.set(false, forced = true)
|
||||
inAir.set(false, forced = true)
|
||||
jumpTicks.set(0, forced = true)
|
||||
sprintTickTimer.set(0, forced = true)
|
||||
forwardHeldLastTick.set(false, forced = true)
|
||||
}
|
||||
|
||||
override fun copy() = HorseState().also {
|
||||
@@ -654,6 +682,8 @@ class HorseState : RidingBehaviourState() {
|
||||
it.sprintToggleable.set(this.sprintToggleable.get(), forced = true)
|
||||
it.inAir.set(this.inAir.get(), forced = true)
|
||||
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
|
||||
it.sprintTickTimer.set(this.sprintTickTimer.get(), forced = true)
|
||||
it.forwardHeldLastTick.set(this.forwardHeldLastTick.get(), forced = true)
|
||||
}
|
||||
|
||||
override fun shouldSync(previous: RidingBehaviourState): Boolean {
|
||||
|
||||
@@ -12,6 +12,7 @@ package com.cobblemon.mod.common.api.riding.sound
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.getIsSubmerged
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
|
||||
@@ -65,6 +66,21 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
|
||||
return
|
||||
}
|
||||
|
||||
// Mute the sound if it's not meant to play for the passenger
|
||||
if (this.isPassenger && !this.soundSettings.playForPassengers) {
|
||||
this.volume = 0.0f
|
||||
return
|
||||
}
|
||||
|
||||
// Mute sounds when submerged unless they support underwater sounds, underwater sounds don't play above water
|
||||
if (this.ride.getIsSubmerged() && !this.soundSettings.submerged) {
|
||||
this.volume = 0.0f
|
||||
return
|
||||
} else if (!this.ride.getIsSubmerged() && this.soundSettings.submerged) {
|
||||
this.volume = 0.0f
|
||||
return
|
||||
}
|
||||
|
||||
// Perform data driven pitch and volume calculation
|
||||
this.volume = ((soundSettings.volumeExpr.let { ride.runtime.resolveDouble(it) })).toFloat()
|
||||
this.volume *= this.fade
|
||||
|
||||
+8
@@ -27,7 +27,9 @@ data class RideSoundSettings(
|
||||
val soundLocation: ResourceLocation,
|
||||
@SerializedName("volumeExpr") private val _volumeExpr: Expression? = "1.0".asExpression(),
|
||||
@SerializedName("pitchExpr") private val _pitchExpr: Expression? = "1.0".asExpression(),
|
||||
val playForPassengers: Boolean = true,
|
||||
val playForNonPassengers: Boolean = false,
|
||||
val submerged: Boolean = false,
|
||||
val muffleEnabled: Boolean = false,
|
||||
@SerializedName("attenuationModel") private val _attenuationModel: RideAttenuationModel? = RideAttenuationModel.NONE
|
||||
): Encodable {
|
||||
@@ -46,7 +48,9 @@ data class RideSoundSettings(
|
||||
buffer.writeResourceLocation(soundLocation)
|
||||
buffer.writeExpression(volumeExpr)
|
||||
buffer.writeExpression(pitchExpr)
|
||||
buffer.writeBoolean(playForPassengers)
|
||||
buffer.writeBoolean(playForNonPassengers)
|
||||
buffer.writeBoolean(submerged)
|
||||
buffer.writeBoolean(muffleEnabled)
|
||||
buffer.writeEnum(attenuationModel)
|
||||
}
|
||||
@@ -57,7 +61,9 @@ data class RideSoundSettings(
|
||||
val soundLocation = buffer.readResourceLocation()
|
||||
val volumeExpr = buffer.readExpression()
|
||||
val pitchExpr = buffer.readExpression()
|
||||
val playForPassengers = buffer.readBoolean()
|
||||
val playForNonPassengers = buffer.readBoolean()
|
||||
val submerged = buffer.readBoolean()
|
||||
val muffleEnabled = buffer.readBoolean()
|
||||
val attenuationModel = buffer.readEnum(RideAttenuationModel::class.java)
|
||||
|
||||
@@ -65,7 +71,9 @@ data class RideSoundSettings(
|
||||
soundLocation,
|
||||
volumeExpr,
|
||||
pitchExpr,
|
||||
playForPassengers,
|
||||
playForNonPassengers,
|
||||
submerged,
|
||||
muffleEnabled,
|
||||
attenuationModel
|
||||
)
|
||||
|
||||
@@ -44,11 +44,7 @@ abstract class SpawnAction<R>(
|
||||
|
||||
spawnablePosition.applyInfluences { it.affectAction(this) }
|
||||
val result = run()
|
||||
if (result != null) {
|
||||
future.complete(result)
|
||||
} else {
|
||||
future.completeExceptionally(Exception("Nothing was spawned."))
|
||||
}
|
||||
future.complete(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
+10
-3
@@ -61,7 +61,9 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
val baitEVStat = effects.firstOrNull { it.type == Effects.EV }?.subcategory?.path?.let { Stats.getStat(it) }
|
||||
|
||||
if (detailSpecies != null && baitEVStat != null) {
|
||||
val evYieldValue = detailSpecies.evYield[baitEVStat]?.toFloat() ?: 0f
|
||||
val detailAspects = detail.pokemon.aspects
|
||||
val formData = detailSpecies.getForm(detailAspects)
|
||||
val evYieldValue = formData.evYield[baitEVStat]?.toFloat() ?: 0f
|
||||
return when {
|
||||
evYieldValue > 0 -> {
|
||||
markUsed()
|
||||
@@ -83,7 +85,9 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
val baitTypingEffect = baitEffect?.subcategory?.path?.let { ElementalTypes.get(it) }
|
||||
|
||||
if (detailSpecies != null && baitTypingEffect != null) {
|
||||
val isMatchingType = detailSpecies.types.contains(baitTypingEffect)
|
||||
val detailAspects = detail.pokemon.aspects
|
||||
val formData = detailSpecies.getForm(detailAspects)
|
||||
val isMatchingType = formData.types.contains(baitTypingEffect)
|
||||
return when {
|
||||
isMatchingType -> {
|
||||
markUsed()
|
||||
@@ -100,6 +104,9 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
|
||||
|
||||
if (detailSpecies != null) {
|
||||
val detailAspects = detail.pokemon.aspects
|
||||
val formData = detailSpecies.getForm(detailAspects)
|
||||
|
||||
// Collect all the egg group effects
|
||||
val eggGroupEffects = effects.filter { it.type == Effects.EGG_GROUP }
|
||||
|
||||
@@ -111,7 +118,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
LOGGER.warn("Unknown egg group identifier: $effectEggGroupKey")
|
||||
return@firstOrNull false
|
||||
}
|
||||
detailSpecies.eggGroups.contains(eggGroup)
|
||||
formData.eggGroups.contains(eggGroup)
|
||||
}
|
||||
|
||||
if (matchingEffect != null) {
|
||||
|
||||
@@ -8,30 +8,58 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.stats
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.stats.StatFormatter
|
||||
|
||||
object CobblemonStats {
|
||||
val stats = mutableMapOf<String, ResourceLocation>()
|
||||
val stats = mutableMapOf<String, CobblemonStat>()
|
||||
|
||||
var CAPTURED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "captured") //makeCustomStat("captured", StatFormatter.DEFAULT)
|
||||
val SHINIES_CAPTURED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "shinies_captured") //makeCustomStat("shinies_captured", StatFormatter.DEFAULT)
|
||||
val RELEASED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "released") //makeCustomStat("released", StatFormatter.DEFAULT)
|
||||
val EVOLVED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "evolved") //makeCustomStat("evolved", StatFormatter.DEFAULT)
|
||||
val LEVEL_UP: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "level_up") //makeCustomStat("level_up", StatFormatter.DEFAULT)
|
||||
val BATTLES_WON: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_won") //makeCustomStat("battles_won", StatFormatter.DEFAULT)
|
||||
val BATTLES_LOST: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_lost") //makeCustomStat("battles_lost", StatFormatter.DEFAULT)
|
||||
val BATTLES_FLED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_fled") //makeCustomStat("battles_fled", StatFormatter.DEFAULT)
|
||||
val BATTLES_TOTAL: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_total") //makeCustomStat("battles_total", StatFormatter.DEFAULT)
|
||||
val DEX_ENTRIES: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "dex_entries") //makeCustomStat("dex_entries", StatFormatter.DEFAULT)
|
||||
val EGGS_COLLECTED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "eggs_collected") //makeCustomStat("eggs_collected", StatFormatter.DEFAULT)
|
||||
val EGGS_HATCHED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "eggs_hatched") //makeCustomStat("eggs_hatched", StatFormatter.DEFAULT)
|
||||
val TRADED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "traded") //makeCustomStat("traded", StatFormatter.DEFAULT)
|
||||
val FOSSILS_REVIVED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "fossils_revived") //makeCustomStat("fossils_revived", StatFormatter.DEFAULT)
|
||||
val TIMES_RIDDEN: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "times_ridden")
|
||||
val ROD_CASTS: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "rod_casts")
|
||||
val REEL_INS: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "reel_ins")
|
||||
val POKEMON_INTERACTED_WITH: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "pokemon_interacted_with") //makeCustomStat("pokemon_interacted_with", StatFormatter.DEFAULT)
|
||||
//TODO block, stats (interact, gimmi), riding (styles?)
|
||||
@JvmField
|
||||
var CAPTURED: CobblemonStat = CobblemonStat("captured")
|
||||
@JvmField
|
||||
val SHINIES_CAPTURED: CobblemonStat = CobblemonStat("shinies_captured")
|
||||
@JvmField
|
||||
val RELEASED: CobblemonStat = CobblemonStat("released")
|
||||
@JvmField
|
||||
val EVOLVED: CobblemonStat = CobblemonStat("evolved")
|
||||
@JvmField
|
||||
val LEVEL_UP: CobblemonStat = CobblemonStat("level_up")
|
||||
@JvmField
|
||||
val BATTLES_WON: CobblemonStat = CobblemonStat("battles_won")
|
||||
@JvmField
|
||||
val BATTLES_LOST: CobblemonStat = CobblemonStat("battles_lost")
|
||||
@JvmField
|
||||
val BATTLES_FLED: CobblemonStat = CobblemonStat("battles_fled")
|
||||
@JvmField
|
||||
val BATTLES_TOTAL: CobblemonStat = CobblemonStat("battles_total")
|
||||
@JvmField
|
||||
val DEX_ENTRIES: CobblemonStat = CobblemonStat("dex_entries")
|
||||
@JvmField
|
||||
val EGGS_COLLECTED: CobblemonStat = CobblemonStat("eggs_collected")
|
||||
@JvmField
|
||||
val EGGS_HATCHED: CobblemonStat = CobblemonStat("eggs_hatched")
|
||||
@JvmField
|
||||
val TRADED: CobblemonStat = CobblemonStat("traded")
|
||||
@JvmField
|
||||
val FOSSILS_REVIVED: CobblemonStat = CobblemonStat("fossils_revived")
|
||||
@JvmField
|
||||
val TIMES_RIDDEN: CobblemonStat = CobblemonStat("times_ridden")
|
||||
@JvmField
|
||||
val ROD_CASTS: CobblemonStat = CobblemonStat("rod_casts")
|
||||
@JvmField
|
||||
val REEL_INS: CobblemonStat = CobblemonStat("reel_ins")
|
||||
@JvmField
|
||||
val POKEMON_INTERACTED_WITH: CobblemonStat = CobblemonStat("pokemon_interacted_with")
|
||||
@JvmField
|
||||
val RIDING_LAND: CobblemonStat = CobblemonStat("riding_land", StatFormatter.DISTANCE)
|
||||
@JvmField
|
||||
val RIDING_AIR: CobblemonStat = CobblemonStat("riding_air", StatFormatter.DISTANCE)
|
||||
@JvmField
|
||||
val RIDING_LIQUID: CobblemonStat = CobblemonStat("riding_liquid", StatFormatter.DISTANCE)
|
||||
|
||||
//TODO block, stats (interact, gimmi)
|
||||
|
||||
fun registerStats() {
|
||||
stats["captured"] = CAPTURED
|
||||
@@ -52,5 +80,22 @@ object CobblemonStats {
|
||||
stats["rod_casts"] = ROD_CASTS
|
||||
stats["reel_ins"] = REEL_INS
|
||||
stats["pokemon_interacted_with"] = POKEMON_INTERACTED_WITH
|
||||
stats["riding_land"] = RIDING_LAND
|
||||
stats["riding_air"] = RIDING_AIR
|
||||
stats["riding_liquid"] = RIDING_LIQUID
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getStat(cobblemonStat: CobblemonStat) : ResourceLocation {
|
||||
val resourceLocation = cobblemonStat.resourceLocation
|
||||
val stat = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation)
|
||||
if (stat == null) {
|
||||
Cobblemon.LOGGER.error("Could not find stat with id {}", resourceLocation)
|
||||
}
|
||||
return stat ?: throw NullPointerException("Could not find stat with id $resourceLocation")
|
||||
}
|
||||
|
||||
data class CobblemonStat(val path: String, val formatter: StatFormatter = StatFormatter.DEFAULT) {
|
||||
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", path);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -88,7 +88,6 @@ abstract class OneToOneFileStoreAdapter<S>(
|
||||
result
|
||||
}
|
||||
else {
|
||||
storeClass.getConstructor(UUID::class.java).newInstance(uuid)
|
||||
var result = storeClass.getConstructor(UUID::class.java).newInstance(uuid)
|
||||
save(storeClass, uuid, serialize(result, registryAccess))
|
||||
oldFile.delete()
|
||||
|
||||
@@ -0,0 +1,671 @@
|
||||
package com.cobblemon.mod.common.battles.ai
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
import com.cobblemon.mod.common.battles.ai.competitive.*
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
|
||||
import kotlin.random.Random
|
||||
|
||||
class CompetitiveBattleAI(private val skill: Int) : BattleAI {
|
||||
|
||||
companion object {
|
||||
private const val DEBUG_TOP_MOVES = 4
|
||||
}
|
||||
|
||||
private val skillLevel = skill.coerceIn(0, 5)
|
||||
|
||||
private val aiDebugEnabled = isAiDebugEnabled()
|
||||
|
||||
private val battleTracker = EnhancedActiveTracker()
|
||||
private val opponentPredictor = OpponentPredictor()
|
||||
private val strategyAnalyzer = StrategyAnalyzer()
|
||||
private lateinit var damageCalculator: DamageCalculator
|
||||
private lateinit var moveEvaluator: MoveEvaluator
|
||||
private lateinit var switchDecisionMaker: SwitchDecisionMaker
|
||||
private lateinit var doublesDecisionMaker: DoublesDecisionMaker
|
||||
|
||||
private var currentBattle: PokemonBattle? = null
|
||||
private var currentGamePlan: GamePlan? = null
|
||||
private var battleState: BattleState? = null
|
||||
private var lastMoveEvaluation: MoveEvaluator.MoveEvaluation? = null
|
||||
|
||||
private var isDoublesMode: Boolean = false
|
||||
private var currentSlot: Int = 0
|
||||
private var currentTurn: Int = -1
|
||||
private val turnDecisions: MutableMap<Int, DoublesDecisionMaker.MoveTargetPair> = mutableMapOf()
|
||||
private var firstSlotDecided: Boolean = false
|
||||
|
||||
override fun choose(
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
battle: PokemonBattle,
|
||||
aiSide: BattleSide,
|
||||
moveset: ShowdownMoveset?,
|
||||
forceSwitch: Boolean
|
||||
): ShowdownActionResponse {
|
||||
try {
|
||||
initializeComponents(battle)
|
||||
updateBattleState(activeBattlePokemon, battle)
|
||||
|
||||
if (currentGamePlan == null) {
|
||||
val npcTeam = battle.side2.actors.first().pokemonList.map { it.effectedPokemon }
|
||||
currentGamePlan = strategyAnalyzer.analyzeTeamComposition(npcTeam)
|
||||
}
|
||||
|
||||
if (battle.turn != currentTurn) {
|
||||
currentTurn = battle.turn
|
||||
turnDecisions.clear()
|
||||
firstSlotDecided = false
|
||||
}
|
||||
|
||||
if (forceSwitch || activeBattlePokemon.isGone()) {
|
||||
return handleForcedSwitch(activeBattlePokemon, battle)
|
||||
}
|
||||
|
||||
if (!passesSkillCheck()) {
|
||||
debugLog("Skill check failed; selecting random move.")
|
||||
return makeRandomMove(moveset, activeBattlePokemon)
|
||||
}
|
||||
|
||||
updateOpponentModel(battle)
|
||||
|
||||
if (isDoublesMode) {
|
||||
return chooseForDoubles(activeBattlePokemon, battle, moveset)
|
||||
}
|
||||
|
||||
if (shouldAttemptSwitch() && shouldSwitch(activeBattlePokemon, battle)) {
|
||||
val switchTarget = findBestSwitch(activeBattlePokemon, battle)
|
||||
if (switchTarget != null) {
|
||||
switchTarget.willBeSwitchedIn = true
|
||||
logSwitchDecision("voluntary_switch", battle, listOf(switchTarget), switchTarget)
|
||||
return SwitchActionResponse(switchTarget.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
val bestMove = chooseBestMove(moveset, activeBattlePokemon, battle)
|
||||
logMoveDecision("move_select", bestMove, activeBattlePokemon)
|
||||
return executeMove(bestMove, activeBattlePokemon)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return PassActionResponse
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseForDoubles(
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
battle: PokemonBattle,
|
||||
moveset: ShowdownMoveset?
|
||||
): ShowdownActionResponse {
|
||||
val state = battleState ?: return PassActionResponse
|
||||
val plan = currentGamePlan ?: return PassActionResponse
|
||||
val myPokemon = battleTracker.p2Active.getActiveAt(currentSlot) ?: return PassActionResponse
|
||||
val partnerSlot = 1 - currentSlot
|
||||
val partner = battleTracker.p2Active.getActiveAt(partnerSlot)
|
||||
val opponents = battleTracker.p1Active.getAllActive()
|
||||
|
||||
val decision = doublesDecisionMaker.findBestMoveAndTarget(
|
||||
moveset,
|
||||
activeBattlePokemon,
|
||||
myPokemon,
|
||||
opponents,
|
||||
partner,
|
||||
state,
|
||||
plan
|
||||
)
|
||||
|
||||
if (decision == null) {
|
||||
debugLog("Doubles: no evaluated option; selecting random move.")
|
||||
return makeRandomMove(moveset, activeBattlePokemon)
|
||||
}
|
||||
|
||||
turnDecisions[currentSlot] = decision
|
||||
firstSlotDecided = true
|
||||
|
||||
debugLog(
|
||||
"Doubles: chose move=${decision.move.id} target=${decision.targetPNX ?: "auto"} " +
|
||||
"score=${"%.2f".format(decision.evaluation.totalScore)} " +
|
||||
"expectedDamage=${"%.1f".format(decision.evaluation.expectedDamage)} " +
|
||||
"strategic=${"%.1f".format(decision.evaluation.strategicValue)} " +
|
||||
"risk=${"%.2f".format(decision.evaluation.riskFactor)}"
|
||||
)
|
||||
return executeDoublesMove(decision, activeBattlePokemon)
|
||||
}
|
||||
|
||||
private fun executeDoublesMove(
|
||||
decision: DoublesDecisionMaker.MoveTargetPair,
|
||||
activeBattlePokemon: ActiveBattlePokemon
|
||||
): ShowdownActionResponse {
|
||||
return if (decision.targetPNX != null) {
|
||||
MoveActionResponse(decision.move.id, decision.targetPNX)
|
||||
} else {
|
||||
MoveActionResponse(decision.move.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeComponents(battle: PokemonBattle) {
|
||||
if (currentBattle != battle) {
|
||||
currentBattle = battle
|
||||
isDoublesMode = battle.format.battleType.pokemonPerSide > 1
|
||||
damageCalculator = DamageCalculator(battle)
|
||||
moveEvaluator = MoveEvaluator(damageCalculator, opponentPredictor, isDoublesMode)
|
||||
switchDecisionMaker = SwitchDecisionMaker(damageCalculator, opponentPredictor, strategyAnalyzer)
|
||||
doublesDecisionMaker = DoublesDecisionMaker(moveEvaluator, damageCalculator)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBattleState(activeBattlePokemon: ActiveBattlePokemon, battle: PokemonBattle) {
|
||||
val request = activeBattlePokemon.actor.request ?: return
|
||||
|
||||
// Determine current slot from PNX (e.g., "p2a" = slot 0, "p2b" = slot 1)
|
||||
val pnx = activeBattlePokemon.getPNX()
|
||||
currentSlot = if (pnx.length >= 3) pnx[2] - 'a' else 0
|
||||
|
||||
if (isDoublesMode) {
|
||||
battle.side1.activePokemon.forEachIndexed { index, active ->
|
||||
active.battlePokemon?.let { pokemon ->
|
||||
battleTracker.updatePokemonSlot(battleTracker.p1Active, index, pokemon)
|
||||
}
|
||||
}
|
||||
|
||||
battle.side2.activePokemon.forEachIndexed { index, active ->
|
||||
active.battlePokemon?.let { pokemon ->
|
||||
battleTracker.updatePokemonSlot(battleTracker.p2Active, index, pokemon)
|
||||
}
|
||||
}
|
||||
|
||||
battleTracker.p1Active.activePokemon =
|
||||
battleTracker.p1Active.getActiveAt(currentSlot) ?: battleTracker.p1Active.activePokemon
|
||||
battleTracker.p2Active.activePokemon =
|
||||
battleTracker.p2Active.getActiveAt(currentSlot) ?: battleTracker.p2Active.activePokemon
|
||||
} else {
|
||||
battle.side1.activePokemon.firstOrNull()?.battlePokemon?.let { playerPokemon ->
|
||||
battleTracker.updatePokemon(battleTracker.p1Active.activePokemon, playerPokemon)
|
||||
battleTracker.updateParty(battleTracker.p1Active, battleTracker.p1Active.activePokemon)
|
||||
}
|
||||
|
||||
activeBattlePokemon.battlePokemon?.let { aiPokemon ->
|
||||
battleTracker.updatePokemon(battleTracker.p2Active.activePokemon, aiPokemon)
|
||||
battleTracker.updateParty(battleTracker.p2Active, battleTracker.p2Active.activePokemon)
|
||||
}
|
||||
}
|
||||
|
||||
updateRevealedMoves(battle)
|
||||
battleState = createBattleState(battle, activeBattlePokemon)
|
||||
}
|
||||
|
||||
private fun createBattleState(battle: PokemonBattle, activeBattlePokemon: ActiveBattlePokemon): BattleState? {
|
||||
val p1Actor = battle.side1.actors.firstOrNull() ?: return null
|
||||
val p2Actor = battle.side2.actors.firstOrNull() ?: return null
|
||||
|
||||
val playerActiveTracker = if (isDoublesMode) {
|
||||
battleTracker.p1Active.getActiveAt(currentSlot) ?: battleTracker.p1Active.activePokemon
|
||||
} else {
|
||||
battleTracker.p1Active.activePokemon
|
||||
}
|
||||
val npcActiveTracker = if (isDoublesMode) {
|
||||
battleTracker.p2Active.getActiveAt(currentSlot) ?: battleTracker.p2Active.activePokemon
|
||||
} else {
|
||||
battleTracker.p2Active.activePokemon
|
||||
}
|
||||
|
||||
val playerActive = playerActiveTracker.pokemon ?: return null
|
||||
val npcActive = npcActiveTracker.pokemon ?: return null
|
||||
val playerActiveSlots = battleTracker.p1Active.getAllActive()
|
||||
.mapNotNull { it.pokemon }
|
||||
.ifEmpty { listOf(playerActive) }
|
||||
|
||||
val npcActiveSlots = battleTracker.p2Active.getAllActive()
|
||||
.mapNotNull { it.pokemon }
|
||||
.ifEmpty { listOf(npcActive) }
|
||||
|
||||
val partnerSlot = if (isDoublesMode) {
|
||||
val mySlot = currentSlot
|
||||
1 - mySlot
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return BattleState(
|
||||
turn = battle.turn,
|
||||
playerTeam = p1Actor.pokemonList.map { it.effectedPokemon },
|
||||
npcTeam = p2Actor.pokemonList.map { it.effectedPokemon },
|
||||
playerActive = playerActive,
|
||||
npcActive = npcActive,
|
||||
weather = getWeather(battle),
|
||||
terrain = getTerrain(battle),
|
||||
playerHazards = getHazards(battle.side1),
|
||||
npcHazards = getHazards(battle.side2),
|
||||
playerBoosts = battleTracker.p1Active.activePokemon.boosts,
|
||||
npcBoosts = battleTracker.p2Active.activePokemon.boosts,
|
||||
battleContext = battle.contextManager,
|
||||
isDoubles = isDoublesMode,
|
||||
playerActiveSlots = playerActiveSlots,
|
||||
npcActiveSlots = npcActiveSlots,
|
||||
mySlotIndex = currentSlot,
|
||||
partnerSlotIndex = partnerSlot
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleForcedSwitch(activeBattlePokemon: ActiveBattlePokemon, battle: PokemonBattle): ShowdownActionResponse {
|
||||
val availableSwitches = getAvailableSwitches(activeBattlePokemon)
|
||||
|
||||
if (availableSwitches.isEmpty()) {
|
||||
return ForfeitActionResponse()
|
||||
}
|
||||
|
||||
val state = battleState
|
||||
val plan = currentGamePlan
|
||||
|
||||
val bestSwitch = if (state != null && plan != null) {
|
||||
switchDecisionMaker.findBestSwitch(
|
||||
availableSwitches,
|
||||
battleTracker.p1Active.activePokemon,
|
||||
state,
|
||||
plan
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val switchTarget = bestSwitch ?: availableSwitches.first()
|
||||
switchTarget.willBeSwitchedIn = true
|
||||
logSwitchDecision("forced_switch", battle, availableSwitches, switchTarget)
|
||||
return SwitchActionResponse(switchTarget.uuid)
|
||||
}
|
||||
|
||||
private fun shouldSwitch(activeBattlePokemon: ActiveBattlePokemon, battle: PokemonBattle): Boolean {
|
||||
val availableSwitches = getAvailableSwitches(activeBattlePokemon)
|
||||
if (availableSwitches.isEmpty()) return false
|
||||
|
||||
val state = battleState ?: return false
|
||||
val plan = currentGamePlan ?: return false
|
||||
|
||||
return switchDecisionMaker.shouldVoluntarilySwitch(
|
||||
battleTracker.p2Active.activePokemon,
|
||||
battleTracker.p1Active.activePokemon,
|
||||
state,
|
||||
plan,
|
||||
availableSwitches
|
||||
)
|
||||
}
|
||||
|
||||
private fun findBestSwitch(activeBattlePokemon: ActiveBattlePokemon, battle: PokemonBattle): BattlePokemon? {
|
||||
val availableSwitches = getAvailableSwitches(activeBattlePokemon)
|
||||
|
||||
val state = battleState ?: return null
|
||||
val plan = currentGamePlan ?: return null
|
||||
|
||||
return switchDecisionMaker.findBestSwitch(
|
||||
availableSwitches,
|
||||
battleTracker.p1Active.activePokemon,
|
||||
state,
|
||||
plan
|
||||
)
|
||||
}
|
||||
|
||||
private fun chooseBestMove(
|
||||
moveset: ShowdownMoveset?,
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
battle: PokemonBattle
|
||||
): InBattleMove? {
|
||||
if (moveset == null) return null
|
||||
|
||||
val usableMoves = moveset.moves.filter { it.canBeUsed() }
|
||||
if (usableMoves.isEmpty()) return null
|
||||
|
||||
val state = battleState ?: return null
|
||||
val plan = currentGamePlan ?: return null
|
||||
|
||||
val evaluations = usableMoves.map { move ->
|
||||
moveEvaluator.evaluateMove(
|
||||
move,
|
||||
battleTracker.p2Active.activePokemon,
|
||||
battleTracker.p1Active.activePokemon,
|
||||
state,
|
||||
plan
|
||||
)
|
||||
}
|
||||
|
||||
val bestEvaluation = evaluations.maxByOrNull { it.totalScore }
|
||||
logMoveRanking(evaluations)
|
||||
lastMoveEvaluation = bestEvaluation
|
||||
return bestEvaluation?.move
|
||||
}
|
||||
|
||||
private fun executeMove(move: InBattleMove?, activeBattlePokemon: ActiveBattlePokemon): ShowdownActionResponse {
|
||||
if (move == null) {
|
||||
return makeRandomMove(null, activeBattlePokemon)
|
||||
}
|
||||
|
||||
val target = if (move.mustBeUsed()) {
|
||||
null
|
||||
} else {
|
||||
move.target.targetList(activeBattlePokemon)
|
||||
}
|
||||
|
||||
return if (target == null) {
|
||||
MoveActionResponse(move.id)
|
||||
} else {
|
||||
val chosenTarget = target.filter { !it.isAllied(activeBattlePokemon) }.randomOrNull()
|
||||
?: target.random()
|
||||
MoveActionResponse(move.id, (chosenTarget as ActiveBattlePokemon).getPNX())
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeRandomMove(moveset: ShowdownMoveset?, activeBattlePokemon: ActiveBattlePokemon): ShowdownActionResponse {
|
||||
if (moveset == null) return PassActionResponse
|
||||
|
||||
val usableMoves = moveset.moves.filter { it.canBeUsed() }
|
||||
val move = usableMoves.randomOrNull() ?: return PassActionResponse
|
||||
|
||||
val target = if (move.mustBeUsed()) null else move.target.targetList(activeBattlePokemon)
|
||||
|
||||
return if (target == null) {
|
||||
MoveActionResponse(move.id)
|
||||
} else {
|
||||
val chosenTarget = target.filter { !it.isAllied(activeBattlePokemon) }.randomOrNull()
|
||||
?: target.random()
|
||||
MoveActionResponse(move.id, (chosenTarget as ActiveBattlePokemon).getPNX())
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOpponentModel(battle: PokemonBattle) {
|
||||
val situation = categorizeSituation(battleState ?: return)
|
||||
|
||||
val opponentActives = if (isDoublesMode) {
|
||||
battleTracker.p1Active.getAllActive()
|
||||
} else {
|
||||
listOf(battleTracker.p1Active.activePokemon)
|
||||
}
|
||||
|
||||
for (opponentActive in opponentActives) {
|
||||
for (move in opponentActive.revealedMoves) {
|
||||
opponentPredictor.updateMoveHistory(
|
||||
opponentActive.species ?: "unknown",
|
||||
move,
|
||||
situation.name.lowercase()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHealthChange(packet: BattleHealthChangePacket) {
|
||||
try {
|
||||
val battle = currentBattle ?: return
|
||||
val pnx = packet.pnx
|
||||
|
||||
val activePokemon = battle.activePokemon.find { it.getPNX() == pnx } ?: return
|
||||
val newHealth = packet.newHealth.toInt()
|
||||
val isPlayerPokemon = activePokemon.actor == battle.side1.actors.firstOrNull()
|
||||
val teamTracker = if (isPlayerPokemon) {
|
||||
battleTracker.p1Active
|
||||
} else {
|
||||
battleTracker.p2Active
|
||||
}
|
||||
val tracker = battleTracker.getTrackedPokemonByPNX(pnx, teamTracker) ?: teamTracker.activePokemon
|
||||
|
||||
val oldHealth = tracker.currentHp
|
||||
val hpChange = newHealth - oldHealth
|
||||
|
||||
tracker.previousHp = oldHealth
|
||||
tracker.currentHp = newHealth
|
||||
tracker.lastHpChange = hpChange
|
||||
tracker.currentHpPercent = if (tracker.maxHp > 0) newHealth.toDouble() / tracker.maxHp else 0.0
|
||||
|
||||
updateMomentum(hpChange, isPlayerPokemon)
|
||||
|
||||
detectCriticalThresholds(tracker, oldHealth, newHealth, isPlayerPokemon)
|
||||
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun updateMomentum(hpChange: Int, isPlayerPokemon: Boolean) {
|
||||
val momentumChange = when {
|
||||
hpChange > 0 -> -5.0 // Opponent healing shifts momentum toward them
|
||||
hpChange < 0 -> kotlin.math.abs(hpChange) / 10.0 // Damage shifts momentum
|
||||
else -> 0.0
|
||||
}
|
||||
|
||||
if (isPlayerPokemon) {
|
||||
// Player took damage = AI momentum increases
|
||||
battleTracker.p2Active.momentum += momentumChange
|
||||
battleTracker.p1Active.momentum -= momentumChange
|
||||
} else {
|
||||
// AI took damage = Player momentum increases
|
||||
battleTracker.p2Active.momentum -= momentumChange
|
||||
battleTracker.p1Active.momentum += momentumChange
|
||||
}
|
||||
|
||||
// Clamp momentum to -100 to 100
|
||||
battleTracker.p1Active.momentum = battleTracker.p1Active.momentum.coerceIn(-100.0, 100.0)
|
||||
battleTracker.p2Active.momentum = battleTracker.p2Active.momentum.coerceIn(-100.0, 100.0)
|
||||
}
|
||||
|
||||
private fun detectCriticalThresholds(
|
||||
tracker: TrackedPokemon,
|
||||
oldHealth: Int,
|
||||
newHealth: Int,
|
||||
isPlayerPokemon: Boolean
|
||||
) {
|
||||
if (newHealth <= 0 && oldHealth > 0) {
|
||||
if (isPlayerPokemon) {
|
||||
battleTracker.p2Active.momentum += 20.0
|
||||
} else {
|
||||
battleTracker.p1Active.momentum += 20.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun categorizeSituation(battleState: BattleState): BattleSituation {
|
||||
val myHP = battleState.npcActive.currentHealth.toDouble() / battleState.npcActive.maxHealth
|
||||
val oppHP = battleState.playerActive.currentHealth.toDouble() / battleState.playerActive.maxHealth
|
||||
|
||||
return when {
|
||||
myHP < 0.25 -> BattleSituation.DESPERATE
|
||||
oppHP < 0.25 -> BattleSituation.WINNING
|
||||
myHP > 0.8 && oppHP > 0.8 -> BattleSituation.EARLY_GAME
|
||||
battleState.npcTeam.count { it.currentHealth > 0 } <= 2 -> BattleSituation.ENDGAME
|
||||
battleState.playerBoosts.values.any { it >= 2 } -> BattleSituation.OPPONENT_SETUP
|
||||
battleState.npcBoosts.values.any { it >= 2 } -> BattleSituation.MY_SETUP
|
||||
battleState.weather != null -> BattleSituation.WEATHER_CONTROL
|
||||
else -> BattleSituation.NEUTRAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAvailableSwitches(activeBattlePokemon: ActiveBattlePokemon): List<BattlePokemon> {
|
||||
val currentUuid = activeBattlePokemon.battlePokemon?.uuid
|
||||
|
||||
return activeBattlePokemon.actor.pokemonList.filter {
|
||||
it.health > 0 &&
|
||||
it.uuid != currentUuid &&
|
||||
try { it.canBeSentOut() } catch (e: Exception) { true }
|
||||
}
|
||||
}
|
||||
|
||||
private fun passesSkillCheck(): Boolean {
|
||||
if (skillLevel == 5) return true
|
||||
if (skillLevel == 0) return false
|
||||
|
||||
val baseChance = skillLevel * 20
|
||||
val turnBonus = if ((battleState?.turn ?: 0) > 10) 10 else 0
|
||||
|
||||
return Random.nextInt(100) < (baseChance + turnBonus)
|
||||
}
|
||||
|
||||
private fun shouldAttemptSwitch(): Boolean {
|
||||
return when (skillLevel) {
|
||||
in 0..2 -> false
|
||||
3 -> Random.nextDouble() < 0.25
|
||||
4 -> Random.nextDouble() < 0.70
|
||||
5 -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWeather(battle: PokemonBattle): String? {
|
||||
return try {
|
||||
battle.contextManager.get(BattleContext.Type.WEATHER)
|
||||
?.firstOrNull()?.id
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTerrain(battle: PokemonBattle): String? {
|
||||
return try {
|
||||
battle.contextManager.get(BattleContext.Type.TERRAIN)
|
||||
?.firstOrNull()?.id
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHazards(side: BattleSide): List<String> {
|
||||
return try {
|
||||
side.contextManager.get(BattleContext.Type.HAZARD)
|
||||
?.map { it.id } ?: emptyList()
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRevealedMoves(battle: PokemonBattle) {
|
||||
updateTeamRevealedMoves(battle, battleTracker.p1Active)
|
||||
updateTeamRevealedMoves(battle, battleTracker.p2Active)
|
||||
}
|
||||
|
||||
private fun updateTeamRevealedMoves(battle: PokemonBattle, team: TeamTracker) {
|
||||
val active = team.getAllActive().ifEmpty { listOf(team.activePokemon) }
|
||||
for (tracked in active) {
|
||||
val pokemon = tracked.pokemon ?: continue
|
||||
val majorMessage = battle.majorBattleActions[pokemon.uuid]
|
||||
val minorMessage = battle.minorBattleActions[pokemon.uuid]
|
||||
val candidates = listOfNotNull(majorMessage, minorMessage)
|
||||
for (message in candidates) {
|
||||
val id = message.id.lowercase()
|
||||
if (id != "move" && id != "-singlemove" && id != "singlemove") continue
|
||||
val moveId = message.effectAt(1)?.id?.lowercase() ?: continue
|
||||
tracked.revealedMoves.add(moveId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logMoveDecision(
|
||||
reason: String,
|
||||
move: InBattleMove?,
|
||||
activeBattlePokemon: ActiveBattlePokemon
|
||||
) {
|
||||
if (!aiDebugEnabled || move == null) return
|
||||
val eval = lastMoveEvaluation
|
||||
val self = activeBattlePokemon.battlePokemon?.effectedPokemon?.species?.name ?: "unknown"
|
||||
val opp = battleTracker.p1Active.activePokemon.species ?: "unknown"
|
||||
val plan = currentGamePlan?.winCondition?.name ?: "unknown"
|
||||
if (eval == null) {
|
||||
debugLog("Decision=$reason self=$self opp=$opp move=${move.id} plan=$plan")
|
||||
return
|
||||
}
|
||||
debugLog(
|
||||
"Decision=$reason self=$self opp=$opp move=${move.id} plan=$plan " +
|
||||
"score=${"%.2f".format(eval.totalScore)} " +
|
||||
"expectedDamage=${"%.1f".format(eval.expectedDamage)} " +
|
||||
"strategic=${"%.1f".format(eval.strategicValue)} " +
|
||||
"risk=${"%.2f".format(eval.riskFactor)}"
|
||||
)
|
||||
}
|
||||
|
||||
private fun logMoveRanking(evaluations: List<MoveEvaluator.MoveEvaluation>) {
|
||||
if (!aiDebugEnabled || evaluations.isEmpty()) return
|
||||
val plan = currentGamePlan?.winCondition?.name ?: "unknown"
|
||||
val situation = battleState?.let { categorizeSituation(it).name } ?: "unknown"
|
||||
val top = evaluations.sortedByDescending { it.totalScore }.take(DEBUG_TOP_MOVES)
|
||||
val state = battleState
|
||||
val planData = currentGamePlan
|
||||
val attacker = battleTracker.p2Active.activePokemon
|
||||
val defender = battleTracker.p1Active.activePokemon
|
||||
val details = top.joinToString(", ") { eval ->
|
||||
val debug = if (state != null && planData != null) {
|
||||
moveEvaluator.buildDebugDetails(eval.move, attacker, defender, state, planData)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val flags = if (debug != null) {
|
||||
listOf(
|
||||
"stab=${debug.hasStab}",
|
||||
"eff=${"%.2f".format(debug.effectiveness)}",
|
||||
"acc=${"%.2f".format(debug.accuracy)}",
|
||||
"prio=${debug.priority}",
|
||||
"status=${debug.isStatusMove}",
|
||||
"setup=${debug.isSetup}",
|
||||
"hazardSet=${debug.setsHazards}",
|
||||
"hazardClear=${debug.removesHazards}",
|
||||
"weather=${debug.setsWeather}",
|
||||
"recovery=${debug.isRecovery}",
|
||||
"pivot=${debug.isPivot}",
|
||||
"risky=${debug.isRisky}"
|
||||
).joinToString(" ")
|
||||
} else {
|
||||
"n/a"
|
||||
}
|
||||
"${eval.move.id}:" +
|
||||
"score=${"%.2f".format(eval.totalScore)} " +
|
||||
"raw=${"%.1f".format(eval.rawDamage)} " +
|
||||
"dmg=${"%.1f".format(eval.expectedDamage)} " +
|
||||
"strat=${"%.1f".format(eval.strategicValue)} " +
|
||||
"align=${"%.1f".format(eval.gamePlanAlignment)} " +
|
||||
"turn=${"%.1f".format(eval.turnAdvantage)} " +
|
||||
"risk=${"%.2f".format(eval.riskFactor)} " +
|
||||
flags
|
||||
}
|
||||
debugLog("MoveRank plan=$plan situation=$situation top=[$details]")
|
||||
}
|
||||
|
||||
private fun logSwitchDecision(
|
||||
reason: String,
|
||||
battle: PokemonBattle,
|
||||
availableSwitches: List<BattlePokemon>,
|
||||
chosen: BattlePokemon?
|
||||
) {
|
||||
if (!aiDebugEnabled) return
|
||||
val state = battleState
|
||||
val plan = currentGamePlan
|
||||
val opponent = battleTracker.p1Active.activePokemon
|
||||
val ranked = if (state != null && plan != null) {
|
||||
availableSwitches.map { candidate ->
|
||||
val score = switchDecisionMaker.evaluateSwitchOption(
|
||||
candidate.effectedPokemon,
|
||||
opponent,
|
||||
state,
|
||||
plan
|
||||
)
|
||||
candidate to score
|
||||
}.sortedByDescending { it.second }
|
||||
.take(3)
|
||||
.joinToString(", ") { (candidate, score) ->
|
||||
"${candidate.effectedPokemon.species.name}:${"%.1f".format(score)}"
|
||||
}
|
||||
} else {
|
||||
"n/a"
|
||||
}
|
||||
|
||||
val chosenName = chosen?.effectedPokemon?.species?.name ?: "none"
|
||||
debugLog("Decision=$reason chosen=$chosenName options=[$ranked]")
|
||||
}
|
||||
|
||||
private fun debugLog(message: String) {
|
||||
if (aiDebugEnabled) {
|
||||
Cobblemon.LOGGER.info("[CompetitiveBattleAI] $message")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAiDebugEnabled(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
object AiBattleUtils {
|
||||
fun normalizeWeatherId(weather: String?): String? {
|
||||
val id = weather?.lowercase() ?: return null
|
||||
return when (id) {
|
||||
"sun", "sunny", "sunnyday", "harshsunlight" -> "sunnyday"
|
||||
"rain", "raindance", "heavyrain" -> "raindance"
|
||||
"sand", "sandstorm" -> "sandstorm"
|
||||
"hail" -> "hail"
|
||||
"snow", "snowscape" -> "snow"
|
||||
else -> id
|
||||
}
|
||||
}
|
||||
|
||||
fun hasStab(moveTypeName: String, pokemonTypes: List<String>): Boolean {
|
||||
val moveType = moveTypeName.lowercase()
|
||||
return pokemonTypes.any { it.lowercase() == moveType }
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.battles.interpreter.ContextManager
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
|
||||
data class BattleState(
|
||||
val turn: Int,
|
||||
val playerTeam: List<Pokemon>,
|
||||
val npcTeam: List<Pokemon>,
|
||||
val playerActive: Pokemon,
|
||||
val npcActive: Pokemon,
|
||||
val weather: String?,
|
||||
val terrain: String?,
|
||||
val playerHazards: List<String>,
|
||||
val npcHazards: List<String>,
|
||||
val playerBoosts: Map<Stat, Int>,
|
||||
val npcBoosts: Map<Stat, Int>,
|
||||
val battleContext: ContextManager,
|
||||
val gamePhase: GamePhase = determineGamePhase(turn, npcTeam, playerTeam),
|
||||
val isDoubles: Boolean = false,
|
||||
val playerActiveSlots: List<Pokemon> = listOf(playerActive),
|
||||
val npcActiveSlots: List<Pokemon> = listOf(npcActive),
|
||||
val mySlotIndex: Int = 0,
|
||||
val partnerSlotIndex: Int? = null
|
||||
) {
|
||||
companion object {
|
||||
private fun determineGamePhase(turn: Int, npcTeam: List<Pokemon>, playerTeam: List<Pokemon>): GamePhase {
|
||||
val npcAlive = npcTeam.count { it.currentHealth > 0 }
|
||||
val playerAlive = playerTeam.count { it.currentHealth > 0 }
|
||||
|
||||
return when {
|
||||
turn <= 3 -> GamePhase.EARLY_GAME
|
||||
npcAlive <= 2 || playerAlive <= 2 -> GamePhase.ENDGAME
|
||||
turn >= 15 -> GamePhase.LATE_GAME
|
||||
else -> GamePhase.MID_GAME
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class GamePhase {
|
||||
EARLY_GAME,
|
||||
MID_GAME,
|
||||
LATE_GAME,
|
||||
ENDGAME
|
||||
}
|
||||
|
||||
data class TrackedPokemon(
|
||||
var pokemon: Pokemon? = null,
|
||||
var originalPokemon: Pokemon? = null,
|
||||
var species: String? = null,
|
||||
|
||||
var currentHp: Int = 0,
|
||||
var currentHpPercent: Double = 0.0,
|
||||
var maxHp: Int = 0,
|
||||
var previousHp: Int = 0,
|
||||
var lastHpChange: Int = 0,
|
||||
|
||||
var boosts: Map<Stat, Int> = mapOf(),
|
||||
var atkBoost: Int = 0,
|
||||
var defBoost: Int = 0,
|
||||
var spaBoost: Int = 0,
|
||||
var spdBoost: Int = 0,
|
||||
var speBoost: Int = 0,
|
||||
|
||||
var currentAbility: String? = null,
|
||||
var currentTypes: MutableList<String>? = null,
|
||||
|
||||
var stats: Map<Stat, Int> = mapOf(),
|
||||
var moves: List<Move> = listOf(),
|
||||
var revealedMoves: MutableSet<String> = mutableSetOf(),
|
||||
|
||||
var cachedAttack: Double = 0.0,
|
||||
var cachedDefense: Double = 0.0,
|
||||
var cachedSpAttack: Double = 0.0,
|
||||
var cachedSpDefense: Double = 0.0,
|
||||
var cachedSpeed: Double = 0.0,
|
||||
|
||||
var sideConditions: Map<String, Any> = mapOf(),
|
||||
var firstTurn: Int = 0,
|
||||
var protectCount: Int = 0,
|
||||
var lastMoveDamage: Int = 0,
|
||||
var statusTurns: Int = 0,
|
||||
var switchInTurn: Int = 0,
|
||||
|
||||
var predictedRole: PokemonRole = PokemonRole.UNKNOWN,
|
||||
var threatLevel: Int = 0
|
||||
)
|
||||
|
||||
|
||||
enum class PokemonRole {
|
||||
UNKNOWN,
|
||||
SWEEPER,
|
||||
SETUP_SWEEPER,
|
||||
WALLBREAKER,
|
||||
REVENGE_KILLER,
|
||||
TANK,
|
||||
SUPPORT,
|
||||
PIVOT,
|
||||
BALANCED
|
||||
}
|
||||
|
||||
data class TeamTracker(
|
||||
var party: MutableList<TrackedPokemon> = mutableListOf(),
|
||||
var uuid: String? = null,
|
||||
var activePokemon: TrackedPokemon = TrackedPokemon(),
|
||||
var activeSlots: MutableList<TrackedPokemon> = mutableListOf(),
|
||||
var nRemainingMons: Int = 0,
|
||||
var hazardDamage: Int = 0,
|
||||
var teamSynergy: Double = 0.0,
|
||||
var momentum: Double = 0.0
|
||||
) {
|
||||
fun getActiveAt(slotIndex: Int): TrackedPokemon? = activeSlots.getOrNull(slotIndex)
|
||||
|
||||
fun getAllActive(): List<TrackedPokemon> = activeSlots.filter { it.currentHp > 0 }
|
||||
|
||||
fun syncLegacyActivePokemon() {
|
||||
if (activeSlots.isNotEmpty()) {
|
||||
activePokemon = activeSlots[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnhancedActiveTracker {
|
||||
var p1Active: TeamTracker = TeamTracker()
|
||||
var p2Active: TeamTracker = TeamTracker()
|
||||
|
||||
private var lastStateUpdateTurn: Int = -1
|
||||
|
||||
fun updatePokemonSlot(team: TeamTracker, slotIndex: Int, battlePokemon: BattlePokemon) {
|
||||
while (team.activeSlots.size <= slotIndex) {
|
||||
team.activeSlots.add(TrackedPokemon())
|
||||
}
|
||||
|
||||
updatePokemon(team.activeSlots[slotIndex], battlePokemon)
|
||||
team.syncLegacyActivePokemon()
|
||||
}
|
||||
|
||||
fun getTrackedPokemonByPNX(pnx: String, team: TeamTracker): TrackedPokemon? {
|
||||
if (pnx.length < 3) return null
|
||||
val slotIndex = pnx[2] - 'a'
|
||||
return team.getActiveAt(slotIndex)
|
||||
}
|
||||
|
||||
fun updatePokemon(tracker: TrackedPokemon, battlePokemon: BattlePokemon) {
|
||||
val pokemon = battlePokemon.effectedPokemon
|
||||
|
||||
tracker.pokemon = pokemon
|
||||
tracker.originalPokemon = battlePokemon.originalPokemon
|
||||
tracker.species = pokemon.species.name
|
||||
tracker.currentHp = pokemon.currentHealth
|
||||
tracker.maxHp = pokemon.maxHealth
|
||||
tracker.currentHpPercent = pokemon.currentHealth.toDouble() / pokemon.maxHealth
|
||||
tracker.currentTypes = pokemon.types.map { it.name.lowercase() }.toMutableList()
|
||||
tracker.currentAbility = pokemon.ability.name
|
||||
tracker.moves = pokemon.moveSet.getMoves()
|
||||
|
||||
tracker.boosts = battlePokemon.statChanges
|
||||
tracker.atkBoost = battlePokemon.statChanges[Stats.ATTACK] ?: 0
|
||||
tracker.defBoost = battlePokemon.statChanges[Stats.DEFENCE] ?: 0
|
||||
tracker.spaBoost = battlePokemon.statChanges[Stats.SPECIAL_ATTACK] ?: 0
|
||||
tracker.spdBoost = battlePokemon.statChanges[Stats.SPECIAL_DEFENCE] ?: 0
|
||||
tracker.speBoost = battlePokemon.statChanges[Stats.SPEED] ?: 0
|
||||
|
||||
cacheStats(tracker)
|
||||
}
|
||||
|
||||
private fun cacheStats(tracker: TrackedPokemon) {
|
||||
val pokemon = tracker.pokemon ?: return
|
||||
|
||||
tracker.cachedAttack = calculateStat(pokemon, Stats.ATTACK)
|
||||
tracker.cachedDefense = calculateStat(pokemon, Stats.DEFENCE)
|
||||
tracker.cachedSpAttack = calculateStat(pokemon, Stats.SPECIAL_ATTACK)
|
||||
tracker.cachedSpDefense = calculateStat(pokemon, Stats.SPECIAL_DEFENCE)
|
||||
tracker.cachedSpeed = calculateStat(pokemon, Stats.SPEED)
|
||||
}
|
||||
|
||||
private fun calculateStat(pokemon: Pokemon, stat: Stat): Double {
|
||||
val base = pokemon.species.baseStats[stat] ?: 0
|
||||
val iv = pokemon.ivs[stat] ?: 31
|
||||
val ev = pokemon.evs[stat] ?: 0
|
||||
val level = pokemon.level
|
||||
|
||||
return ((2.0 * base + iv + ev / 4.0) * level / 100.0 + 5.0)
|
||||
}
|
||||
|
||||
fun updateParty(team: TeamTracker, pokemon: TrackedPokemon) {
|
||||
val existingIndex = team.party.indexOfFirst { it.pokemon?.uuid == pokemon.pokemon?.uuid }
|
||||
if (existingIndex == -1) {
|
||||
team.party.add(pokemon)
|
||||
} else {
|
||||
team.party[existingIndex] = pokemon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FearThreat(
|
||||
val pokemon: Pokemon,
|
||||
val hasEndeavor: Boolean,
|
||||
val hasPriorityMove: Boolean,
|
||||
val hasFocusItem: Boolean,
|
||||
val threatLevel: Int
|
||||
)
|
||||
|
||||
enum class BattleSituation {
|
||||
DESPERATE,
|
||||
WINNING,
|
||||
EARLY_GAME,
|
||||
ENDGAME,
|
||||
OPPONENT_SETUP,
|
||||
MY_SETUP,
|
||||
NEUTRAL,
|
||||
HAZARD_ADVANTAGE,
|
||||
WEATHER_CONTROL
|
||||
}
|
||||
|
||||
data class MatchupEvaluation(
|
||||
val offensiveAdvantage: Double,
|
||||
val defensiveAdvantage: Double,
|
||||
val speedAdvantage: Double,
|
||||
val overallScore: Double,
|
||||
val recommendation: MatchupRecommendation
|
||||
)
|
||||
|
||||
enum class MatchupRecommendation {
|
||||
STAY_IN_AND_ATTACK,
|
||||
STAY_IN_AND_SETUP,
|
||||
STAY_IN_AND_STATUS,
|
||||
CONSIDER_SWITCHING,
|
||||
SWITCH_OUT,
|
||||
PIVOT_OUT
|
||||
}
|
||||
+483
@@ -0,0 +1,483 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.InBattleMove
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
class DamageCalculator(
|
||||
private val battle: PokemonBattle?,
|
||||
private val consumedItems: MutableMap<UUID, MutableList<String>> = mutableMapOf()
|
||||
) {
|
||||
|
||||
fun calculateDamage(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
weather: String? = null,
|
||||
criticalHit: Boolean = false,
|
||||
isSpreadMove: Boolean = false,
|
||||
targetCount: Int = 1
|
||||
): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 0.0
|
||||
var power = moveData.power.toDouble()
|
||||
|
||||
if (power <= 0) return 0.0
|
||||
|
||||
if (isSpreadMove && targetCount > 1) {
|
||||
power *= 0.75
|
||||
}
|
||||
|
||||
val immunityMultiplier = checkAllImmunities(moveData, attacker, defender)
|
||||
if (immunityMultiplier == 0.0) return 0.0
|
||||
|
||||
val level = attacker.pokemon?.level ?: 50
|
||||
val attackStat = getAttackStat(moveData, attacker)
|
||||
val defenseStat = getDefenseStat(moveData, defender)
|
||||
|
||||
var damage = ((2.0 * level / 5.0 + 2.0) * power * (attackStat / defenseStat)) / 50.0 + 2.0
|
||||
|
||||
damage *= calculateCriticalMultiplier(criticalHit)
|
||||
damage *= calculateSTABMultiplier(attacker, moveData)
|
||||
damage *= TypeEffectiveness.calculateTypeEffectiveness(moveData.elementalType, defender.currentTypes ?: listOf())
|
||||
damage *= calculateWeatherMultiplier(moveData, AiBattleUtils.normalizeWeatherId(weather))
|
||||
damage *= calculateAbilityMultiplier(attacker, defender, moveData)
|
||||
damage *= calculateItemMultiplier(attacker, defender, moveData)
|
||||
damage *= calculateBurnMultiplier(attacker, moveData)
|
||||
damage *= calculateScreenMultiplier(attacker, defender, moveData)
|
||||
damage *= calculateRandomFactor()
|
||||
damage = calculateMultiHitDamage(moveData, damage)
|
||||
|
||||
val finalDamage = max(1.0, damage)
|
||||
|
||||
if (finalDamage > 0) {
|
||||
trackConsumedItems(defender, moveData)
|
||||
}
|
||||
|
||||
return finalDamage
|
||||
}
|
||||
|
||||
private fun getAttackStat(moveData: MoveTemplate, attacker: TrackedPokemon): Double {
|
||||
val pokemon = attacker.pokemon ?: return 100.0
|
||||
|
||||
val baseStat = if (moveData.damageCategory == DamageCategories.PHYSICAL) {
|
||||
getEffectiveStat(pokemon, Stats.ATTACK)
|
||||
} else {
|
||||
getEffectiveStat(pokemon, Stats.SPECIAL_ATTACK)
|
||||
}
|
||||
|
||||
val boost = if (moveData.damageCategory == DamageCategories.PHYSICAL) {
|
||||
attacker.atkBoost
|
||||
} else {
|
||||
attacker.spaBoost
|
||||
}
|
||||
|
||||
return applyStatBoosts(baseStat, boost)
|
||||
}
|
||||
|
||||
private fun getDefenseStat(moveData: MoveTemplate, defender: TrackedPokemon): Double {
|
||||
val pokemon = defender.pokemon ?: return 100.0
|
||||
|
||||
val baseStat = if (moveData.damageCategory == DamageCategories.PHYSICAL) {
|
||||
getEffectiveStat(pokemon, Stats.DEFENCE)
|
||||
} else {
|
||||
getEffectiveStat(pokemon, Stats.SPECIAL_DEFENCE)
|
||||
}
|
||||
|
||||
val boost = if (moveData.damageCategory == DamageCategories.PHYSICAL) {
|
||||
defender.defBoost
|
||||
} else {
|
||||
defender.spdBoost
|
||||
}
|
||||
|
||||
return applyStatBoosts(baseStat, boost)
|
||||
}
|
||||
|
||||
|
||||
private fun applyStatBoosts(baseStat: Double, boost: Int): Double {
|
||||
val clampedBoost = boost.coerceIn(-6, 6)
|
||||
return if (clampedBoost >= 0) {
|
||||
baseStat * (2.0 + clampedBoost) / 2.0
|
||||
} else {
|
||||
baseStat * 2.0 / (2.0 - clampedBoost)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getEffectiveStat(pokemon: Pokemon, stat: Stat): Double {
|
||||
val base = pokemon.species.baseStats[stat] ?: 0
|
||||
val iv = pokemon.ivs[stat] ?: 31
|
||||
val ev = pokemon.evs[stat] ?: 0
|
||||
val level = pokemon.level
|
||||
|
||||
return if (stat == Stats.HP) {
|
||||
((2.0 * base + iv + ev / 4.0) * level / 100.0 + level + 10.0)
|
||||
} else {
|
||||
((2.0 * base + iv + ev / 4.0) * level / 100.0 + 5.0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAllImmunities(
|
||||
moveData: MoveTemplate,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon
|
||||
): Double {
|
||||
if (checkTypeImmunity(moveData, defender)) return 0.0
|
||||
if (checkAbilityImmunity(moveData, attacker, defender)) return 0.0
|
||||
if (checkItemImmunity(moveData, defender)) return 0.0
|
||||
if (checkMoveSpecificImmunity(moveData, defender)) return 0.0
|
||||
return 1.0
|
||||
}
|
||||
|
||||
private fun checkTypeImmunity(moveData: MoveTemplate, defender: TrackedPokemon): Boolean {
|
||||
val defenderTypes = (defender.currentTypes ?: defender.pokemon?.types?.map { it.name } ?: listOf())
|
||||
.map { it.lowercase() }
|
||||
|
||||
return when (moveData.elementalType) {
|
||||
ElementalTypes.GROUND -> "flying" in defenderTypes
|
||||
ElementalTypes.ELECTRIC -> "ground" in defenderTypes
|
||||
ElementalTypes.NORMAL, ElementalTypes.FIGHTING -> "ghost" in defenderTypes
|
||||
ElementalTypes.PSYCHIC -> "dark" in defenderTypes
|
||||
ElementalTypes.POISON -> "steel" in defenderTypes
|
||||
ElementalTypes.DRAGON -> "fairy" in defenderTypes
|
||||
ElementalTypes.GHOST -> "normal" in defenderTypes
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAbilityImmunity(
|
||||
moveData: MoveTemplate,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon
|
||||
): Boolean {
|
||||
val defenderAbility = defender.currentAbility?.lowercase()
|
||||
?: defender.pokemon?.ability?.name?.lowercase()
|
||||
?: return false
|
||||
|
||||
val attackerAbility = attacker.currentAbility?.lowercase()
|
||||
?: attacker.pokemon?.ability?.name?.lowercase()
|
||||
if (attackerAbility in listOf("moldbreaker", "turboblaze", "teravolt")) {
|
||||
if (defenderAbility != "wonderguard") return false
|
||||
}
|
||||
|
||||
return when (defenderAbility) {
|
||||
"levitate" -> moveData.elementalType == ElementalTypes.GROUND
|
||||
"voltabsorb", "lightningrod", "motordrive" -> moveData.elementalType == ElementalTypes.ELECTRIC
|
||||
"flashfire", "wellbakedbody" -> moveData.elementalType == ElementalTypes.FIRE
|
||||
"waterabsorb", "stormdrain", "dryskin" -> moveData.elementalType == ElementalTypes.WATER
|
||||
"sapsipper" -> moveData.elementalType == ElementalTypes.GRASS
|
||||
"eartheater" -> moveData.elementalType == ElementalTypes.GROUND
|
||||
"soundproof" -> isSoundMove(moveData)
|
||||
"bulletproof" -> isBallOrBombMove(moveData)
|
||||
"queenlymajesty", "dazzling" -> moveData.priority > 0
|
||||
"wonderguard" -> {
|
||||
val effectiveness = TypeEffectiveness.calculateTypeEffectiveness(
|
||||
moveData.elementalType,
|
||||
defender.currentTypes ?: listOf()
|
||||
)
|
||||
effectiveness <= 1.0
|
||||
}
|
||||
"goodasgold" -> moveData.power.toInt() == 0
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkItemImmunity(moveData: MoveTemplate, defender: TrackedPokemon): Boolean {
|
||||
val defenderItem = defender.pokemon?.heldItem()?.item?.let {
|
||||
BuiltInRegistries.ITEM.getKey(it).toString()
|
||||
} ?: return false
|
||||
|
||||
return when (defenderItem) {
|
||||
"cobblemon:air_balloon" -> {
|
||||
val notConsumed = consumedItems[defender.pokemon?.uuid]?.contains("airballoon") != true
|
||||
notConsumed && moveData.elementalType == ElementalTypes.GROUND
|
||||
}
|
||||
"cobblemon:safety_goggles" -> isPowderMove(moveData) || isWeatherDamageMove(moveData)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMoveSpecificImmunity(moveData: MoveTemplate, defender: TrackedPokemon): Boolean {
|
||||
val defenderTypes = (defender.currentTypes ?: defender.pokemon?.types?.map { it.name } ?: listOf())
|
||||
.map { it.lowercase() }
|
||||
val defenderAbility = defender.currentAbility?.lowercase()
|
||||
?: defender.pokemon?.ability?.name?.lowercase()
|
||||
|
||||
return when (moveData.name.lowercase()) {
|
||||
"thunderwave" -> {
|
||||
"electric" in defenderTypes || "ground" in defenderTypes ||
|
||||
defenderAbility in listOf("limber", "volt absorb", "lightningrod", "motordrive")
|
||||
}
|
||||
"willowisp" -> {
|
||||
"fire" in defenderTypes ||
|
||||
defenderAbility in listOf("waterveil", "waterbubble", "flashfire")
|
||||
}
|
||||
"toxic", "poisonpowder", "poisongas" -> {
|
||||
"poison" in defenderTypes || "steel" in defenderTypes ||
|
||||
defenderAbility in listOf("immunity", "poisonheal", "pastelveil")
|
||||
}
|
||||
"sleeppowder", "spore", "hypnosis", "sing" -> {
|
||||
defenderAbility in listOf("insomnia", "vitalspirit", "comatose", "sweetveil")
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateCriticalMultiplier(criticalHit: Boolean): Double {
|
||||
return if (criticalHit) 1.5 else 1.0
|
||||
}
|
||||
|
||||
private fun calculateSTABMultiplier(attacker: TrackedPokemon, moveData: MoveTemplate): Double {
|
||||
val pokemonTypes = attacker.currentTypes ?: attacker.pokemon?.types?.map { it.name } ?: return 1.0
|
||||
return if (AiBattleUtils.hasStab(moveData.elementalType.name, pokemonTypes)) {
|
||||
if (attacker.currentAbility?.lowercase() == "adaptability") 2.0 else 1.5
|
||||
} else 1.0
|
||||
}
|
||||
|
||||
private fun calculateWeatherMultiplier(moveData: MoveTemplate, weather: String?): Double {
|
||||
return when (weather) {
|
||||
"sunnyday" -> when {
|
||||
moveData.elementalType == ElementalTypes.FIRE -> 1.5
|
||||
moveData.elementalType == ElementalTypes.WATER && moveData.name != "hydrosteam" -> 0.5
|
||||
moveData.name == "hydrosteam" -> 1.5
|
||||
else -> 1.0
|
||||
}
|
||||
"raindance" -> when {
|
||||
moveData.elementalType == ElementalTypes.WATER -> 1.5
|
||||
moveData.elementalType == ElementalTypes.FIRE -> 0.5
|
||||
else -> 1.0
|
||||
}
|
||||
"sandstorm", "hail", "snow" -> if (moveData.name == "weatherball") 2.0 else 1.0
|
||||
else -> 1.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateAbilityMultiplier(
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
moveData: MoveTemplate
|
||||
): Double {
|
||||
var multiplier = 1.0
|
||||
|
||||
when (attacker.currentAbility?.lowercase()) {
|
||||
"hugepower", "purepower" -> {
|
||||
if (moveData.damageCategory == DamageCategories.PHYSICAL) multiplier *= 2.0
|
||||
}
|
||||
"guts" -> {
|
||||
if (attacker.pokemon?.status != null && moveData.damageCategory == DamageCategories.PHYSICAL) {
|
||||
multiplier *= 1.5
|
||||
}
|
||||
}
|
||||
"overgrow" -> {
|
||||
if (moveData.elementalType == ElementalTypes.GRASS && attacker.currentHpPercent <= 0.33) {
|
||||
multiplier *= 1.5
|
||||
}
|
||||
}
|
||||
"blaze" -> {
|
||||
if (moveData.elementalType == ElementalTypes.FIRE && attacker.currentHpPercent <= 0.33) {
|
||||
multiplier *= 1.5
|
||||
}
|
||||
}
|
||||
"torrent" -> {
|
||||
if (moveData.elementalType == ElementalTypes.WATER && attacker.currentHpPercent <= 0.33) {
|
||||
multiplier *= 1.5
|
||||
}
|
||||
}
|
||||
"swarm" -> {
|
||||
if (moveData.elementalType == ElementalTypes.BUG && attacker.currentHpPercent <= 0.33) {
|
||||
multiplier *= 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (defender.currentAbility?.lowercase()) {
|
||||
"thickfat" -> {
|
||||
if (moveData.elementalType == ElementalTypes.FIRE || moveData.elementalType == ElementalTypes.ICE) {
|
||||
multiplier *= 0.5
|
||||
}
|
||||
}
|
||||
"filter", "solidrock" -> {
|
||||
val effectiveness = TypeEffectiveness.calculateTypeEffectiveness(
|
||||
moveData.elementalType,
|
||||
defender.currentTypes ?: listOf()
|
||||
)
|
||||
if (effectiveness > 1.0) multiplier *= 0.75
|
||||
}
|
||||
}
|
||||
|
||||
return multiplier
|
||||
}
|
||||
|
||||
private fun calculateItemMultiplier(
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
moveData: MoveTemplate
|
||||
): Double {
|
||||
var multiplier = 1.0
|
||||
|
||||
val attackerItem = attacker.pokemon?.heldItem()?.item?.let {
|
||||
BuiltInRegistries.ITEM.getKey(it).toString()
|
||||
}
|
||||
|
||||
when (attackerItem) {
|
||||
"cobblemon:choice_band" -> {
|
||||
if (moveData.damageCategory == DamageCategories.PHYSICAL) multiplier *= 1.5
|
||||
}
|
||||
"cobblemon:choice_specs" -> {
|
||||
if (moveData.damageCategory == DamageCategories.SPECIAL) multiplier *= 1.5
|
||||
}
|
||||
"cobblemon:life_orb" -> multiplier *= 1.3
|
||||
}
|
||||
|
||||
return multiplier
|
||||
}
|
||||
|
||||
private fun calculateBurnMultiplier(attacker: TrackedPokemon, moveData: MoveTemplate): Double {
|
||||
return if (attacker.pokemon?.status?.status?.showdownName == "burn" &&
|
||||
moveData.damageCategory == DamageCategories.PHYSICAL &&
|
||||
attacker.currentAbility != "guts") {
|
||||
0.5
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateScreenMultiplier(
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
moveData: MoveTemplate
|
||||
): Double {
|
||||
val attackerAbility = attacker.currentAbility?.lowercase() ?: attacker.pokemon?.ability?.name?.lowercase()
|
||||
if (attackerAbility == "infiltrator") {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
val sideConditions = defender.sideConditions.keys.map { it.lowercase() }
|
||||
|
||||
val hasReflect = "reflect" in sideConditions
|
||||
val hasLightScreen = "lightscreen" in sideConditions || "light screen" in sideConditions
|
||||
val hasAuroraVeil = "auroraveil" in sideConditions || "aurora veil" in sideConditions
|
||||
|
||||
val multiplier = when (moveData.damageCategory) {
|
||||
DamageCategories.PHYSICAL -> {
|
||||
when {
|
||||
hasAuroraVeil -> 0.5
|
||||
hasReflect -> 0.5
|
||||
else -> 1.0
|
||||
}
|
||||
}
|
||||
DamageCategories.SPECIAL -> {
|
||||
when {
|
||||
hasAuroraVeil -> 0.5
|
||||
hasLightScreen -> 0.5
|
||||
else -> 1.0
|
||||
}
|
||||
}
|
||||
else -> 1.0
|
||||
}
|
||||
|
||||
return multiplier
|
||||
}
|
||||
|
||||
private fun calculateRandomFactor(): Double = 0.925
|
||||
|
||||
private fun calculateMultiHitDamage(moveData: MoveTemplate, baseDamage: Double): Double {
|
||||
val moveName = moveData.name.lowercase()
|
||||
|
||||
return when {
|
||||
moveName == "populationbomb" -> {
|
||||
val accuracy = moveData.accuracy / 100.0
|
||||
var expectedDamage = 0.0
|
||||
var probability = 1.0
|
||||
|
||||
for (hit in 1..10) {
|
||||
expectedDamage += baseDamage * probability
|
||||
probability *= accuracy
|
||||
}
|
||||
expectedDamage
|
||||
}
|
||||
|
||||
moveName in listOf("triplekick", "tripleaxel") -> {
|
||||
val accuracy = moveData.accuracy / 100.0
|
||||
baseDamage * 1.0 +
|
||||
baseDamage * 2.0 * accuracy +
|
||||
baseDamage * 3.0 * accuracy * accuracy
|
||||
}
|
||||
|
||||
moveName in MoveData.multiHitMoves.keys -> {
|
||||
val hitRange = MoveData.multiHitMoves[moveName]!!
|
||||
val minHits = hitRange.keys.minOrNull() ?: 1
|
||||
val maxHits = hitRange.values.maxOrNull() ?: 1
|
||||
|
||||
when {
|
||||
minHits == 2 && maxHits == 5 -> baseDamage * 3.0
|
||||
minHits == 2 && maxHits == 2 -> baseDamage * 2.0
|
||||
minHits == 3 && maxHits == 3 -> baseDamage * 3.0
|
||||
else -> baseDamage * ((minHits + maxHits) / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
else -> baseDamage
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSoundMove(moveData: MoveTemplate): Boolean {
|
||||
return moveData.name.lowercase() in listOf(
|
||||
"boomburst", "bugbuzz", "chatter", "clangoroussoul", "disarmingvoice",
|
||||
"echoedvoice", "grasswhistle", "growl", "hypervoice", "metalsound",
|
||||
"nobleroar", "overdrive", "perishsong", "relicsong", "roar", "round",
|
||||
"screech", "sing", "snore", "sparklingaria", "supersonic", "uproar"
|
||||
)
|
||||
}
|
||||
|
||||
private fun isBallOrBombMove(moveData: MoveTemplate): Boolean {
|
||||
return moveData.name.lowercase() in listOf(
|
||||
"acidspray", "aurasphere", "barrage", "bulletseed", "eggbomb",
|
||||
"electroball", "energyball", "focusblast", "gyroball", "iceball",
|
||||
"magnetbomb", "mistball", "mudbomb", "octazooka", "pollenpuff",
|
||||
"rockblast", "searingshot", "seedbomb", "shadowball", "sludgebomb",
|
||||
"weatherball", "zapcannon"
|
||||
)
|
||||
}
|
||||
|
||||
private fun isPowderMove(moveData: MoveTemplate): Boolean {
|
||||
return moveData.name.lowercase() in listOf(
|
||||
"sleeppowder", "stunspore", "poisonpowder", "cottonspore",
|
||||
"ragepowder", "spore"
|
||||
)
|
||||
}
|
||||
|
||||
private fun isWeatherDamageMove(moveData: MoveTemplate): Boolean {
|
||||
return moveData.name.lowercase() in listOf("sandstorm", "hail")
|
||||
}
|
||||
|
||||
private fun trackConsumedItems(defender: TrackedPokemon, moveData: MoveTemplate) {
|
||||
val pokemon = defender.pokemon ?: return
|
||||
val defenderItem = pokemon.heldItem()?.item?.let {
|
||||
BuiltInRegistries.ITEM.getKey(it).toString()
|
||||
} ?: return
|
||||
|
||||
if (defenderItem == "cobblemon:air_balloon") {
|
||||
val alreadyConsumed = consumedItems[pokemon.uuid]?.contains("airballoon") == true
|
||||
if (!alreadyConsumed) {
|
||||
addConsumedItem(pokemon.uuid, "airballoon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addConsumedItem(pokemonUuid: UUID, item: String) {
|
||||
val items = consumedItems.getOrPut(pokemonUuid) { mutableListOf() }
|
||||
items.add(item)
|
||||
}
|
||||
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.InBattleMove
|
||||
import com.cobblemon.mod.common.battles.ShowdownMoveset
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
|
||||
class DoublesDecisionMaker(
|
||||
private val moveEvaluator: MoveEvaluator,
|
||||
private val damageCalculator: DamageCalculator
|
||||
) {
|
||||
|
||||
data class MoveTargetPair(
|
||||
val move: InBattleMove,
|
||||
val targetPNX: String?,
|
||||
val evaluation: MoveEvaluator.MoveEvaluation
|
||||
)
|
||||
|
||||
fun findBestMoveAndTarget(
|
||||
moveset: ShowdownMoveset?,
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
myPokemon: TrackedPokemon,
|
||||
opponents: List<TrackedPokemon>,
|
||||
partner: TrackedPokemon?,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): MoveTargetPair? {
|
||||
if (moveset == null) return null
|
||||
|
||||
val usableMoves = moveset.moves.filter { it.canBeUsed() }
|
||||
if (usableMoves.isEmpty()) return null
|
||||
|
||||
val allOptions = mutableListOf<MoveTargetPair>()
|
||||
|
||||
for (move in usableMoves) {
|
||||
val moveData = Moves.getByName(move.id) ?: continue
|
||||
val targetList = move.target.targetList(activeBattlePokemon)
|
||||
|
||||
if (targetList == null) {
|
||||
val evaluation = moveEvaluator.evaluateMoveForDoubles(
|
||||
move, myPokemon, opponents.firstOrNull() ?: TrackedPokemon(),
|
||||
opponents, partner, battleState, gamePlan
|
||||
)
|
||||
allOptions.add(MoveTargetPair(move, null, evaluation))
|
||||
} else {
|
||||
val opponentTargets = targetList.filterIsInstance<ActiveBattlePokemon>()
|
||||
.filter { !it.isAllied(activeBattlePokemon) }
|
||||
|
||||
for (target in opponentTargets) {
|
||||
val targetPokemon = opponents.find {
|
||||
it.pokemon?.uuid == target.battlePokemon?.uuid
|
||||
} ?: continue
|
||||
|
||||
val evaluation = moveEvaluator.evaluateMoveForDoubles(
|
||||
move, myPokemon, targetPokemon,
|
||||
opponents, partner, battleState, gamePlan
|
||||
)
|
||||
|
||||
allOptions.add(MoveTargetPair(move, target.getPNX(), evaluation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allOptions.maxByOrNull { it.evaluation.totalScore }
|
||||
}
|
||||
|
||||
fun scoreTarget(
|
||||
target: TrackedPokemon,
|
||||
myPokemon: TrackedPokemon,
|
||||
allOpponents: List<TrackedPokemon>,
|
||||
battleState: BattleState
|
||||
): Double {
|
||||
var score = 0.0
|
||||
|
||||
score += target.threatLevel * 20.0
|
||||
|
||||
val mySpeed = myPokemon.cachedSpeed
|
||||
val targetSpeed = target.cachedSpeed
|
||||
if (targetSpeed > mySpeed && target.currentHpPercent < 0.5) {
|
||||
score += 100.0
|
||||
}
|
||||
|
||||
if (target.boosts.values.any { it >= 2 }) {
|
||||
score += 120.0
|
||||
}
|
||||
|
||||
if (target.cachedAttack > 100 || target.cachedSpAttack > 100) {
|
||||
score += 40.0
|
||||
}
|
||||
|
||||
if (target.currentHpPercent < 0.3) {
|
||||
score += 80.0
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
fun selectBestTarget(
|
||||
move: InBattleMove,
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
myPokemon: TrackedPokemon,
|
||||
opponents: List<TrackedPokemon>,
|
||||
battleState: BattleState
|
||||
): String? {
|
||||
val targetList = move.target.targetList(activeBattlePokemon) ?: return null
|
||||
|
||||
val opponentTargets = targetList.filterIsInstance<ActiveBattlePokemon>()
|
||||
.filter { !it.isAllied(activeBattlePokemon) }
|
||||
|
||||
if (opponentTargets.isEmpty()) return null
|
||||
|
||||
val targetScores = opponentTargets.mapNotNull { target ->
|
||||
val targetPokemon = opponents.find {
|
||||
it.pokemon?.uuid == target.battlePokemon?.uuid
|
||||
}
|
||||
|
||||
if (targetPokemon != null) {
|
||||
val damage = damageCalculator.calculateDamage(
|
||||
move, myPokemon, targetPokemon, battleState.weather
|
||||
)
|
||||
|
||||
val targetScore = scoreTarget(targetPokemon, myPokemon, opponents, battleState)
|
||||
val totalScore = damage + targetScore
|
||||
|
||||
target to totalScore
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return targetScores.maxByOrNull { it.second }?.first?.getPNX()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
|
||||
|
||||
object MoveData {
|
||||
val multiHitMoves: Map<String, Map<Int, Int>> = mapOf(
|
||||
"armthrust" to mapOf(2 to 5),
|
||||
"barrage" to mapOf(2 to 5),
|
||||
"bonerush" to mapOf(2 to 5),
|
||||
"bulletseed" to mapOf(2 to 5),
|
||||
"cometpunch" to mapOf(2 to 5),
|
||||
"doubleslap" to mapOf(2 to 5),
|
||||
"furyattack" to mapOf(2 to 5),
|
||||
"furyswipes" to mapOf(2 to 5),
|
||||
"iciclespear" to mapOf(2 to 5),
|
||||
"pinmissile" to mapOf(2 to 5),
|
||||
"rockblast" to mapOf(2 to 5),
|
||||
"scaleshot" to mapOf(2 to 5),
|
||||
"spikecannon" to mapOf(2 to 5),
|
||||
"tailslap" to mapOf(2 to 5),
|
||||
"watershuriken" to mapOf(2 to 5),
|
||||
"bonemerang" to mapOf(2 to 2),
|
||||
"doublehit" to mapOf(2 to 2),
|
||||
"doubleironbash" to mapOf(2 to 2),
|
||||
"doublekick" to mapOf(2 to 2),
|
||||
"dragondarts" to mapOf(2 to 2),
|
||||
"dualchop" to mapOf(2 to 2),
|
||||
"dualwingbeat" to mapOf(2 to 2),
|
||||
"geargrind" to mapOf(2 to 2),
|
||||
"twinbeam" to mapOf(2 to 2),
|
||||
"twineedle" to mapOf(2 to 2),
|
||||
"surgingstrikes" to mapOf(3 to 3),
|
||||
"tripledive" to mapOf(3 to 3),
|
||||
"tripleaxel" to mapOf(1 to 3),
|
||||
"triplekick" to mapOf(1 to 3),
|
||||
"populationbomb" to mapOf(1 to 10)
|
||||
)
|
||||
|
||||
val statusMoves: Map<MoveTemplate?, String> = mapOf(
|
||||
Moves.getByName("willowisp") to Statuses.BURN.showdownName,
|
||||
Moves.getByName("scald") to Statuses.BURN.showdownName,
|
||||
Moves.getByName("scorchingsands") to Statuses.BURN.showdownName,
|
||||
Moves.getByName("flamethrower") to Statuses.BURN.showdownName,
|
||||
Moves.getByName("sacredfire") to Statuses.BURN.showdownName,
|
||||
Moves.getByName("glare") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("nuzzle") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("stunspore") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("thunderwave") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("bodyslam") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("discharge") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("spark") to Statuses.PARALYSIS.showdownName,
|
||||
Moves.getByName("darkvoid") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("hypnosis") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("lovelykiss") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("relicsong") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("sing") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("sleeppowder") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("spore") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("yawn") to Statuses.SLEEP.showdownName,
|
||||
Moves.getByName("chatter") to "confusion",
|
||||
Moves.getByName("confuseray") to "confusion",
|
||||
Moves.getByName("dynamicpunch") to "confusion",
|
||||
Moves.getByName("flatter") to "confusion",
|
||||
Moves.getByName("supersonic") to "confusion",
|
||||
Moves.getByName("swagger") to "confusion",
|
||||
Moves.getByName("sweetkiss") to "confusion",
|
||||
Moves.getByName("teeterdance") to "confusion",
|
||||
Moves.getByName("poisongas") to Statuses.POISON.showdownName,
|
||||
Moves.getByName("poisonpowder") to Statuses.POISON.showdownName,
|
||||
Moves.getByName("poisonjab") to Statuses.POISON.showdownName,
|
||||
Moves.getByName("sludgebomb") to Statuses.POISON.showdownName,
|
||||
Moves.getByName("crosspoison") to Statuses.POISON.showdownName,
|
||||
Moves.getByName("toxic") to Statuses.POISON_BADLY.showdownName,
|
||||
Moves.getByName("toxicthread") to Statuses.POISON.showdownName,
|
||||
Moves.getByName("curse") to "cursed",
|
||||
Moves.getByName("leechseed") to "leech"
|
||||
)
|
||||
|
||||
val boostFromMoves: Map<String, Map<Stat, Int>> = mapOf(
|
||||
"bellydrum" to mapOf(Stats.ATTACK to 6),
|
||||
"swordsdance" to mapOf(Stats.ATTACK to 2),
|
||||
"honeclaws" to mapOf(Stats.ATTACK to 1, Stats.ACCURACY to 1),
|
||||
"bulkup" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1),
|
||||
"curse" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPEED to -1),
|
||||
"howl" to mapOf(Stats.ATTACK to 1),
|
||||
"meditate" to mapOf(Stats.ATTACK to 1),
|
||||
"sharpen" to mapOf(Stats.ATTACK to 1),
|
||||
"workup" to mapOf(Stats.ATTACK to 1, Stats.SPECIAL_ATTACK to 1),
|
||||
"acidarmor" to mapOf(Stats.DEFENCE to 2),
|
||||
"barrier" to mapOf(Stats.DEFENCE to 2),
|
||||
"cottonguard" to mapOf(Stats.DEFENCE to 3),
|
||||
"defensecurl" to mapOf(Stats.DEFENCE to 1),
|
||||
"irondefense" to mapOf(Stats.DEFENCE to 2),
|
||||
"stockpile" to mapOf(Stats.DEFENCE to 1, Stats.SPECIAL_DEFENCE to 1),
|
||||
"stuffcheeks" to mapOf(Stats.DEFENCE to 2),
|
||||
"shelter" to mapOf(Stats.DEFENCE to 2, Stats.EVASION to 1),
|
||||
"withdraw" to mapOf(Stats.DEFENCE to 1),
|
||||
"harden" to mapOf(Stats.DEFENCE to 1),
|
||||
"nastyplot" to mapOf(Stats.SPECIAL_ATTACK to 2),
|
||||
"calmmind" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1),
|
||||
"quiverdance" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
|
||||
"tailglow" to mapOf(Stats.SPECIAL_ATTACK to 3),
|
||||
"takeheart" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1),
|
||||
"geomancy" to mapOf(Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to 2, Stats.SPEED to 2),
|
||||
"growth" to mapOf(Stats.SPECIAL_ATTACK to 1),
|
||||
"amnesia" to mapOf(Stats.SPECIAL_DEFENCE to 2),
|
||||
"agility" to mapOf(Stats.SPEED to 2),
|
||||
"autotomize" to mapOf(Stats.SPEED to 2),
|
||||
"rockpolish" to mapOf(Stats.SPEED to 2),
|
||||
"dragondance" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 1),
|
||||
"shiftgear" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 2),
|
||||
"clangoroussoul" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
|
||||
"extremeevoboost" to mapOf(Stats.ATTACK to 2, Stats.DEFENCE to 2, Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to 2, Stats.SPEED to 2),
|
||||
"clangoroussoulblaze" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
|
||||
"filletaway" to mapOf(Stats.ATTACK to 2, Stats.SPECIAL_ATTACK to 2, Stats.SPEED to 2),
|
||||
"noretreat" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
|
||||
"shellsmash" to mapOf(Stats.ATTACK to 2, Stats.DEFENCE to -1, Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to -1, Stats.SPEED to 2),
|
||||
"tidyup" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 1),
|
||||
"victorydance" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPEED to 1),
|
||||
"coil" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.ACCURACY to 1),
|
||||
"minimize" to mapOf(Stats.EVASION to 2),
|
||||
"doubleteam" to mapOf(Stats.EVASION to 1)
|
||||
)
|
||||
|
||||
val weatherMoves: Map<String, String> = mapOf(
|
||||
"raindance" to "rain",
|
||||
"sunnyday" to "sun",
|
||||
"sandstorm" to "sand",
|
||||
"hail" to "hail",
|
||||
"snowscape" to "snow"
|
||||
)
|
||||
|
||||
val weatherDependentMoves: Map<String, String> = mapOf(
|
||||
"thunder" to "rain",
|
||||
"hurricane" to "rain",
|
||||
"blizzard" to "hail",
|
||||
"solarbeam" to "sun",
|
||||
"hydrosteam" to "sun",
|
||||
"weatherball" to "any"
|
||||
)
|
||||
|
||||
val weatherAbilities: Map<String, String> = mapOf(
|
||||
"swiftswim" to "rain",
|
||||
"raindish" to "rain",
|
||||
"dryskin" to "rain",
|
||||
"chlorophyll" to "sun",
|
||||
"leafguard" to "sun",
|
||||
"flowergift" to "sun",
|
||||
"solarpower" to "sun",
|
||||
"sandrush" to "sand",
|
||||
"sandveil" to "sand",
|
||||
"sandforce" to "sand",
|
||||
"icebody" to "hail",
|
||||
"snowcloak" to "hail"
|
||||
)
|
||||
|
||||
val entryHazards = listOf("spikes", "stealthrock", "stickyweb", "toxicspikes")
|
||||
val antiHazardsMoves = listOf("rapidspin", "defog", "tidyup")
|
||||
val antiBoostMoves = listOf("clearsmog", "haze", "roar", "whirlwind")
|
||||
val pivotMoves = listOf("uturn", "flipturn", "partingshot", "batonpass", "voltswitch", "teleport")
|
||||
val setupMoves = setOf("tailwind", "trickroom", "auroraveil", "lightscreen", "reflect")
|
||||
val selfRecoveryMoves = listOf("healorder", "milkdrink", "recover", "rest", "roost", "slackoff", "softboiled")
|
||||
val priorityMoves = listOf(
|
||||
"quickattack", "extremespeed", "bulletpunch", "machpunch",
|
||||
"aquajet", "shadowsneak", "accelerock", "iceshard",
|
||||
"suckerpunch", "firstimpression", "fakeout"
|
||||
)
|
||||
|
||||
val recoilMoves = listOf(
|
||||
"doubleedge", "bravebird", "closecombat", "superpower",
|
||||
"overheat", "dracometeor", "leafstorm", "flareblitz"
|
||||
)
|
||||
}
|
||||
+737
@@ -0,0 +1,737 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.battles.InBattleMove
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
|
||||
class MoveEvaluator(
|
||||
private val damageCalculator: DamageCalculator,
|
||||
private val opponentPredictor: OpponentPredictor,
|
||||
private val isDoublesMode: Boolean = false
|
||||
) {
|
||||
|
||||
|
||||
data class MoveEvaluation(
|
||||
val move: InBattleMove,
|
||||
val rawDamage: Double,
|
||||
val expectedDamage: Double,
|
||||
val strategicValue: Double,
|
||||
val riskFactor: Double,
|
||||
val priority: Int,
|
||||
val turnAdvantage: Double,
|
||||
val gamePlanAlignment: Double
|
||||
) {
|
||||
|
||||
val totalScore: Double = (expectedDamage + strategicValue + gamePlanAlignment + turnAdvantage) / riskFactor
|
||||
}
|
||||
|
||||
data class MoveDebugDetails(
|
||||
val moveId: String,
|
||||
val moveType: String,
|
||||
val accuracy: Double,
|
||||
val rawDamage: Double,
|
||||
val expectedDamage: Double,
|
||||
val effectiveness: Double,
|
||||
val hasStab: Boolean,
|
||||
val priority: Int,
|
||||
val isStatusMove: Boolean,
|
||||
val setsWeather: Boolean,
|
||||
val setsHazards: Boolean,
|
||||
val removesHazards: Boolean,
|
||||
val isRecovery: Boolean,
|
||||
val isPivot: Boolean,
|
||||
val isSetup: Boolean,
|
||||
val isRisky: Boolean
|
||||
)
|
||||
|
||||
fun buildDebugDetails(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): MoveDebugDetails? {
|
||||
val moveData = Moves.getByName(move.id) ?: return null
|
||||
val rawDamage = damageCalculator.calculateDamage(move, attacker, defender, battleState.weather)
|
||||
val accuracy = getEffectiveAccuracy(move, attacker, defender, battleState.weather)
|
||||
val expectedDamage = rawDamage * accuracy
|
||||
val effectiveness = TypeEffectiveness.calculateTypeEffectiveness(
|
||||
moveData.elementalType,
|
||||
defender.currentTypes ?: listOf()
|
||||
)
|
||||
val attackerTypes = attacker.currentTypes ?: attacker.pokemon?.types?.map { it.name } ?: listOf()
|
||||
val hasStab = AiBattleUtils.hasStab(moveData.elementalType.name, attackerTypes)
|
||||
val moveId = move.id.lowercase()
|
||||
val isStatusMove = MoveData.statusMoves.keys.any { it?.name?.lowercase() == moveId }
|
||||
val setsWeather = moveId in MoveData.weatherMoves.keys
|
||||
val setsHazards = moveId in MoveData.entryHazards
|
||||
val removesHazards = moveId in MoveData.antiHazardsMoves
|
||||
val isRecovery = moveId in MoveData.selfRecoveryMoves
|
||||
val isPivot = moveId in MoveData.pivotMoves
|
||||
val isSetup = moveId in MoveData.boostFromMoves.keys
|
||||
val isRisky = moveId in gamePlan.riskyMoves
|
||||
|
||||
return MoveDebugDetails(
|
||||
moveId = move.id,
|
||||
moveType = moveData.elementalType.name,
|
||||
accuracy = accuracy,
|
||||
rawDamage = rawDamage,
|
||||
expectedDamage = expectedDamage,
|
||||
effectiveness = effectiveness,
|
||||
hasStab = hasStab,
|
||||
priority = moveData.priority,
|
||||
isStatusMove = isStatusMove,
|
||||
setsWeather = setsWeather,
|
||||
setsHazards = setsHazards,
|
||||
removesHazards = removesHazards,
|
||||
isRecovery = isRecovery,
|
||||
isPivot = isPivot,
|
||||
isSetup = isSetup,
|
||||
isRisky = isRisky
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun evaluateMove(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): MoveEvaluation {
|
||||
val moveData = Moves.getByName(move.id)
|
||||
?: return MoveEvaluation(move, 0.0, 0.0, 0.0, 1.0, 0, 0.0, 0.0)
|
||||
|
||||
|
||||
val rawDamage = damageCalculator.calculateDamage(move, attacker, defender, battleState.weather)
|
||||
val accuracy = getEffectiveAccuracy(move, attacker, defender, battleState.weather)
|
||||
val expectedDamage = rawDamage * accuracy
|
||||
|
||||
|
||||
val strategicValue = calculateStrategicValue(move, attacker, defender, battleState, gamePlan)
|
||||
val riskFactor = calculateRiskFactor(move, attacker, defender, battleState, gamePlan)
|
||||
val turnAdvantage = calculateTurnAdvantage(move, attacker, defender, battleState)
|
||||
val gamePlanAlignment = calculateGamePlanAlignment(move, attacker, defender, battleState, gamePlan)
|
||||
|
||||
return MoveEvaluation(
|
||||
move = move,
|
||||
rawDamage = rawDamage,
|
||||
expectedDamage = expectedDamage,
|
||||
strategicValue = strategicValue,
|
||||
riskFactor = riskFactor,
|
||||
priority = moveData.priority,
|
||||
turnAdvantage = turnAdvantage,
|
||||
gamePlanAlignment = gamePlanAlignment
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun getEffectiveAccuracy(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
weather: String?
|
||||
): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 0.0
|
||||
var accuracy = if (moveData.accuracy <= 0) 1.0 else moveData.accuracy / 100.0
|
||||
val normalizedWeather = AiBattleUtils.normalizeWeatherId(weather)
|
||||
|
||||
|
||||
when (normalizedWeather) {
|
||||
"raindance" -> {
|
||||
if (move.id.lowercase() in listOf("thunder", "hurricane")) accuracy = 1.0
|
||||
}
|
||||
|
||||
"hail", "snow" -> {
|
||||
if (move.id.lowercase() == "blizzard") accuracy = 1.0
|
||||
}
|
||||
|
||||
"sunnyday" -> {
|
||||
if (move.id.lowercase() in listOf("thunder", "hurricane")) accuracy *= 0.5
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
when (attacker.currentAbility?.lowercase()) {
|
||||
"compoundeyes" -> accuracy *= 1.3
|
||||
"noguard" -> accuracy = 1.0
|
||||
}
|
||||
|
||||
when (defender.currentAbility?.lowercase()) {
|
||||
"noguard" -> accuracy = 1.0
|
||||
"sandveil" -> if (normalizedWeather == "sandstorm") accuracy *= 0.8
|
||||
"snowcloak" -> if (normalizedWeather in listOf("hail", "snow")) accuracy *= 0.8
|
||||
}
|
||||
|
||||
|
||||
val accuracyBoost = attacker.boosts.getOrDefault(Stats.ACCURACY, 0)
|
||||
val evasionBoost = defender.boosts.getOrDefault(Stats.EVASION, 0)
|
||||
val netBoost = accuracyBoost - evasionBoost
|
||||
|
||||
if (netBoost != 0) {
|
||||
accuracy *= if (netBoost > 0) {
|
||||
(3.0 + netBoost) / 3.0
|
||||
} else {
|
||||
3.0 / (3.0 - netBoost)
|
||||
}
|
||||
}
|
||||
|
||||
return accuracy.coerceIn(0.0, 1.0)
|
||||
}
|
||||
|
||||
|
||||
private fun calculateStrategicValue(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
var value = 0.0
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.weatherMoves.keys) {
|
||||
value += evaluateWeatherSetting(move, gamePlan, battleState)
|
||||
}
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.boostFromMoves.keys) {
|
||||
value += evaluateSetupMove(move, attacker, defender, battleState, gamePlan)
|
||||
}
|
||||
|
||||
|
||||
val moveId = move.id.lowercase()
|
||||
val isStatusMove = MoveData.statusMoves.keys.any { it?.name?.lowercase() == moveId }
|
||||
if (isStatusMove) {
|
||||
value += evaluateStatusMove(move, attacker, defender, battleState, gamePlan)
|
||||
}
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.entryHazards) {
|
||||
value += evaluateHazardSetting(battleState, gamePlan)
|
||||
}
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.antiHazardsMoves) {
|
||||
value += evaluateHazardRemoval(battleState, gamePlan)
|
||||
}
|
||||
|
||||
|
||||
val moveData = Moves.getByName(move.id)
|
||||
if (moveData != null && moveData.priority > 0) {
|
||||
value += evaluatePriorityMove(move, attacker, defender, battleState)
|
||||
}
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.selfRecoveryMoves) {
|
||||
value += evaluateRecoveryMove(attacker, battleState, gamePlan)
|
||||
}
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.pivotMoves) {
|
||||
value += evaluatePivotMove(move, attacker, defender, battleState)
|
||||
}
|
||||
|
||||
|
||||
if (battleState.gamePhase == GamePhase.ENDGAME) {
|
||||
value += evaluateEndgameMove(move, battleState)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
private fun calculateGamePlanAlignment(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 0.0
|
||||
var alignment = 0.0
|
||||
val normalizedWeather = AiBattleUtils.normalizeWeatherId(battleState.weather)
|
||||
|
||||
|
||||
if (move.id.lowercase() in gamePlan.riskyMoves) {
|
||||
alignment -= 100.0
|
||||
}
|
||||
|
||||
|
||||
alignment += when (gamePlan.winCondition) {
|
||||
WinCondition.RAIN_OFFENSE -> {
|
||||
var score = 0.0
|
||||
if (move.id.lowercase() == "raindance") score += 200.0
|
||||
if (normalizedWeather == "raindance") {
|
||||
if (moveData.elementalType.name.lowercase() == "water") score += 50.0
|
||||
if (move.id.lowercase() in listOf("thunder", "hurricane")) score += 100.0
|
||||
}
|
||||
score
|
||||
}
|
||||
|
||||
WinCondition.SUN_OFFENSE -> {
|
||||
var score = 0.0
|
||||
if (move.id.lowercase() == "sunnyday") score += 200.0
|
||||
if (normalizedWeather == "sunnyday") {
|
||||
if (moveData.elementalType.name.lowercase() == "fire") score += 50.0
|
||||
if (move.id.lowercase() == "solarbeam") score += 100.0
|
||||
}
|
||||
score
|
||||
}
|
||||
|
||||
WinCondition.SETUP_SWEEP -> {
|
||||
var score = 0.0
|
||||
if (move.id.lowercase() in MoveData.boostFromMoves.keys &&
|
||||
attacker.species in gamePlan.keyPokemon
|
||||
) {
|
||||
score += 150.0
|
||||
}
|
||||
if (attacker.boosts.values.any { it >= 2 } && moveData.power > 80) {
|
||||
score += 100.0
|
||||
}
|
||||
score
|
||||
}
|
||||
|
||||
WinCondition.AGGRESSIVE_SWEEP -> {
|
||||
var score = 0.0
|
||||
if (moveData.power >= 90) score += 60.0
|
||||
if (moveData.priority > 0 && defender.currentHpPercent < 0.5) score += 80.0
|
||||
score
|
||||
}
|
||||
|
||||
WinCondition.DEFENSIVE_STALL -> {
|
||||
var score = 0.0
|
||||
if (MoveData.statusMoves.keys.any { it?.name?.lowercase() == move.id.lowercase() }) score += 70.0
|
||||
if (move.id.lowercase() in MoveData.selfRecoveryMoves) score += 60.0
|
||||
if (moveData.power > 80) score -= 30.0
|
||||
score
|
||||
}
|
||||
|
||||
WinCondition.HAZARD_CONTROL -> {
|
||||
var score = 0.0
|
||||
if (move.id.lowercase() == "stealthrock" && "stealthrock" !in battleState.playerHazards) {
|
||||
score += 250.0
|
||||
}
|
||||
if (move.id.lowercase() in MoveData.entryHazards) score += 100.0
|
||||
score
|
||||
}
|
||||
|
||||
WinCondition.REVENGE_KILLING -> {
|
||||
var score = 0.0
|
||||
if (moveData.priority > 0) score += 120.0
|
||||
if (defender.currentHp > 50 && moveData.power >= 40 && moveData.power <= 80) score += 40.0
|
||||
score
|
||||
}
|
||||
|
||||
else -> 0.0
|
||||
}
|
||||
|
||||
|
||||
if (moveData.power > 0) {
|
||||
alignment += moveData.power * gamePlan.aggressionLevel * 0.3
|
||||
} else {
|
||||
alignment += (1.0 - gamePlan.aggressionLevel) * 30.0
|
||||
}
|
||||
|
||||
return alignment
|
||||
}
|
||||
|
||||
|
||||
private fun calculateRiskFactor(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 1.0
|
||||
var risk = 1.0
|
||||
|
||||
|
||||
val accuracy = getEffectiveAccuracy(move, attacker, defender, battleState.weather)
|
||||
risk += (1.0 - accuracy) * 0.5
|
||||
|
||||
|
||||
if (move.id.lowercase() in MoveData.recoilMoves) {
|
||||
risk += if (attacker.currentHpPercent < 0.5) 0.5 else 0.2
|
||||
}
|
||||
|
||||
|
||||
if (move.id.lowercase() in listOf("explosion", "selfdestruct", "memento", "healingwish")) {
|
||||
|
||||
if (attacker.species in gamePlan.keyPokemon) {
|
||||
risk += 2.0
|
||||
} else if (attacker.currentHpPercent < 0.3) {
|
||||
risk += 0.2
|
||||
} else {
|
||||
risk += 0.8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (move.pp <= 1) {
|
||||
risk += 0.3
|
||||
}
|
||||
|
||||
|
||||
val mySpeed = attacker.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
val oppSpeed = defender.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
if (abs(mySpeed - oppSpeed) < 5) {
|
||||
risk += 0.1
|
||||
}
|
||||
|
||||
|
||||
val teamAdvantage = calculateTeamAdvantage(battleState)
|
||||
if (teamAdvantage > 0) {
|
||||
|
||||
risk *= 0.8
|
||||
} else if (teamAdvantage < -1) {
|
||||
|
||||
risk *= 1.2
|
||||
}
|
||||
|
||||
return max(0.1, risk)
|
||||
}
|
||||
|
||||
|
||||
private fun calculateTurnAdvantage(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState
|
||||
): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 0.0
|
||||
var advantage = 0.0
|
||||
val mySpeed = attacker.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
val oppSpeed = defender.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
if (mySpeed > oppSpeed) advantage += 15.0
|
||||
if (moveData.priority > 0) advantage += moveData.priority * 25.0
|
||||
if (battleState.gamePhase == GamePhase.ENDGAME && moveData.power > 90) {
|
||||
advantage += 20.0
|
||||
}
|
||||
return advantage
|
||||
}
|
||||
|
||||
|
||||
private fun evaluateWeatherSetting(move: InBattleMove, gamePlan: GamePlan, battleState: BattleState): Double {
|
||||
val moveName = move.id.lowercase()
|
||||
val weatherType = MoveData.weatherMoves[moveName] ?: return 0.0
|
||||
|
||||
return when (gamePlan.winCondition) {
|
||||
WinCondition.RAIN_OFFENSE -> if (weatherType == "rain") 300.0 else 0.0
|
||||
WinCondition.SUN_OFFENSE -> if (weatherType == "sun") 300.0 else 0.0
|
||||
WinCondition.SAND_OFFENSE -> if (weatherType == "sand") 300.0 else 0.0
|
||||
WinCondition.WEATHER_CONTROL -> 150.0
|
||||
else -> 30.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateSetupMove(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
val boosts = MoveData.boostFromMoves[move.id.lowercase()] ?: return 0.0
|
||||
var value = 0.0
|
||||
val totalBoosts = boosts.values.sum()
|
||||
value += totalBoosts * 40.0 * gamePlan.setupPriority
|
||||
val relevantBoosts = boosts.keys.mapNotNull { stat -> attacker.boosts[stat] }
|
||||
if (relevantBoosts.any { it >= 3 }) value *= 0.2
|
||||
val predictedMoves = opponentPredictor.predictMoveHeuristically(defender, attacker)
|
||||
val dangerLevel = predictedMoves.count { (moveName, _) ->
|
||||
(Moves.getByName(moveName)?.power ?: 0.0) > 80
|
||||
}
|
||||
if (dangerLevel > 0 && attacker.currentHpPercent < 0.7) {
|
||||
value *= 0.4
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private fun evaluateStatusMove(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
|
||||
if (defender.pokemon?.status != null) return 0.0
|
||||
|
||||
return when (move.id.lowercase()) {
|
||||
"thunderwave", "stunspore", "glare" -> {
|
||||
|
||||
if (defender.predictedRole == PokemonRole.SWEEPER) 90.0 else 50.0
|
||||
}
|
||||
|
||||
"willowisp" -> {
|
||||
|
||||
if (defender.predictedRole in listOf(PokemonRole.WALLBREAKER, PokemonRole.SWEEPER)) {
|
||||
80.0
|
||||
} else 40.0
|
||||
}
|
||||
|
||||
"toxic" -> {
|
||||
|
||||
if (gamePlan.winCondition == WinCondition.DEFENSIVE_STALL) 100.0 else 60.0
|
||||
}
|
||||
|
||||
"spore", "sleeppowder" -> 120.0
|
||||
else -> 30.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateHazardSetting(battleState: BattleState, gamePlan: GamePlan): Double {
|
||||
val remainingOpponents = battleState.playerTeam.count { it.currentHealth > 0 }
|
||||
if (remainingOpponents < 2) return 15.0
|
||||
|
||||
var value = remainingOpponents * 20.0
|
||||
if (gamePlan.winCondition == WinCondition.HAZARD_CONTROL) value *= 1.8
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private fun evaluateHazardRemoval(battleState: BattleState, gamePlan: GamePlan): Double {
|
||||
val myHazards = battleState.npcHazards.size
|
||||
if (myHazards == 0) return 5.0
|
||||
|
||||
var value = myHazards * 40.0
|
||||
val remainingTeam = battleState.npcTeam.count { it.currentHealth > 0 }
|
||||
if (remainingTeam >= 3) value *= 1.3
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private fun evaluatePriorityMove(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState
|
||||
): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 0.0
|
||||
var value = moveData.priority * 20.0
|
||||
|
||||
|
||||
if (defender.currentHpPercent < 0.5) {
|
||||
val damage = damageCalculator.calculateDamage(move, attacker, defender, battleState.weather)
|
||||
if (damage >= defender.currentHp) value += 150.0
|
||||
}
|
||||
|
||||
|
||||
val mySpeed = attacker.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
val oppSpeed = defender.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
if (abs(mySpeed - oppSpeed) < 10) value += 50.0
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private fun evaluateRecoveryMove(
|
||||
attacker: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
if (attacker.currentHpPercent > 0.8) return 5.0
|
||||
|
||||
var value = (1.0 - attacker.currentHpPercent) * 140.0
|
||||
|
||||
|
||||
if (gamePlan.winCondition == WinCondition.DEFENSIVE_STALL) value *= 1.5
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private fun evaluatePivotMove(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon,
|
||||
battleState: BattleState
|
||||
): Double {
|
||||
var value = 40.0
|
||||
|
||||
|
||||
val damage = damageCalculator.calculateDamage(move, attacker, defender, battleState.weather)
|
||||
value += damage * 0.3
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private fun evaluateEndgameMove(move: InBattleMove, battleState: BattleState): Double {
|
||||
val moveData = Moves.getByName(move.id) ?: return 0.0
|
||||
var bonus = 0.0
|
||||
|
||||
|
||||
if (moveData.power > 0 && moveData.accuracy >= 90) {
|
||||
bonus += 35.0
|
||||
}
|
||||
|
||||
|
||||
if (moveData.accuracy < 80) {
|
||||
bonus -= 30.0
|
||||
}
|
||||
|
||||
|
||||
if (moveData.priority > 0) {
|
||||
bonus += 50.0
|
||||
}
|
||||
|
||||
return bonus
|
||||
}
|
||||
|
||||
private fun calculateTeamAdvantage(battleState: BattleState): Int {
|
||||
val npcAlive = battleState.npcTeam.count { it.currentHealth > 0 }
|
||||
val playerAlive = battleState.playerTeam.count { it.currentHealth > 0 }
|
||||
return npcAlive - playerAlive
|
||||
}
|
||||
|
||||
private fun getEffectiveStat(pokemon: Pokemon, stat: Stats): Double {
|
||||
val base = pokemon.species.baseStats[stat] ?: 0
|
||||
val iv = pokemon.ivs[stat] ?: 31
|
||||
val ev = pokemon.evs[stat] ?: 0
|
||||
val level = pokemon.level
|
||||
|
||||
return if (stat == Stats.HP) {
|
||||
((2.0 * base + iv + ev / 4.0) * level / 100.0 + level + 10.0)
|
||||
} else {
|
||||
((2.0 * base + iv + ev / 4.0) * level / 100.0 + 5.0)
|
||||
}
|
||||
}
|
||||
|
||||
fun evaluateMoveForDoubles(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
primaryTarget: TrackedPokemon,
|
||||
allOpponents: List<TrackedPokemon>,
|
||||
partner: TrackedPokemon?,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): MoveEvaluation {
|
||||
val moveData = Moves.getByName(move.id)
|
||||
?: return MoveEvaluation(move, 0.0, 0.0, 0.0, 1.0, 0, 0.0, 0.0)
|
||||
|
||||
if (isSpreadMove(moveData.target)) {
|
||||
return evaluateSpreadMove(move, attacker, allOpponents, partner, battleState, gamePlan)
|
||||
}
|
||||
|
||||
val baseEvaluation = evaluateMove(move, attacker, primaryTarget, battleState, gamePlan)
|
||||
|
||||
var targetingBonus = 0.0
|
||||
if (primaryTarget.threatLevel > 5) {
|
||||
targetingBonus += 20.0
|
||||
}
|
||||
|
||||
return MoveEvaluation(
|
||||
move = baseEvaluation.move,
|
||||
rawDamage = baseEvaluation.rawDamage,
|
||||
expectedDamage = baseEvaluation.expectedDamage,
|
||||
strategicValue = baseEvaluation.strategicValue + targetingBonus,
|
||||
riskFactor = baseEvaluation.riskFactor,
|
||||
priority = baseEvaluation.priority,
|
||||
turnAdvantage = baseEvaluation.turnAdvantage,
|
||||
gamePlanAlignment = baseEvaluation.gamePlanAlignment
|
||||
)
|
||||
}
|
||||
|
||||
private fun evaluateSpreadMove(
|
||||
move: InBattleMove,
|
||||
attacker: TrackedPokemon,
|
||||
allOpponents: List<TrackedPokemon>,
|
||||
partner: TrackedPokemon?,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): MoveEvaluation {
|
||||
val moveData = Moves.getByName(move.id)
|
||||
?: return MoveEvaluation(move, 0.0, 0.0, 0.0, 1.0, 0, 0.0, 0.0)
|
||||
|
||||
var totalDamage = 0.0
|
||||
var totalStrategicValue = 0.0
|
||||
val activeOpponents = allOpponents.filter { it.currentHp > 0 }
|
||||
val targetCount = activeOpponents.size + (if (hitsPartner(moveData.target) && partner != null) 1 else 0)
|
||||
|
||||
|
||||
for (opponent in activeOpponents) {
|
||||
val damage = damageCalculator.calculateDamage(
|
||||
move, attacker, opponent, battleState.weather,
|
||||
isSpreadMove = true,
|
||||
targetCount = targetCount
|
||||
)
|
||||
totalDamage += damage
|
||||
|
||||
|
||||
if (damage >= opponent.currentHp) {
|
||||
totalStrategicValue += 150.0
|
||||
}
|
||||
}
|
||||
|
||||
val multiTargetBonus = activeOpponents.size * 40.0
|
||||
totalStrategicValue += multiTargetBonus
|
||||
|
||||
var partnerPenalty = 0.0
|
||||
if (hitsPartner(moveData.target) && partner != null && partner.currentHp > 0) {
|
||||
val partnerDamage = damageCalculator.calculateDamage(
|
||||
move, attacker, partner, battleState.weather,
|
||||
isSpreadMove = true,
|
||||
targetCount = targetCount
|
||||
)
|
||||
|
||||
partnerPenalty = when {
|
||||
partnerDamage >= partner.currentHp -> -999999.0
|
||||
partner.species in gamePlan.keyPokemon -> -partnerDamage * 5.0
|
||||
partner.currentHpPercent < 0.3 -> -partnerDamage * 4.0
|
||||
else -> -partnerDamage * 2.0
|
||||
}
|
||||
|
||||
totalStrategicValue += partnerPenalty
|
||||
}
|
||||
|
||||
val accuracy =
|
||||
getEffectiveAccuracy(move, attacker, activeOpponents.firstOrNull() ?: TrackedPokemon(), battleState.weather)
|
||||
val expectedDamage = totalDamage * accuracy
|
||||
|
||||
val riskFactor = if (partnerPenalty < -1000) {
|
||||
999.0
|
||||
} else if (partnerPenalty < 0) {
|
||||
max(1.5, 1.0 + abs(partnerPenalty) / 100.0)
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
|
||||
val gamePlanAlignment = if (activeOpponents.isNotEmpty()) {
|
||||
calculateGamePlanAlignment(move, attacker, activeOpponents.first(), battleState, gamePlan)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
|
||||
return MoveEvaluation(
|
||||
move = move,
|
||||
rawDamage = totalDamage,
|
||||
expectedDamage = expectedDamage,
|
||||
strategicValue = totalStrategicValue,
|
||||
riskFactor = riskFactor,
|
||||
priority = moveData.priority,
|
||||
turnAdvantage = 0.0,
|
||||
gamePlanAlignment = gamePlanAlignment
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun isSpreadMove(target: MoveTarget): Boolean {
|
||||
return target in listOf(
|
||||
MoveTarget.allAdjacent,
|
||||
MoveTarget.allAdjacentFoes,
|
||||
MoveTarget.all
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun hitsPartner(target: MoveTarget): Boolean {
|
||||
return target == MoveTarget.allAdjacent
|
||||
}
|
||||
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
class OpponentPredictor {
|
||||
private val moveHistory = mutableMapOf<String, MutableList<String>>()
|
||||
private val switchHistory = mutableMapOf<String, MutableList<String>>()
|
||||
private val situationResponses = mutableMapOf<String, MutableMap<String, Int>>()
|
||||
private val aggressionLevel = mutableMapOf<String, Double>()
|
||||
private val setupTendency = mutableMapOf<String, Double>()
|
||||
private val switchFrequency = mutableMapOf<String, Double>()
|
||||
|
||||
|
||||
fun updateMoveHistory(pokemon: String, move: String, situation: String) {
|
||||
moveHistory.getOrPut(pokemon) { mutableListOf() }.add(move)
|
||||
situationResponses.getOrPut(situation) { mutableMapOf() }.merge(move, 1, Int::plus)
|
||||
val moveData = Moves.getByName(move)
|
||||
if (moveData != null) {
|
||||
val currentAggression = aggressionLevel[pokemon] ?: 0.5
|
||||
val newAggression = if (moveData.power > 0) {
|
||||
|
||||
currentAggression * 0.9 + 0.8 * 0.1
|
||||
} else {
|
||||
|
||||
currentAggression * 0.9 + 0.2 * 0.1
|
||||
}
|
||||
aggressionLevel[pokemon] = newAggression
|
||||
if (move in MoveData.boostFromMoves.keys) {
|
||||
val currentSetup = setupTendency[pokemon] ?: 0.3
|
||||
setupTendency[pokemon] = min(1.0, currentSetup + 0.15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSwitchPattern(fromPokemon: String, toPokemon: String, reason: String) {
|
||||
switchHistory.getOrPut(fromPokemon) { mutableListOf() }.add(toPokemon)
|
||||
situationResponses.getOrPut("switch_$reason") { mutableMapOf() }.merge(toPokemon, 1, Int::plus)
|
||||
val totalActions = (moveHistory[fromPokemon]?.size ?: 0) + (switchHistory[fromPokemon]?.size ?: 0)
|
||||
if (totalActions > 0) {
|
||||
switchFrequency[fromPokemon] = (switchHistory[fromPokemon]?.size ?: 0).toDouble() / totalActions
|
||||
}
|
||||
}
|
||||
|
||||
fun predictNextMove(pokemon: String, situation: String): Map<String, Double> {
|
||||
val responses = situationResponses[situation] ?: return emptyMap()
|
||||
val total = responses.values.sum().toDouble()
|
||||
|
||||
return if (total > 0) {
|
||||
responses.mapValues { it.value / total }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSwitchProbability(pokemon: String, situation: String): Double {
|
||||
val switches = switchHistory[pokemon]?.size ?: 0
|
||||
val totalActions = (moveHistory[pokemon]?.size ?: 0) + switches
|
||||
|
||||
|
||||
if (totalActions < 5) return 0.2
|
||||
|
||||
return min(0.8, switches.toDouble() / totalActions + 0.1)
|
||||
}
|
||||
|
||||
|
||||
private fun getAggressionLevel(pokemon: String): Double = aggressionLevel[pokemon] ?: 0.5
|
||||
private fun getSetupTendency(pokemon: String): Double = setupTendency[pokemon] ?: 0.3
|
||||
private fun getSwitchFrequency(pokemon: String): Double = switchFrequency[pokemon] ?: 0.2
|
||||
|
||||
fun predictMoveHeuristically(
|
||||
opponent: TrackedPokemon,
|
||||
myPokemon: TrackedPokemon
|
||||
): Map<String, Double> {
|
||||
val predictions = mutableMapOf<String, Double>()
|
||||
val aggression = getAggressionLevel(opponent.species ?: "unknown")
|
||||
for (moveName in opponent.revealedMoves) {
|
||||
val moveData = Moves.getByName(moveName) ?: continue
|
||||
var probability = 0.25
|
||||
|
||||
probability *= if (moveData.power > 0) {
|
||||
aggression + 0.3
|
||||
} else {
|
||||
(1.0 - aggression) + 0.3
|
||||
}
|
||||
|
||||
val myTypes = myPokemon.currentTypes ?: listOf()
|
||||
val effectiveness = TypeEffectiveness.calculateTypeEffectiveness(moveData.elementalType, myTypes)
|
||||
probability *= (0.5 + effectiveness)
|
||||
|
||||
predictions[moveName] = probability
|
||||
}
|
||||
|
||||
val total = predictions.values.sum()
|
||||
return if (total > 0) {
|
||||
predictions.mapValues { it.value / total }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
fun predictSwitchTarget(
|
||||
fromPokemon: String,
|
||||
myActivePokemon: TrackedPokemon
|
||||
): Map<String, Double> {
|
||||
val pastSwitches = switchHistory[fromPokemon] ?: return emptyMap()
|
||||
|
||||
|
||||
val switchCounts = mutableMapOf<String, Int>()
|
||||
for (target in pastSwitches) {
|
||||
switchCounts.merge(target, 1, Int::plus)
|
||||
}
|
||||
|
||||
val total = switchCounts.values.sum().toDouble()
|
||||
return if (total > 0) {
|
||||
switchCounts.mapValues { it.value / total }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun categorizePlaystyle(pokemon: String): OpponentPlaystyle {
|
||||
val aggression = getAggressionLevel(pokemon)
|
||||
val setupTend = getSetupTendency(pokemon)
|
||||
val switchFreq = getSwitchFrequency(pokemon)
|
||||
|
||||
return when {
|
||||
setupTend > 0.5 -> OpponentPlaystyle.SETUP_ORIENTED
|
||||
aggression > 0.7 && switchFreq < 0.3 -> OpponentPlaystyle.HYPER_OFFENSIVE
|
||||
switchFreq > 0.5 -> OpponentPlaystyle.MOMENTUM_BASED
|
||||
aggression < 0.4 -> OpponentPlaystyle.DEFENSIVE_STALL
|
||||
else -> OpponentPlaystyle.BALANCED
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun reset() {
|
||||
moveHistory.clear()
|
||||
switchHistory.clear()
|
||||
situationResponses.clear()
|
||||
aggressionLevel.clear()
|
||||
setupTendency.clear()
|
||||
switchFrequency.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class OpponentPlaystyle {
|
||||
HYPER_OFFENSIVE,
|
||||
SETUP_ORIENTED,
|
||||
MOMENTUM_BASED,
|
||||
DEFENSIVE_STALL,
|
||||
BALANCED
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import kotlin.math.max
|
||||
|
||||
|
||||
class StrategyAnalyzer {
|
||||
fun analyzeTeamComposition(team: List<Pokemon>): GamePlan {
|
||||
val roles = team.map { identifyPokemonRole(it) }
|
||||
val sweepers = roles.count { it == PokemonRole.SWEEPER }
|
||||
val setupSweepers = roles.count { it == PokemonRole.SETUP_SWEEPER }
|
||||
val wallbreakers = roles.count { it == PokemonRole.WALLBREAKER }
|
||||
val supports = roles.count { it in listOf(PokemonRole.SUPPORT, PokemonRole.TANK) }
|
||||
val revengeKillers = roles.count { it == PokemonRole.REVENGE_KILLER }
|
||||
val weatherStrategy = identifyWeatherStrategy(team)
|
||||
if (weatherStrategy != null) {
|
||||
return createWeatherGamePlan(weatherStrategy, team)
|
||||
}
|
||||
val hazardStrategy = identifyHazardStrategy(team)
|
||||
if (hazardStrategy != null) {
|
||||
return createHazardGamePlan(team)
|
||||
}
|
||||
val winCondition = when {
|
||||
setupSweepers >= 2 && supports >= 2 -> WinCondition.SETUP_SWEEP
|
||||
sweepers + wallbreakers >= 4 -> WinCondition.AGGRESSIVE_SWEEP
|
||||
supports >= 3 -> WinCondition.DEFENSIVE_STALL
|
||||
revengeKillers >= 2 -> WinCondition.REVENGE_KILLING
|
||||
else -> WinCondition.BALANCED
|
||||
}
|
||||
return createGamePlan(winCondition, team, roles)
|
||||
}
|
||||
|
||||
|
||||
private fun identifyWeatherStrategy(team: List<Pokemon>): WinCondition? {
|
||||
val rainSetters = team.count { hasWeatherSettingMove(it, "rain") || hasWeatherSettingAbility(it, "rain") }
|
||||
val rainAbusers = team.count { hasWeatherAbility(it, "rain") || hasWeatherMoves(it, "rain") }
|
||||
val sunSetters = team.count { hasWeatherSettingMove(it, "sun") || hasWeatherSettingAbility(it, "sun") }
|
||||
val sunAbusers = team.count { hasWeatherAbility(it, "sun") || hasWeatherMoves(it, "sun") }
|
||||
val sandSetters = team.count { hasWeatherSettingMove(it, "sand") || hasWeatherSettingAbility(it, "sand") }
|
||||
val sandAbusers = team.count { hasWeatherAbility(it, "sand") }
|
||||
return when {
|
||||
rainSetters >= 1 && rainAbusers >= 2 -> WinCondition.RAIN_OFFENSE
|
||||
sunSetters >= 1 && sunAbusers >= 2 -> WinCondition.SUN_OFFENSE
|
||||
sandSetters >= 1 && sandAbusers >= 2 -> WinCondition.SAND_OFFENSE
|
||||
rainSetters >= 1 || sunSetters >= 1 || sandSetters >= 1 -> WinCondition.WEATHER_CONTROL
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun identifyHazardStrategy(team: List<Pokemon>): Boolean {
|
||||
val hazardSetters = team.count { pokemon ->
|
||||
pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.entryHazards }
|
||||
}
|
||||
val hazardRemovers = team.count { pokemon ->
|
||||
pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.antiHazardsMoves }
|
||||
}
|
||||
return hazardSetters >= 2 && hazardRemovers >= 1
|
||||
}
|
||||
private fun createWeatherGamePlan(winCondition: WinCondition, team: List<Pokemon>): GamePlan {
|
||||
val weatherType = when (winCondition) {
|
||||
WinCondition.RAIN_OFFENSE -> "rain"
|
||||
WinCondition.SUN_OFFENSE -> "sun"
|
||||
WinCondition.SAND_OFFENSE -> "sand"
|
||||
else -> "any"
|
||||
}
|
||||
val setters =
|
||||
team.filter { hasWeatherSettingMove(it, weatherType) || hasWeatherSettingAbility(it, weatherType) }
|
||||
val abusers = team.filter { hasWeatherAbility(it, weatherType) || hasWeatherMoves(it, weatherType) }
|
||||
val keyPokemon = (setters + abusers).distinct().map { it.species.name }
|
||||
val priorities = when (winCondition) {
|
||||
WinCondition.RAIN_OFFENSE -> listOf(
|
||||
"set_rain_immediately",
|
||||
"preserve_rain_setter",
|
||||
"sweep_with_rain_abusers",
|
||||
"use_perfect_accuracy_moves",
|
||||
"prevent_weather_changes"
|
||||
)
|
||||
WinCondition.SUN_OFFENSE -> listOf(
|
||||
"set_sun_immediately",
|
||||
"preserve_sun_setter",
|
||||
"sweep_with_chlorophyll",
|
||||
"use_solar_beam",
|
||||
"prevent_weather_changes"
|
||||
)
|
||||
WinCondition.SAND_OFFENSE -> listOf(
|
||||
"set_sandstorm",
|
||||
"abuse_sand_rush",
|
||||
"chip_damage_from_sand",
|
||||
"prevent_weather_changes"
|
||||
)
|
||||
else -> listOf("maintain_weather", "use_weather_abusers")
|
||||
}
|
||||
|
||||
val riskyMoves = listOf(
|
||||
"explosion",
|
||||
"selfdestruct",
|
||||
"memento"
|
||||
)
|
||||
|
||||
return GamePlan(
|
||||
winCondition = winCondition,
|
||||
keyPokemon = keyPokemon,
|
||||
priorities = priorities,
|
||||
riskyMoves = riskyMoves,
|
||||
aggressionLevel = 0.8,
|
||||
setupPriority = 0.9
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun createHazardGamePlan(team: List<Pokemon>): GamePlan {
|
||||
val hazardSetters = team.filter { pokemon ->
|
||||
pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.entryHazards }
|
||||
}.map { it.species.name }
|
||||
|
||||
return GamePlan(
|
||||
winCondition = WinCondition.HAZARD_CONTROL,
|
||||
keyPokemon = hazardSetters,
|
||||
priorities = listOf(
|
||||
"set_stealth_rock_early",
|
||||
"stack_hazards_if_safe",
|
||||
"remove_opponent_hazards",
|
||||
"force_switches",
|
||||
"preserve_hazard_setters"
|
||||
),
|
||||
riskyMoves = listOf("explosion"),
|
||||
aggressionLevel = 0.6,
|
||||
setupPriority = 0.7
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun createGamePlan(winCondition: WinCondition, team: List<Pokemon>, roles: List<PokemonRole>): GamePlan {
|
||||
val keyPokemon = identifyKeyPokemon(winCondition, team, roles)
|
||||
|
||||
val priorities = when (winCondition) {
|
||||
WinCondition.SETUP_SWEEP -> listOf(
|
||||
"create_setup_opportunity",
|
||||
"execute_setup_moves",
|
||||
"sweep_after_setup",
|
||||
"preserve_setup_sweeper",
|
||||
"remove_priority_threats"
|
||||
)
|
||||
|
||||
WinCondition.AGGRESSIVE_SWEEP -> listOf(
|
||||
"maintain_offensive_pressure",
|
||||
"use_coverage_moves",
|
||||
"preserve_sweepers",
|
||||
"use_priority_to_finish",
|
||||
"momentum_over_setup"
|
||||
)
|
||||
|
||||
WinCondition.DEFENSIVE_STALL -> listOf(
|
||||
"spread_status_conditions",
|
||||
"use_recovery_moves",
|
||||
"set_up_hazards",
|
||||
"stall_out_threats",
|
||||
"remove_opponent_hazards"
|
||||
)
|
||||
|
||||
WinCondition.REVENGE_KILLING -> listOf(
|
||||
"preserve_revenge_killer",
|
||||
"use_priority_strategically",
|
||||
"chip_into_priority_range",
|
||||
"scout_with_protect",
|
||||
"maintain_momentum"
|
||||
)
|
||||
|
||||
WinCondition.HAZARD_CONTROL -> listOf(
|
||||
"set_stealth_rock_early",
|
||||
"stack_hazards_safely",
|
||||
"remove_opponent_hazards",
|
||||
"force_switches"
|
||||
)
|
||||
|
||||
else -> listOf(
|
||||
"maintain_balance",
|
||||
"adapt_to_opponent",
|
||||
"resource_management",
|
||||
"take_calculated_risks"
|
||||
)
|
||||
}
|
||||
|
||||
val riskyMoves = when (winCondition) {
|
||||
WinCondition.SETUP_SWEEP -> listOf("explosion", "memento", "healingwish", "lunardance")
|
||||
WinCondition.REVENGE_KILLING -> listOf("doubleedge", "bravebird", "flareblitz")
|
||||
WinCondition.DEFENSIVE_STALL -> MoveData.recoilMoves
|
||||
else -> listOf("explosion", "memento")
|
||||
}
|
||||
|
||||
val aggressionLevel = when (winCondition) {
|
||||
WinCondition.AGGRESSIVE_SWEEP -> 0.9
|
||||
WinCondition.SETUP_SWEEP -> 0.7
|
||||
WinCondition.REVENGE_KILLING -> 0.75
|
||||
WinCondition.DEFENSIVE_STALL -> 0.3
|
||||
else -> 0.6
|
||||
}
|
||||
|
||||
val setupPriority = when (winCondition) {
|
||||
WinCondition.SETUP_SWEEP -> 0.95
|
||||
WinCondition.DEFENSIVE_STALL -> 0.6
|
||||
else -> 0.5
|
||||
}
|
||||
|
||||
return GamePlan(
|
||||
winCondition = winCondition,
|
||||
keyPokemon = keyPokemon,
|
||||
priorities = priorities,
|
||||
riskyMoves = riskyMoves,
|
||||
aggressionLevel = aggressionLevel,
|
||||
setupPriority = setupPriority
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun identifyKeyPokemon(
|
||||
winCondition: WinCondition,
|
||||
team: List<Pokemon>,
|
||||
roles: List<PokemonRole>
|
||||
): List<String> {
|
||||
return when (winCondition) {
|
||||
WinCondition.SETUP_SWEEP -> {
|
||||
team.filterIndexed { index, _ -> roles[index] == PokemonRole.SETUP_SWEEPER }
|
||||
.map { it.species.name }
|
||||
}
|
||||
|
||||
WinCondition.AGGRESSIVE_SWEEP -> {
|
||||
team.filterIndexed { index, _ -> roles[index] in listOf(PokemonRole.SWEEPER, PokemonRole.WALLBREAKER) }
|
||||
.map { it.species.name }
|
||||
}
|
||||
|
||||
WinCondition.REVENGE_KILLING -> {
|
||||
team.filterIndexed { index, _ -> roles[index] == PokemonRole.REVENGE_KILLER }
|
||||
.map { it.species.name }
|
||||
}
|
||||
|
||||
WinCondition.DEFENSIVE_STALL -> {
|
||||
team.filterIndexed { index, _ -> roles[index] in listOf(PokemonRole.TANK, PokemonRole.SUPPORT) }
|
||||
.map { it.species.name }
|
||||
}
|
||||
|
||||
else -> team.take(2).map { it.species.name }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun identifyPokemonRole(pokemon: Pokemon): PokemonRole {
|
||||
val attack = pokemon.species.baseStats[Stats.ATTACK] ?: 0
|
||||
val spAttack = pokemon.species.baseStats[Stats.SPECIAL_ATTACK] ?: 0
|
||||
val defense = pokemon.species.baseStats[Stats.DEFENCE] ?: 0
|
||||
val spDefense = pokemon.species.baseStats[Stats.SPECIAL_DEFENCE] ?: 0
|
||||
val speed = pokemon.species.baseStats[Stats.SPEED] ?: 0
|
||||
val hp = pokemon.species.baseStats[Stats.HP] ?: 0
|
||||
|
||||
val maxAttack = max(attack, spAttack)
|
||||
val avgDefense = (defense + spDefense) / 2.0
|
||||
val bulk = hp + avgDefense
|
||||
|
||||
val hasSetupMoves = pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.boostFromMoves.keys }
|
||||
val hasPriorityMoves = pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.priorityMoves }
|
||||
val hasPivotMoves = pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.pivotMoves }
|
||||
val hasRecoveryMoves = pokemon.moveSet.getMoves().any { it.name.lowercase() in MoveData.selfRecoveryMoves }
|
||||
|
||||
return when {
|
||||
hasPivotMoves -> PokemonRole.PIVOT
|
||||
hasSetupMoves && speed >= 90 && maxAttack >= 90 -> PokemonRole.SETUP_SWEEPER
|
||||
speed >= 110 && hasPriorityMoves -> PokemonRole.REVENGE_KILLER
|
||||
speed >= 100 && maxAttack >= 100 -> PokemonRole.SWEEPER
|
||||
maxAttack >= 120 -> PokemonRole.WALLBREAKER
|
||||
bulk >= 180 && maxAttack < 80 -> PokemonRole.TANK
|
||||
hasRecoveryMoves || hasSetupMoves -> PokemonRole.SUPPORT
|
||||
else -> PokemonRole.BALANCED
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun hasWeatherSettingMove(pokemon: Pokemon, weather: String): Boolean {
|
||||
val moves = pokemon.moveSet.getMoves().map { it.name.lowercase() }
|
||||
return when (weather) {
|
||||
"rain" -> "raindance" in moves
|
||||
"sun" -> "sunnyday" in moves
|
||||
"sand" -> "sandstorm" in moves
|
||||
else -> moves.any { it in MoveData.weatherMoves.keys }
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasWeatherSettingAbility(pokemon: Pokemon, weather: String): Boolean {
|
||||
val ability = pokemon.ability.name.lowercase()
|
||||
return when (weather) {
|
||||
"rain" -> ability == "drizzle"
|
||||
"sun" -> ability == "drought"
|
||||
"sand" -> ability == "sandstream"
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasWeatherAbility(pokemon: Pokemon, weather: String): Boolean {
|
||||
val ability = pokemon.ability.name.lowercase()
|
||||
return when (weather) {
|
||||
"rain" -> ability in listOf("swiftswim", "raindish", "dryskin")
|
||||
"sun" -> ability in listOf("chlorophyll", "leafguard", "flowergift", "solarpower")
|
||||
"sand" -> ability in listOf("sandrush", "sandveil", "sandforce")
|
||||
else -> MoveData.weatherAbilities[ability] == weather
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasWeatherMoves(pokemon: Pokemon, weather: String): Boolean {
|
||||
val moves = pokemon.moveSet.getMoves().map { it.name.lowercase() }
|
||||
return when (weather) {
|
||||
"rain" -> moves.any { it in listOf("thunder", "hurricane") }
|
||||
"sun" -> moves.any { it in listOf("solarbeam", "hydrosteam") }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class WinCondition {
|
||||
AGGRESSIVE_SWEEP,
|
||||
SETUP_SWEEP,
|
||||
DEFENSIVE_STALL,
|
||||
HAZARD_CONTROL,
|
||||
REVENGE_KILLING,
|
||||
BALANCED,
|
||||
RAIN_OFFENSE,
|
||||
SUN_OFFENSE,
|
||||
SAND_OFFENSE,
|
||||
WEATHER_CONTROL
|
||||
}
|
||||
|
||||
|
||||
data class GamePlan(
|
||||
val winCondition: WinCondition,
|
||||
val keyPokemon: List<String>,
|
||||
val priorities: List<String>,
|
||||
val riskyMoves: List<String>,
|
||||
val aggressionLevel: Double = 0.6,
|
||||
val setupPriority: Double = 0.5
|
||||
)
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import kotlin.math.max
|
||||
|
||||
|
||||
class SwitchDecisionMaker(
|
||||
private val damageCalculator: DamageCalculator,
|
||||
private val opponentPredictor: OpponentPredictor,
|
||||
private val strategyAnalyzer: StrategyAnalyzer
|
||||
) {
|
||||
|
||||
|
||||
fun shouldVoluntarilySwitch(
|
||||
myPokemon: TrackedPokemon,
|
||||
opponentPokemon: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan,
|
||||
availableSwitches: List<BattlePokemon>
|
||||
): Boolean {
|
||||
if (availableSwitches.isEmpty()) return false
|
||||
|
||||
|
||||
val currentMatchup = evaluateMatchup(myPokemon, opponentPokemon, battleState)
|
||||
|
||||
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { switchOption ->
|
||||
evaluateSwitchOption(
|
||||
switchOption.effectedPokemon,
|
||||
opponentPokemon,
|
||||
battleState,
|
||||
gamePlan
|
||||
)
|
||||
} ?: 0.0
|
||||
|
||||
|
||||
val switchThreshold = 40.0
|
||||
if (bestSwitchScore > currentMatchup.overallScore + switchThreshold) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
val shouldSwitch = checkSwitchConditions(
|
||||
myPokemon,
|
||||
opponentPokemon,
|
||||
battleState,
|
||||
gamePlan,
|
||||
currentMatchup
|
||||
)
|
||||
|
||||
return shouldSwitch
|
||||
}
|
||||
|
||||
|
||||
fun findBestSwitch(
|
||||
availableSwitches: List<BattlePokemon>,
|
||||
opponentPokemon: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): BattlePokemon? {
|
||||
if (availableSwitches.isEmpty()) return null
|
||||
|
||||
return availableSwitches.maxByOrNull { switchOption ->
|
||||
evaluateSwitchOption(
|
||||
switchOption.effectedPokemon,
|
||||
opponentPokemon,
|
||||
battleState,
|
||||
gamePlan
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun evaluateSwitchOption(
|
||||
candidate: Pokemon,
|
||||
opponent: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan
|
||||
): Double {
|
||||
var score = 0.0
|
||||
|
||||
|
||||
val candidateTracker = createTrackerPokemon(candidate)
|
||||
|
||||
|
||||
val matchup = evaluateMatchup(candidateTracker, opponent, battleState)
|
||||
score += matchup.overallScore
|
||||
|
||||
|
||||
val hpFactor = candidateTracker.currentHpPercent
|
||||
if (hpFactor < 0.5) {
|
||||
score -= (1.0 - hpFactor) * 40.0
|
||||
}
|
||||
|
||||
|
||||
if (gamePlan.keyPokemon.contains(candidate.species.name)) {
|
||||
score += 20.0
|
||||
}
|
||||
|
||||
|
||||
val mySpeed = getEffectiveStat(candidate, Stats.SPEED)
|
||||
val oppSpeed = opponent.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
if (mySpeed > oppSpeed) score += 25.0
|
||||
|
||||
|
||||
val hazardDamage = calculateEntryHazardDamage(candidate, battleState.npcHazards)
|
||||
score -= hazardDamage * 0.5
|
||||
|
||||
|
||||
val role = strategyAnalyzer.identifyPokemonRole(candidate)
|
||||
score += evaluateRoleSynergy(role, gamePlan, battleState)
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
|
||||
private fun evaluateMatchup(
|
||||
myPokemon: TrackedPokemon,
|
||||
opponent: TrackedPokemon,
|
||||
battleState: BattleState
|
||||
): MatchupEvaluation {
|
||||
|
||||
val ourBestEffectiveness = calculateBestMoveEffectiveness(myPokemon, opponent)
|
||||
val offensiveAdvantage = (ourBestEffectiveness - 1.0) * 40.0
|
||||
|
||||
|
||||
val theirBestEffectiveness = calculateBestMoveEffectiveness(opponent, myPokemon)
|
||||
val defensiveAdvantage = -(theirBestEffectiveness - 1.0) * 35.0
|
||||
|
||||
|
||||
val mySpeed = myPokemon.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
val oppSpeed = opponent.pokemon?.let { getEffectiveStat(it, Stats.SPEED) } ?: 0.0
|
||||
val speedAdvantage = if (mySpeed > oppSpeed) 20.0 else -15.0
|
||||
|
||||
|
||||
val overallScore = offensiveAdvantage + defensiveAdvantage + speedAdvantage
|
||||
|
||||
|
||||
val recommendation = when {
|
||||
overallScore > 60 && myPokemon.currentHpPercent > 0.7 -> MatchupRecommendation.STAY_IN_AND_SETUP
|
||||
overallScore > 30 -> MatchupRecommendation.STAY_IN_AND_ATTACK
|
||||
overallScore > 0 -> MatchupRecommendation.CONSIDER_SWITCHING
|
||||
overallScore > -30 -> MatchupRecommendation.PIVOT_OUT
|
||||
else -> MatchupRecommendation.SWITCH_OUT
|
||||
}
|
||||
|
||||
return MatchupEvaluation(
|
||||
offensiveAdvantage = offensiveAdvantage,
|
||||
defensiveAdvantage = defensiveAdvantage,
|
||||
speedAdvantage = speedAdvantage,
|
||||
overallScore = overallScore,
|
||||
recommendation = recommendation
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun checkSwitchConditions(
|
||||
myPokemon: TrackedPokemon,
|
||||
opponent: TrackedPokemon,
|
||||
battleState: BattleState,
|
||||
gamePlan: GamePlan,
|
||||
matchup: MatchupEvaluation
|
||||
): Boolean {
|
||||
|
||||
val theirBestEffectiveness = calculateBestMoveEffectiveness(opponent, myPokemon)
|
||||
if (theirBestEffectiveness >= 2.0 && myPokemon.currentHpPercent > 0.3) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
if (myPokemon.currentHpPercent < 0.3 && matchup.overallScore < 30) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
if (opponent.boosts.values.any { it >= 2 } && matchup.overallScore < 20) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
if (myPokemon.species in gamePlan.keyPokemon &&
|
||||
myPokemon.currentHpPercent < 0.5 &&
|
||||
theirBestEffectiveness > 1.0
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
private fun calculateBestMoveEffectiveness(
|
||||
attacker: TrackedPokemon,
|
||||
defender: TrackedPokemon
|
||||
): Double {
|
||||
val moves = attacker.pokemon?.moveSet?.getMoves() ?: return 1.0
|
||||
return moves.maxOfOrNull { move ->
|
||||
TypeEffectiveness.calculateTypeEffectiveness(
|
||||
move.type,
|
||||
defender.currentTypes ?: listOf()
|
||||
)
|
||||
} ?: 1.0
|
||||
}
|
||||
|
||||
|
||||
private fun calculateEntryHazardDamage(pokemon: Pokemon, hazards: List<String>): Double {
|
||||
var damage = 0.0
|
||||
val maxHP = getEffectiveStat(pokemon, Stats.HP)
|
||||
|
||||
for (hazard in hazards) {
|
||||
when (hazard.lowercase()) {
|
||||
"stealthrock" -> {
|
||||
|
||||
val rockEffectiveness = pokemon.types.fold(1.0) { acc, type ->
|
||||
acc * TypeEffectiveness.getDamageMultiplier(
|
||||
com.cobblemon.mod.common.api.types.ElementalTypes.ROCK,
|
||||
type
|
||||
)
|
||||
}
|
||||
damage += maxHP * rockEffectiveness * 0.125
|
||||
}
|
||||
|
||||
"spikes" -> damage += maxHP * 0.125
|
||||
"toxicspikes" -> {
|
||||
|
||||
if (!pokemon.types.any { it.name.lowercase() in listOf("poison", "steel") }) {
|
||||
damage += maxHP * 0.15
|
||||
}
|
||||
}
|
||||
|
||||
"stickyweb" -> damage += 10.0
|
||||
}
|
||||
}
|
||||
|
||||
return damage
|
||||
}
|
||||
|
||||
|
||||
private fun evaluateRoleSynergy(
|
||||
role: PokemonRole,
|
||||
gamePlan: GamePlan,
|
||||
battleState: BattleState
|
||||
): Double {
|
||||
return when (gamePlan.winCondition) {
|
||||
WinCondition.SETUP_SWEEP -> {
|
||||
when (role) {
|
||||
PokemonRole.SETUP_SWEEPER -> 50.0
|
||||
PokemonRole.SUPPORT -> if (battleState.gamePhase == GamePhase.EARLY_GAME) 30.0 else 10.0
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
WinCondition.AGGRESSIVE_SWEEP -> {
|
||||
when (role) {
|
||||
PokemonRole.SWEEPER, PokemonRole.WALLBREAKER -> 40.0
|
||||
PokemonRole.REVENGE_KILLER -> 30.0
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
WinCondition.DEFENSIVE_STALL -> {
|
||||
when (role) {
|
||||
PokemonRole.TANK, PokemonRole.SUPPORT -> 45.0
|
||||
else -> -10.0
|
||||
}
|
||||
}
|
||||
|
||||
WinCondition.REVENGE_KILLING -> {
|
||||
when (role) {
|
||||
PokemonRole.REVENGE_KILLER -> 60.0
|
||||
PokemonRole.PIVOT -> 25.0
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
WinCondition.RAIN_OFFENSE, WinCondition.SUN_OFFENSE, WinCondition.SAND_OFFENSE -> {
|
||||
|
||||
when (role) {
|
||||
PokemonRole.SWEEPER -> 35.0
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun createTrackerPokemon(pokemon: Pokemon): TrackedPokemon {
|
||||
val tracker = TrackedPokemon(
|
||||
pokemon = pokemon,
|
||||
originalPokemon = pokemon,
|
||||
species = pokemon.species.name,
|
||||
currentHp = pokemon.currentHealth,
|
||||
maxHp = pokemon.maxHealth,
|
||||
currentHpPercent = pokemon.currentHealth.toDouble() / pokemon.maxHealth,
|
||||
currentTypes = pokemon.types.map { it.name.lowercase() }.toMutableList(),
|
||||
currentAbility = pokemon.ability.name,
|
||||
moves = pokemon.moveSet.getMoves(),
|
||||
predictedRole = strategyAnalyzer.identifyPokemonRole(pokemon),
|
||||
cachedAttack = getEffectiveStat(pokemon, Stats.ATTACK),
|
||||
cachedDefense = getEffectiveStat(pokemon, Stats.DEFENCE),
|
||||
cachedSpAttack = getEffectiveStat(pokemon, Stats.SPECIAL_ATTACK),
|
||||
cachedSpDefense = getEffectiveStat(pokemon, Stats.SPECIAL_DEFENCE),
|
||||
cachedSpeed = getEffectiveStat(pokemon, Stats.SPEED)
|
||||
)
|
||||
return tracker
|
||||
}
|
||||
|
||||
private fun getEffectiveStat(pokemon: Pokemon, stat: Stats): Double {
|
||||
val base = pokemon.species.baseStats[stat] ?: 0
|
||||
val iv = pokemon.ivs[stat] ?: 31
|
||||
val ev = pokemon.evs[stat] ?: 0
|
||||
val level = pokemon.level
|
||||
|
||||
return if (stat == Stats.HP) {
|
||||
((2.0 * base + iv + ev / 4.0) * level / 100.0 + level + 10.0)
|
||||
} else {
|
||||
((2.0 * base + iv + ev / 4.0) * level / 100.0 + 5.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
package com.cobblemon.mod.common.battles.ai.competitive
|
||||
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
|
||||
|
||||
object TypeEffectiveness {
|
||||
|
||||
|
||||
val typeEffectivenessMap: Map<ElementalType, Map<ElementalType, Double>> = mapOf(
|
||||
ElementalTypes.NORMAL to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 0.5,
|
||||
ElementalTypes.GHOST to 0.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.FIRE to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 0.5,
|
||||
ElementalTypes.WATER to 0.5,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 2.0,
|
||||
ElementalTypes.ICE to 2.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 2.0,
|
||||
ElementalTypes.ROCK to 0.5,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 0.5,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 2.0,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.WATER to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 2.0,
|
||||
ElementalTypes.WATER to 0.5,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 0.5,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 2.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 2.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 0.5,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 1.0,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.ELECTRIC to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 2.0,
|
||||
ElementalTypes.ELECTRIC to 0.5,
|
||||
ElementalTypes.GRASS to 0.5,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 0.0,
|
||||
ElementalTypes.FLYING to 2.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 0.5,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 1.0,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.GRASS to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 0.5,
|
||||
ElementalTypes.WATER to 2.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 0.5,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 0.5,
|
||||
ElementalTypes.GROUND to 2.0,
|
||||
ElementalTypes.FLYING to 0.5,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 0.5,
|
||||
ElementalTypes.ROCK to 2.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 0.5,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.ICE to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 0.5,
|
||||
ElementalTypes.WATER to 0.5,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 2.0,
|
||||
ElementalTypes.ICE to 0.5,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 2.0,
|
||||
ElementalTypes.FLYING to 2.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 2.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.FIGHTING to mapOf(
|
||||
ElementalTypes.NORMAL to 2.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 2.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 0.5,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 0.5,
|
||||
ElementalTypes.PSYCHIC to 0.5,
|
||||
ElementalTypes.BUG to 0.5,
|
||||
ElementalTypes.ROCK to 2.0,
|
||||
ElementalTypes.GHOST to 0.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 2.0,
|
||||
ElementalTypes.STEEL to 2.0,
|
||||
ElementalTypes.FAIRY to 0.5
|
||||
),
|
||||
ElementalTypes.POISON to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 2.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 0.5,
|
||||
ElementalTypes.GROUND to 0.5,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 0.5,
|
||||
ElementalTypes.GHOST to 0.5,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.0,
|
||||
ElementalTypes.FAIRY to 2.0
|
||||
),
|
||||
ElementalTypes.GROUND to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 2.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 2.0,
|
||||
ElementalTypes.GRASS to 0.5,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 2.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 0.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 0.5,
|
||||
ElementalTypes.ROCK to 2.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 2.0,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.FLYING to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 0.5,
|
||||
ElementalTypes.GRASS to 2.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 2.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 2.0,
|
||||
ElementalTypes.ROCK to 0.5,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.PSYCHIC to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 2.0,
|
||||
ElementalTypes.POISON to 2.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 0.5,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 0.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.BUG to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 0.5,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 2.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 0.5,
|
||||
ElementalTypes.POISON to 0.5,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 0.5,
|
||||
ElementalTypes.PSYCHIC to 2.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 0.5,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 2.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 0.5
|
||||
),
|
||||
ElementalTypes.ROCK to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 2.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 2.0,
|
||||
ElementalTypes.FIGHTING to 0.5,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 0.5,
|
||||
ElementalTypes.FLYING to 2.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 2.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.GHOST to mapOf(
|
||||
ElementalTypes.NORMAL to 0.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 2.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 2.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 0.5,
|
||||
ElementalTypes.STEEL to 1.0,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
),
|
||||
ElementalTypes.DRAGON to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 2.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 0.0
|
||||
),
|
||||
ElementalTypes.DARK to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 1.0,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 0.5,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 2.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 2.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 0.5,
|
||||
ElementalTypes.STEEL to 1.0,
|
||||
ElementalTypes.FAIRY to 0.5
|
||||
),
|
||||
ElementalTypes.STEEL to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 0.5,
|
||||
ElementalTypes.WATER to 0.5,
|
||||
ElementalTypes.ELECTRIC to 0.5,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 2.0,
|
||||
ElementalTypes.FIGHTING to 1.0,
|
||||
ElementalTypes.POISON to 1.0,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 2.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 1.0,
|
||||
ElementalTypes.DARK to 1.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 2.0
|
||||
),
|
||||
ElementalTypes.FAIRY to mapOf(
|
||||
ElementalTypes.NORMAL to 1.0,
|
||||
ElementalTypes.FIRE to 0.5,
|
||||
ElementalTypes.WATER to 1.0,
|
||||
ElementalTypes.ELECTRIC to 1.0,
|
||||
ElementalTypes.GRASS to 1.0,
|
||||
ElementalTypes.ICE to 1.0,
|
||||
ElementalTypes.FIGHTING to 2.0,
|
||||
ElementalTypes.POISON to 0.5,
|
||||
ElementalTypes.GROUND to 1.0,
|
||||
ElementalTypes.FLYING to 1.0,
|
||||
ElementalTypes.PSYCHIC to 1.0,
|
||||
ElementalTypes.BUG to 1.0,
|
||||
ElementalTypes.ROCK to 1.0,
|
||||
ElementalTypes.GHOST to 1.0,
|
||||
ElementalTypes.DRAGON to 2.0,
|
||||
ElementalTypes.DARK to 2.0,
|
||||
ElementalTypes.STEEL to 0.5,
|
||||
ElementalTypes.FAIRY to 1.0
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
fun getDamageMultiplier(attackerType: ElementalType, defenderType: ElementalType): Double {
|
||||
return typeEffectivenessMap[attackerType]?.get(defenderType) ?: 1.0
|
||||
}
|
||||
|
||||
|
||||
fun calculateTypeEffectiveness(moveType: ElementalType, defenderTypes: List<String>): Double {
|
||||
var multiplier = 1.0
|
||||
for (typeString in defenderTypes) {
|
||||
val defenderType = ElementalTypes.get(typeString.lowercase())
|
||||
if (defenderType != null) {
|
||||
multiplier *= getDamageMultiplier(moveType, defenderType)
|
||||
}
|
||||
}
|
||||
return multiplier
|
||||
}
|
||||
|
||||
|
||||
fun getEffectivenessCategory(effectiveness: Double): EffectivenessCategory {
|
||||
return when {
|
||||
effectiveness == 0.0 -> EffectivenessCategory.IMMUNE
|
||||
effectiveness < 1.0 -> EffectivenessCategory.NOT_VERY_EFFECTIVE
|
||||
effectiveness == 1.0 -> EffectivenessCategory.NEUTRAL
|
||||
else -> EffectivenessCategory.SUPER_EFFECTIVE
|
||||
}
|
||||
}
|
||||
|
||||
enum class EffectivenessCategory {
|
||||
IMMUNE, NOT_VERY_EFFECTIVE, NEUTRAL, SUPER_EFFECTIVE
|
||||
}
|
||||
}
|
||||
-1
@@ -31,7 +31,6 @@ class WeatherInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val weather = message.effectAt(0)?.id ?: return
|
||||
val source = message.battlePokemonFromOptional(battle)
|
||||
source?.let { ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), source) }
|
||||
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
val lang = when {
|
||||
message.hasOptionalArgument("upkeep") -> battleLang("weather.$weather.upkeep")
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.block
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
|
||||
import com.cobblemon.mod.common.util.isServerSide
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.core.dispenser.DispenseItemBehavior
|
||||
@@ -263,21 +264,36 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
|
||||
val isGlassBottle = itemStack.`is`(Items.GLASS_BOTTLE)
|
||||
val isHoneyBottle = itemStack.`is`(Items.HONEY_BOTTLE)
|
||||
|
||||
if (isGlassBottle && !isAtMinAge(state)) {
|
||||
if (isGlassBottle && isAtMaxAge(state)) {
|
||||
if (level.isClientSide) return ItemInteractionResult.SUCCESS
|
||||
// Decrement stack if not in creative mode
|
||||
itemStack.consume(1, player)
|
||||
|
||||
// Give player honey bottle for now
|
||||
player.addItem(Items.HONEY_BOTTLE.defaultInstance)
|
||||
// Give player a honey bottle
|
||||
if (stack.isEmpty) {
|
||||
// Replace the consumed empty bottle with a honey bottle
|
||||
player.setItemInHand(hand, ItemStack(Items.HONEY_BOTTLE));
|
||||
} else {
|
||||
player.addItem(Items.HONEY_BOTTLE.defaultInstance)
|
||||
}
|
||||
|
||||
level.playSound(null, pos, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS)
|
||||
level.setBlock(pos, state.setValue(AGE, 0), UPDATE_CLIENTS)
|
||||
level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos)
|
||||
return ItemInteractionResult.SUCCESS
|
||||
} else if (isHoneyBottle && !isAtMaxAge(state)) {
|
||||
if (level.isClientSide) return ItemInteractionResult.SUCCESS
|
||||
|
||||
// Decrement stack if not in creative mode
|
||||
itemStack.consume(1, player)
|
||||
player.addItem(Items.GLASS_BOTTLE.defaultInstance)
|
||||
if (!player.isCreative) {
|
||||
itemStack.consume(1, player)
|
||||
if (stack.isEmpty) {
|
||||
// Replace the consumed honey bottle with an empty bottle
|
||||
player.setItemInHand(hand, ItemStack(Items.GLASS_BOTTLE));
|
||||
} else {
|
||||
player.addItem(Items.GLASS_BOTTLE.defaultInstance)
|
||||
}
|
||||
}
|
||||
|
||||
level.playSound(null, pos, SoundEvents.HONEY_BLOCK_PLACE, SoundSource.BLOCKS)
|
||||
level.setBlock(pos, state.setValue(AGE, 2), UPDATE_CLIENTS)
|
||||
|
||||
+6
@@ -592,6 +592,10 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
}
|
||||
|
||||
private fun switchForm(nextIndex: Boolean) {
|
||||
if (visibleForms.isEmpty() || currentEntry == null) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedPoseIndex = 0
|
||||
|
||||
if (nextIndex) {
|
||||
@@ -602,6 +606,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
else selectedFormIndex = visibleForms.lastIndex
|
||||
}
|
||||
|
||||
selectedFormIndex = selectedFormIndex.coerceIn(0, visibleForms.lastIndex)
|
||||
|
||||
setupButtons(currentEntry!!, visibleForms[selectedFormIndex])
|
||||
updateAspects()
|
||||
}
|
||||
|
||||
+9
-9
@@ -143,18 +143,18 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
|
||||
}
|
||||
|
||||
private fun canAttemptDismount(player: LocalPlayer, selectedPartyPokemon: Pokemon?): Boolean {
|
||||
if (player.vehicle !is PokemonEntity) return false
|
||||
val vehicle = player.vehicle as PokemonEntity
|
||||
if (player != vehicle.controllingPassenger) {
|
||||
return true
|
||||
}
|
||||
val vehicle = player.vehicle as? PokemonEntity ?: return false
|
||||
|
||||
if (player != vehicle.controllingPassenger) return true
|
||||
|
||||
val isAirRide = vehicle.ridingController?.context?.style == RidingStyle.AIR
|
||||
val hasLandRide = vehicle.rideProp.behaviours?.get(RidingStyle.LAND) != null
|
||||
return if (isAirRide && hasLandRide) {
|
||||
false
|
||||
} else {
|
||||
vehicle.pokemon.uuid == selectedPartyPokemon?.uuid
|
||||
|
||||
if (!Cobblemon.config.enableInFlightDismounting && isAirRide && hasLandRide) {
|
||||
return false
|
||||
}
|
||||
|
||||
return vehicle.pokemon.uuid == selectedPartyPokemon?.uuid
|
||||
}
|
||||
|
||||
private fun isRidingPokemon(player: LocalPlayer, ignoreControlling: Boolean = false): Boolean {
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@ import com.mojang.blaze3d.platform.InputConstants
|
||||
|
||||
object RidingFreelookBinding : CobblemonBlockingKeyBinding(
|
||||
"key.cobblemon.ridingfreelook",
|
||||
InputConstants.Type.MOUSE,
|
||||
InputConstants.MOUSE_BUTTON_MIDDLE,
|
||||
InputConstants.Type.KEYSYM,
|
||||
InputConstants.KEY_LALT,
|
||||
KeybindCategories.COBBLEMON_CATEGORY
|
||||
) {
|
||||
override fun onTick() {
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.data
|
||||
|
||||
import com.cobblemon.mod.common.api.interaction.PokemonInteractions
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.data.PokemonInteractionsSyncPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object PokemonInteractionsSyncHandler : ClientNetworkPacketHandler<PokemonInteractionsSyncPacket> {
|
||||
|
||||
override fun handle(
|
||||
packet: PokemonInteractionsSyncPacket,
|
||||
client: Minecraft,
|
||||
) {
|
||||
PokemonInteractions.speciesInteractions.clear()
|
||||
PokemonInteractions.speciesInteractions.addAll(packet.speciesInteractions)
|
||||
PokemonInteractions.generalInteractions.clear()
|
||||
PokemonInteractions.generalInteractions.addAll(packet.generalInteractions)
|
||||
}
|
||||
}
|
||||
+21
-5
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.persisted
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.config.CobblemonConfig
|
||||
import java.io.BufferedReader
|
||||
import java.io.BufferedWriter
|
||||
@@ -31,7 +33,14 @@ object ClientPersistedData {
|
||||
fun readSnapshotsData() : SnapshotAcknowledgementData? {
|
||||
if (snapshotsPath.exists()) {
|
||||
BufferedReader(InputStreamReader(FileInputStream(snapshotsPath.toFile()))).use { reader ->
|
||||
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
|
||||
try {
|
||||
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
|
||||
} catch (exception: Exception) {
|
||||
LOGGER.error("Failed to load snapshots.json... Resetting File")
|
||||
val data = SnapshotAcknowledgementData(Cobblemon.VERSION, false)
|
||||
writeSnapshotsData(data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,16 +55,23 @@ object ClientPersistedData {
|
||||
fun readRidingPerspectivesData() : RidingPerspectiveData? {
|
||||
if (ridingPerspectivesPath.exists()) {
|
||||
BufferedReader(InputStreamReader(FileInputStream(ridingPerspectivesPath.toFile()))).use { reader ->
|
||||
return gson.fromJson(reader, RidingPerspectiveData::class.java)
|
||||
try {
|
||||
return gson.fromJson(reader, RidingPerspectiveData::class.java)
|
||||
} catch (exception: Exception) {
|
||||
LOGGER.error("Failed to load riding_perspectives.json... Resetting File")
|
||||
val data = RidingPerspectiveData()
|
||||
saveRidingPerspectivesData(data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun saveRidingPerspectivesData() {
|
||||
fun saveRidingPerspectivesData(data: RidingPerspectiveData) {
|
||||
ridingPerspectivesPath.toFile().parentFile.mkdirs()
|
||||
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(ridingPerspectives, writer) }
|
||||
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(data, writer) }
|
||||
}
|
||||
|
||||
data class SnapshotAcknowledgementData(val version: String, val dontShowAgain: Boolean)
|
||||
@@ -64,7 +80,7 @@ object ClientPersistedData {
|
||||
) {
|
||||
fun updatePerspective(key: ResourceLocation, cameraType: CameraType) {
|
||||
perspectives[key] = cameraType
|
||||
saveRidingPerspectivesData()
|
||||
saveRidingPerspectivesData(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen9
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.util.isBattling
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class TinkatinkModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
|
||||
override val rootPart = root.registerChildWithAllChildren("tinkatink")
|
||||
override val head = getPart("head")
|
||||
override val rightArm = getPart("arm_right")
|
||||
override val leftArm = getPart("arm_left")
|
||||
override val rightLeg = getPart("leg_right")
|
||||
override val leftLeg = getPart("leg_left")
|
||||
|
||||
override var portraitScale = 2.0F
|
||||
override var portraitTranslation = Vec3(0.0, -1.01, 0.0)
|
||||
|
||||
override var profileScale = 1.0F
|
||||
override var profileTranslation = Vec3(0.0, 0.25, 0.0)
|
||||
|
||||
lateinit var standing: Pose
|
||||
lateinit var walking: Pose
|
||||
lateinit var sleep: Pose
|
||||
lateinit var battleidle: Pose
|
||||
lateinit var shoulderLeft: Pose
|
||||
lateinit var shoulderRight: Pose
|
||||
|
||||
val shoulderOffset = 1
|
||||
|
||||
override val cryAnimation = CryProvider { bedrockStateful("tinkatink", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk { bedrockStateful("tinkatink", "blink") }
|
||||
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
animations = arrayOf(bedrock("tinkatink", "sleep"))
|
||||
)
|
||||
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
|
||||
transformTicks = 10,
|
||||
condition = { !it.isBattling },
|
||||
quirks = arrayOf(blink),
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatink", "ground_idle")
|
||||
)
|
||||
)
|
||||
|
||||
walking = registerPose(
|
||||
poseName = "walking",
|
||||
poseTypes = PoseType.MOVING_POSES,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatink", "ground_walk")
|
||||
)
|
||||
)
|
||||
|
||||
battleidle = registerPose(
|
||||
poseName = "battle_idle",
|
||||
poseTypes = PoseType.STATIONARY_POSES,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
condition = { it.isBattling },
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatink", "battle_idle")
|
||||
)
|
||||
)
|
||||
|
||||
shoulderLeft = registerPose(
|
||||
poseType = PoseType.SHOULDER_LEFT,
|
||||
quirks = arrayOf(blink),
|
||||
condition = { !it.isBattling },
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatink", "shoulder_left")
|
||||
),
|
||||
transformedParts = arrayOf(
|
||||
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, shoulderOffset)
|
||||
)
|
||||
)
|
||||
|
||||
shoulderRight = registerPose(
|
||||
poseType = PoseType.SHOULDER_RIGHT,
|
||||
quirks = arrayOf(blink),
|
||||
condition = { !it.isBattling },
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatink", "shoulder_right")
|
||||
),
|
||||
transformedParts = arrayOf(
|
||||
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, -shoulderOffset)
|
||||
)
|
||||
)
|
||||
}
|
||||
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walking, battleidle, sleep)) bedrockStateful("tinkatink", "faint") else null
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen9
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.util.isBattling
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class TinkatuffModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
|
||||
override val rootPart = root.registerChildWithAllChildren("tinkatuff")
|
||||
override val head = getPart("head")
|
||||
override val rightArm = getPart("arm_right")
|
||||
override val leftArm = getPart("arm_left")
|
||||
override val rightLeg = getPart("leg_right")
|
||||
override val leftLeg = getPart("leg_left")
|
||||
|
||||
override var portraitScale = 1.9F
|
||||
override var portraitTranslation = Vec3(0.0, -0.41, 0.0)
|
||||
|
||||
override var profileScale = 0.9F
|
||||
override var profileTranslation = Vec3(0.0, 0.4, 0.0)
|
||||
|
||||
lateinit var standing: Pose
|
||||
lateinit var walking: Pose
|
||||
lateinit var sleep: Pose
|
||||
lateinit var battleidle: Pose
|
||||
|
||||
override val cryAnimation = CryProvider { bedrockStateful("tinkatuff", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk { bedrockStateful("tinkatuff", "blink") }
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
animations = arrayOf(bedrock("tinkatuff", "sleep"))
|
||||
)
|
||||
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
|
||||
transformTicks = 10,
|
||||
condition = { !it.isBattling },
|
||||
quirks = arrayOf(blink),
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatuff", "ground_idle")
|
||||
)
|
||||
)
|
||||
|
||||
walking = registerPose(
|
||||
poseName = "walking",
|
||||
poseTypes = PoseType.MOVING_POSES,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatuff", "ground_walk")
|
||||
)
|
||||
)
|
||||
|
||||
battleidle = registerPose(
|
||||
poseName = "battle_idle",
|
||||
poseTypes = PoseType.STATIONARY_POSES,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
condition = { it.isBattling },
|
||||
animations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("tinkatuff", "battle_idle")
|
||||
)
|
||||
)
|
||||
}
|
||||
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walking, battleidle, sleep)) bedrockStateful("tinkatuff", "faint") else null
|
||||
}
|
||||
-2
@@ -477,8 +477,6 @@ object VaryingModelRepository {
|
||||
inbuilt("illumise", ::IllumiseModel)
|
||||
inbuilt("yamper", ::YamperModel)
|
||||
inbuilt("boltund", ::BoltundModel)
|
||||
inbuilt("tinkatink", ::TinkatinkModel)
|
||||
inbuilt("tinkatuff", ::TinkatuffModel)
|
||||
inbuilt("fuecoco", :: FuecocoModel)
|
||||
inbuilt("crocalor", :: CrocalorModel)
|
||||
inbuilt("skeledirge", :: SkeledirgeModel)
|
||||
|
||||
@@ -329,6 +329,9 @@ class CobblemonConfig {
|
||||
@CobblemonConfigField(Category.Riding, lang = "remember_riding_camera", CLIENT)
|
||||
var rememberRidingCamera = false
|
||||
|
||||
@CobblemonConfigField(Category.Riding, lang = "enable_in_flight_dismounting", CLIENT)
|
||||
var enableInFlightDismounting = false
|
||||
|
||||
@CobblemonConfigField(Category.Debug, lang = "enable_debug_keys", CLIENT)
|
||||
var enableDebugKeys = false
|
||||
|
||||
|
||||
@@ -39,15 +39,16 @@ class FollowWalkTargetTask(
|
||||
private var lookTargetPos: BlockPos? = null
|
||||
private var speed = 0f
|
||||
|
||||
override fun checkExtraStartConditions(arg: ServerLevel, arg2: PathfinderMob): Boolean {
|
||||
override fun checkExtraStartConditions(level: ServerLevel, entity: PathfinderMob): Boolean {
|
||||
if (this.pathUpdateCountdownTicks > 0) {
|
||||
--this.pathUpdateCountdownTicks
|
||||
return false
|
||||
} else {
|
||||
val brain = arg2.brain
|
||||
val brain = entity.brain
|
||||
val walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get()
|
||||
val hasReached = this.hasReached(arg2, walkTarget)
|
||||
if (!hasReached && this.hasFinishedPath(arg2, walkTarget, arg.gameTime)) {
|
||||
val hasReached = this.hasReached(entity, walkTarget)
|
||||
resetPathUpdateCountdownTicks(entity)
|
||||
if (!hasReached && this.hasFinishedPath(entity, walkTarget, level.gameTime)) {
|
||||
this.lookTargetPos = walkTarget.target.currentBlockPosition()
|
||||
return true
|
||||
} else {
|
||||
@@ -61,6 +62,10 @@ class FollowWalkTargetTask(
|
||||
}
|
||||
}
|
||||
|
||||
fun resetPathUpdateCountdownTicks(entity: PathfinderMob) {
|
||||
pathUpdateCountdownTicks = entity.random.nextInt(20) + 20
|
||||
}
|
||||
|
||||
override fun canStillUse(arg: ServerLevel, arg2: PathfinderMob, l: Long): Boolean {
|
||||
if (this.path != null && this.lookTargetPos != null) {
|
||||
val optional = arg2.brain.getMemory(MemoryModuleType.WALK_TARGET)
|
||||
@@ -75,7 +80,7 @@ class FollowWalkTargetTask(
|
||||
override fun stop(world: ServerLevel, entity: PathfinderMob, l: Long) {
|
||||
val walkTarget = entity.brain.getMemory(MemoryModuleType.WALK_TARGET).orElse(null)
|
||||
if (walkTarget != null && !this.hasReached(entity, walkTarget) && entity.navigation.isStuck) {
|
||||
this.pathUpdateCountdownTicks = world.getRandom().nextInt(40)
|
||||
resetPathUpdateCountdownTicks(entity)
|
||||
}
|
||||
|
||||
entity.navigation.stop()
|
||||
|
||||
@@ -269,9 +269,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
// If we can fly and we're airborne, return the current Y position
|
||||
return vec.y
|
||||
}
|
||||
if (world.getBlockState(blockPos).block == CobblemonBlocks.SACCHARINE_LEAVES && pather.canPathThroughSaccLeaves()) {
|
||||
return vec.y + 0.5
|
||||
}
|
||||
return if ((canFloat()) && blockGetter.getFluidState(blockPos).`is`(FluidTags.WATER)) vec.y + 0.5 else super.getGroundY(vec)
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -802,13 +802,14 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
|
||||
|
||||
spawnAction.complete()
|
||||
val result = spawnAction.future
|
||||
if (result.isCompletedExceptionally) {
|
||||
val resultingSpawn = result.get()
|
||||
|
||||
if (resultingSpawn == null) {
|
||||
noBite(player)
|
||||
return false
|
||||
}
|
||||
|
||||
var spawnedPokemon: PokemonEntity? = null
|
||||
val resultingSpawn = result.get()
|
||||
|
||||
if (resultingSpawn is EntitySpawnResult) {
|
||||
for (entity in resultingSpawn.entities) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.battles.ai.CompetitiveBattleAI
|
||||
import com.cobblemon.mod.common.battles.ai.StrongBattleAI
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
|
||||
@@ -30,7 +31,7 @@ class NPCBattleActor(
|
||||
) : AIBattleActor(
|
||||
gameId = npc.uuid,
|
||||
pokemonList = pokemonList.let { if (npc.npc.randomizePartyOrder) it.shuffled() else it },
|
||||
battleAI = StrongBattleAI(skill)
|
||||
battleAI = CompetitiveBattleAI(skill)
|
||||
), EntityBackedBattleActor<NPCEntity> {
|
||||
override val entity = npc
|
||||
override val type = ActorType.NPC
|
||||
|
||||
@@ -547,6 +547,11 @@ open class PokemonEntity(
|
||||
ifRidingAvailable { _, _, state -> pokemon.rideStamina = state.stamina.get() }
|
||||
ridingController?.context?.state?.reset()
|
||||
ridingAnimationData.clear()
|
||||
|
||||
// reset riding orientation
|
||||
if (this is OrientationControllable) {
|
||||
this.orientationController.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,14 +1208,13 @@ open class PokemonEntity(
|
||||
}
|
||||
}
|
||||
|
||||
if (hand == InteractionHand.MAIN_HAND && player is ServerPlayer) {
|
||||
if (player.isShiftKeyDown) {
|
||||
if (hand == InteractionHand.MAIN_HAND) {
|
||||
if (player.isShiftKeyDown && player is ServerPlayer) {
|
||||
showInteractionWheel(player, itemStack)
|
||||
return InteractionResult.sidedSuccess(level().isClientSide)
|
||||
}
|
||||
else if (pokemon.getOwnerPlayer() == player) {
|
||||
// TODO #105
|
||||
if (this.attemptItemInteraction(player, player.getItemInHand(hand))) return InteractionResult.SUCCESS
|
||||
else if (this.ownerUUID == player.uuid && this.attemptItemInteraction(player, player.getItemInHand(hand))) {
|
||||
return InteractionResult.sidedSuccess(level().isClientSide)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1277,7 +1281,7 @@ open class PokemonEntity(
|
||||
}
|
||||
|
||||
override fun shouldBeSaved(): Boolean {
|
||||
if (ownerUUID == null && !pokemon.isNPCOwned() && (Cobblemon.config.savePokemonToWorld || isPersistenceRequired || this.pokemon.canDropHeldItem)) {
|
||||
if (ownerUUID == null && !pokemon.isNPCOwned() && (Cobblemon.config.savePokemonToWorld || isPersistenceRequired)) {
|
||||
CobblemonEvents.POKEMON_ENTITY_SAVE_TO_WORLD.postThen(PokemonEntitySaveToWorldEvent(this)) {
|
||||
return true
|
||||
}
|
||||
@@ -1386,18 +1390,24 @@ open class PokemonEntity(
|
||||
return bagItemLike.handleInteraction(player, battlePokemon, stack)
|
||||
}
|
||||
|
||||
if (player !is ServerPlayer || this.isBusy) {
|
||||
if (this.isBusy) {
|
||||
return false
|
||||
}
|
||||
|
||||
val interaction = PokemonInteractions.findInteraction(this)
|
||||
|
||||
if (interaction != null && !pokemon.isOnInteractionCooldown(interaction.grouping)) {
|
||||
interaction.effects.forEach { it.applyEffect(this, player) }
|
||||
pokemon.interactionCooldowns.put(interaction.grouping, runtime.resolveInt(interaction.cooldown))
|
||||
if (player is ServerPlayer) {
|
||||
interaction.effects.forEach { it.applyEffect(this, player) }
|
||||
pokemon.interactionCooldowns.put(interaction.grouping, runtime.resolveInt(interaction.cooldown))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (player !is ServerPlayer) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Evolution item logic
|
||||
if (pokemon.getOwnerPlayer() == player) {
|
||||
val context = ItemInteractionEvolution.ItemInteractionContext(stack, player.level())
|
||||
|
||||
+14
-2
@@ -10,8 +10,11 @@ package com.cobblemon.mod.common.entity.pokemon.ai.sensors
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.Mob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
@@ -28,8 +31,17 @@ class DefendOwnerSensor : Sensor<PokemonEntity>(10) {
|
||||
}
|
||||
|
||||
private fun setNearestAttacker(entity: PokemonEntity, visibleMobs: NearestVisibleLivingEntities, owner: LivingEntity) {
|
||||
val nearestAttacker = visibleMobs.findClosest { mob ->
|
||||
mob.lastHurtMob == owner
|
||||
val nearestAttacker = visibleMobs.findClosest { livingEntity ->
|
||||
// Prevents Pokémons from attacking players as owned Pokémons are currently invulnerable
|
||||
if (livingEntity is ServerPlayer) return@findClosest false
|
||||
|
||||
if (livingEntity.lastHurtMob == owner) return@findClosest true
|
||||
if (livingEntity is Mob && livingEntity.target == owner) return@findClosest true
|
||||
|
||||
val mobAttackTarget = livingEntity.brain.getMemorySafely(MemoryModuleType.ATTACK_TARGET)
|
||||
if (mobAttackTarget.isPresent && mobAttackTarget.get() == owner) return@findClosest true
|
||||
|
||||
return@findClosest false
|
||||
}.map { mob -> mob as LivingEntity }
|
||||
|
||||
entity.brain.setMemory(CobblemonMemories.NEAREST_VISIBLE_ATTACKER, nearestAttacker)
|
||||
|
||||
-8
@@ -10,18 +10,10 @@ package com.cobblemon.mod.common.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.ai.behavior.Behavior
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.ai.targeting.TargetingConditions
|
||||
|
||||
object DefendOwnerTask {
|
||||
fun create(): OneShot<PokemonEntity> {
|
||||
|
||||
@@ -30,21 +30,13 @@ import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
|
||||
import com.cobblemon.mod.common.api.events.fishing.PokerodCastEvent
|
||||
import com.cobblemon.mod.common.api.events.fishing.PokerodReelEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.CollectEggEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.FossilRevivedEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.HatchEggEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.LevelUpEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.PokemonGainedEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.RidePokemonEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.TradeEvent
|
||||
import com.cobblemon.mod.common.api.events.pokemon.*
|
||||
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
|
||||
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
|
||||
import com.cobblemon.mod.common.api.stats.CobblemonStats.getStat
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
|
||||
object StatHandler : EventHandler {
|
||||
override fun registerListeners() {
|
||||
@@ -100,13 +92,13 @@ object StatHandler : EventHandler {
|
||||
|
||||
fun onBattleStart(event : BattleStartedEvent) {
|
||||
event.battle.players.forEach { player ->
|
||||
player.awardStat(this.getStat(getStat(Cobblemon.statistics.BATTLES_TOTAL)))
|
||||
player.awardStat(getStat(Cobblemon.statistics.BATTLES_TOTAL))
|
||||
}
|
||||
}
|
||||
|
||||
fun onDexEntryGain(event : PokemonGainedEvent) {
|
||||
val player = event.pokemon.getOwnerPlayer()
|
||||
player?.awardStat(getStat(getStat(Cobblemon.statistics.DEX_ENTRIES)))
|
||||
player?.awardStat(getStat(Cobblemon.statistics.DEX_ENTRIES))
|
||||
}
|
||||
|
||||
fun onCollectEgg(event : CollectEggEvent) {
|
||||
@@ -139,12 +131,4 @@ object StatHandler : EventHandler {
|
||||
fun onReelIn(event : PokerodReelEvent) {
|
||||
event.player.awardStat(getStat(Cobblemon.statistics.REEL_INS))
|
||||
}
|
||||
|
||||
fun getStat(resourceLocation: ResourceLocation) : ResourceLocation {
|
||||
val stat = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation)
|
||||
if (stat == null) {
|
||||
Cobblemon.LOGGER.error("Could not find stat with id {}", resourceLocation)
|
||||
}
|
||||
return stat ?: throw NullPointerException("Could not find stat with id $resourceLocation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ class MochiItem(stat: Stats): EVIncreaseItem(stat, 4) {
|
||||
}
|
||||
|
||||
pokemon.feedPokemon(1)
|
||||
|
||||
stack.consume(1, player)
|
||||
|
||||
|
||||
return super.applyToPokemon(player, stack, pokemon)
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -35,9 +35,16 @@ class SinisterTeaItem : Item(
|
||||
val effectsComponent = stack.get(CobblemonItemComponents.MOB_EFFECTS)
|
||||
|
||||
if (effectsComponent != null) {
|
||||
for (effect in effectsComponent.mobEffects) {
|
||||
val effectCopy = MobEffectInstance(effect)
|
||||
user.addEffect(effectCopy)
|
||||
for (raw in effectsComponent.mobEffects) {
|
||||
val inst = MobEffectInstance(raw)
|
||||
val holder = inst.effect
|
||||
val effect = holder.value()
|
||||
|
||||
if (effect.isInstantenous) {
|
||||
effect.applyInstantenousEffect(user as? Player, null, user, inst.amplifier, 1.0)
|
||||
} else {
|
||||
user.addEffect(inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.data
|
||||
|
||||
import com.cobblemon.mod.common.api.interaction.PokemonInteractionSet
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class PokemonInteractionsSyncPacket(
|
||||
val speciesInteractions: List<PokemonInteractionSet>,
|
||||
val generalInteractions: List<PokemonInteractionSet>,
|
||||
) : NetworkPacket<PokemonInteractionsSyncPacket> {
|
||||
override val id = ID
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeCollection(speciesInteractions) { it, speciesInteraction ->
|
||||
speciesInteraction.encode(it)
|
||||
}
|
||||
buffer.writeCollection(generalInteractions) { it, generalInteraction ->
|
||||
generalInteraction.encode(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("pokemon_interactions_sync")
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): PokemonInteractionsSyncPacket {
|
||||
val speciesInteractions = buffer.readList { it ->
|
||||
PokemonInteractionSet.decode(it)
|
||||
}
|
||||
val generalInteractions = buffer.readList { it ->
|
||||
PokemonInteractionSet.decode(it)
|
||||
}
|
||||
|
||||
return PokemonInteractionsSyncPacket(
|
||||
speciesInteractions,
|
||||
generalInteractions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -21,7 +21,9 @@ class PokemonStateUpdatePacket(pokemon: () -> Pokemon?, value: PokemonState): Si
|
||||
value.writeToBuffer(buffer)
|
||||
}
|
||||
|
||||
override fun set(pokemon: Pokemon, value: PokemonState) { pokemon.state = value }
|
||||
override fun set(pokemon: Pokemon, value: PokemonState) {
|
||||
pokemon.state = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("state_update")
|
||||
|
||||
+24
-10
@@ -13,30 +13,24 @@ import com.cobblemon.mod.common.api.mark.Marks
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.entity.PlatformType
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readEnumConstant
|
||||
import com.cobblemon.mod.common.util.readIdentifier
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.readText
|
||||
import com.cobblemon.mod.common.util.writeEnumConstant
|
||||
import com.cobblemon.mod.common.util.writeIdentifier
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import java.util.UUID
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.client.multiplayer.ClientLevel
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.Entity
|
||||
import java.util.*
|
||||
|
||||
class SpawnPokemonPacket(
|
||||
var ownerId: UUID?,
|
||||
var pokemonUUID: UUID,
|
||||
var storageUUID: UUID?,
|
||||
var scaleModifier: Float,
|
||||
var speciesId: ResourceLocation,
|
||||
var gender: Gender,
|
||||
@@ -61,6 +55,7 @@ class SpawnPokemonPacket(
|
||||
var tickSpawned: Int,
|
||||
var rideBoosts: Map<RidingStat, Float>,
|
||||
var rideStamina: Float,
|
||||
var silent: Boolean,
|
||||
vanillaSpawnPacket: ClientboundAddEntityPacket,
|
||||
) : SpawnExtraDataEntityPacket<SpawnPokemonPacket, PokemonEntity>(vanillaSpawnPacket) {
|
||||
|
||||
@@ -69,6 +64,7 @@ class SpawnPokemonPacket(
|
||||
constructor(entity: PokemonEntity, vanillaSpawnPacket: ClientboundAddEntityPacket) : this(
|
||||
entity.ownerUUID,
|
||||
entity.pokemon.uuid,
|
||||
entity.pokemon.storeCoordinates.get()?.store?.uuid,
|
||||
entity.pokemon.scaleModifier,
|
||||
entity.exposedSpecies.resourceIdentifier,
|
||||
entity.pokemon.gender,
|
||||
@@ -93,12 +89,14 @@ class SpawnPokemonPacket(
|
||||
entity.tickCount,
|
||||
entity.entityData.get(PokemonEntity.RIDE_BOOSTS),
|
||||
entity.entityData.get(PokemonEntity.RIDE_STAMINA),
|
||||
entity.isSilent,
|
||||
vanillaSpawnPacket
|
||||
)
|
||||
|
||||
override fun encodeEntityData(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeNullable(ownerId) { _, v -> buffer.writeUUID(v) }
|
||||
buffer.writeUUID(this.pokemonUUID)
|
||||
buffer.writeNullable(this.storageUUID) { pb, value -> pb.writeUUID(value) }
|
||||
buffer.writeFloat(this.scaleModifier)
|
||||
buffer.writeIdentifier(this.speciesId)
|
||||
buffer.writeEnumConstant(this.gender)
|
||||
@@ -127,10 +125,21 @@ class SpawnPokemonPacket(
|
||||
{ _, value -> buffer.writeFloat(value) }
|
||||
)
|
||||
buffer.writeFloat(rideStamina)
|
||||
buffer.writeBoolean(silent)
|
||||
}
|
||||
|
||||
override fun applyData(entity: PokemonEntity, level: ClientLevel) {
|
||||
entity.ownerUUID = ownerId
|
||||
|
||||
// Reuse the stored Pokémon instance to avoid creating a duplicate,
|
||||
// preventing client/entity desync issues.
|
||||
this@SpawnPokemonPacket.storageUUID?.let {
|
||||
val storedPokemon = CobblemonClient.storage.locatePokemon(it, this@SpawnPokemonPacket.pokemonUUID)
|
||||
if (storedPokemon != null) {
|
||||
entity.pokemon = storedPokemon
|
||||
}
|
||||
}
|
||||
|
||||
entity.pokemon.apply {
|
||||
uuid = this@SpawnPokemonPacket.pokemonUUID
|
||||
scaleModifier = this@SpawnPokemonPacket.scaleModifier
|
||||
@@ -162,6 +171,7 @@ class SpawnPokemonPacket(
|
||||
entity.entityData.set(PokemonEntity.RIDE_BOOSTS, rideBoosts)
|
||||
entity.entityData.set(PokemonEntity.RIDE_STAMINA, rideStamina)
|
||||
entity.entityData.set(PokemonEntity.SCALE_MODIFIER, scaleModifier)
|
||||
entity.isSilent = silent
|
||||
|
||||
entity.ejectPassengers()
|
||||
passengers.forEach {
|
||||
@@ -179,6 +189,7 @@ class SpawnPokemonPacket(
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): SpawnPokemonPacket {
|
||||
val ownerId = buffer.readNullable { buffer.readUUID() }
|
||||
val pokemonUUID = buffer.readUUID()
|
||||
val storageUUID = buffer.readNullable { buffer.readUUID() }
|
||||
val scaleModifier = buffer.readFloat()
|
||||
val speciesId = buffer.readIdentifier()
|
||||
val gender = buffer.readEnumConstant(Gender::class.java)
|
||||
@@ -206,11 +217,13 @@ class SpawnPokemonPacket(
|
||||
{ buffer.readFloat() }
|
||||
)
|
||||
val rideStamina = buffer.readFloat()
|
||||
val silent = buffer.readBoolean()
|
||||
val vanillaPacket = decodeVanillaPacket(buffer)
|
||||
|
||||
return SpawnPokemonPacket(
|
||||
ownerId,
|
||||
pokemonUUID,
|
||||
storageUUID,
|
||||
scaleModifier,
|
||||
speciesId,
|
||||
gender,
|
||||
@@ -235,6 +248,7 @@ class SpawnPokemonPacket(
|
||||
tickSpawned,
|
||||
rideBoosts,
|
||||
rideStamina,
|
||||
silent,
|
||||
vanillaPacket
|
||||
)
|
||||
}
|
||||
|
||||
+7
-2
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.net.serverhandling.riding
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
|
||||
import net.minecraft.server.MinecraftServer
|
||||
@@ -25,12 +26,16 @@ object DismountPokemonPacketHandler : ServerNetworkPacketHandler<DismountPokemon
|
||||
|
||||
val pokemon = player.vehicle as PokemonEntity
|
||||
if (!canPlayerStopRidingPokemon(pokemon, player)) return
|
||||
|
||||
val isRidingInAir = pokemon.ridingController?.context?.style == RidingStyle.AIR
|
||||
|
||||
if (pokemon.controllingPassenger == player) {
|
||||
pokemon.passengers.forEach { it.stopRiding() }
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
player.stopRiding()
|
||||
}
|
||||
|
||||
if (isRidingInAir) pokemon.setFlying(true)
|
||||
}
|
||||
|
||||
private fun canPlayerStopRidingPokemon(pokemon: PokemonEntity, player: ServerPlayer): Boolean {
|
||||
|
||||
@@ -1437,6 +1437,11 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
}
|
||||
|
||||
fun getOwnerEntity(): LivingEntity? {
|
||||
if (isClient) {
|
||||
val ownerUUID = entity?.ownerUUID ?: return null
|
||||
return entity?.level()?.getPlayerByUUID(ownerUUID)
|
||||
}
|
||||
|
||||
return storeCoordinates.get()?.let {
|
||||
if (isPlayerOwned()) {
|
||||
server()?.playerList?.getPlayer(it.store.uuid)
|
||||
|
||||
+55
-7
@@ -8,13 +8,22 @@
|
||||
|
||||
package com.cobblemon.mod.common.pokemon.adapters
|
||||
|
||||
import com.cobblemon.mod.common.api.interaction.InteractionEffect
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.adapters.RequirementAdapter
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
|
||||
import com.cobblemon.mod.common.pokemon.requirements.*
|
||||
import com.cobblemon.mod.common.util.adapters.*
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.google.common.collect.HashBiMap
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.*
|
||||
import net.minecraft.advancements.critereon.ItemPredicate
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.LowerCaseEnumTypeAdapterFactory
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@@ -60,21 +69,60 @@ object CobblemonRequirementAdapter : RequirementAdapter {
|
||||
this.registerType(OwnerHoldsItemRequirement.ADAPTER_VARIANT, OwnerHoldsItemRequirement::class)
|
||||
}
|
||||
|
||||
val gson: Gson by lazy {
|
||||
GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
|
||||
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
}
|
||||
|
||||
override fun <T : Requirement> registerType(id: String, type: KClass<T>) {
|
||||
this.types[id.lowercase()] = type
|
||||
}
|
||||
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Requirement {
|
||||
val variant = json.asJsonObject.get(VARIANT).asString.lowercase()
|
||||
val type = this.types[variant] ?: throw IllegalArgumentException("Cannot resolve evolution requirement type for variant $variant")
|
||||
val type = getRequirementType(variant)
|
||||
return context.deserialize(json, type.java)
|
||||
}
|
||||
|
||||
override fun serialize(src: Requirement, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
val json = context.serialize(src, src::class.java).asJsonObject
|
||||
val variant = this.types.inverse()[src::class] ?: throw IllegalArgumentException("Cannot resolve evolution requirement for type ${src::class.qualifiedName}")
|
||||
override fun serialize(requirement: Requirement, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
val json = context.serialize(requirement, requirement::class.java).asJsonObject
|
||||
val variant = getVariant(requirement)
|
||||
json.addProperty(VARIANT, variant)
|
||||
return json
|
||||
}
|
||||
|
||||
fun encode(requirement: Requirement, buffer: FriendlyByteBuf) {
|
||||
buffer.writeString(gson.toJson(requirement, Requirement::class.java))
|
||||
}
|
||||
|
||||
fun decode(buffer: FriendlyByteBuf): Requirement {
|
||||
return gson.fromJson(buffer.readString(), Requirement::class.java)
|
||||
}
|
||||
|
||||
private fun getVariant(requirement: Requirement): String {
|
||||
val variant = this.types.inverse()[requirement::class]
|
||||
if (variant == null) {
|
||||
throw IllegalArgumentException("Cannot resolve evolution requirement for type ${requirement::class.qualifiedName}")
|
||||
}
|
||||
|
||||
return variant
|
||||
}
|
||||
|
||||
private fun getRequirementType(variant: String): KClass<out Requirement> {
|
||||
val requirementType = this.types[variant]
|
||||
if (requirementType == null) {
|
||||
throw IllegalArgumentException("Cannot resolve evolution requirement type for variant $variant")
|
||||
}
|
||||
|
||||
return requirementType
|
||||
}
|
||||
}
|
||||
@@ -562,8 +562,8 @@ class OmniPathNodeMaker : NodeEvaluator() {
|
||||
//// PathType.WALKABLE
|
||||
//// } else if (blockState.canPathfindThrough(world, pos, NavigationType.AIR) && !blockStateBelow.isSideSolid(world, below, Direction.UP, SideShapeType.FULL)) {
|
||||
//// PathType.OPEN
|
||||
// } else if (blockState.`is`(BlockTags.LEAVES) && blockState.block == CobblemonBlocks.SACCHARINE_LEAVES && canPathThroughLeaves()) {
|
||||
// return PathType.OPEN
|
||||
if (canPathThroughLeaves() && blockState.block == CobblemonBlocks.SACCHARINE_LEAVES)
|
||||
return PathType.OPEN
|
||||
// } else PathType.BLOCKED
|
||||
|
||||
// This is the 1.6.1 logic but with some optimizations
|
||||
@@ -633,9 +633,6 @@ class OmniPathNodeMaker : NodeEvaluator() {
|
||||
pfContext, x, y, z, sizeX, sizeY, sizeZ, canOpenDoors, canPassDoors, set, PathType.BLOCKED, BlockPos(x, y, z), returnedEarlyPathType
|
||||
)
|
||||
|
||||
if (PathType.LEAVES in set) {
|
||||
return PathType.OPEN
|
||||
}
|
||||
|
||||
if (PathType.FENCE in set) {
|
||||
return PathType.FENCE
|
||||
@@ -755,7 +752,7 @@ class OmniPathNodeMaker : NodeEvaluator() {
|
||||
} else if (type == PathType.RAIL && block !is BaseRailBlock && pfContext.getCachedBlockState(pos.below()).block !is BaseRailBlock) {
|
||||
PathType.UNPASSABLE_RAIL
|
||||
} else if (type == PathType.LEAVES) {
|
||||
if (blockState.block === CobblemonBlocks.SACCHARINE_LEAVES && canPathThroughLeaves()) {
|
||||
if (canPathThroughLeaves() && blockState.block === CobblemonBlocks.SACCHARINE_LEAVES) {
|
||||
PathType.OPEN
|
||||
} else {
|
||||
PathType.BLOCKED
|
||||
|
||||
+4
-8
@@ -6,19 +6,15 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.pokemon.properties.tags
|
||||
package com.cobblemon.mod.common.pokemon.properties
|
||||
|
||||
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
|
||||
import com.cobblemon.mod.common.pokemon.properties.StringProperty
|
||||
|
||||
object PokemonFlagProperty : CustomPokemonPropertyType<StringProperty> {
|
||||
|
||||
private const val KEY = "tag"
|
||||
|
||||
override val keys = setOf(KEY)
|
||||
object LabelProperty : CustomPokemonPropertyType<StringProperty> {
|
||||
override val keys = setOf("label", "tag")
|
||||
override val needsKey = true
|
||||
|
||||
override fun fromString(value: String?) = if (value == null) null else StringProperty(KEY, value, { _, _ -> }, { pokemon, underlyingValue -> pokemon.hasLabels(underlyingValue) })
|
||||
override fun fromString(value: String?) = if (value == null) null else StringProperty(keys.first(), value, { _, _ -> }, { pokemon, underlyingValue -> pokemon.hasLabels(underlyingValue) })
|
||||
|
||||
override fun examples() = emptySet<String>()
|
||||
}
|
||||
+7
-4
@@ -11,9 +11,9 @@ package com.cobblemon.mod.common.pokemon.requirements
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import kotlin.collections.iterator
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
/**
|
||||
* An [com.cobblemon.mod.common.api.pokemon.requirement.Requirement] that checks if the player has a certain [net.minecraft.advancements.Advancement]
|
||||
@@ -26,13 +26,16 @@ class AdvancementRequirement(val requiredAdvancement: ResourceLocation) : OwnerQ
|
||||
|
||||
override fun checkPlayer(
|
||||
pokemon: Pokemon,
|
||||
owner: ServerPlayer
|
||||
owner: Player
|
||||
): Boolean {
|
||||
for (entry in owner.advancements.progress) {
|
||||
val serverPlayer = owner as? ServerPlayer ?: return false
|
||||
|
||||
for (entry in serverPlayer.advancements.progress) {
|
||||
if (entry.key.id == requiredAdvancement && entry.value.isDone) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -12,11 +12,9 @@ import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.advancements.critereon.ItemPredicate
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import kotlin.collections.iterator
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
/**
|
||||
* An [OwnerQueryRequirement] that checks if the player/npc is holding a certain [ItemPredicate]
|
||||
@@ -30,7 +28,7 @@ class OwnerHoldsItemRequirement(val itemCondition: ItemPredicate)
|
||||
|
||||
override fun checkPlayer(
|
||||
pokemon: Pokemon,
|
||||
owner: ServerPlayer
|
||||
owner: Player
|
||||
): Boolean {
|
||||
return checkItem(owner)
|
||||
}
|
||||
|
||||
+12
-8
@@ -8,22 +8,22 @@
|
||||
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.*
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.JsonOps
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class CodecBackedAdapter<T>(val codec: Codec<T>) : JsonDeserializer<T>, JsonSerializer<T> {
|
||||
|
||||
override fun deserialize(
|
||||
jElement: JsonElement,
|
||||
typeOfT: Type,
|
||||
context: JsonDeserializationContext
|
||||
): T {
|
||||
return JsonOps.INSTANCE.withDecoder(codec).apply(jElement).result().get().first as T
|
||||
val ops = RegistryOpsProvider.getOpsWithDefaultFallback()
|
||||
val result = ops.withDecoder(codec).apply(jElement)
|
||||
return result.result().orElseThrow {
|
||||
IllegalStateException("Failed to deserialize $jElement: ${result.error().orElse(null)}")
|
||||
}.first
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
@@ -31,6 +31,10 @@ class CodecBackedAdapter<T>(val codec: Codec<T>) : JsonDeserializer<T>, JsonSeri
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonOps.INSTANCE.withEncoder(codec).apply(src).result().get()
|
||||
val ops = RegistryOpsProvider.getOpsWithDefaultFallback()
|
||||
val result = ops.withEncoder(codec).apply(src)
|
||||
return result.result().orElseThrow {
|
||||
IllegalStateException("Failed to serialize $src: ${result.error().orElse(null)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Environment
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import com.google.gson.JsonElement
|
||||
import com.mojang.serialization.DynamicOps
|
||||
import com.mojang.serialization.JsonOps
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.resources.RegistryOps
|
||||
|
||||
/**
|
||||
* Utility for obtaining RegistryOps with the correct client/server registry access.
|
||||
* Used when manually encoding/decoding registry-backed values (e.g., items, predicates).
|
||||
*/
|
||||
object RegistryOpsProvider {
|
||||
fun getOpsWithDefaultFallback(): DynamicOps<JsonElement> {
|
||||
if (Cobblemon.implementation.environment() == Environment.CLIENT) {
|
||||
val minecraft = Minecraft.getInstance()
|
||||
if (minecraft != null && minecraft.level != null)
|
||||
return RegistryOps.create(JsonOps.INSTANCE, minecraft.level!!.registryAccess())
|
||||
}
|
||||
|
||||
|
||||
if (Cobblemon.implementation.environment() == Environment.SERVER) {
|
||||
val server = server()
|
||||
if (server != null)
|
||||
return RegistryOps.create(JsonOps.INSTANCE, server.registryAccess())
|
||||
}
|
||||
|
||||
return JsonOps.INSTANCE
|
||||
}
|
||||
}
|
||||
+5
@@ -297,6 +297,11 @@
|
||||
"1.0833": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sound_effects": {
|
||||
"0.25": {
|
||||
"effect": "pokemon.ditto.cry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.ditto.blink": {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"animations": {
|
||||
"animation.metagross.ground_idle": {
|
||||
"loop": true,
|
||||
"animation_length": 11.4167,
|
||||
"animation_length": 0,
|
||||
"bones": {
|
||||
"body2": {
|
||||
"position": [0, -11.9, 0]
|
||||
|
||||
+8
@@ -299,6 +299,14 @@
|
||||
"rotation": [0, 0, 15]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.eelektrik.cry": {
|
||||
"animation_length": 1.25,
|
||||
"sound_effects": {
|
||||
"0.0417": {
|
||||
"effect": "pokemon.eelektrik.cry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -869,6 +869,14 @@
|
||||
"position": [-0.1, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.eelektross.cry": {
|
||||
"animation_length": 1.25,
|
||||
"sound_effects": {
|
||||
"0.0417": {
|
||||
"effect": "pokemon.eelektross.cry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+357
-149
@@ -84,7 +84,7 @@
|
||||
"position": [0, "0+Math.cos((query.anim_time) * 90 * 8 - 60) * -0.1", 0]
|
||||
},
|
||||
"hair3": {
|
||||
"rotation": ["0+Math.cos((query.anim_time) * 90 * 8 - 100) * 3", 0, 0]
|
||||
"rotation": ["-2+Math.cos((query.anim_time) * 90 * 8 - 100) * 3", 0, 0]
|
||||
},
|
||||
"hair": {
|
||||
"rotation": ["0+Math.cos((query.anim_time) * 90 * 8 - 120) * 10", 12.5, "0+Math.cos((query.anim_time) * 90 * 4 - 180) * 10"]
|
||||
@@ -356,7 +356,7 @@
|
||||
"position": [-1.63659, -1.07651, 3.16823]
|
||||
},
|
||||
"arm_right": {
|
||||
"rotation": [-80.89079, -82.69097, -6.55875]
|
||||
"rotation": [-99.86576, -52.80463, 0.06108]
|
||||
},
|
||||
"finger_right": {
|
||||
"rotation": [0, 0, 17.5]
|
||||
@@ -375,6 +375,21 @@
|
||||
},
|
||||
"arm_hammer_left": {
|
||||
"rotation": [-85, 100, 0]
|
||||
},
|
||||
"hand_right": {
|
||||
"rotation": [0, 0, -22.5]
|
||||
},
|
||||
"brow_right": {
|
||||
"position": [0, -0.75, 0]
|
||||
},
|
||||
"brow_left": {
|
||||
"position": [0, -0.75, 0]
|
||||
},
|
||||
"eyes_ditto": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eyes_blink_ditto": {
|
||||
"position": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -460,40 +475,26 @@
|
||||
"1.875": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"eyelid_right": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": [0, 0, -0.125],
|
||||
"0.625": [0, 0, -0.125],
|
||||
"0.6667": [0, 0, 0],
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": [0, 0, -0.125],
|
||||
"1.5833": [0, 0, -0.125],
|
||||
"1.625": [0, 0, 0],
|
||||
"1.7083": [0, 0, 0],
|
||||
"1.75": [0, 0, -0.125]
|
||||
}
|
||||
},
|
||||
"eyelid_left": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": [0, 0, -0.125],
|
||||
"0.625": [0, 0, -0.125],
|
||||
"0.4167": [0, 0, -0.1],
|
||||
"0.625": [0, 0, -0.1],
|
||||
"0.6667": [0, 0, 0],
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": [0, 0, -0.125],
|
||||
"1.5833": [0, 0, -0.125],
|
||||
"1.625": [0, 0, 0],
|
||||
"0.875": [0, 0, -0.1],
|
||||
"1.5833": [0, 0, -0.1],
|
||||
"1.625": [0, 0, 1],
|
||||
"1.7083": [0, 0, 0],
|
||||
"1.75": [0, 0, -0.125]
|
||||
"1.75": [0, 0, -0.1]
|
||||
}
|
||||
},
|
||||
"mouth_open": {
|
||||
"mouth_open_cry": {
|
||||
"position": {
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": [0, 0, -0.125],
|
||||
"1.875": [0, 0, -0.125],
|
||||
"1.9167": [0, 0, 0]
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.875": {"pre": [0, 0, -0.1], "post": [0, 0, -0.1]},
|
||||
"1.9167": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"arm_left": {
|
||||
@@ -556,13 +557,6 @@
|
||||
"1.75": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"eye_right": {
|
||||
"scale": {
|
||||
"1.5833": [1, 1, 1],
|
||||
"1.625": [1.4, 1.4, 3.3],
|
||||
"1.7083": [1, 1, 1]
|
||||
}
|
||||
},
|
||||
"eye_left": {
|
||||
"scale": {
|
||||
"1.5833": [1, 1, 1],
|
||||
@@ -722,6 +716,55 @@
|
||||
"1.7083": [1, 1, 1]
|
||||
}
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.2917": [0, -20, -2.5],
|
||||
"0.4167": [-5.68, -13.75, -2.5],
|
||||
"0.5833": [-32.36, -7.5, -2.5],
|
||||
"0.7083": [-40.5, 2.5, -2.5],
|
||||
"0.75": [-33.83, 2.5, -2.5],
|
||||
"0.8333": [-30.5, 2.5, -2.5]
|
||||
},
|
||||
"position": {
|
||||
"0.4167": [0, 0, 0],
|
||||
"0.5833": [0, 0, -0.75],
|
||||
"1.5833": [0, 0, -0.75],
|
||||
"1.625": [1.25, 0, -0.75],
|
||||
"1.75": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"arm_hammer_left": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 17.5],
|
||||
"0.25": [0, -25, 5],
|
||||
"0.4167": [0, -32.5, 3.5],
|
||||
"0.5": [-11.25, 40.75, -16.75],
|
||||
"0.5833": [-22.5, 46.75, -37],
|
||||
"0.7083": [-25, 44, -40.75]
|
||||
}
|
||||
},
|
||||
"eye_right": {
|
||||
"scale": {
|
||||
"1.5833": [1, 1, 1],
|
||||
"1.625": [1.4, 1.4, 3.3],
|
||||
"1.7083": [1, 1, 1]
|
||||
}
|
||||
},
|
||||
"eyelid_right": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": [0, 0, -0.1],
|
||||
"0.625": [0, 0, -0.1],
|
||||
"0.6667": [0, 0, 0],
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": [0, 0, -0.1],
|
||||
"1.5833": [0, 0, -0.1],
|
||||
"1.625": [0, 0, 1],
|
||||
"1.7083": [0, 0, 0],
|
||||
"1.75": [0, 0, -0.1]
|
||||
}
|
||||
},
|
||||
"arm_right": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, -20],
|
||||
@@ -753,24 +796,6 @@
|
||||
"finger_right3": {
|
||||
"rotation": [0, 0, -22.5]
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.2917": [0, -20, -2.5],
|
||||
"0.4167": [-5.68, -13.75, -2.5],
|
||||
"0.5833": [-32.36, -7.5, -2.5],
|
||||
"0.7083": [-40.5, 2.5, -2.5],
|
||||
"0.75": [-33.83, 2.5, -2.5],
|
||||
"0.8333": [-30.5, 2.5, -2.5]
|
||||
},
|
||||
"position": {
|
||||
"0.4167": [0, 0, 0],
|
||||
"0.5833": [0, 0, -0.75],
|
||||
"1.5833": [0, 0, -0.75],
|
||||
"1.625": [1.25, 0, -0.75],
|
||||
"1.75": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
@@ -789,33 +814,146 @@
|
||||
"1.75": [-0.5, 0, 0]
|
||||
}
|
||||
},
|
||||
"arm_hammer_left": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 17.5],
|
||||
"0.25": [0, -25, 5],
|
||||
"0.4167": [0, -32.5, 3.5],
|
||||
"0.5": [-11.25, 40.75, -16.75],
|
||||
"0.5833": [-22.5, 46.75, -37],
|
||||
"0.7083": [-25, 44, -40.75]
|
||||
"brow_left": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": [0, -0.75, 0],
|
||||
"0.625": [0, -0.75, 0],
|
||||
"0.6667": [0, 0, 0],
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": [0, -0.75, 0],
|
||||
"1.5833": [0, -0.75, 0],
|
||||
"1.625": [0, 0, 0],
|
||||
"1.7083": [0, 0, 0],
|
||||
"1.75": [0, -0.75, 0]
|
||||
}
|
||||
},
|
||||
"brow_right": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": [0, -0.75, 0],
|
||||
"0.625": [0, -0.75, 0],
|
||||
"0.6667": [0, 0, 0],
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": [0, -0.75, 0],
|
||||
"1.5833": [0, -0.75, 0],
|
||||
"1.625": [0, 0, 0],
|
||||
"1.7083": [0, 0, 0],
|
||||
"1.75": [0, -0.75, 0]
|
||||
}
|
||||
},
|
||||
"mouth_closed": {
|
||||
"position": {
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, -0.5, 0]},
|
||||
"1.875": {"pre": [0, -0.5, 0], "post": [0, -0.5, 0]},
|
||||
"1.9167": {"pre": [0, -0.5, 0], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_lip_top": {
|
||||
"position": {
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"1.875": {"pre": [0, 0, 0.1], "post": [0, 0, 0.1]},
|
||||
"1.9167": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"eyes_ditto": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"0.625": {"pre": [0, 0, 0.1], "post": [0, 0, 0.1]},
|
||||
"0.6667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]},
|
||||
"0.8333": {"pre": [0, 0, 0], "post": [0, 0, 0]},
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"1.5833": {"pre": [0, 0, 0.1], "post": [0, 0, 0]},
|
||||
"1.75": {"pre": [0, 0, 0], "post": [0, 0, 0.1]}
|
||||
},
|
||||
"scale": {
|
||||
"1.5833": [1, 1, 1],
|
||||
"1.625": [1.4, 1.4, 3.3],
|
||||
"1.7083": [1, 1, 1]
|
||||
}
|
||||
},
|
||||
"eyes_blink_ditto": {
|
||||
"position": {
|
||||
"0.375": [0, 0, 0],
|
||||
"0.4167": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"0.625": {"pre": [0, 0, -0.1], "post": [0, 0, -0.1]},
|
||||
"0.6667": {"pre": [0, 0, -0.1], "post": [0, 0, 0]},
|
||||
"0.8333": {"pre": [0, 0, 0], "post": [0, 0, 0]},
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.5833": {"pre": [0, 0, -0.1], "post": [0, 0, 0]},
|
||||
"1.75": {"pre": [0, 0, 0], "post": [0, 0, -0.1]}
|
||||
}
|
||||
},
|
||||
"mouth_ditto": {
|
||||
"position": {
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"1.875": {"pre": [0, 0, 0.1], "post": [0, 0, 0.1]},
|
||||
"1.9167": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_open_ditto": {
|
||||
"position": {
|
||||
"0.8333": [0, 0, 0],
|
||||
"0.875": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.875": {"pre": [0, 0, -0.1], "post": [0, 0, -0.1]},
|
||||
"1.9167": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.blink": {
|
||||
"animation_length": 0.16667,
|
||||
"animation_length": 0.1667,
|
||||
"bones": {
|
||||
"eyelid_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.125": [0, 0, -0.1],
|
||||
"0.1667": [0, 0, 0]
|
||||
"0.1667": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"eyelid_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.125": [0, 0, -0.1],
|
||||
"0.1667": [0, 0, 0]
|
||||
"0.1667": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"brow_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"brow_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"eye_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"eye_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"eyes_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"eyes_blink_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.1667": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -823,38 +961,6 @@
|
||||
"animation.tinkatink.cry": {
|
||||
"animation_length": 1.5,
|
||||
"bones": {
|
||||
"neck": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [10, 0, 0],
|
||||
"1.25": [10, 0, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"mouth": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [0, -2, 0],
|
||||
"1.25": [0, -2, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"mouth2": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [0, -5, 0],
|
||||
"1.25": [0, -5, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"jaw": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [0, -3, 0],
|
||||
"1.25": [0, -3, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"head": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
@@ -863,60 +969,12 @@
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"head2": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [-15, 0, 0],
|
||||
"1.25": [-15, 0, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"mouth_closed2": {
|
||||
"position": {
|
||||
"0.25": [0, 0, -0.1],
|
||||
"0.3333": [0, 0, 0],
|
||||
"1.1667": [0, 0, 0],
|
||||
"1.25": [0, 0, -0.1]
|
||||
}
|
||||
},
|
||||
"mouth_open2": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.25": [0, 0, 0],
|
||||
"1.25": [0, 0, 0],
|
||||
"1.5": [0, 0, 0.1]
|
||||
}
|
||||
},
|
||||
"head3": {
|
||||
"rotation": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [-15, 0, 0],
|
||||
"1.25": [-15, 0, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"mouth_closed3": {
|
||||
"position": {
|
||||
"0.25": [0, 0, -0.1],
|
||||
"0.3333": [0, 0, 0],
|
||||
"1.1667": [0, 0, 0],
|
||||
"1.25": [0, 0, -0.1]
|
||||
}
|
||||
},
|
||||
"mouth_open3": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.25": [0, 0, 0],
|
||||
"1.25": [0, 0, 0],
|
||||
"1.5": [0, 0, 0.1]
|
||||
}
|
||||
},
|
||||
"mouth_open": {
|
||||
"mouth_open_cry": {
|
||||
"position": {
|
||||
"0.0833": [0, 0, 0],
|
||||
"0.1667": [0, 0, -0.15],
|
||||
"1.25": [0, 0, -0.15],
|
||||
"1.3333": [0, 0, 0]
|
||||
"0.1667": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.25": {"pre": [0, 0, -0.1], "post": [0, 0, -0.1]},
|
||||
"1.3333": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"arm_left": {
|
||||
@@ -948,10 +1006,38 @@
|
||||
"1.25": [0, 0, -2.4],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"mouth_closed": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.1667": {"pre": [0, 0, 0], "post": [0, -0.5, 0]},
|
||||
"1.3333": {"pre": [0, -0.5, 0], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_lip_top": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.1667": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"1.3333": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.1667": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"1.3333": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_open_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.1667": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.3333": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sound_effects": {
|
||||
"0.25": {
|
||||
"0.0": {
|
||||
"effect": "pokemon.tinkatink.cry"
|
||||
}
|
||||
}
|
||||
@@ -1062,6 +1148,128 @@
|
||||
"rotation": [0, 0, 32.5]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.ditto_face": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"face": {
|
||||
"position": [0, 0, 0.1],
|
||||
"scale": 0
|
||||
},
|
||||
"face_ditto": {
|
||||
"position": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.happy": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"brow_left": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"brow_right": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eye_left": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eye_right": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"mouth_closed": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"mouth_lip_top": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"mouth_open_happy": {
|
||||
"position": [0, 0, -0.1]
|
||||
},
|
||||
"eyelid_happy_left": {
|
||||
"position": [0, 0, -0.1]
|
||||
},
|
||||
"eyelid_happy_right": {
|
||||
"position": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.angry": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"eye_emote_left": {
|
||||
"scale": [1, 0.5, 1]
|
||||
},
|
||||
"eye_emote_right": {
|
||||
"scale": [1, 0.5, 1]
|
||||
},
|
||||
"eye_emote_left2": {
|
||||
"position": [0.25, 0, 0],
|
||||
"scale": [0.5, 1, 1]
|
||||
},
|
||||
"eye_emote_right2": {
|
||||
"position": [-0.25, 0, 0],
|
||||
"scale": [0.5, 1, 1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.very_sad": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"eye_emote_left": {
|
||||
"scale": [1, 0.5, 1]
|
||||
},
|
||||
"eye_emote_right": {
|
||||
"scale": [1, 0.5, 1]
|
||||
},
|
||||
"eye_emote_left2": {
|
||||
"position": [-0.25, 0, 0],
|
||||
"scale": [0.5, 1, 1]
|
||||
},
|
||||
"eye_emote_right2": {
|
||||
"position": [0.25, 0, 0],
|
||||
"scale": [0.5, 1, 1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.hurt": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"brow_left": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"brow_right": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eye_left": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eye_right": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eyelid_hurt_left": {
|
||||
"position": [0, 0, -0.1]
|
||||
},
|
||||
"eyelid_hurt_right": {
|
||||
"position": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatink.disinterested": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"eye_emote_left": {
|
||||
"scale": [1, 0.5, 1]
|
||||
},
|
||||
"eye_emote_right": {
|
||||
"scale": [1, 0.5, 1]
|
||||
},
|
||||
"eye_emote_left2": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eye_emote_right2": {
|
||||
"position": [0, 0, 0.1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+533
-200
@@ -4,12 +4,6 @@
|
||||
"animation.tinkatuff.ground_idle": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"tail": {
|
||||
"rotation": [12.5, 0, 0]
|
||||
},
|
||||
"tail2": {
|
||||
"rotation": [12.5, 0, 0]
|
||||
},
|
||||
"arm_right": {
|
||||
"rotation": [55.98303, 0.22836, "-40.7651+Math.cos((query.anim_time) * 90 * 2 - 120) * 1"],
|
||||
"position": [-0.25, -0.5, 0]
|
||||
@@ -21,7 +15,8 @@
|
||||
"rotation": [0, 0, -5]
|
||||
},
|
||||
"finger_right2": {
|
||||
"rotation": [0, -10, 37.5]
|
||||
"rotation": [0, -17.5, 37.5],
|
||||
"position": [-0.25, 0, 0]
|
||||
},
|
||||
"arm_left": {
|
||||
"rotation": [-84.39003, 4.58778, "53.5046+Math.cos((query.anim_time) * 90 * 2 - 120) * 1"]
|
||||
@@ -30,17 +25,11 @@
|
||||
"rotation": [0, -15, 55]
|
||||
},
|
||||
"finger_left": {
|
||||
"rotation": [0, 0, -25]
|
||||
"rotation": [0, 0, -40]
|
||||
},
|
||||
"finger_left2": {
|
||||
"rotation": [20, 0, 0]
|
||||
},
|
||||
"hand_left": {
|
||||
"rotation": [0, 17.5, 0]
|
||||
},
|
||||
"hand_right": {
|
||||
"rotation": [0, -17.5, 0]
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": [0, -12.5, -5]
|
||||
},
|
||||
@@ -48,7 +37,7 @@
|
||||
"rotation": [0, 12.5, 5]
|
||||
},
|
||||
"finger_left3": {
|
||||
"rotation": [0, 0, 57.5]
|
||||
"rotation": [0, 0, 67.5]
|
||||
},
|
||||
"finger_right3": {
|
||||
"rotation": [0, 0, -20]
|
||||
@@ -78,9 +67,6 @@
|
||||
"head": {
|
||||
"rotation": ["12.5+Math.cos((query.anim_time) * 90 * 2 - 50) * 2", 0, 0]
|
||||
},
|
||||
"mouth_closed": {
|
||||
"position": [0, 0, 0]
|
||||
},
|
||||
"ponytail": {
|
||||
"rotation": ["0+Math.cos((query.anim_time) * 90 * 2 - 120) * 1", 0, 0]
|
||||
},
|
||||
@@ -187,10 +173,24 @@
|
||||
}
|
||||
},
|
||||
"finger_right2": {
|
||||
"rotation": [7.5, 0, 10]
|
||||
"rotation": [7.5, 0, 42.5],
|
||||
"position": [-0.25, 0.25, 0]
|
||||
},
|
||||
"finger_right3": {
|
||||
"rotation": [-0.88045, -9.96156, 5.07673]
|
||||
},
|
||||
"finger_left": {
|
||||
"rotation": [0, 0, -55],
|
||||
"position": [0, 0.5, 0]
|
||||
},
|
||||
"finger_left2": {
|
||||
"rotation": [0, 0, -17.5]
|
||||
},
|
||||
"finger_left3": {
|
||||
"rotation": [0, 0, 22.5]
|
||||
},
|
||||
"finger_right": {
|
||||
"rotation": [0, 0, 20]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -210,7 +210,7 @@
|
||||
"rotation": [-15, -2.5, 62.5]
|
||||
},
|
||||
"finger_left3": {
|
||||
"rotation": [0, 0, 52.5]
|
||||
"rotation": [0, 0, 22.5]
|
||||
},
|
||||
"arm_right": {
|
||||
"rotation": [22.5, -10, -35],
|
||||
@@ -299,18 +299,29 @@
|
||||
},
|
||||
"arm_hammer_right2": {
|
||||
"rotation": [0, -70, 0]
|
||||
},
|
||||
"finger_left": {
|
||||
"rotation": [0, 0, -52.5],
|
||||
"position": [0, 0.75, 0]
|
||||
},
|
||||
"finger_left2": {
|
||||
"rotation": [0, 0, -15]
|
||||
},
|
||||
"finger_right": {
|
||||
"rotation": [0, 0, 20]
|
||||
},
|
||||
"finger_right2": {
|
||||
"rotation": [7.5, 0, 37.5],
|
||||
"position": [-0.25, 0.25, 0]
|
||||
},
|
||||
"finger_right3": {
|
||||
"rotation": [0, 0, -32.5]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatuff.battle_idle": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"tail": {
|
||||
"rotation": [12.5, 0, 0]
|
||||
},
|
||||
"tail2": {
|
||||
"rotation": [12.5, 0, 0]
|
||||
},
|
||||
"arm_right": {
|
||||
"rotation": [40.98303, 0.22836, "-45.7651+Math.cos((query.anim_time) * 90 * 2 - 40) * 2"]
|
||||
},
|
||||
@@ -318,7 +329,8 @@
|
||||
"rotation": [0, -52.5, 0]
|
||||
},
|
||||
"finger_right": {
|
||||
"rotation": [0, 0, -5]
|
||||
"rotation": [0, 0, 37.5],
|
||||
"position": [0, 0.25, 0]
|
||||
},
|
||||
"finger_right2": {
|
||||
"rotation": [0, 0, 37.5]
|
||||
@@ -330,17 +342,12 @@
|
||||
"rotation": [0, 0, "55+Math.cos((query.anim_time) * 90 * 2 - 40) * -2"]
|
||||
},
|
||||
"finger_left": {
|
||||
"rotation": [0, 0, -25]
|
||||
"rotation": [0, 0, -47.5],
|
||||
"position": [0, 0.5, 0]
|
||||
},
|
||||
"finger_left2": {
|
||||
"rotation": [20, 0, 0]
|
||||
},
|
||||
"hand_left": {
|
||||
"rotation": [0, 17.5, 0]
|
||||
},
|
||||
"hand_right": {
|
||||
"rotation": [0, -17.5, 0]
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": ["-11.2025+Math.cos((query.anim_time) * 90 * 2) * 3", -21.49336, -9.33448]
|
||||
},
|
||||
@@ -348,10 +355,11 @@
|
||||
"rotation": ["-13.5954+Math.cos((query.anim_time) * 90 * 2) * 3", 22.38126, 10.45107]
|
||||
},
|
||||
"finger_left3": {
|
||||
"rotation": [0, 0, 57.5]
|
||||
"rotation": [0, 0, 42.5],
|
||||
"position": [-0.5, -0.25, 0]
|
||||
},
|
||||
"finger_right3": {
|
||||
"rotation": [0, 0, -20]
|
||||
"rotation": [0, 0, -40]
|
||||
},
|
||||
"hammer": {
|
||||
"rotation": [-12.18122, -2.58318, 113.79667]
|
||||
@@ -416,7 +424,8 @@
|
||||
"position": [-0.5, 0, 0]
|
||||
},
|
||||
"hammer": {
|
||||
"rotation": [15, 0, 0]
|
||||
"rotation": [15, 0, 0],
|
||||
"position": [0, 0.5, 3.75]
|
||||
},
|
||||
"hammer2": {
|
||||
"rotation": ["90+Math.cos((query.anim_time) * 90 * 1) * -1", 0, 0],
|
||||
@@ -456,43 +465,18 @@
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": [-90, 20, 0]
|
||||
},
|
||||
"eyes_ditto": {
|
||||
"position": [0, 0, 0.1]
|
||||
},
|
||||
"eyes_blink_ditto": {
|
||||
"position": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatuff.faint": {
|
||||
"animation_length": 3,
|
||||
"bones": {
|
||||
"arm_right": {
|
||||
"rotation": {
|
||||
"0.0": [40.98303, 0.22836, -45.7651],
|
||||
"0.2083": [5.98, 0.23, -45.77],
|
||||
"0.2917": [35.98, 0.23, -45.77],
|
||||
"0.75": [18.48, 5.23, -23.27],
|
||||
"0.9167": [60.98, -12.27, -35.77],
|
||||
"1.2083": [30.98, -59.77, -35.77],
|
||||
"1.3333": [32.05, -62.98, -35.77],
|
||||
"1.5": [33.48, -67.27, -35.77],
|
||||
"1.8333": [15.98, -79.77, -35.77],
|
||||
"1.9167": [67.65, -129.77, -110.77],
|
||||
"1.9583": [93.48, -154.77, -148.27],
|
||||
"2.0833": [93.48, -174.77, -168.27],
|
||||
"2.3333": [93.48, -169.77, -168.27]
|
||||
}
|
||||
},
|
||||
"arm_right2": {
|
||||
"rotation": {
|
||||
"0.0": [0, -52.5, 0],
|
||||
"1.0": [0, -52.5, 0],
|
||||
"1.2083": [0, -57.5, 0],
|
||||
"1.5": [0, -30, 0]
|
||||
}
|
||||
},
|
||||
"finger_right": {
|
||||
"rotation": [0, 0, -5]
|
||||
},
|
||||
"finger_right2": {
|
||||
"rotation": [0, 0, 37.5]
|
||||
},
|
||||
"arm_left": {
|
||||
"rotation": {
|
||||
"0.0": [-84.39003, 24.58778, 53.5046],
|
||||
@@ -547,30 +531,9 @@
|
||||
"2.375": [-30.37, 0, -5]
|
||||
}
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": {
|
||||
"0.0": [-13.6, 22.38, 10.45],
|
||||
"0.1667": [-31.1, 22.38, 10.45],
|
||||
"0.25": [-26.1, 22.38, 10.45],
|
||||
"0.5": [-3.6, 22.38, 10.45],
|
||||
"0.7917": [6.4, 22.38, 10.45],
|
||||
"0.9167": [-31.1, 22.38, 10.45],
|
||||
"1.2083": [-31.1, 42.38, 10.45],
|
||||
"1.375": [-30, 0, 0],
|
||||
"1.6667": [-30, 0, 5],
|
||||
"1.875": [70, 0, 5],
|
||||
"2.0417": [40.36, 0, 5],
|
||||
"2.1667": [-30, 0, 5],
|
||||
"2.25": [-45, 0, 5],
|
||||
"2.375": [-35, 0, 5]
|
||||
}
|
||||
},
|
||||
"finger_left3": {
|
||||
"rotation": [0, 0, 57.5]
|
||||
},
|
||||
"finger_right3": {
|
||||
"rotation": [0, 0, -20]
|
||||
},
|
||||
"hammer": {
|
||||
"rotation": {
|
||||
"0.0": [-0.78, -6.26, 38.97],
|
||||
@@ -593,9 +556,6 @@
|
||||
"skirt_left": {
|
||||
"rotation": [0, 0, -15]
|
||||
},
|
||||
"skirt_right": {
|
||||
"rotation": [-10, 0, 15]
|
||||
},
|
||||
"leg_left2": {
|
||||
"rotation": {
|
||||
"0.0": [20, 0, 0],
|
||||
@@ -611,18 +571,6 @@
|
||||
"2.0417": [15, 0, 0]
|
||||
}
|
||||
},
|
||||
"leg_right2": {
|
||||
"rotation": {
|
||||
"0.0": [15, 0, -10],
|
||||
"0.1667": [35, 0, -10],
|
||||
"0.25": [-10, 0, -10],
|
||||
"0.5": [27.5, 0, 5],
|
||||
"0.7917": [5, 0, 5],
|
||||
"0.9167": [35, 0, 5],
|
||||
"1.375": [35, 0, 0],
|
||||
"1.6667": [22.5, 0, 0]
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"rotation": {
|
||||
"0.9167": [0, 0, 0],
|
||||
@@ -851,134 +799,519 @@
|
||||
},
|
||||
"eyelid_left": {
|
||||
"position": {
|
||||
"0.2083": [0, 0, 0],
|
||||
"0.25": [0, 0, -0.15],
|
||||
"0.375": [0, 0, -0.15],
|
||||
"0.4167": [0, 0, 0],
|
||||
"0.9167": [0, 0, 0],
|
||||
"0.9583": [0, 0, -0.15],
|
||||
"1.75": [0, 0, -0.15],
|
||||
"1.7917": [0, 0, 0],
|
||||
"2.0": [0, 0, 0],
|
||||
"2.0417": [0, 0, -0.15]
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_top": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0.5, 0]
|
||||
},
|
||||
"0.9167": {
|
||||
"pre": [0, 0.5, 0],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"1.2917": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0.5, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0.5, 0],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eyelid_right": {
|
||||
"position": {
|
||||
"0.2083": [0, 0, 0],
|
||||
"0.25": [0, 0, -0.15],
|
||||
"0.375": [0, 0, -0.15],
|
||||
"0.4167": [0, 0, 0],
|
||||
"0.9167": [0, 0, 0],
|
||||
"0.9583": [0, 0, -0.15],
|
||||
"1.75": [0, 0, -0.15],
|
||||
"1.7917": [0, 0, 0],
|
||||
"2.0": [0, 0, 0],
|
||||
"2.0417": [0, 0, -0.15]
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_open": {
|
||||
"position": {
|
||||
"0.2083": [0, 0, 0],
|
||||
"0.25": [0, 0, -0.15],
|
||||
"0.875": [0, 0, -0.15],
|
||||
"0.9167": [0, 0, 0],
|
||||
"1.25": [0, 0, 0],
|
||||
"1.2917": [0, 0, -0.15],
|
||||
"2.0": [0, 0, -0.15],
|
||||
"2.0417": [0, 0, 0]
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"0.9167": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"1.2917": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_closed": {
|
||||
"position": {
|
||||
"0.2083": [0, 0, 0],
|
||||
"0.25": [0, 0, 0.1],
|
||||
"0.875": [0, 0, 0.1],
|
||||
"0.9167": [0, 0, 0],
|
||||
"1.25": [0, 0, 0],
|
||||
"1.2917": [0, 0, 0.1],
|
||||
"2.0": [0, 0, 0.1],
|
||||
"2.0417": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatuff.blink": {
|
||||
"animation_length": 0.16667,
|
||||
"bones": {
|
||||
"eyelid_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.125": [0, 0, -0.1],
|
||||
"0.1667": [0, 0, 0]
|
||||
"arm_right": {
|
||||
"rotation": {
|
||||
"0.0": [40.98303, 0.22836, -45.7651],
|
||||
"0.2083": [5.98, 0.23, -45.77],
|
||||
"0.2917": [35.98, 0.23, -45.77],
|
||||
"0.75": [18.48, 5.23, -23.27],
|
||||
"0.9167": [60.98, -12.27, -35.77],
|
||||
"1.2083": [30.98, -59.77, -35.77],
|
||||
"1.3333": [32.05, -62.98, -35.77],
|
||||
"1.5": [33.48, -67.27, -35.77],
|
||||
"1.8333": [15.98, -79.77, -35.77],
|
||||
"1.9167": [67.65, -129.77, -110.77],
|
||||
"1.9583": [93.48, -154.77, -148.27],
|
||||
"2.0833": [93.48, -174.77, -168.27],
|
||||
"2.3333": [93.48, -169.77, -168.27]
|
||||
}
|
||||
},
|
||||
"eyelid_left": {
|
||||
"arm_right2": {
|
||||
"rotation": {
|
||||
"0.0": [0, -52.5, 0],
|
||||
"1.0": [0, -52.5, 0],
|
||||
"1.2083": [0, -57.5, 0],
|
||||
"1.5": [0, -30, 0]
|
||||
}
|
||||
},
|
||||
"finger_right": {
|
||||
"rotation": [0, 0, -5]
|
||||
},
|
||||
"finger_right2": {
|
||||
"rotation": [0, 0, 37.5]
|
||||
},
|
||||
"finger_right3": {
|
||||
"rotation": [0, 0, -20]
|
||||
},
|
||||
"skirt_right": {
|
||||
"rotation": [-10, 0, 15]
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": {
|
||||
"0.0": [-13.6, 22.38, 10.45],
|
||||
"0.1667": [-31.1, 22.38, 10.45],
|
||||
"0.25": [-26.1, 22.38, 10.45],
|
||||
"0.5": [-3.6, 22.38, 10.45],
|
||||
"0.7917": [6.4, 22.38, 10.45],
|
||||
"0.9167": [-31.1, 22.38, 10.45],
|
||||
"1.2083": [-31.1, 42.38, 10.45],
|
||||
"1.375": [-30, 0, 0],
|
||||
"1.6667": [-30, 0, 5],
|
||||
"1.875": [70, 0, 5],
|
||||
"2.0417": [40.36, 0, 5],
|
||||
"2.1667": [-30, 0, 5],
|
||||
"2.25": [-45, 0, 5],
|
||||
"2.375": [-35, 0, 5]
|
||||
}
|
||||
},
|
||||
"leg_right2": {
|
||||
"rotation": {
|
||||
"0.0": [15, 0, -10],
|
||||
"0.1667": [35, 0, -10],
|
||||
"0.25": [-10, 0, -10],
|
||||
"0.5": [27.5, 0, 5],
|
||||
"0.7917": [5, 0, 5],
|
||||
"0.9167": [35, 0, 5],
|
||||
"1.375": [35, 0, 0],
|
||||
"1.6667": [22.5, 0, 0]
|
||||
}
|
||||
},
|
||||
"brow_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.125": [0, 0, -0.1],
|
||||
"0.1667": [0, 0, 0]
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"brow_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_bottom": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, -0.5, 0]
|
||||
},
|
||||
"0.9167": {
|
||||
"pre": [0, -0.5, 0],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"1.2917": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, -0.5, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, -0.5, 0],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eye_emote_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eye_emote_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eyes_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eyes_blink_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"0.4167": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"0.9583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"1.7917": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"0.9167": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"1.2917": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, 0.1]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_open_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"0.9167": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
},
|
||||
"1.2917": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0, 0, -0.1]
|
||||
},
|
||||
"2.0417": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatuff.cry": {
|
||||
"animation_length": 0.91667,
|
||||
"animation_length": 1.5,
|
||||
"bones": {
|
||||
"head": {
|
||||
"rotation": {
|
||||
"0.0": {
|
||||
"post": [0, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"0.25": {
|
||||
"post": [-15, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"0.6667": {
|
||||
"post": [-15, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"0.9167": {
|
||||
"post": [0, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouth_closed": {
|
||||
"position": {
|
||||
"0.25": [0, 0, 0],
|
||||
"0.3333": [0, 0, 0.1],
|
||||
"0.5833": [0, 0, 0.1],
|
||||
"0.6667": [0, 0, 0]
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": [-15, 0, 0],
|
||||
"1.25": [-15, 0, 0],
|
||||
"1.5": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"mouth_open": {
|
||||
"position": {
|
||||
"0.0": {
|
||||
"post": [0, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"0.25": {
|
||||
"post": [0, 0, -0.1],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"0.6667": {
|
||||
"post": [0, 0, -0.1],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"0.9167": {
|
||||
"post": [0, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
}
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.3333": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_top": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {"pre": [0, 0, 0], "post": [0, 0.5, 0]},
|
||||
"1.3333": {"pre": [0, 0.5, 0], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_bottom": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {"pre": [0, 0, 0], "post": [0, -0.5, 0]},
|
||||
"1.3333": {"pre": [0, -0.5, 0], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {"pre": [0, 0, 0], "post": [0, 0, 0.1]},
|
||||
"1.3333": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"mouth_open_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0],
|
||||
"0.25": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
|
||||
"1.3333": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sound_effects": {
|
||||
"0.1667": {
|
||||
"0.0": {
|
||||
"effect": "pokemon.tinkatuff.cry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatuff.blink": {
|
||||
"animation_length": 0.1667,
|
||||
"bones": {
|
||||
"eyelid_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eyelid_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"brow_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"brow_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eye_left": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eye_right": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eyes_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, 0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, 0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"eyes_blink_ditto": {
|
||||
"position": {
|
||||
"0.0": [0, 0, -0.1],
|
||||
"0.1667": {
|
||||
"pre": [0, 0, -0.1],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.tinkatuff.ditto_face": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"face": {
|
||||
"position": [0, 0, 0.1],
|
||||
"scale": 0
|
||||
},
|
||||
"face_ditto": {
|
||||
"position": [0, 0, -0.1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7292
-2163
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -90,7 +90,7 @@
|
||||
"parent": "chest",
|
||||
"pivot": [0, 17, -5],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 16, -7.5], "size": [5.5, 5, 5], "uv": [22, 0]},
|
||||
{"origin": [-2.25, 16, -7.5], "size": [5, 5, 5], "uv": [22, 0]},
|
||||
{"origin": [0, 18, -2.5], "size": [0, 4, 2], "uv": [30, 37]}
|
||||
]
|
||||
},
|
||||
|
||||
+2
-2
@@ -486,7 +486,7 @@
|
||||
"parent": "eye_left",
|
||||
"pivot": [4.2, 34.1, -10.53],
|
||||
"cubes": [
|
||||
{"origin": [4.98, 33.99, -10.53], "size": [3, 1, 1], "inflate": 0.01, "uv": [132, 116]}
|
||||
{"origin": [4.98, 34.01, -10.53], "size": [3, 1, 1], "inflate": 0.01, "uv": [132, 116]}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -782,7 +782,7 @@
|
||||
"parent": "eye_right",
|
||||
"pivot": [-4.2, 34.1, -10.53],
|
||||
"cubes": [
|
||||
{"origin": [-7.98, 33.99, -10.53], "size": [3, 1, 1], "inflate": 0.01, "uv": [140, 116]}
|
||||
{"origin": [-7.98, 34.01, -10.53], "size": [3, 1, 1], "inflate": 0.01, "uv": [140, 116]}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+450
-100
@@ -15,18 +15,58 @@
|
||||
"name": "tinkatink",
|
||||
"pivot": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "locator_root",
|
||||
"parent": "tinkatink",
|
||||
"pivot": [0, 0, 0],
|
||||
"locators": {
|
||||
"root": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_top",
|
||||
"parent": "tinkatink",
|
||||
"pivot": [0, 14, 0],
|
||||
"locators": {
|
||||
"top": [0, 14, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_special",
|
||||
"parent": "tinkatink",
|
||||
"pivot": [0, 6, -7],
|
||||
"locators": {
|
||||
"special": [0, 6, -7]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"parent": "tinkatink",
|
||||
"pivot": [0, 3.75, 0]
|
||||
},
|
||||
{
|
||||
"name": "locator_target",
|
||||
"parent": "body",
|
||||
"pivot": [0, 6, -6],
|
||||
"locators": {
|
||||
"target": [0, 6, -6]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_middle",
|
||||
"parent": "body",
|
||||
"pivot": [0, 6, 0],
|
||||
"locators": {
|
||||
"middle": [0, 6, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "torso",
|
||||
"parent": "body",
|
||||
"pivot": [0, 1.75, -0.25],
|
||||
"pivot": [0, 2.5, -0.25],
|
||||
"cubes": [
|
||||
{"origin": [-2, 1.5, -2], "size": [4, 3, 4], "uv": [0, 12]},
|
||||
{"origin": [-1, 2, -2.75], "size": [2, 2, 1], "pivot": [0, 3, -2.25], "rotation": [0, 0, 45], "uv": [12, 12]}
|
||||
{"origin": [-1.5, 2, -1.5], "size": [3, 3, 3], "uv": [30, 6]},
|
||||
{"origin": [-1, 1.5, -2.5], "size": [2, 2, 1], "pivot": [0, 2.5, -2], "rotation": [0, 0, 45], "uv": [47, 12]}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -34,199 +74,509 @@
|
||||
"parent": "torso",
|
||||
"pivot": [0, 4.25, -0.25],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 4.25, -3.25], "size": [7, 6, 6], "uv": [0, 0]}
|
||||
{"origin": [-4, 4.25, -3.75], "size": [8, 7, 7], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair3",
|
||||
"name": "locator_item_hat",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.25, -0.25],
|
||||
"rotation": [-5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 10.25, -3.25], "size": [5, 0, 3], "uv": [11, 21]}
|
||||
]
|
||||
"pivot": [0, 12.25, -0.25],
|
||||
"locators": {
|
||||
"item_hat": [0, 12.25, -0.25]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hair4",
|
||||
"parent": "hair3",
|
||||
"pivot": [0, 10.25, -3.25],
|
||||
"rotation": [-15, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 8.25, -3.25], "size": [5, 2, 0], "uv": [43, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"name": "locator_head",
|
||||
"parent": "head",
|
||||
"pivot": [-2.75, 6.5, -2.76],
|
||||
"rotation": [0, 0, -5],
|
||||
"pivot": [0, 11.25, -3.25],
|
||||
"locators": {
|
||||
"head": [0, 11.25, -3.25]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_mouth",
|
||||
"parent": "head",
|
||||
"pivot": [0, 5.7, -3.75],
|
||||
"locators": {
|
||||
"mouth": [0, 5.7, -3.75]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item_face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 8.25, -3.75],
|
||||
"locators": {
|
||||
"item_face": [0, 8.25, -3.75]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 7.25, -3.75],
|
||||
"locators": {
|
||||
"face": [0, 7.25, -3.75]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "face_ditto",
|
||||
"parent": "head",
|
||||
"pivot": [0, 7, -3.65]
|
||||
},
|
||||
{
|
||||
"name": "eyes_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 7.75, -3.67],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 6, -3.26], "size": [1, 2, 1], "uv": [51, 2]}
|
||||
{"origin": [-3, 7.25, -3.67], "size": [6, 1, 0], "uv": [51, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-0.5, 3.5, -0.15],
|
||||
"name": "eyes_blink_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 7.75, -3.57],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 6, -3.16], "size": [1, 2, 1], "inflate": 0.01, "uv": [47, 2]}
|
||||
{"origin": [-3.5, 7.25, -3.57], "size": [7, 1, 0], "uv": [49, 27]},
|
||||
{"origin": [-3, 7.75, -3.58], "size": [6, 1, 0], "uv": [51, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 6, -3.65],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 5.25, -3.66], "size": [5, 1, 0], "uv": [53, 19]},
|
||||
{"origin": [-3.5, 5.75, -3.66], "size": [7, 1, 0], "uv": [49, 25]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 5.75, -3.56],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 4.75, -3.56], "size": [7, 2, 0], "uv": [49, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 6.75, -3.75]
|
||||
},
|
||||
{
|
||||
"name": "eyes",
|
||||
"parent": "face",
|
||||
"pivot": [0, 6.75, -3.26]
|
||||
},
|
||||
{
|
||||
"name": "brow_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.25, 8.5, -3.25],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 8, -3.75], "size": [1, 1, 1], "inflate": 0.01, "pivot": [2.25, 8.5, -3.25], "rotation": [0, 0, 5], "uv": [32, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "brow_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.25, 8.5, -3.25],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 8, -3.75], "size": [1, 1, 1], "inflate": 0.01, "pivot": [-2.25, 8.5, -3.25], "rotation": [0, 0, -5], "uv": [32, 25]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "head",
|
||||
"pivot": [2.75, 6.5, -2.76],
|
||||
"rotation": [0, 0, 5],
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 7.75, -3.26],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 6, -3.26], "size": [1, 2, 1], "uv": [26, 7]}
|
||||
{"origin": [2, 6.75, -3.75], "size": [1, 1, 1], "inflate": 0.01, "uv": [28, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_eye_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.5, 7.7, -3.75],
|
||||
"locators": {
|
||||
"eye2": [2.5, 7.7, -3.75]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.5, 7.75, -3.75],
|
||||
"cubes": [
|
||||
{"origin": [2, 7.75, -3.75], "size": [1, 1, 1], "inflate": 0.025, "uv": [24, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_left2",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.5, 7.75, -3.75],
|
||||
"cubes": [
|
||||
{"origin": [2, 7.75, -3.75], "size": [1, 1, 1], "inflate": 0.02, "uv": [20, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 7.75, -3.26],
|
||||
"cubes": [
|
||||
{"origin": [-3, 6.75, -3.75], "size": [1, 1, 1], "inflate": 0.01, "uv": [28, 25]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_eye_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 7.7, -3.75],
|
||||
"locators": {
|
||||
"eye1": [-2.5, 7.7, -3.75]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 7.75, -3.75],
|
||||
"cubes": [
|
||||
{"origin": [-3, 7.75, -3.75], "size": [1, 1, 1], "inflate": 0.025, "uv": [24, 25]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_right2",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 7.75, -3.75],
|
||||
"cubes": [
|
||||
{"origin": [-3, 7.75, -3.75], "size": [1, 1, 1], "inflate": 0.02, "uv": [20, 25]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [0.5, 3.5, -0.15],
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 7.75, -3.16],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 6, -3.16], "size": [1, 2, 1], "inflate": 0.01, "uv": [32, 8]}
|
||||
{"origin": [1.5, 6.75, -3.65], "size": [2, 2, 1], "inflate": 0.03, "uv": [0, 28]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair",
|
||||
"name": "eyelid_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 7.75, -3.16],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 6.75, -3.65], "size": [2, 2, 1], "inflate": 0.03, "uv": [0, 24]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_happy_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 7.75, -3.16],
|
||||
"cubes": [
|
||||
{"origin": [1, 6.75, -3.66], "size": [3, 2, 1], "uv": [12, 28]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_happy_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 7.75, -3.16],
|
||||
"cubes": [
|
||||
{"origin": [-4, 6.75, -3.66], "size": [3, 2, 1], "uv": [12, 24]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_hurt_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 7.75, -3.16],
|
||||
"cubes": [
|
||||
{"origin": [1.5, 6.75, -3.65], "size": [2, 2, 1], "inflate": 0.03, "uv": [6, 28]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_hurt_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 7.75, -3.16],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 6.75, -3.65], "size": [2, 2, 1], "inflate": 0.03, "uv": [6, 24]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouths",
|
||||
"parent": "face",
|
||||
"pivot": [0, 4.25, -0.75]
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"parent": "mouths",
|
||||
"pivot": [0, 5.75, -3.25],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 4.75, -3.76], "size": [3, 1, 1], "inflate": 0.02, "uv": [38, 13]},
|
||||
{"origin": [-0.5, 5.25, -3.76], "size": [1, 1, 1], "inflate": 0.04, "uv": [46, 16]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_lip_top",
|
||||
"parent": "mouth_closed",
|
||||
"pivot": [0, 6.25, -3.25],
|
||||
"cubes": [
|
||||
{"origin": [-1, 5.75, -3.77], "size": [2, 1, 1], "uv": [39, 16]},
|
||||
{"origin": [-2, 5.25, -3.76], "size": [4, 1, 1], "uv": [29, 22]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open_cry",
|
||||
"parent": "mouths",
|
||||
"pivot": [0.4, 4.25, -1.75],
|
||||
"cubes": [
|
||||
{"origin": [-2, 4.75, -3.65], "size": [4, 2, 1], "inflate": 0.01, "uv": [29, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open_happy",
|
||||
"parent": "mouths",
|
||||
"pivot": [0.4, 4.25, -1.75],
|
||||
"cubes": [
|
||||
{"origin": [-2, 4.75, -3.65], "size": [4, 2, 1], "inflate": 0.01, "uv": [40, 19]},
|
||||
{"origin": [-1.5, 4.75, -3.65], "size": [3, 1, 1], "inflate": 0.02, "uv": [40, 22]},
|
||||
{"origin": [-0.5, 5.4, -3.65], "size": [1, 1, 1], "inflate": 0.03, "uv": [42, 7]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_main",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.25, 0.25],
|
||||
"pivot": [0, 10.75, -0.75]
|
||||
},
|
||||
{
|
||||
"name": "hair",
|
||||
"parent": "hair_main",
|
||||
"pivot": [0, 10.75, -0.75],
|
||||
"rotation": [12.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [0, 9.75, -0.75], "size": [0, 2, 1], "uv": [4, 0]}
|
||||
{"origin": [-0.5, 10.25, -1.75], "size": [1, 2, 1], "uv": [25, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair2",
|
||||
"parent": "hair",
|
||||
"pivot": [0, 11.75, 0.25],
|
||||
"rotation": [22.5, 0, 0],
|
||||
"pivot": [1, 12.25, -0.75],
|
||||
"rotation": [35, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [0, 11.75, -1.75], "size": [0, 3, 2], "inflate": 0.01, "uv": [0, 10]}
|
||||
{"origin": [-1, 12.25, -2.25], "size": [2, 3, 2], "inflate": 0.01, "uv": [30, 13]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"parent": "head",
|
||||
"pivot": [0, 4.25, -0.25],
|
||||
"name": "hair3",
|
||||
"parent": "hair_main",
|
||||
"pivot": [0, 11.25, -0.75],
|
||||
"rotation": [-2.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 4.75, -3.2575], "size": [5, 2, 0], "inflate": 0.001, "uv": [26, 5]}
|
||||
{"origin": [-2.5, 11.25, -3.75], "size": [5, 0, 3], "uv": [34, 25]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open",
|
||||
"parent": "head",
|
||||
"pivot": [-0.1, 4.25, -1.25],
|
||||
"name": "hair4",
|
||||
"parent": "hair3",
|
||||
"pivot": [0, 11.25, -3.75],
|
||||
"rotation": [-7.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 4.75, -3.1575], "size": [3, 2, 0], "uv": [25, 12]}
|
||||
{"origin": [-3, 9.25, -3.75], "size": [6, 2, 0], "uv": [36, 29]},
|
||||
{"origin": [-0.5, 9.25, -3.75], "size": [1, 2, 0], "inflate": 0.01, "uv": [43, 10]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_left",
|
||||
"name": "arms",
|
||||
"parent": "torso",
|
||||
"pivot": [2, 3.25, 0]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "arm_hammer_left",
|
||||
"pivot": [6.75, 3, -0.5]
|
||||
},
|
||||
{
|
||||
"name": "hammer2",
|
||||
"parent": "hammer",
|
||||
"pivot": [6.75, 7.41875, -0.525],
|
||||
"cubes": [
|
||||
{"origin": [5.25, 5.25, -2], "size": [3, 6, 3], "uv": [16, 12]},
|
||||
{"origin": [6.25, 1.75, -1], "size": [1, 4, 1], "uv": [0, 0]},
|
||||
{"origin": [5.75, 7, -5.65], "size": [2, 0, 2], "pivot": [6.5, 6.4375, -2.575], "rotation": [-90, 0, 0], "uv": [22, 22]},
|
||||
{"origin": [6.25, 6.925, -5.8], "size": [1, 0, 1], "pivot": [6.5, 6.4375, -2.575], "rotation": [-90, 0, 0], "uv": [23, 21]}
|
||||
]
|
||||
"pivot": [0, 3.25, 0]
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
"parent": "torso",
|
||||
"pivot": [2, 3.25, 0],
|
||||
"parent": "arms",
|
||||
"pivot": [1.5, 3.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [2, 2.25, -1.5], "size": [4, 2, 3], "uv": [0, 19]}
|
||||
{"origin": [1.25, 2.25, -1], "size": [2, 2, 2], "uv": [8, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"name": "hand_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 3.5, 0],
|
||||
"pivot": [3.25, 3.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [6, 2.5, -0.5], "size": [1, 1, 1], "uv": [26, 10]}
|
||||
{"origin": [3.25, 2.25, -1.5], "size": [3, 2, 3], "inflate": 0.25, "uv": [12, 14]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_secondary",
|
||||
"parent": "hand_left",
|
||||
"pivot": [6, 3.25, 0],
|
||||
"rotation": [0, -90, 0],
|
||||
"locators": {
|
||||
"hand_secondary": [6, 3.25, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fingers_left",
|
||||
"parent": "hand_left",
|
||||
"pivot": [6.5, 3.85, 0]
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"parent": "fingers_left",
|
||||
"pivot": [6.5, 3.85, 0],
|
||||
"cubes": [
|
||||
{"origin": [6.5, 2.85, -0.5], "size": [1, 1, 1], "inflate": 0.1, "uv": [20, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left2",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 3.5, -0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"parent": "fingers_left",
|
||||
"pivot": [6.5, 3.85, -0.6],
|
||||
"rotation": [0, 17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [6, 2.5, -1.5], "size": [1, 1, 1], "uv": [43, 2]}
|
||||
{"origin": [6.5, 2.85, -1.7], "size": [1, 1, 1], "inflate": 0.1, "uv": [16, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left3",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 3.5, 0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"parent": "fingers_left",
|
||||
"pivot": [6.5, 3.85, 0.6],
|
||||
"rotation": [0, -17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [6, 2.5, 0.5], "size": [1, 1, 1], "uv": [11, 19]}
|
||||
{"origin": [6.5, 2.85, 0.7], "size": [1, 1, 1], "inflate": 0.1, "uv": [24, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2, 3.25, 0],
|
||||
"parent": "arms",
|
||||
"pivot": [-1.5, 3.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [-6, 2.25, -1.5], "size": [4, 2, 3], "uv": [20, 0]}
|
||||
{"origin": [-3.25, 2.25, -1], "size": [2, 2, 2], "uv": [0, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"name": "hand_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 3.5, 0],
|
||||
"pivot": [-3.25, 3.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 2.5, -0.5], "size": [1, 1, 1], "uv": [28, 14]}
|
||||
{"origin": [-6.25, 2.25, -1.5], "size": [3, 2, 3], "inflate": 0.25, "uv": [0, 14]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_primary",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-6, 3.25, 0],
|
||||
"rotation": [0, 90, 0],
|
||||
"locators": {
|
||||
"hand_primary": [-6, 3.25, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-6, 2.25, 0],
|
||||
"locators": {
|
||||
"item": [-6, 2.25, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fingers_right",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-6.5, 3.85, 0]
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"parent": "fingers_right",
|
||||
"pivot": [-6.5, 3.85, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7.5, 2.85, -0.5], "size": [1, 1, 1], "inflate": 0.1, "uv": [20, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right2",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 3.5, -0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"parent": "fingers_right",
|
||||
"pivot": [-6.5, 3.85, -0.6],
|
||||
"rotation": [0, -17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 2.5, -1.5], "size": [1, 1, 1], "uv": [56, 9]}
|
||||
{"origin": [-7.5, 2.85, -1.7], "size": [1, 1, 1], "inflate": 0.1, "uv": [16, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right3",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 3.5, 0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"parent": "fingers_right",
|
||||
"pivot": [-6.5, 3.85, 0.6],
|
||||
"rotation": [0, 17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 2.5, 0.5], "size": [1, 1, 1], "uv": [52, 9]}
|
||||
{"origin": [-7.5, 2.85, 0.7], "size": [1, 1, 1], "inflate": 0.1, "uv": [24, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_left",
|
||||
"parent": "arms",
|
||||
"pivot": [1.5, 3.25, 0]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "arm_hammer_left",
|
||||
"pivot": [7.25, 3, -0.5]
|
||||
},
|
||||
{
|
||||
"name": "hammer2",
|
||||
"parent": "hammer",
|
||||
"pivot": [7.5, 7.41875, -0.525],
|
||||
"cubes": [
|
||||
{"origin": [7, 1.25, -1], "size": [1, 4, 1], "uv": [54, 12]},
|
||||
{"origin": [7, 1.25, -1], "size": [1, 4, 1], "inflate": 0.1, "uv": [59, 12]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "rattle",
|
||||
"parent": "hammer2",
|
||||
"pivot": [7.5, 9.75, -2],
|
||||
"cubes": [
|
||||
{"origin": [7, 9.25, -2.5], "size": [1, 1, 1], "inflate": 0.01, "pivot": [7.5, 9.75, -2], "rotation": [0, 0, 45], "uv": [60, 1]},
|
||||
{"origin": [6.5, 7.25, -2.4], "size": [2, 2, 1], "inflate": 0.11, "pivot": [7.5, 8.25, -1.9], "rotation": [0, 0, 45], "uv": [46, 0]},
|
||||
{"origin": [5.5, 4.75, -2.5], "size": [4, 7, 4], "uv": [48, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_physical",
|
||||
"parent": "rattle",
|
||||
"pivot": [7.5, 11.75, -0.5],
|
||||
"locators": {
|
||||
"physical": [7.5, 11.75, -0.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "legs",
|
||||
"parent": "body",
|
||||
"pivot": [0, 1.5, 0]
|
||||
},
|
||||
{
|
||||
"name": "leg_left",
|
||||
"parent": "body",
|
||||
"pivot": [0.75, 1.5, 0],
|
||||
"parent": "legs",
|
||||
"pivot": [1.25, 1.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 0, -2], "size": [2, 2, 4], "inflate": 0.01, "uv": [35, 0]}
|
||||
{"origin": [-0.25, -0.25, -1.5], "size": [3, 3, 3], "inflate": -0.25, "uv": [35, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_foot_secondary",
|
||||
"parent": "leg_left",
|
||||
"pivot": [1.4, 0.5, 0.1],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"foot_secondary": [1.4, 0.5, 0.1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
"pivot": [-0.75, 1.5, 0],
|
||||
"parent": "legs",
|
||||
"pivot": [-1.25, 1.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 0, -2], "size": [2, 2, 4], "inflate": 0.01, "uv": [52, 3]}
|
||||
{"origin": [-2.75, -0.25, -1.5], "size": [3, 3, 3], "inflate": -0.25, "uv": [23, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_foot_primary",
|
||||
"parent": "leg_right",
|
||||
"pivot": [-1.4, 0.5, 0.1],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"foot_primary": [-1.4, 0.5, 0.1]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
-233
@@ -1,233 +0,0 @@
|
||||
{
|
||||
"format_version": "1.12.0",
|
||||
"minecraft:geometry": [
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.tinkatink_dot",
|
||||
"texture_width": 64,
|
||||
"texture_height": 32,
|
||||
"visible_bounds_width": 2,
|
||||
"visible_bounds_height": 2.5,
|
||||
"visible_bounds_offset": [0, 0.75, 0]
|
||||
},
|
||||
"bones": [
|
||||
{
|
||||
"name": "tinkatink",
|
||||
"pivot": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"parent": "tinkatink",
|
||||
"pivot": [0, 3.75, 0]
|
||||
},
|
||||
{
|
||||
"name": "torso",
|
||||
"parent": "body",
|
||||
"pivot": [0, 1.75, -0.25],
|
||||
"cubes": [
|
||||
{"origin": [-2, 1.5, -2], "size": [4, 3, 4], "uv": [0, 12]},
|
||||
{"origin": [-1, 2, -2.75], "size": [2, 2, 1], "pivot": [0, 3, -2.25], "rotation": [0, 0, 45], "uv": [12, 12]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 4.25, -0.25],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 4.25, -3.25], "size": [7, 6, 6], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair3",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.25, -0.25],
|
||||
"rotation": [-5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 10.25, -3.25], "size": [5, 0, 3], "uv": [11, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair4",
|
||||
"parent": "hair3",
|
||||
"pivot": [0, 10.25, -3.25],
|
||||
"rotation": [-15, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 8.25, -3.25], "size": [5, 2, 0], "uv": [43, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "head",
|
||||
"pivot": [-2.75, 6.5, -2.76],
|
||||
"rotation": [0, 0, -5],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 6, -3.26], "size": [1, 2, 1], "uv": [51, 2]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-0.5, 3.5, -0.15],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 6, -3.16], "size": [1, 2, 1], "inflate": 0.01, "uv": [47, 2]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "head",
|
||||
"pivot": [2.75, 6.5, -2.76],
|
||||
"rotation": [0, 0, 5],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 6, -3.26], "size": [1, 2, 1], "uv": [26, 7]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [0.5, 3.5, -0.15],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 6, -3.16], "size": [1, 2, 1], "inflate": 0.01, "uv": [32, 8]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.25, 0.25],
|
||||
"rotation": [12.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [0, 9.75, -0.75], "size": [0, 2, 1], "uv": [4, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair2",
|
||||
"parent": "hair",
|
||||
"pivot": [0, 11.75, 0.25],
|
||||
"rotation": [22.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [0, 11.75, -1.75], "size": [0, 3, 2], "inflate": 0.01, "uv": [0, 10]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"parent": "head",
|
||||
"pivot": [0, 4.25, -0.25],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 4.75, -3.2575], "size": [5, 2, 0], "inflate": 0.001, "uv": [26, 5]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open",
|
||||
"parent": "head",
|
||||
"pivot": [-0.1, 4.25, -1.25],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 4.75, -3.1575], "size": [3, 2, 0], "uv": [25, 12]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_left",
|
||||
"parent": "torso",
|
||||
"pivot": [2, 3.25, 0]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "arm_hammer_left",
|
||||
"pivot": [6.75, 3, -0.5]
|
||||
},
|
||||
{
|
||||
"name": "hammer2",
|
||||
"parent": "hammer",
|
||||
"pivot": [6.75, 7.41875, -0.525],
|
||||
"cubes": [
|
||||
{"origin": [5.25, 5.25, -2], "size": [3, 6, 3], "uv": [16, 12]},
|
||||
{"origin": [6.25, 1.75, -1], "size": [1, 4, 1], "uv": [0, 0]},
|
||||
{"origin": [5.75, 8.2625, -3.0125], "size": [2, 0, 2], "pivot": [6.75, 8.2625, -2.0125], "rotation": [-90, 0, 45], "uv": [-2, 24]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
"parent": "torso",
|
||||
"pivot": [2, 3.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [2, 2.25, -1.5], "size": [4, 2, 3], "uv": [0, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 3.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [6, 2.5, -0.5], "size": [1, 1, 1], "uv": [26, 10]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left2",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 3.5, -0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [6, 2.5, -1.5], "size": [1, 1, 1], "uv": [43, 2]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left3",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 3.5, 0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [6, 2.5, 0.5], "size": [1, 1, 1], "uv": [11, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2, 3.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [-6, 2.25, -1.5], "size": [4, 2, 3], "uv": [20, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 3.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 2.5, -0.5], "size": [1, 1, 1], "uv": [28, 14]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right2",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 3.5, -0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 2.5, -1.5], "size": [1, 1, 1], "uv": [56, 9]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right3",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 3.5, 0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 2.5, 0.5], "size": [1, 1, 1], "uv": [52, 9]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_left",
|
||||
"parent": "body",
|
||||
"pivot": [0.75, 1.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 0, -2], "size": [2, 2, 4], "inflate": 0.01, "uv": [35, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
"pivot": [-0.75, 1.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 0, -2], "size": [2, 2, 4], "inflate": 0.01, "uv": [52, 3]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+581
-190
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.tinkatuff",
|
||||
"texture_width": 64,
|
||||
"texture_width": 128,
|
||||
"texture_height": 64,
|
||||
"visible_bounds_width": 3,
|
||||
"visible_bounds_height": 2.5,
|
||||
@@ -15,289 +15,680 @@
|
||||
"name": "tinkatuff",
|
||||
"pivot": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "locator_root",
|
||||
"parent": "tinkatuff",
|
||||
"pivot": [0, 0, 0],
|
||||
"locators": {
|
||||
"root": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_top",
|
||||
"parent": "tinkatuff",
|
||||
"pivot": [0, 22, 0],
|
||||
"locators": {
|
||||
"top": [0, 22, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_special",
|
||||
"parent": "tinkatuff",
|
||||
"pivot": [0, 8, -8],
|
||||
"locators": {
|
||||
"special": [0, 8, -8]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"parent": "tinkatuff",
|
||||
"pivot": [0, 6, 0]
|
||||
"pivot": [0, 6.25, 0]
|
||||
},
|
||||
{
|
||||
"name": "locator_target",
|
||||
"parent": "body",
|
||||
"pivot": [0, 8, -6],
|
||||
"locators": {
|
||||
"target": [0, 8, -6]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_middle",
|
||||
"parent": "body",
|
||||
"pivot": [0, 9, 0],
|
||||
"locators": {
|
||||
"middle": [0, 9, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "torso",
|
||||
"parent": "body",
|
||||
"pivot": [0, 4.25, 0],
|
||||
"pivot": [0, 4.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 7.5, -1.5], "size": [4, 3, 3], "uv": [41, 41]},
|
||||
{"origin": [-2.5, 3, -2], "size": [5, 5, 4], "uv": [36, 0]}
|
||||
{"origin": [-2.5, 3.25, -2], "size": [5, 3, 4], "inflate": 0.25, "uv": [0, 14]},
|
||||
{"origin": [-2.5, 3.25, -2], "size": [5, 3, 4], "inflate": 0.5, "uv": [18, 14]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_left",
|
||||
"name": "chest",
|
||||
"parent": "torso",
|
||||
"pivot": [2.5, 3, 0],
|
||||
"rotation": [0, 0, 45],
|
||||
"pivot": [0, 6, 0],
|
||||
"cubes": [
|
||||
{"origin": [2.5, 3, -1.5], "size": [3, 0, 3], "uv": [6, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2.5, 3, 0],
|
||||
"rotation": [0, 0, -45],
|
||||
"cubes": [
|
||||
{"origin": [-5.5, 3, -1.5], "size": [3, 0, 3], "uv": [0, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
"parent": "torso",
|
||||
"pivot": [2, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [1, 7, -1], "size": [4, 2, 2], "uv": [35, 47]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left2",
|
||||
"parent": "arm_left",
|
||||
"pivot": [5, 9, 1],
|
||||
"cubes": [
|
||||
{"origin": [5, 7, -1.5], "size": [4, 2, 3], "inflate": 0.001, "uv": [44, 35]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 7.75, -0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7.75, -1.5], "size": [1, 1, 1], "uv": [33, 44]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left2",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 7.75, 0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7.75, 0.5], "size": [1, 1, 1], "uv": [40, 30]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left3",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7, -0.5], "size": [1, 1, 1], "uv": [36, 30]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2, 8, 0]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right2",
|
||||
"parent": "arm_hammer_right",
|
||||
"pivot": [-5, 9, 1]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "arm_hammer_right2",
|
||||
"pivot": [-9.75, 8, 0]
|
||||
},
|
||||
{
|
||||
"name": "hammer2",
|
||||
"parent": "hammer",
|
||||
"pivot": [-9.55, 7.9675, -9.0675],
|
||||
"cubes": [
|
||||
{"origin": [-11, 6.5, -20], "size": [3, 3, 6], "uv": [30, 9]},
|
||||
{"origin": [-10.5, 6.475, -18.25], "size": [2, 0, 2], "uv": [-1, 54]},
|
||||
{"origin": [-10, 6.4, -18.4], "size": [1, 0, 1], "uv": [-1, 54]},
|
||||
{"origin": [-11, 6.5, -12.5], "size": [3, 3, 3], "uv": [0, 47]},
|
||||
{"origin": [-10, 7.5, -14.5], "size": [1, 1, 18], "inflate": 0.001, "uv": [0, 0]},
|
||||
{"origin": [-15.5, 4.025, -18.525], "size": [12, 11, 0], "pivot": [-9.5, 9.525, -18.525], "rotation": [-90, -180, 0], "uv": [0, 36]},
|
||||
{"origin": [-15.5, 3.5, -30.5], "size": [0, 8, 5], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [34, 31]},
|
||||
{"origin": [-15.5, 3.5, -30.5], "size": [3, 8, 0], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [47, 47]},
|
||||
{"origin": [-7.5, 2.5, -30.5], "size": [4, 9, 0], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [12, 47]},
|
||||
{"origin": [-3.5, 2.525, -20.025], "size": [0, 9, 5], "pivot": [-3.5, 7.025, -17.525], "rotation": [90, 180, 0], "uv": [24, 31]},
|
||||
{"origin": [-13.5, 12.5, -29.5], "size": [8, 1, 0], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [0, 7]},
|
||||
{"origin": [-13.5, 7.525, -26.025], "size": [8, 0, 4], "pivot": [-9.5, 7.525, -24.025], "rotation": [90, 180, 0], "uv": [17, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [-5, 7, -1], "size": [4, 2, 2], "uv": [0, 32]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right2",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-5, 9, 1],
|
||||
"cubes": [
|
||||
{"origin": [-9, 7, -1.5], "size": [4, 2, 3], "inflate": 0.001, "uv": [44, 30]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 7.75, -0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7.75, -1.5], "size": [1, 1, 1], "uv": [36, 2]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right2",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 7.75, 0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7.75, 0.5], "size": [1, 1, 1], "uv": [36, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right3",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7, -0.5], "size": [1, 1, 1], "uv": [10, 32]}
|
||||
{"origin": [-2, 5.75, -2], "size": [4, 4, 4], "uv": [24, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 9, 0.5],
|
||||
"parent": "chest",
|
||||
"pivot": [0, 8.75, 0.5],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 9, -3.5], "size": [7, 6, 7], "uv": [0, 19]},
|
||||
{"origin": [-4, 17.30092, -3.63266], "size": [8, 0, 8], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [12, 0]},
|
||||
{"origin": [4, 10.30092, -3.63266], "size": [0, 7, 8], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [20, 0]},
|
||||
{"origin": [-4, 10.30092, 4.36734], "size": [8, 7, 0], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [0, 0]},
|
||||
{"origin": [-4, 11.30092, -3.63266], "size": [8, 6, 0], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [42, 9]},
|
||||
{"origin": [-4, 10.30092, -3.63266], "size": [0, 7, 8], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [0, 0]}
|
||||
{"origin": [-4, 8.75, -4], "size": [8, 6, 8], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_mouth",
|
||||
"parent": "head",
|
||||
"pivot": [0, 9.75, -4],
|
||||
"locators": {
|
||||
"mouth": [0, 9.75, -4]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item_face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 12.25, -4],
|
||||
"locators": {
|
||||
"item_face": [0, 12.25, -4]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 11.75, -4],
|
||||
"locators": {
|
||||
"face": [0, 11.75, -4]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "face_ditto",
|
||||
"parent": "head",
|
||||
"pivot": [0, 11, -3.9]
|
||||
},
|
||||
{
|
||||
"name": "eyes_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 11.75, -3.92],
|
||||
"cubes": [
|
||||
{"origin": [-3, 11.25, -3.92], "size": [6, 1, 0], "uv": [22, 49]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyes_blink_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 11.75, -3.82],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 11.25, -3.82], "size": [7, 1, 0], "uv": [21, 51]},
|
||||
{"origin": [-3, 11.75, -3.83], "size": [6, 1, 0], "uv": [22, 53]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 10, -3.9],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 9.25, -3.91], "size": [5, 1, 0], "uv": [23, 57]},
|
||||
{"origin": [-3.5, 9.75, -3.91], "size": [7, 1, 0], "uv": [21, 55]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open_ditto",
|
||||
"parent": "face_ditto",
|
||||
"pivot": [0, 9.75, -3.81],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 8.75, -3.81], "size": [7, 2, 0], "uv": [21, 59]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 11.75, -4.01]
|
||||
},
|
||||
{
|
||||
"name": "eyes",
|
||||
"parent": "face",
|
||||
"pivot": [0, 12, -3.51]
|
||||
},
|
||||
{
|
||||
"name": "brow_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.25, 12.75, -3.5],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 12.25, -4], "size": [1, 1, 1], "inflate": 0.01, "uv": [16, 52]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "brow_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.25, 12.75, -3.5],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 12.25, -4], "size": [1, 1, 1], "inflate": 0.01, "uv": [11, 52], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "head",
|
||||
"pivot": [2.25, 12.25, -3.01],
|
||||
"rotation": [0, 0, -7.5],
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 12, -3.51],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 11.25, -3.51], "size": [1, 2, 1], "uv": [21, 23]}
|
||||
{"origin": [2, 11, -4], "size": [1, 1, 1], "inflate": 0.01, "uv": [6, 52]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_left",
|
||||
"name": "locator_eye_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.25, 12.25, -2.91],
|
||||
"pivot": [2.5, 12, -4],
|
||||
"locators": {
|
||||
"eye2": [2.5, 12, -4]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.5, 12, -4],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 11.25, -3.41], "size": [1, 2, 1], "inflate": 0.01, "uv": [26, 15]}
|
||||
{"origin": [2, 12, -4], "size": [1, 1, 1], "inflate": 0.025, "uv": [6, 49]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_left2",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.5, 12, -4],
|
||||
"cubes": [
|
||||
{"origin": [2, 12, -4], "size": [1, 1, 1], "inflate": 0.02, "uv": [16, 49]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "head",
|
||||
"pivot": [-2.25, 12.25, -3.01],
|
||||
"rotation": [0, 0, 7.5],
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 12, -3.51],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 11.25, -3.51], "size": [1, 2, 1], "uv": [0, 21]}
|
||||
{"origin": [-3, 11, -4], "size": [1, 1, 1], "inflate": 0.01, "uv": [1, 52], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_eye_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 12, -4],
|
||||
"locators": {
|
||||
"eye1": [-2.5, 12, -4]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 12, -4],
|
||||
"cubes": [
|
||||
{"origin": [-3, 12, -4], "size": [1, 1, 1], "inflate": 0.025, "uv": [1, 49], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_emote_right2",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 12, -4],
|
||||
"cubes": [
|
||||
{"origin": [-3, 12, -4], "size": [1, 1, 1], "inflate": 0.02, "uv": [11, 49], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 12, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [1.5, 11, -3.9], "size": [2, 2, 1], "inflate": 0.03, "uv": [8, 45]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.25, 12.25, -2.91],
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 12, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 11.25, -3.41], "size": [1, 2, 1], "inflate": 0.01, "uv": [3, 23]}
|
||||
{"origin": [-3.5, 11, -3.9], "size": [2, 2, 1], "inflate": 0.03, "uv": [1, 45], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_hurt_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 12, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [1.5, 11, -3.9], "size": [2, 2, 1], "inflate": 0.03, "uv": [22, 45]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_hurt_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 12, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 11, -3.9], "size": [2, 2, 1], "inflate": 0.03, "uv": [15, 45], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_happy_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.5, 12, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [1, 11, -3.91], "size": [3, 2, 1], "uv": [38, 45]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_happy_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.5, 12, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [-4, 11, -3.91], "size": [3, 2, 1], "uv": [29, 45], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouths",
|
||||
"parent": "face",
|
||||
"pivot": [0, 10.25, -3.91],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 9.75, -4], "size": [3, 1, 1], "inflate": 0.01, "uv": [1, 55]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_top",
|
||||
"parent": "mouths",
|
||||
"pivot": [0, 11.25, -4.01],
|
||||
"cubes": [
|
||||
{"origin": [-0.5, 10.27, -4], "size": [1, 1, 1], "inflate": 0.03, "uv": [10, 55]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_bottom",
|
||||
"parent": "mouths",
|
||||
"pivot": [0, 10.25, -4.01],
|
||||
"cubes": [
|
||||
{"origin": [-0.5, 9.23, -4], "size": [1, 1, 1], "inflate": 0.03, "uv": [10, 59]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.5, -3.41],
|
||||
"parent": "mouths",
|
||||
"pivot": [0, 10.25, -3.91],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 9.5, -3.41], "size": [3, 2, 0], "uv": [20, 15]}
|
||||
{"origin": [-1.5, 9.25, -3.9], "size": [3, 2, 1], "inflate": 0.02, "uv": [1, 58]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"name": "hair",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.5, -3.51],
|
||||
"pivot": [0, 17.25, 0.7],
|
||||
"rotation": [-10, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 9.5, -3.51], "size": [3, 2, 0], "uv": [0, 19]}
|
||||
{"origin": [4.5, 10.25, -3.8], "size": [0, 3, 9], "pivot": [4.5, 13.25, 0.7], "rotation": [0, 0, 9], "uv": [49, 47]},
|
||||
{"origin": [-4.5, 10.25, -3.8], "size": [0, 3, 9], "pivot": [-4.5, 13.25, 0.7], "rotation": [0, 0, -9], "uv": [49, 51], "mirror": true},
|
||||
{"origin": [-4.5, 13.25, -3.8], "size": [9, 4, 9], "uv": [33, 0]},
|
||||
{"origin": [-2.5, 10.25, -3.8], "size": [5, 3, 0], "pivot": [0, 13.25, -3.8], "rotation": [13.5, 0, 0], "uv": [53, 48]},
|
||||
{"origin": [0.5, 10.25, 5.2], "size": [4, 3, 0], "pivot": [4.5, 13.25, 5.2], "rotation": [0, 0, 9], "uv": [49, 52]},
|
||||
{"origin": [-4.5, 10.25, 5.2], "size": [4, 3, 0], "pivot": [-4.5, 13.25, 5.2], "rotation": [0, 0, -9], "uv": [59, 52], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_head",
|
||||
"parent": "hair",
|
||||
"pivot": [0, 16.25, -3.8],
|
||||
"locators": {
|
||||
"head": [0, 16.25, -3.8]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item_hat",
|
||||
"parent": "hair",
|
||||
"pivot": [0, 17.25, 0.7],
|
||||
"locators": {
|
||||
"item_hat": [0, 17.25, 0.7]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hair2",
|
||||
"parent": "hair",
|
||||
"pivot": [4.5, 14.25, -0.8],
|
||||
"rotation": [22.5, -90, 0]
|
||||
},
|
||||
{
|
||||
"name": "hair3",
|
||||
"parent": "hair2",
|
||||
"pivot": [4.5, 14.25, -0.8],
|
||||
"cubes": [
|
||||
{"origin": [2.5, 14.25, -0.8], "size": [4, 7, 0], "uv": [40, 56]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ponytail",
|
||||
"parent": "head",
|
||||
"pivot": [0, 16.81524, 1.82582],
|
||||
"rotation": [2.5, 0, 0],
|
||||
"parent": "hair",
|
||||
"pivot": [0, 16.25, 1.2],
|
||||
"rotation": [-20, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 16.62133, -0.28751], "size": [4, 4, 8], "uv": [20, 24]}
|
||||
{"origin": [-4, 15.25, 0.2], "size": [8, 7, 7], "uv": [37, 13]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ponytail2",
|
||||
"parent": "ponytail",
|
||||
"pivot": [0, 16.62133, 3.71249],
|
||||
"pivot": [0, 18.25, 6.7],
|
||||
"rotation": [-22.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 9.62133, 3.71249], "size": [4, 7, 4], "inflate": -0.001, "uv": [36, 19]}
|
||||
{"origin": [-3, 15.75, 5.2], "size": [6, 5, 7], "uv": [39, 27]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ponytail3",
|
||||
"parent": "ponytail2",
|
||||
"pivot": [0, 11.62133, 7.71249],
|
||||
"pivot": [0, 17.75, 11.2],
|
||||
"rotation": [52.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 9.62133, 7.71249], "size": [4, 2, 3], "inflate": -0.002, "uv": [21, 45]}
|
||||
{"origin": [-1.5, 16.25, 10.7], "size": [3, 3, 5], "uv": [60, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair2",
|
||||
"parent": "head",
|
||||
"pivot": [3.25, 16.625, -1.925],
|
||||
"rotation": [-30, 0, 27.5]
|
||||
"name": "arms",
|
||||
"parent": "chest",
|
||||
"pivot": [0, 7.5, 0]
|
||||
},
|
||||
{
|
||||
"name": "hair3",
|
||||
"parent": "hair2",
|
||||
"pivot": [3.25, 16.625, -1.925],
|
||||
"name": "arm_left",
|
||||
"parent": "arms",
|
||||
"pivot": [2, 7.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [1.25, 16.625, -1.925], "size": [4, 6, 0], "uv": [48, 15]}
|
||||
{"origin": [1, 6.5, -1.5], "size": [4, 2, 3], "uv": [14, 28]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left2",
|
||||
"parent": "arm_left",
|
||||
"pivot": [5, 8.5, 1],
|
||||
"cubes": [
|
||||
{"origin": [5, 6, -2], "size": [4, 3, 4], "inflate": 0.001, "uv": [16, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_secondary",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [8.5, 7.45, 0],
|
||||
"rotation": [0, -90, 0],
|
||||
"locators": {
|
||||
"hand_secondary": [8.5, 7.45, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fingers_left",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 7.25, -0.5]
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"parent": "fingers_left",
|
||||
"pivot": [9, 7.75, -0.75],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7.75, -1.75], "size": [1, 1, 1], "inflate": 0.25, "uv": [0, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left2",
|
||||
"parent": "fingers_left",
|
||||
"pivot": [9, 7.75, 0.75],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7.75, 0.75], "size": [1, 1, 1], "inflate": 0.25, "uv": [12, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left3",
|
||||
"parent": "fingers_left",
|
||||
"pivot": [9, 7.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 6.25, -0.5], "size": [1, 1, 1], "inflate": 0.25, "uv": [16, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right",
|
||||
"parent": "arms",
|
||||
"pivot": [-2, 7.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-5, 6.5, -1.5], "size": [4, 2, 3], "uv": [0, 28], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right2",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-5, 8.5, 1.5],
|
||||
"cubes": [
|
||||
{"origin": [-9, 6, -2], "size": [4, 3, 4], "inflate": 0.001, "uv": [0, 21], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_primary",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-8.5, 7.45, 0],
|
||||
"rotation": [0, 90, 0],
|
||||
"locators": {
|
||||
"hand_primary": [-8.5, 7.45, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fingers_right",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 7.25, -0.5]
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"parent": "fingers_right",
|
||||
"pivot": [-9, 7.75, -0.75],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7.75, -1.75], "size": [1, 1, 1], "inflate": 0.25, "uv": [0, 21], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right2",
|
||||
"parent": "fingers_right",
|
||||
"pivot": [-9, 7.75, 0.75],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7.75, 0.75], "size": [1, 1, 1], "inflate": 0.25, "uv": [12, 21], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right3",
|
||||
"parent": "fingers_right",
|
||||
"pivot": [-9, 7.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 6.25, -0.5], "size": [1, 1, 1], "inflate": 0.25, "uv": [16, 21], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right",
|
||||
"parent": "arms",
|
||||
"pivot": [-2, 7.5, 0]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right2",
|
||||
"parent": "arm_hammer_right",
|
||||
"pivot": [-5, 8.5, 1.5]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "arm_hammer_right2",
|
||||
"pivot": [-10, 7.5, 0]
|
||||
},
|
||||
{
|
||||
"name": "hammer2",
|
||||
"parent": "hammer",
|
||||
"pivot": [-10.05, 7.5, -11.25],
|
||||
"cubes": [
|
||||
{"origin": [-11, 6.5, -11.85], "size": [2, 2, 16], "inflate": -0.1, "uv": [91, 18]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ball",
|
||||
"parent": "hammer2",
|
||||
"pivot": [-10, 7.5, -14.75],
|
||||
"cubes": [
|
||||
{"origin": [-11.5, 6, -14.75], "size": [3, 3, 3], "uv": [94, 27]},
|
||||
{"origin": [-11.5, 6, -14.75], "size": [3, 3, 3], "inflate": 0.1, "uv": [112, 27]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "rattle",
|
||||
"parent": "hammer2",
|
||||
"pivot": [-10, 7.5, -15.25],
|
||||
"cubes": [
|
||||
{"origin": [-11.5, 6, -22.75], "size": [3, 3, 6], "uv": [84, 37]},
|
||||
{"origin": [-10.5, 7, -16.75], "size": [1, 1, 2], "uv": [97, 23]},
|
||||
{"origin": [-10.5, 6, -21.25], "size": [1, 1, 1], "inflate": 0.02, "pivot": [-10, 6.5, -20.75], "rotation": [0, -45, 0], "uv": [97, 40]},
|
||||
{"origin": [-11, 6, -20.25], "size": [2, 1, 2], "inflate": 0.02, "pivot": [-10, 6.5, -19.25], "rotation": [0, -45, 0], "uv": [81, 38]},
|
||||
{"origin": [-11, 6, -20.75], "size": [2, 1, 2], "inflate": 0.01, "uv": [114, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_item",
|
||||
"parent": "rattle",
|
||||
"pivot": [-10, 5.3, -19],
|
||||
"locators": {
|
||||
"item": {
|
||||
"offset": [-10, 5.3, -19],
|
||||
"rotation": [0, 135, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "plates",
|
||||
"parent": "hammer2",
|
||||
"pivot": [-10, 9.025, -20.275]
|
||||
},
|
||||
{
|
||||
"name": "plate_top",
|
||||
"parent": "plates",
|
||||
"pivot": [-3.5, 9.525, -20.275],
|
||||
"cubes": [
|
||||
{"origin": [-4, 4.5, -20.26], "size": [6, 8, 0], "pivot": [-4, 10, -20.25], "rotation": [-90, -180, 0], "uv": [115, 46]},
|
||||
{"origin": [0, -1.5, -20.26], "size": [4, 8, 0], "pivot": [-2, 4, -20.25], "rotation": [-90, -180, 0], "uv": [117, 37]},
|
||||
{"origin": [8, 4.5, -20.26], "size": [0, 8, 6], "pivot": [2, 10, -20.25], "rotation": [-90, -180, 0], "uv": [115, 49]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "plate_middle",
|
||||
"parent": "plates",
|
||||
"pivot": [-6.5, 9.525, -21.775],
|
||||
"cubes": [
|
||||
{"origin": [-10, 4.5, -21.75], "size": [8, 9, 0], "pivot": [-8, 10, -21.75], "rotation": [-90, -180, 0], "uv": [85, 47]},
|
||||
{"origin": [-10, 4.5, -30.75], "size": [8, 0, 6], "pivot": [-8, 10, -30.75], "rotation": [-90, -180, 0], "uv": [79, 57]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "plate_bottom",
|
||||
"parent": "plates",
|
||||
"pivot": [-16.5, 10.525, -20.775],
|
||||
"cubes": [
|
||||
{"origin": [-21, 4.5, -20.74], "size": [5, 8, 0], "pivot": [-16, 10, -20.75], "rotation": [-90, 180, 0], "uv": [103, 46]},
|
||||
{"origin": [-24, -1.5, -20.74], "size": [4, 8, 0], "pivot": [-18, 4, -20.75], "rotation": [-90, 180, 0], "uv": [104, 37], "mirror": true},
|
||||
{"origin": [-28, 4.5, -20.74], "size": [0, 8, 6], "pivot": [-22, 10, -20.75], "rotation": [-90, 180, 0], "uv": [102, 49], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_physical",
|
||||
"parent": "plate_bottom",
|
||||
"pivot": [-16, 7, -19],
|
||||
"locators": {
|
||||
"physical": [-16, 7, -19]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bandage",
|
||||
"parent": "plates",
|
||||
"pivot": [-8, 10, -22.25],
|
||||
"cubes": [
|
||||
{"origin": [-10.75, 10.2, -23.25], "size": [2, 0, 6], "pivot": [-9.75, 13, -20.25], "rotation": [0, 45, 0], "uv": [74, 57]},
|
||||
{"origin": [-10.75, 10.1, -23.25], "size": [2, 0, 6], "pivot": [-9.75, 13, -20.25], "rotation": [0, -55, 0], "uv": [69, 57]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 3, 0]
|
||||
},
|
||||
{
|
||||
"name": "skirt_left",
|
||||
"parent": "skirt",
|
||||
"pivot": [2.7, 3, -0.5],
|
||||
"rotation": [0, 0, 30],
|
||||
"cubes": [
|
||||
{"origin": [2.7, 3, -2], "size": [3, 0, 3], "uv": [65, 60]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_right",
|
||||
"parent": "skirt",
|
||||
"pivot": [-2.7, 3, -0.5],
|
||||
"rotation": [0, 0, -30],
|
||||
"cubes": [
|
||||
{"origin": [-5.7, 3, -2], "size": [3, 0, 3], "uv": [65, 56], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "legs",
|
||||
"parent": "body",
|
||||
"pivot": [-0.2, 4.5, 0]
|
||||
},
|
||||
{
|
||||
"name": "leg_left",
|
||||
"parent": "body",
|
||||
"pivot": [1.25, 4, 0],
|
||||
"parent": "legs",
|
||||
"pivot": [1.55, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 2, -1], "size": [2, 2, 2], "uv": [28, 50]}
|
||||
{"origin": [0.05, 1.5, -1.5], "size": [3, 3, 3], "inflate": -0.25, "uv": [12, 33]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_left2",
|
||||
"parent": "leg_left",
|
||||
"pivot": [1.25, 2, -1],
|
||||
"pivot": [1.55, 1.7, -1.4],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 0, -1], "size": [2, 2, 2], "inflate": 0.2, "uv": [50, 0]}
|
||||
{"origin": [0.05, 0, -1.5], "size": [3, 2, 3], "uv": [12, 39]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_foot_secondary",
|
||||
"parent": "leg_left2",
|
||||
"pivot": [1.6, 0.5, 0],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"foot_secondary": [1.6, 0.5, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
"pivot": [-1.25, 4, 0],
|
||||
"parent": "legs",
|
||||
"pivot": [-1.55, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 2, -1], "size": [2, 2, 2], "uv": [20, 50]}
|
||||
{"origin": [-3.05, 1.5, -1.5], "size": [3, 3, 3], "inflate": -0.25, "uv": [0, 33], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_right2",
|
||||
"parent": "leg_right",
|
||||
"pivot": [-1.25, 2, -1],
|
||||
"pivot": [-1.55, 1.7, -1.4],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 0, -1], "size": [2, 2, 2], "inflate": 0.2, "uv": [12, 32]}
|
||||
{"origin": [-3.05, 0, -1.5], "size": [3, 2, 3], "uv": [0, 39], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_foot_primary",
|
||||
"parent": "leg_right2",
|
||||
"pivot": [-1.6, 0.5, 0],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"foot_primary": [-1.6, 0.5, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
-305
@@ -1,305 +0,0 @@
|
||||
{
|
||||
"format_version": "1.12.0",
|
||||
"minecraft:geometry": [
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.tinkatuff_dot",
|
||||
"texture_width": 64,
|
||||
"texture_height": 64,
|
||||
"visible_bounds_width": 3,
|
||||
"visible_bounds_height": 2.5,
|
||||
"visible_bounds_offset": [0, 0.75, 0]
|
||||
},
|
||||
"bones": [
|
||||
{
|
||||
"name": "tinkatuff",
|
||||
"pivot": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"parent": "tinkatuff",
|
||||
"pivot": [0, 6, 0]
|
||||
},
|
||||
{
|
||||
"name": "torso",
|
||||
"parent": "body",
|
||||
"pivot": [0, 4.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 7.5, -1.5], "size": [4, 3, 3], "uv": [41, 41]},
|
||||
{"origin": [-2.5, 3, -2], "size": [5, 5, 4], "uv": [36, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_left",
|
||||
"parent": "torso",
|
||||
"pivot": [2.5, 3, 0],
|
||||
"rotation": [0, 0, 45],
|
||||
"cubes": [
|
||||
{"origin": [2.5, 3, -1.5], "size": [3, 0, 3], "uv": [6, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2.5, 3, 0],
|
||||
"rotation": [0, 0, -45],
|
||||
"cubes": [
|
||||
{"origin": [-5.5, 3, -1.5], "size": [3, 0, 3], "uv": [0, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
"parent": "torso",
|
||||
"pivot": [2, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [1, 7, -1], "size": [4, 2, 2], "uv": [35, 47]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left2",
|
||||
"parent": "arm_left",
|
||||
"pivot": [5, 9, 1],
|
||||
"cubes": [
|
||||
{"origin": [5, 7, -1.5], "size": [4, 2, 3], "inflate": 0.001, "uv": [44, 35]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 7.75, -0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7.75, -1.5], "size": [1, 1, 1], "uv": [33, 44]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left2",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 7.75, 0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7.75, 0.5], "size": [1, 1, 1], "uv": [40, 30]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left3",
|
||||
"parent": "arm_left2",
|
||||
"pivot": [9, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [9, 7, -0.5], "size": [1, 1, 1], "uv": [36, 30]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2, 8, 0]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right2",
|
||||
"parent": "arm_hammer_right",
|
||||
"pivot": [-5, 9, 1]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "arm_hammer_right2",
|
||||
"pivot": [-9.75, 8, 0]
|
||||
},
|
||||
{
|
||||
"name": "hammer2",
|
||||
"parent": "hammer",
|
||||
"pivot": [-9.55, 7.9675, -9.0675],
|
||||
"cubes": [
|
||||
{"origin": [-11, 6.5, -20], "size": [3, 3, 6], "uv": [30, 9]},
|
||||
{"origin": [-10.5, 6.475, -18], "size": [2, 0, 2], "pivot": [-9.5, 6.475, -17], "rotation": [0, 45, 0], "uv": [-1, 54]},
|
||||
{"origin": [-11, 6.5, -12.5], "size": [3, 3, 3], "uv": [0, 47]},
|
||||
{"origin": [-10, 7.5, -14.5], "size": [1, 1, 18], "inflate": 0.001, "uv": [0, 0]},
|
||||
{"origin": [-15.5, 4.025, -18.525], "size": [12, 11, 0], "pivot": [-9.5, 9.525, -18.525], "rotation": [-90, -180, 0], "uv": [0, 36]},
|
||||
{"origin": [-15, 6.53, -18.04], "size": [7, 7, 0], "pivot": [-11, 9.53, -18.04], "rotation": [90, -45, -180], "uv": [42, 56]},
|
||||
{"origin": [-15.5, 3.5, -30.5], "size": [0, 8, 5], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [34, 31]},
|
||||
{"origin": [-15.5, 3.5, -30.5], "size": [3, 8, 0], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [47, 47]},
|
||||
{"origin": [-7.5, 2.5, -30.5], "size": [4, 9, 0], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [12, 47]},
|
||||
{"origin": [-3.5, 2.525, -20.025], "size": [0, 9, 5], "pivot": [-3.5, 7.025, -17.525], "rotation": [90, 180, 0], "uv": [24, 31]},
|
||||
{"origin": [-13.5, 12.5, -29.5], "size": [8, 1, 0], "pivot": [-9.5, 12.25, -22.775], "rotation": [90, 0, 0], "uv": [0, 7]},
|
||||
{"origin": [-13.5, 7.525, -26.025], "size": [8, 0, 4], "pivot": [-9.5, 7.525, -24.025], "rotation": [90, 180, 0], "uv": [17, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-2, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [-5, 7, -1], "size": [4, 2, 2], "uv": [0, 32]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right2",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-5, 9, 1],
|
||||
"cubes": [
|
||||
{"origin": [-9, 7, -1.5], "size": [4, 2, 3], "inflate": 0.001, "uv": [44, 30]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 7.75, -0.5],
|
||||
"rotation": [0, -12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7.75, -1.5], "size": [1, 1, 1], "uv": [36, 2]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right2",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 7.75, 0.5],
|
||||
"rotation": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7.75, 0.5], "size": [1, 1, 1], "uv": [36, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right3",
|
||||
"parent": "arm_right2",
|
||||
"pivot": [-9, 8, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10, 7, -0.5], "size": [1, 1, 1], "uv": [10, 32]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 9, 0.5],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 9, -3.5], "size": [7, 6, 7], "uv": [0, 19]},
|
||||
{"origin": [-4, 17.30092, -3.63266], "size": [8, 0, 8], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [12, 0]},
|
||||
{"origin": [4, 10.30092, -3.63266], "size": [0, 7, 8], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [20, 0]},
|
||||
{"origin": [-4, 10.30092, 4.36734], "size": [8, 7, 0], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [0, 0]},
|
||||
{"origin": [-4, 11.30092, -3.63266], "size": [8, 6, 0], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [42, 9]},
|
||||
{"origin": [-4, 10.30092, -3.63266], "size": [0, 7, 8], "pivot": [0, 13.80092, 0.36734], "rotation": [-7.5, 0, 0], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "head",
|
||||
"pivot": [2.25, 12.25, -3.01],
|
||||
"rotation": [0, 0, -7.5],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 11.25, -3.51], "size": [1, 2, 1], "uv": [21, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.25, 12.25, -2.91],
|
||||
"cubes": [
|
||||
{"origin": [1.75, 11.25, -3.41], "size": [1, 2, 1], "inflate": 0.01, "uv": [26, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "head",
|
||||
"pivot": [-2.25, 12.25, -3.01],
|
||||
"rotation": [0, 0, 7.5],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 11.25, -3.51], "size": [1, 2, 1], "uv": [0, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.25, 12.25, -2.91],
|
||||
"cubes": [
|
||||
{"origin": [-2.75, 11.25, -3.41], "size": [1, 2, 1], "inflate": 0.01, "uv": [3, 23]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.5, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 9.5, -3.41], "size": [3, 2, 0], "uv": [20, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.5, -3.51],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 9.5, -3.51], "size": [3, 2, 0], "uv": [0, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ponytail",
|
||||
"parent": "head",
|
||||
"pivot": [0, 16.81524, 1.82582],
|
||||
"rotation": [2.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 16.62133, -0.28751], "size": [4, 4, 8], "uv": [20, 24]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ponytail2",
|
||||
"parent": "ponytail",
|
||||
"pivot": [0, 16.62133, 3.71249],
|
||||
"cubes": [
|
||||
{"origin": [-2, 9.62133, 3.71249], "size": [4, 7, 4], "inflate": -0.001, "uv": [36, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ponytail3",
|
||||
"parent": "ponytail2",
|
||||
"pivot": [0, 11.62133, 7.71249],
|
||||
"cubes": [
|
||||
{"origin": [-2, 9.62133, 7.71249], "size": [4, 2, 3], "inflate": -0.002, "uv": [21, 45]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair2",
|
||||
"parent": "head",
|
||||
"pivot": [3.25, 16.625, -1.925],
|
||||
"rotation": [-30, 0, 27.5]
|
||||
},
|
||||
{
|
||||
"name": "hair3",
|
||||
"parent": "hair2",
|
||||
"pivot": [3.25, 16.625, -1.925],
|
||||
"cubes": [
|
||||
{"origin": [1.25, 16.625, -1.925], "size": [4, 6, 0], "uv": [48, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_left",
|
||||
"parent": "body",
|
||||
"pivot": [1.25, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 2, -1], "size": [2, 2, 2], "uv": [28, 50]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_left2",
|
||||
"parent": "leg_left",
|
||||
"pivot": [1.25, 2, -1],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 0, -1], "size": [2, 2, 2], "inflate": 0.2, "uv": [50, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
"pivot": [-1.25, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 2, -1], "size": [2, 2, 2], "uv": [20, 50]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_right2",
|
||||
"parent": "leg_right",
|
||||
"pivot": [-1.25, 2, -1],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 0, -1], "size": [2, 2, 2], "inflate": 0.2, "uv": [12, 32]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+939
-394
File diff suppressed because it is too large
Load Diff
-650
@@ -1,650 +0,0 @@
|
||||
{
|
||||
"format_version": "1.12.0",
|
||||
"minecraft:geometry": [
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.tinkaton_dot",
|
||||
"texture_width": 128,
|
||||
"texture_height": 128,
|
||||
"visible_bounds_width": 5,
|
||||
"visible_bounds_height": 4.5,
|
||||
"visible_bounds_offset": [0, 1.75, 0]
|
||||
},
|
||||
"bones": [
|
||||
{
|
||||
"name": "bb_main",
|
||||
"pivot": [0, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 0, 0], "size": [2, 2, 2], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tinkaton",
|
||||
"pivot": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "body_hammer",
|
||||
"parent": "tinkaton",
|
||||
"pivot": [0, 7, 0]
|
||||
},
|
||||
{
|
||||
"name": "torso_hammer",
|
||||
"parent": "body_hammer",
|
||||
"pivot": [0, 4, 0]
|
||||
},
|
||||
{
|
||||
"name": "arms_hammer",
|
||||
"parent": "torso_hammer",
|
||||
"pivot": [0, 7, 0]
|
||||
},
|
||||
{
|
||||
"name": "arm_hammer_right",
|
||||
"parent": "arms_hammer",
|
||||
"pivot": [-1.5, 6.5, 0]
|
||||
},
|
||||
{
|
||||
"name": "hand_hammer_right",
|
||||
"parent": "arm_hammer_right",
|
||||
"pivot": [-4.5, 6.5, 1.5]
|
||||
},
|
||||
{
|
||||
"name": "hammer",
|
||||
"parent": "hand_hammer_right",
|
||||
"pivot": [-8, 5.125, -0.5]
|
||||
},
|
||||
{
|
||||
"name": "hammer_handle",
|
||||
"parent": "hammer",
|
||||
"pivot": [-8, 5.375, -15.75],
|
||||
"cubes": [
|
||||
{"origin": [-8, 1.25, -23], "size": [0, 4, 4], "uv": [64, 48]},
|
||||
{"origin": [-9.5, 5.25, -23], "size": [3, 4, 5], "inflate": -0.001, "uv": [87, 0]},
|
||||
{"origin": [-9.5, 3.25, -19], "size": [3, 5, 4], "uv": [88, 34]},
|
||||
{"origin": [-9, 4.25, -15], "size": [2, 2, 21], "uv": [0, 27]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hammer_middle",
|
||||
"parent": "hammer_handle",
|
||||
"pivot": [-8.8, 5.25, -29],
|
||||
"cubes": [
|
||||
{"origin": [-14, 12.252, -35], "size": [12, 0, 12], "inflate": 0.001, "uv": [37, 27]},
|
||||
{"origin": [-14, -0.752, -35], "size": [12, 0, 12], "inflate": 0.001, "uv": [13, 27]},
|
||||
{"origin": [-14, -0.75, -35], "size": [12, 13, 0], "inflate": 0.001, "uv": [80, 60]},
|
||||
{"origin": [-14, -0.75, -35], "size": [0, 13, 12], "inflate": 0.001, "uv": [24, 50]},
|
||||
{"origin": [-14, -0.75, -23], "size": [12, 13, 0], "inflate": 0.001, "uv": [20, 75]},
|
||||
{"origin": [-2, -0.75, -35], "size": [0, 13, 12], "inflate": 0.001, "uv": [0, 38]},
|
||||
{"origin": [-8, -2.75, -46], "size": [0, 12, 11], "uv": [48, 51]},
|
||||
{"origin": [-9.3375, 5.7875, -30.025], "size": [2, 0, 2], "pivot": [-8.3375, 5.7875, -29.025], "rotation": [0, -45, -90], "uv": [93, 14]},
|
||||
{"origin": [-13, -0.75, -24], "size": [10, 13, 0], "uv": [68, 83]},
|
||||
{"origin": [-13, -0.75, -34], "size": [0, 13, 10], "uv": [0, 53]},
|
||||
{"origin": [-3, -0.75, -34], "size": [0, 13, 10], "uv": [0, 17]},
|
||||
{"origin": [-13, -0.75, -34], "size": [10, 13, 0], "uv": [0, 76]},
|
||||
{"origin": [-13, -0.75, -34], "size": [10, 0, 10], "uv": [47, 0]},
|
||||
{"origin": [-13, 12.25, -34], "size": [10, 0, 10], "uv": [36, 39]},
|
||||
{"origin": [-10.5, 0.75, -28], "size": [1, 8, 1], "pivot": [-10, 5.75, -27.5], "rotation": [90, 0, 0], "uv": [20, 63]},
|
||||
{"origin": [-11.5, 2.75, -30.5], "size": [3, 6, 3], "pivot": [-10, 5.75, -29], "rotation": [90, 0, -90], "uv": [57, 10]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hammer_front",
|
||||
"parent": "hammer_middle",
|
||||
"pivot": [-7.4, 12.3, -28.5],
|
||||
"cubes": [
|
||||
{"origin": [-12, 12.25, -33], "size": [8, 3, 8], "uv": [64, 41]},
|
||||
{"origin": [-17.5, 15.25, -38.25], "size": [19, 8, 19], "uv": [0, 0]},
|
||||
{"origin": [-17.5, 15.25, -40.25], "size": [0, 8, 2], "uv": [16, 38]},
|
||||
{"origin": [-17.5, 15.25, -40.25], "size": [5, 0, 2], "uv": [0, 46]},
|
||||
{"origin": [-17.5, 23.25, -40.25], "size": [5, 0, 2], "uv": [0, 17]},
|
||||
{"origin": [-17.5, 15.25, -40.25], "size": [15, 8, 0], "uv": [73, 52]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nail",
|
||||
"parent": "hammer_front",
|
||||
"pivot": [-5.5, 20.25, -38.25],
|
||||
"rotation": [10, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-6.5, 19.25, -42.25], "size": [2, 2, 5], "uv": [88, 73]},
|
||||
{"origin": [-8, 17.75, -43.25], "size": [5, 5, 1], "uv": [24, 56]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hammer",
|
||||
"parent": "hammer_front",
|
||||
"pivot": [-8, 23, -28.75],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"hammer": [-8, 23, -28.75],
|
||||
"physical": [-8, 23, -28.75],
|
||||
"special2": [-8, 23, -28.75]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hammer_back",
|
||||
"parent": "hammer_middle",
|
||||
"pivot": [-8, -1, -29],
|
||||
"cubes": [
|
||||
{"origin": [-11, -3.75, -32], "size": [6, 3, 6], "uv": [44, 77]},
|
||||
{"origin": [-12.5, -7.75, -33.5], "size": [9, 4, 9], "uv": [37, 49]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seat_1_locator",
|
||||
"parent": "hammer_middle",
|
||||
"pivot": [-8, 0, -35],
|
||||
"rotation": [105, 0, 0],
|
||||
"locators": {
|
||||
"seat_1": [-8, 0, -35]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"parent": "tinkaton",
|
||||
"pivot": [0, 7, 0]
|
||||
},
|
||||
{
|
||||
"name": "torso",
|
||||
"parent": "body",
|
||||
"pivot": [0, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 6.5, -1.5], "size": [4, 3, 3], "inflate": 0.001, "uv": [88, 43]},
|
||||
{"origin": [-2.5, 3, -2], "size": [5, 4, 4], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_front_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-1.75, 3, -1.25],
|
||||
"rotation": [30, 45, 0],
|
||||
"cubes": [
|
||||
{"origin": [-3.25, 3, -4.25], "size": [3, 0, 3], "uv": [37, 50]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_front_left",
|
||||
"parent": "torso",
|
||||
"pivot": [1.75, 3, -1.25],
|
||||
"rotation": [30, -45, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 3, -4.25], "size": [3, 0, 3], "uv": [36, 45]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_back_left",
|
||||
"parent": "torso",
|
||||
"pivot": [1.75, 3, 1.25],
|
||||
"rotation": [-30, 45, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 3, 1.25], "size": [3, 0, 3], "uv": [36, 42]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "skirt_back_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-1.75, 3, 1.25],
|
||||
"rotation": [-30, -45, 0],
|
||||
"cubes": [
|
||||
{"origin": [-3.25, 3, 1.25], "size": [3, 0, 3], "uv": [36, 39]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 7.75, 0],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 7.75, -3.5], "size": [7, 7, 7], "uv": [73, 20]},
|
||||
{"origin": [-3.5, 9.25, -4], "size": [0, 6, 8], "inflate": 0.01, "uv": [24, 42]},
|
||||
{"origin": [3.5, 9.25, -4], "size": [0, 6, 8], "inflate": 0.01, "uv": [0, 32]},
|
||||
{"origin": [-3.5, 8.25, 4], "size": [7, 7, 0], "inflate": 0.01, "uv": [88, 80]},
|
||||
{"origin": [-3.5, 11.25, -4], "size": [7, 4, 0], "inflate": 0.01, "uv": [28, 88]},
|
||||
{"origin": [-3.5, 15.25, -4], "size": [7, 0, 8], "inflate": 0.01, "uv": [17, 39]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ear_left",
|
||||
"parent": "head",
|
||||
"pivot": [0, 14.75, 0],
|
||||
"rotation": [0, 0, 10],
|
||||
"cubes": [
|
||||
{"origin": [0, 14.75, -1], "size": [3, 6, 2], "uv": [0, 89]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ear_left2",
|
||||
"parent": "head",
|
||||
"pivot": [3, 14.75, 0],
|
||||
"rotation": [0, 0, 20],
|
||||
"cubes": [
|
||||
{"origin": [3, 13.75, 0], "size": [2, 3, 0], "uv": [44, 78]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ear_right",
|
||||
"parent": "head",
|
||||
"pivot": [0, 14.75, 0],
|
||||
"rotation": [0, 0, -10],
|
||||
"cubes": [
|
||||
{"origin": [-3, 14.75, -1], "size": [3, 6, 2], "uv": [88, 87]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ear_right2",
|
||||
"parent": "head",
|
||||
"pivot": [-3, 14.75, 0],
|
||||
"cubes": [
|
||||
{"origin": [-5, 13.75, 0], "size": [2, 3, 0], "pivot": [-3, 14.75, -0.5], "rotation": [0, 0, -20], "uv": [76, 22]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "head",
|
||||
"pivot": [2.5, 11.25, -3.025],
|
||||
"cubes": [
|
||||
{"origin": [2, 10.25, -3.525], "size": [1, 2, 1], "uv": [14, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.5, 11.25, -2.925],
|
||||
"cubes": [
|
||||
{"origin": [2, 10.25, -3.425], "size": [1, 2, 1], "inflate": 0.01, "uv": [76, 19]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_eye_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [-2.5, 11.25, -3.5],
|
||||
"locators": {
|
||||
"eye1": [-2.5, 11.25, -3.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "head",
|
||||
"pivot": [-2.5, 11.25, -3.025],
|
||||
"cubes": [
|
||||
{"origin": [-3, 10.25, -3.525], "size": [1, 2, 1], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyelid_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.5, 11.25, -2.925],
|
||||
"cubes": [
|
||||
{"origin": [-3, 10.25, -3.425], "size": [1, 2, 1], "inflate": 0.01, "uv": [20, 72]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_eye_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [2.5, 11.25, -3.5],
|
||||
"locators": {
|
||||
"eye2": [2.5, 11.25, -3.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mouths",
|
||||
"parent": "head",
|
||||
"pivot": [0, 9.25, -3.525]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open",
|
||||
"parent": "mouths",
|
||||
"pivot": [0, 9.25, -3.61],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 8.25, -3.41], "size": [5, 2, 0], "uv": [77, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_mouth",
|
||||
"parent": "mouth_open",
|
||||
"pivot": [0, 9.25, -3.5],
|
||||
"locators": {
|
||||
"mouth": [0, 9.25, -3.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"parent": "mouths",
|
||||
"pivot": [0, 9.25, -3.41],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 8.25, -3.51], "size": [7, 2, 0], "uv": [48, 74]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_right",
|
||||
"parent": "head",
|
||||
"pivot": [-3.5, 13.75, 0],
|
||||
"cubes": [
|
||||
{"origin": [-7, 9.75, -3], "size": [4, 5, 6], "uv": [14, 88]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_right2",
|
||||
"parent": "hair_right",
|
||||
"pivot": [-3, 9.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-8, 0.75, -4], "size": [5, 9, 8], "uv": [69, 2]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_right3",
|
||||
"parent": "hair_right2",
|
||||
"pivot": [-5.5, 0.75, 0],
|
||||
"cubes": [
|
||||
{"origin": [-5.5, -3.25, -4.5], "size": [0, 4, 9], "uv": [0, 4]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_left",
|
||||
"parent": "head",
|
||||
"pivot": [3.5, 14.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [3, 10.25, -3], "size": [4, 5, 6], "uv": [38, 86]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_left2",
|
||||
"parent": "hair_left",
|
||||
"pivot": [3, 10, 0],
|
||||
"cubes": [
|
||||
{"origin": [3, 1.25, -4], "size": [5, 9, 8], "uv": [62, 66]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hair_left3",
|
||||
"parent": "hair_left2",
|
||||
"pivot": [5.5, 1.25, 0],
|
||||
"cubes": [
|
||||
{"origin": [5.5, -2.75, -4.5], "size": [0, 4, 9], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_head",
|
||||
"parent": "head",
|
||||
"pivot": [0, 15.25, -2.5],
|
||||
"locators": {
|
||||
"head": [0, 15.25, -2.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item_hat",
|
||||
"parent": "head",
|
||||
"pivot": [0, 16.25, 0],
|
||||
"locators": {
|
||||
"item_hat": [0, 16.25, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_tera_hat",
|
||||
"parent": "head",
|
||||
"pivot": [0, 15.25, 0],
|
||||
"locators": {
|
||||
"tera_hat": [0, 15.25, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 10.25, -3.5],
|
||||
"locators": {
|
||||
"face": [0, 10.25, -3.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item_face",
|
||||
"parent": "head",
|
||||
"pivot": [0, 11.5, -3.5],
|
||||
"locators": {
|
||||
"item_face": [0, 11.5, -3.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "arms",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 7, 0]
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
"parent": "arms",
|
||||
"pivot": [1.5, 6.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [1.5, 5.5, -1.5], "size": [3, 2, 3], "uv": [94, 16]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hand_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [4.5, 6.5, 1.5],
|
||||
"cubes": [
|
||||
{"origin": [4.5, 5.5, -2], "size": [4, 2, 4], "inflate": 0.01, "uv": [52, 86]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_secondary",
|
||||
"parent": "hand_left",
|
||||
"pivot": [7.75, 6.5, 0],
|
||||
"rotation": [0, -90, 0],
|
||||
"locators": {
|
||||
"hand_secondary": [7.75, 6.5, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item",
|
||||
"parent": "hand_left",
|
||||
"pivot": [9.5, 5.75, 0],
|
||||
"locators": {
|
||||
"item": [9.5, 5.75, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "finger_left",
|
||||
"parent": "hand_left",
|
||||
"pivot": [8.25, 7, -1.5],
|
||||
"rotation": [0, 17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [8.25, 6, -2], "size": [2, 1, 1], "uv": [44, 76]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left2",
|
||||
"parent": "hand_left",
|
||||
"pivot": [8.5, 7, -0.5],
|
||||
"rotation": [0, 5, 0],
|
||||
"cubes": [
|
||||
{"origin": [8.5, 6, -1], "size": [2, 1, 1], "uv": [73, 60]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left3",
|
||||
"parent": "hand_left",
|
||||
"pivot": [8.5, 7, 0.5],
|
||||
"rotation": [0, -5, 0],
|
||||
"cubes": [
|
||||
{"origin": [8.5, 6, 0], "size": [2, 1, 1], "uv": [66, 47]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_left4",
|
||||
"parent": "hand_left",
|
||||
"pivot": [8.25, 7, 1.5],
|
||||
"rotation": [0, -17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [8.25, 6, 1], "size": [2, 1, 1], "uv": [66, 45]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "thumb_left",
|
||||
"parent": "hand_left",
|
||||
"pivot": [5, 5.5, -0.5],
|
||||
"rotation": [0, 0, 42.5],
|
||||
"cubes": [
|
||||
{"origin": [5, 5.5, -1.5], "size": [2, 1, 2], "inflate": 0.01, "uv": [94, 24]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_right",
|
||||
"parent": "arms",
|
||||
"pivot": [-1.5, 6.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-4.5, 5.5, -1.5], "size": [3, 2, 3], "uv": [58, 93]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hand_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-4.5, 6.5, 1.5],
|
||||
"cubes": [
|
||||
{"origin": [-8.5, 5.5, -2], "size": [4, 2, 4], "inflate": 0.01, "uv": [69, 35]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_primary",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-7.75, 6.5, 0],
|
||||
"rotation": [0, 90, 0],
|
||||
"locators": {
|
||||
"hand_primary": [-7.75, 6.5, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "finger_right",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-8.25, 7, -1.5],
|
||||
"rotation": [0, -17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10.25, 6, -2], "size": [2, 1, 1], "uv": [66, 43]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right2",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-8.5, 7, -0.5],
|
||||
"rotation": [0, -5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10.5, 6, -1], "size": [2, 1, 1], "uv": [66, 41]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right3",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-8.5, 7, 0.5],
|
||||
"rotation": [0, 5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10.5, 6, 0], "size": [2, 1, 1], "uv": [64, 56]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "finger_right4",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-8.25, 7, 1.5],
|
||||
"rotation": [0, 17.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-10.25, 6, 1], "size": [2, 1, 1], "uv": [12, 17]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "thumb_right",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-5, 5.5, -0.5],
|
||||
"rotation": [0, 0, -42.5],
|
||||
"cubes": [
|
||||
{"origin": [-7, 5.5, -1.5], "size": [2, 1, 2], "inflate": 0.01, "uv": [94, 21]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
"pivot": [-1.25, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 2, -1], "size": [2, 2, 2], "uv": [10, 89]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_right2",
|
||||
"parent": "leg_right",
|
||||
"pivot": [-1.25, 2, -1],
|
||||
"cubes": [
|
||||
{"origin": [-2.25, 0, -1], "size": [2, 2, 2], "inflate": 0.2, "uv": [81, 34]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_foot_primary",
|
||||
"parent": "leg_right2",
|
||||
"pivot": [-1.25, 0.5, 0],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"foot_primary": [-1.25, 0.5, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_left",
|
||||
"parent": "body",
|
||||
"pivot": [1.25, 4, 0],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 2, -1], "size": [2, 2, 2], "uv": [70, 62]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leg_left2",
|
||||
"parent": "leg_left",
|
||||
"pivot": [1.25, 2, -1],
|
||||
"cubes": [
|
||||
{"origin": [0.25, 0, -1], "size": [2, 2, 2], "inflate": 0.2, "uv": [38, 54]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_foot_secondary",
|
||||
"parent": "leg_left2",
|
||||
"pivot": [1.25, 0.5, 0],
|
||||
"rotation": [90, 0, 0],
|
||||
"locators": {
|
||||
"foot_secondary": [1.25, 0.5, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_middle",
|
||||
"parent": "body",
|
||||
"pivot": [0, 7, 0],
|
||||
"locators": {
|
||||
"middle": [0, 7, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_target",
|
||||
"parent": "body",
|
||||
"pivot": [0, 7, -6],
|
||||
"locators": {
|
||||
"target": [0, 7, -6]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_root",
|
||||
"parent": "tinkaton",
|
||||
"pivot": [0, 0, 0],
|
||||
"locators": {
|
||||
"root": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_special",
|
||||
"parent": "tinkaton",
|
||||
"pivot": [0, 7, -8],
|
||||
"locators": {
|
||||
"special": [0, 7, -8]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_top",
|
||||
"parent": "tinkaton",
|
||||
"pivot": [0, 23, 0],
|
||||
"locators": {
|
||||
"top": [0, 23, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
-2
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"walking": {
|
||||
"poseTypes": ["WALK", "SWIM"],
|
||||
"condition": "!q.in_air && q.has_entity ? !q.is_sprinting : 1.0",
|
||||
"condition": "!q.in_air && !q.is_sprinting",
|
||||
"animations": [
|
||||
"q.bedrock('heracross', 'ground_walk')",
|
||||
{
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
"running": {
|
||||
"poseTypes": ["WALK", "SWIM"],
|
||||
"condition": "!q.in_air && q.has_entity ? q.is_sprinting : 0.0",
|
||||
"condition": "!q.in_air && q.is_sprinting",
|
||||
"animations": [
|
||||
"q.bedrock('heracross', 'ground_run')",
|
||||
{
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@
|
||||
"profileTranslation": [0, 0.46, 0],
|
||||
"rootBone": "mudkip",
|
||||
"animations": {
|
||||
"cry": "q.bedrock_stateful('mudkip', 'cry')",
|
||||
"recoil": "q.bedrock_stateful('mudkip', 'recoil')"
|
||||
"cry": "q.bedrock_stateful('mudkip', 'cry')"
|
||||
},
|
||||
"poses": {
|
||||
"standing": {
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@
|
||||
"profileTranslation": [0, 0.65, 0],
|
||||
"rootBone": "marshtomp",
|
||||
"animations": {
|
||||
"cry": "q.bedrock_stateful('marshtomp', 'cry')",
|
||||
"recoil": "q.bedrock_stateful('marshtomp', 'recoil')"
|
||||
"cry": "q.bedrock_stateful('marshtomp', 'cry')"
|
||||
},
|
||||
"poses": {
|
||||
"standing": {
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@
|
||||
"profileTranslation": [0, 0.96, 0],
|
||||
"rootBone": "swampert",
|
||||
"animations": {
|
||||
"cry": "q.bedrock_stateful('swampert', 'cry')",
|
||||
"recoil": "q.bedrock_stateful('swampert', 'recoil')"
|
||||
"cry": "q.bedrock_stateful('swampert', 'cry')"
|
||||
},
|
||||
"poses": {
|
||||
"standing": {
|
||||
|
||||
+9
-27
@@ -1,16 +1,8 @@
|
||||
{
|
||||
"portraitScale": 2.48,
|
||||
"portraitTranslation": [
|
||||
-0.33,
|
||||
-0.93,
|
||||
0
|
||||
],
|
||||
"portraitTranslation": [-0.33, -0.93, 0],
|
||||
"profileScale": 0.7,
|
||||
"profileTranslation": [
|
||||
0.02,
|
||||
0.76,
|
||||
0
|
||||
],
|
||||
"profileTranslation": [0.02, 0.76, 0],
|
||||
"rootBone": "rufflet",
|
||||
"animations": {
|
||||
"cry": "q.bedrock_stateful('rufflet', 'cry')",
|
||||
@@ -18,13 +10,7 @@
|
||||
},
|
||||
"poses": {
|
||||
"standing": {
|
||||
"poseTypes": [
|
||||
"STAND",
|
||||
"NONE",
|
||||
"PORTRAIT",
|
||||
"PROFILE"
|
||||
],
|
||||
"isBattle": false,
|
||||
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('rufflet', 'ground_idle')"
|
||||
@@ -36,10 +22,7 @@
|
||||
]
|
||||
},
|
||||
"walking": {
|
||||
"poseTypes": [
|
||||
"WALK",
|
||||
"SWIM"
|
||||
],
|
||||
"poseTypes": ["WALK", "SWIM"],
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('rufflet', 'ground_walk')"
|
||||
@@ -51,9 +34,7 @@
|
||||
]
|
||||
},
|
||||
"hover": {
|
||||
"poseTypes": [
|
||||
"HOVER"
|
||||
],
|
||||
"poseTypes": ["HOVER"],
|
||||
"animations": [
|
||||
"q.pitch_tilt('body', 1)",
|
||||
"q.look('head')",
|
||||
@@ -65,9 +46,7 @@
|
||||
]
|
||||
},
|
||||
"fly": {
|
||||
"poseTypes": [
|
||||
"FLY"
|
||||
],
|
||||
"poseTypes": ["FLY"],
|
||||
"animations": [
|
||||
"q.pitch_tilt('body', 1)",
|
||||
"q.look('head')",
|
||||
@@ -81,6 +60,9 @@
|
||||
},
|
||||
"sleep": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"namedAnimations": {
|
||||
"cry": "q.bedrock_stateful('dummy', 'cry')"
|
||||
},
|
||||
"animations": ["q.bedrock('rufflet', 'sleep')"],
|
||||
"transformTicks": 10
|
||||
}
|
||||
|
||||
+3
-1
@@ -22,7 +22,6 @@
|
||||
"poses": {
|
||||
"standing": {
|
||||
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"isBattle": false,
|
||||
"animations": [
|
||||
"q.look('neck1', 1, 1, 5, -5, 10, -10)",
|
||||
"q.look('neck2', 1, 1, 35, -10, 15, -15)",
|
||||
@@ -96,6 +95,9 @@
|
||||
},
|
||||
"sleep": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"namedAnimations": {
|
||||
"cry": "q.bedrock_stateful('dummy', 'cry')"
|
||||
},
|
||||
"animations": ["q.bedrock('braviary', 'sleep')"],
|
||||
"transformTicks": 10
|
||||
}
|
||||
|
||||
+3
-1
@@ -22,7 +22,6 @@
|
||||
"poses": {
|
||||
"standing": {
|
||||
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"isBattle": false,
|
||||
"animations": [
|
||||
"q.look('neck', 1, 1, 3, -20, 10, -10)",
|
||||
"q.look('neck2', 1, 1, 1, -35, 15, -15)",
|
||||
@@ -96,6 +95,9 @@
|
||||
},
|
||||
"sleep": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"namedAnimations": {
|
||||
"cry": "q.bedrock_stateful('dummy', 'cry')"
|
||||
},
|
||||
"animations": ["q.bedrock('braviary_hisuian', 'sleep')"],
|
||||
"transformTicks": 10
|
||||
}
|
||||
|
||||
+31
-32
@@ -19,28 +19,9 @@
|
||||
"cry": "q.bedrock_stateful('mudsdale', 'cry')"
|
||||
},
|
||||
"poses": {
|
||||
"standing": {
|
||||
"poseName": "standing",
|
||||
"poseTypes": ["STAND", "FLOAT", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"condition": "q.has_entity ? !q.in_air : 1.0",
|
||||
"animations": [
|
||||
"q.look('neck', 0.5, 0.5, 15, -15, 30, -30)",
|
||||
"q.look('neck2', 0.5, 0.5, 10, -10, 25, -25)",
|
||||
"q.look('head', 0.4, 0.25, 5, -5, 10, -10)",
|
||||
"q.bedrock('mudsdale', 'ground_idle')",
|
||||
"q.bedrock_stateful('mudsdale', 'blink')"
|
||||
],
|
||||
"transformTicks": 10
|
||||
},
|
||||
"sleep": {
|
||||
"poseName": "sleep",
|
||||
"poseTypes": ["SLEEP"],
|
||||
"animations": ["q.bedrock('mudsdale', 'sleep')"]
|
||||
},
|
||||
"in-air": {
|
||||
"poseTypes": ["STAND", "WALK"],
|
||||
"condition": "q.in_air",
|
||||
"isBattle": false,
|
||||
"animations": [
|
||||
"q.look('neck', 0.5, 0.5, 15, -15, 30, -30)",
|
||||
"q.look('neck2', 0.5, 0.5, 10, -10, 25, -25)",
|
||||
@@ -52,26 +33,21 @@
|
||||
"transformTicks": 5,
|
||||
"transformToTicks": 5
|
||||
},
|
||||
"walk": {
|
||||
"poseName": "walk",
|
||||
"poseTypes": ["WALK", "SWIM"],
|
||||
"condition": "!q.is_sprinting",
|
||||
"standing": {
|
||||
"poseTypes": ["STAND", "FLOAT", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"condition": "!q.in_air",
|
||||
"animations": [
|
||||
"q.look('neck', 0.5, 0.5, 15, -15, 30, -30)",
|
||||
"q.look('neck2', 0.5, 0.5, 10, -10, 25, -25)",
|
||||
"q.look('head', 0.4, 0.25, 5, -5, 10, -10)",
|
||||
"q.bedrock('mudsdale', 'ground_walk')",
|
||||
"q.bedrock_stateful('mudsdale', 'blink')",
|
||||
{
|
||||
"condition": "q.is_ridden && q.has_entity && !q.in_air",
|
||||
"animation": "q.bedrock('mudsdale', 'ride_ground_walk')"
|
||||
}
|
||||
"q.bedrock('mudsdale', 'ground_idle')",
|
||||
"q.bedrock_stateful('mudsdale', 'blink')"
|
||||
],
|
||||
"transformTicks": 10
|
||||
"transformTicks": 5
|
||||
},
|
||||
"run": {
|
||||
"poseTypes": ["WALK", "SWIM"],
|
||||
"condition": "q.is_sprinting",
|
||||
"condition": "!q.in_air && q.is_sprinting",
|
||||
"animations": [
|
||||
"q.look('neck', 0.5, 0.5, 15, -15, 30, -30)",
|
||||
"q.look('neck2', 0.5, 0.5, 10, -10, 25, -25)",
|
||||
@@ -83,7 +59,30 @@
|
||||
"animation": "q.bedrock('mudsdale', 'ride_ground_run')"
|
||||
}
|
||||
],
|
||||
"transformTicks": 10
|
||||
"transformTicks": 5
|
||||
},
|
||||
"walk": {
|
||||
"poseTypes": ["WALK", "SWIM"],
|
||||
"condition": "!q.in_air && !q.is_sprinting",
|
||||
"animations": [
|
||||
"q.look('neck', 0.5, 0.5, 15, -15, 30, -30)",
|
||||
"q.look('neck2', 0.5, 0.5, 10, -10, 25, -25)",
|
||||
"q.look('head', 0.4, 0.25, 5, -5, 10, -10)",
|
||||
"q.bedrock('mudsdale', 'ground_walk')",
|
||||
"q.bedrock_stateful('mudsdale', 'blink')",
|
||||
{
|
||||
"condition": "q.is_ridden && q.has_entity && !q.in_air",
|
||||
"animation": "q.bedrock('mudsdale', 'ride_ground_walk')"
|
||||
}
|
||||
],
|
||||
"transformTicks": 5
|
||||
},
|
||||
"sleep": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"namedAnimations": {
|
||||
"cry": "q.bedrock_stateful('dummy', 'cry')"
|
||||
},
|
||||
"animations": ["q.bedrock('mudsdale', 'sleep')"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@
|
||||
"q.bedrock_quirk('ceruledge', 'blink')"
|
||||
],
|
||||
"transitions": {
|
||||
"battle-standing": "q.bedrock_primary('ceruledge', 'battle_intro')q.curve('symmetrical_wide'))"
|
||||
"battle-standing": "q.bedrock_primary('ceruledge', 'battle_intro', q.curve('one'))"
|
||||
}
|
||||
},
|
||||
"walking": {
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"portraitScale": 2,
|
||||
"portraitTranslation": [0, -1.01, 0.0],
|
||||
"profileScale": 1,
|
||||
"profileTranslation": [0.0, 0.25, 0.0],
|
||||
"rootBone": "tinkatink",
|
||||
"animations": {
|
||||
"faint": "q.bedrock_primary('tinkatink', 'faint', q.curve('one'))",
|
||||
"cry": "q.bedrock_stateful('tinkatink', 'cry')"
|
||||
},
|
||||
"poses": {
|
||||
"battle-standing": {
|
||||
"poseTypes": ["STAND"],
|
||||
"isBattle": true,
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatink', 'battle_idle')"
|
||||
],
|
||||
"quirks": ["q.bedrock_quirk('tinkatink', 'blink')"]
|
||||
},
|
||||
"standing": {
|
||||
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"isBattle": false,
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatink', 'ground_idle')"
|
||||
],
|
||||
"quirks": ["q.bedrock_quirk('tinkatink', 'blink')"]
|
||||
},
|
||||
"walking": {
|
||||
"poseTypes": ["WALK"],
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatink', 'ground_walk')"
|
||||
],
|
||||
"quirks": ["q.bedrock_quirk('tinkatink', 'blink')"]
|
||||
},
|
||||
"sleeping": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"animations": ["q.bedrock('tinkatink', 'sleep')"],
|
||||
"namedAnimations": {
|
||||
"cry": "q.bedrock_stateful('dummy', 'cry')"
|
||||
}
|
||||
},
|
||||
"shoulder_left": {
|
||||
"poseTypes": ["SHOULDER_LEFT"],
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatink', 'shoulder_left')"
|
||||
],
|
||||
"quirks": [
|
||||
"q.bedrock_quirk('tinkatink', 'blink')"
|
||||
]
|
||||
},
|
||||
"shoulder_right": {
|
||||
"poseTypes": ["SHOULDER_RIGHT"],
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatink', 'shoulder_right')"
|
||||
],
|
||||
"quirks": [
|
||||
"q.bedrock_quirk('tinkatink', 'blink')"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"portraitScale": 1.9,
|
||||
"portraitTranslation": [0, -0.41, 0.0],
|
||||
"profileScale": 0.9,
|
||||
"profileTranslation": [0.0, 0.4, 0.0],
|
||||
"rootBone": "tinkatuff",
|
||||
"animations": {
|
||||
"faint": "q.bedrock_primary('tinkatuff', 'faint', q.curve('one'))",
|
||||
"cry": "q.bedrock_stateful('tinkatuff', 'cry')"
|
||||
},
|
||||
"poses": {
|
||||
"battle-standing": {
|
||||
"poseTypes": ["STAND"],
|
||||
"isBattle": true,
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatuff', 'battle_idle')"
|
||||
],
|
||||
"quirks": ["q.bedrock_quirk('tinkatuff', 'blink')"]
|
||||
},
|
||||
"standing": {
|
||||
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
|
||||
"isBattle": false,
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatuff', 'ground_idle')"
|
||||
],
|
||||
"quirks": ["q.bedrock_quirk('tinkatuff', 'blink')"]
|
||||
},
|
||||
"walking": {
|
||||
"poseTypes": ["WALK"],
|
||||
"animations": [
|
||||
"q.look('head')",
|
||||
"q.bedrock('tinkatuff', 'ground_walk')"
|
||||
],
|
||||
"quirks": ["q.bedrock_quirk('tinkatuff', 'blink')"]
|
||||
},
|
||||
"sleeping": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"animations": ["q.bedrock('tinkatuff', 'sleep')"],
|
||||
"namedAnimations": {
|
||||
"cry": "q.bedrock_stateful('dummy', 'cry')"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user