mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-25 00:26:26 -04:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bebdf1da5 | ||
|
|
97d3f8750b | ||
|
|
a50d174ce6 | ||
|
|
14c89872bc | ||
|
|
56687b1529 | ||
|
|
da73ba6668 | ||
|
|
c398def166 | ||
|
|
76bcc622c5 | ||
|
|
df673e7636 | ||
|
|
ab2d79463c | ||
|
|
4a5ee0902e | ||
|
|
773828f7ef |
@@ -45,3 +45,6 @@ common/.fabric
|
||||
# MacOS
|
||||
.DS_Store
|
||||
/common/config/*
|
||||
|
||||
# Temp Files
|
||||
/temp/
|
||||
@@ -157,9 +157,6 @@ build:
|
||||
paths:
|
||||
- neoforge/build/libs
|
||||
- fabric/build/libs
|
||||
exclude:
|
||||
- neoforge/build/libs/*-dev-shadow.jar
|
||||
- fabric/build/libs/*-dev-shadow.jar
|
||||
|
||||
publish:
|
||||
stage: deploy
|
||||
|
||||
+35
-120
@@ -1,145 +1,60 @@
|
||||
# Changelog
|
||||
## [1.8.2 (MONTH xth, 2026)](#1-8-2)
|
||||
## [1.8.1 (MONTH Xth, 2026)](#1-8-1)
|
||||
|
||||
### Additions
|
||||
- The following items can now be placed on the ground as blocks:
|
||||
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
|
||||
- Iron Ball, Metal Coat, Throat Spray, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
|
||||
- The following items can now be placed on various surfaces:
|
||||
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
|
||||
- The following items can now be placed as blocks and have additional functionality:
|
||||
- Ring Target, Metronome, and Eject Button.
|
||||
- The following items can now be worn by Players and Pokémon:
|
||||
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
|
||||
- Wearable items can now be equipped by a player by holding it and right-clicking (or rebound use/interact)
|
||||
- Wearable items that can be placed down are exempt from this behaviour.
|
||||
- The evolution tree can now be viewed on the Pokédex, showing a Pokémon's full family line and what's needed to evolve into each stage.
|
||||
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders and dice.
|
||||
|
||||
### New rideable Pokémon
|
||||
|
||||
### Pokémon Added
|
||||
#### Generation 4
|
||||
- Snover
|
||||
- Abomasnow
|
||||
- Burmy
|
||||
- Wormadam
|
||||
- Mothim
|
||||
|
||||
#### Generation 5
|
||||
- Tympole
|
||||
- Palpitoad
|
||||
- Seismitoad
|
||||
|
||||
#### Generation 6
|
||||
- Helioptile
|
||||
- Heliolisk
|
||||
|
||||
#### Generation 7
|
||||
- Grubbin
|
||||
- Charjabug
|
||||
- Vikavolt
|
||||
- Minior
|
||||
|
||||
#### Generation 8
|
||||
- Eternatus
|
||||
|
||||
#### Generation 9
|
||||
- Greavard
|
||||
- Houndstone
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Sudowoodo
|
||||
- Spheal
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Binacle
|
||||
- Barbaracle
|
||||
- Glimmet
|
||||
- Glimmora
|
||||
- Deerling
|
||||
- Sawsbuck (All forms)
|
||||
- Absol
|
||||
- Poliwag
|
||||
- Politoed
|
||||
- Ekans
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
- Shroomish
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Treecko (New Shoulder Mount!)
|
||||
- Bonsly (New Shoulder Mount!)
|
||||
- Sudowoodo
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Ekans
|
||||
- Arbok
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
|
||||
### Changes
|
||||
- Updated various recipes to display in Recipe Groups within the Recipe Book
|
||||
- Updated most recipes to display more logically/consistently within the Recipe Book's categories
|
||||
- Make ride rotations less jittery in multiplayer.
|
||||
- Changes tm shelf discs to use textures instead of tinting and overlays to line up with item display
|
||||
- Blank TM can now be inserted into the Disc Shelf
|
||||
- Recall animation now renders ball in hand with the player arm raised.
|
||||
- Made regional and Vivillon split-evo locations now mutually exclusive
|
||||
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
|
||||
- Exceptions being "forced" evolutions which will continue to stop after the first one
|
||||
|
||||
### Fixes
|
||||
- Correctly grants "They're Minerals!" advancement when planting a type gem
|
||||
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
|
||||
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
|
||||
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??)
|
||||
- Some other recipes without advancements have also been corrected
|
||||
- Corrected the TM Recipes for Charge Beam and Payday
|
||||
- Type Gem blocks now properly generate clusters off of the block.
|
||||
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport, and you are not viewing top or bottom edges
|
||||
- Random Starter option now properly handled in height calculation, so now it won't run off into other categories or be un-accessible due to scroll limits if it causes a new row
|
||||
- Mochi and Feathers no longer drop glass bottles on use
|
||||
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix issue
|
||||
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
|
||||
- Fixed Rockruff line not blinking
|
||||
- Fixed Flygon sometimes using its running animation instead of walking
|
||||
- Fixed Bramblin always sleeping outside of battle
|
||||
- Fixed Dragonair using its battle idle as a normal idle
|
||||
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
|
||||
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
|
||||
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
|
||||
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
|
||||
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
|
||||
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
|
||||
- Fixed NPCs getting their own TM progress file.
|
||||
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
|
||||
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
|
||||
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
|
||||
- Fixed form typo in Rockruff dusk spawn entry.
|
||||
- Fixed a potential world gen stall with the Type Gem clusters.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait
|
||||
- Fixed Ampharos' eyes not emitting particles
|
||||
- Fixed Alolan Raticate's cry not playing
|
||||
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
|
||||
- Fixed Blastoise having an unintentional passenger seat
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now flag the dependency for being outdated correctly.
|
||||
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
|
||||
- Fixed a potential world gen stall caused by Type Gem clusters.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
|
||||
- Fixed Ampharos' eyes not emitting particles.
|
||||
- Fixed Alolan Raticate's cry not playing.
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
|
||||
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
|
||||
- Removed unused, placeholder habitat pools.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to a having an expression mistakenly assigned as a recoil animation.
|
||||
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to a having an expression mistakenly assigned as a recoil animation.
|
||||
- Fixed Lycanroc's head movement
|
||||
- Added head movement to Archaludon
|
||||
- Fixed Duraludon line not having any sleep animations
|
||||
- Fixed Duraludon line not having their sleep animations
|
||||
- Fixed Diglett line not having their faint animations
|
||||
- Fixed Kingambit moving its legs while walking
|
||||
- Fixed Alolan Diglett's cry not playing
|
||||
- Fixed shiny alpha Snorlax using the non-shiny texture
|
||||
- Various Structure fixes
|
||||
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs
|
||||
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations
|
||||
- Fixed ball capture beam origin coming from behind the ball.
|
||||
- Fixed Safari Ball catch rate outside of battle
|
||||
|
||||
|
||||
### Developer
|
||||
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
|
||||
```json
|
||||
{
|
||||
"variant": "attack_defence_ratio",
|
||||
"pokedexIcon": "minecraft:diamond_sword",
|
||||
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
|
||||
}
|
||||
```
|
||||
- Fixed wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing pokemon to appear without entity link
|
||||
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon
|
||||
|
||||
### Molang & Datapacks
|
||||
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
|
||||
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
|
||||
|
||||
### Particles
|
||||
|
||||
#### New Move Animations
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Updated translations for:
|
||||
@@ -0,0 +1,9 @@
|
||||
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
|
||||
--- global posers? stacked posers?
|
||||
--- red texture seems to be what some people wanted.
|
||||
|
||||
/- shoulder mounts part of it
|
||||
- omni boost
|
||||
- roar when player comes within reach, adjust levels
|
||||
|
||||
- Riding settings to control what the min scale is for a pokemon to be ridable.
|
||||
+2
-5
@@ -1,6 +1,3 @@
|
||||
import utilities.gitBranch
|
||||
import utilities.gitShortCommit
|
||||
|
||||
plugins {
|
||||
id("cobblemon.root-conventions")
|
||||
}
|
||||
@@ -9,6 +6,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
|
||||
|
||||
val isSnapshot = project.property("snapshot")?.equals("true") == true
|
||||
if (isSnapshot) {
|
||||
val fixedBranchName = gitBranch().substringAfter("/")
|
||||
version = "$version-${fixedBranchName}-${gitShortCommit()}"
|
||||
val fixedBranchName = versioning.info.branch.substringAfter("/")
|
||||
version = "$version-${fixedBranchName}-${versioning.info.build}"
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Changelog
|
||||
## [1.8.1 (September 13th, 2026)](#1-8-1)
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
|
||||
|
||||
### Fixes
|
||||
- Fixed a potential world generation stall caused by Type Gem clusters.
|
||||
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
|
||||
- Fixed NPCs getting their own TM progress file.
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
|
||||
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
|
||||
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
|
||||
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
|
||||
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
|
||||
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
|
||||
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
|
||||
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
|
||||
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
|
||||
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
|
||||
- Fixed Blastoise having an unintentional passenger seat.
|
||||
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
|
||||
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
|
||||
- Fixed Ampharos' eyes not emitting particles.
|
||||
- Fixed Alolan Raticate's cry not playing.
|
||||
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
|
||||
- Removed unused, placeholder habitat pools.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
|
||||
- Fixed Lucario's aura particles not emitting.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having their sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Japanese
|
||||
+6
-15
@@ -1,8 +1,9 @@
|
||||
|
||||
import utilities.isSnapshot
|
||||
import utilities.version
|
||||
import utilities.gitBranch
|
||||
import utilities.gitCommit
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/*
|
||||
*
|
||||
@@ -71,14 +72,6 @@ tasks.withType<Test> {
|
||||
}
|
||||
}
|
||||
|
||||
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
|
||||
val writeGitInfo by tasks.registering(WriteProperties::class) {
|
||||
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
|
||||
property("commit", gitCommit())
|
||||
property("branch", gitBranch())
|
||||
}
|
||||
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
|
||||
|
||||
tasks.processResources {
|
||||
exclude("data/**/README.md")
|
||||
}
|
||||
@@ -105,13 +98,11 @@ sourceSets {
|
||||
property("modid", "cobblemon")
|
||||
property("version", project.version())
|
||||
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
|
||||
property("gitCommit", versioning.info.commit)
|
||||
property("branch", versioning.info.branch)
|
||||
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
|
||||
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
|
||||
tasks.assemble {
|
||||
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
|
||||
}
|
||||
|
||||
@@ -10,13 +10,11 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.cobblemon.mod.common.duck.RidePassenger;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -98,14 +96,4 @@ public class HumanoidModelMixin {
|
||||
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupAnim*", at = @At("TAIL"))
|
||||
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
|
||||
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
|
||||
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
|
||||
if (raise <= 0f) return;
|
||||
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
|
||||
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
|
||||
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ItemInHandLayer.class)
|
||||
public class ItemInHandLayerMixin {
|
||||
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
|
||||
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
-4
@@ -9,7 +9,6 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -32,9 +31,6 @@ public class ItemInHandRendererMixin {
|
||||
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
|
||||
|
||||
}
|
||||
|
||||
@@ -8,12 +8,8 @@ object CobblemonBuildDetails {
|
||||
const val VERSION = "{{ version }}"
|
||||
const val SNAPSHOT = {{ isSnapshot }}
|
||||
|
||||
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
|
||||
private val git = java.util.Properties().apply {
|
||||
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
|
||||
}
|
||||
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
|
||||
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
|
||||
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
|
||||
const val BRANCH = "{{ branch | default("Not Specified") }}"
|
||||
|
||||
fun smallCommitHash(): String {
|
||||
return this.GIT_COMMIT.substring(0, 7)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.battles.TeamPreviewManager
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBuildDetails.smallCommitHash
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.SeasonResolver
|
||||
@@ -254,6 +256,7 @@ object Cobblemon {
|
||||
}
|
||||
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
TeamPreviewManager.remove(it.player)
|
||||
BattleRegistry.onPlayerDisconnect(it.player)
|
||||
storage.onPlayerDisconnect(it.player)
|
||||
playerDataManager.onPlayerDisconnect(it.player)
|
||||
@@ -450,6 +453,7 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
PlatformEvents.SERVER_STOPPED.subscribe {
|
||||
TeamPreviewManager.clear()
|
||||
storage.unregisterAll(it.server.registryAccess())
|
||||
playerDataManager.saveAllStores()
|
||||
playerDataManager.saveExecutor.shutdown()
|
||||
|
||||
@@ -16,8 +16,6 @@ import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
|
||||
import com.cobblemon.mod.common.block.chest.GildedChestBlock
|
||||
import com.cobblemon.mod.common.block.decor.ActivatableDecorationBlock
|
||||
import com.cobblemon.mod.common.block.decor.RingTargetBlock
|
||||
import com.cobblemon.mod.common.block.general.BaleBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
@@ -623,119 +621,37 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
|
||||
|
||||
@JvmField
|
||||
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
|
||||
@JvmField
|
||||
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
|
||||
@JvmField
|
||||
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
|
||||
@JvmField
|
||||
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
|
||||
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
@JvmField
|
||||
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
|
||||
@JvmField
|
||||
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
|
||||
@JvmField
|
||||
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
|
||||
@JvmField
|
||||
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
|
||||
@JvmField
|
||||
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
|
||||
|
||||
@JvmField
|
||||
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
|
||||
@JvmField
|
||||
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
|
||||
@JvmField
|
||||
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
|
||||
|
||||
@JvmField
|
||||
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
|
||||
@JvmField
|
||||
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
|
||||
|
||||
@JvmField
|
||||
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
|
||||
@JvmField
|
||||
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
|
||||
@JvmField
|
||||
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
|
||||
@JvmField
|
||||
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
@JvmField
|
||||
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
|
||||
@JvmField
|
||||
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
|
||||
@JvmField
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
@JvmField
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
|
||||
@JvmField
|
||||
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
@@ -754,23 +670,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
|
||||
@JvmField
|
||||
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
|
||||
@JvmField
|
||||
@@ -786,7 +685,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val TM_MACHINE = create(
|
||||
"tm_machine",
|
||||
TMMachineBlock(
|
||||
@@ -815,7 +713,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
|
||||
@JvmField
|
||||
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
|
||||
@JvmField
|
||||
|
||||
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
|
||||
|
||||
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.mojang.brigadier.arguments.ArgumentType
|
||||
import kotlin.reflect.KClass
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.MinecraftServer
|
||||
@@ -169,16 +168,12 @@ interface CobblemonImplementation {
|
||||
* TODO
|
||||
*
|
||||
* @param identifier
|
||||
* @param reloaderFactory
|
||||
* @param reloader
|
||||
* @param type
|
||||
* @param dependencies
|
||||
*/
|
||||
fun registerResourceReloader(
|
||||
identifier: ResourceLocation,
|
||||
type: PackType,
|
||||
dependencies: Collection<ResourceLocation>,
|
||||
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
|
||||
)
|
||||
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
|
||||
@@ -472,9 +472,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
// Evolution items
|
||||
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
|
||||
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
|
||||
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
|
||||
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
|
||||
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
|
||||
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
|
||||
@JvmField val UPGRADE = noSettingsItem("upgrade")
|
||||
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
|
||||
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
|
||||
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
|
||||
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
|
||||
@@ -483,7 +483,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val PROTECTOR = noSettingsItem("protector")
|
||||
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
|
||||
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
|
||||
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
|
||||
@JvmField val SACHET = noSettingsItem("sachet")
|
||||
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
|
||||
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
|
||||
@@ -492,20 +492,22 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
|
||||
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
|
||||
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
|
||||
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
|
||||
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
|
||||
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
|
||||
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
|
||||
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
|
||||
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
|
||||
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
|
||||
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
|
||||
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
|
||||
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
|
||||
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
|
||||
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
|
||||
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
|
||||
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
|
||||
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
|
||||
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
|
||||
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
|
||||
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
|
||||
@@ -649,17 +651,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
|
||||
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
|
||||
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
|
||||
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
|
||||
@JvmField
|
||||
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
|
||||
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
|
||||
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
|
||||
@JvmField
|
||||
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
|
||||
@JvmField
|
||||
@@ -741,9 +743,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
|
||||
val PP_UP = create("pp_up", PPUpItem(1))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
|
||||
val PP_MAX = create("pp_max", PPUpItem(3))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
|
||||
@@ -920,18 +922,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
|
||||
@JvmField val EXP_SHARE = wearableItem("exp_share")
|
||||
@JvmField val FOCUS_BAND = wearableItem("focus_band")
|
||||
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
|
||||
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
|
||||
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
|
||||
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
|
||||
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
|
||||
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
|
||||
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
|
||||
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
|
||||
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
|
||||
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
|
||||
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
|
||||
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
|
||||
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
|
||||
|
||||
// Held Items
|
||||
@JvmField
|
||||
@@ -959,13 +954,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val CHOICE_SCARF = heldItem("choice_scarf")
|
||||
@JvmField
|
||||
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
|
||||
@JvmField
|
||||
val CLEAR_AMULET = heldItem("clear_amulet")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
|
||||
val COVERT_CLOAK = heldItem("covert_cloak")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = heldItem("destiny_knot")
|
||||
@JvmField
|
||||
val DRAGON_FANG = heldItem("dragon_fang")
|
||||
@JvmField
|
||||
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
|
||||
val EJECT_BUTTON = heldItem("eject_button")
|
||||
@JvmField
|
||||
val EJECT_PACK = heldItem("eject_pack")
|
||||
@JvmField
|
||||
@@ -979,7 +978,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val FAIRY_FEATHER = heldItem("fairy_feather")
|
||||
@JvmField
|
||||
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
|
||||
val FLAME_ORB = heldItem("flame_orb")
|
||||
@JvmField
|
||||
val FLOAT_STONE = heldItem("float_stone")
|
||||
@JvmField
|
||||
@@ -993,32 +992,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
|
||||
@JvmField
|
||||
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
|
||||
val IRON_BALL = heldItem("iron_ball")
|
||||
@JvmField
|
||||
val LAGGING_TAIL = heldItem("lagging_tail")
|
||||
@JvmField
|
||||
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
|
||||
@JvmField
|
||||
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
|
||||
val LIFE_ORB = heldItem("life_orb")
|
||||
@JvmField
|
||||
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
|
||||
val LIGHT_BALL = heldItem("light_ball")
|
||||
@JvmField
|
||||
val LIGHT_CLAY = heldItem("light_clay")
|
||||
@JvmField
|
||||
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
|
||||
val LOADED_DICE = heldItem("loaded_dice")
|
||||
@JvmField
|
||||
val LUCKY_EGG = heldItem("lucky_egg")
|
||||
@JvmField
|
||||
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
|
||||
val LUMINOUS_MOSS = heldItem("luminous_moss")
|
||||
@JvmField
|
||||
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
|
||||
val MAGNET = heldItem("magnet")
|
||||
@JvmField
|
||||
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
|
||||
val METRONOME = heldItem("metronome")
|
||||
@JvmField
|
||||
val MIRACLE_SEED = heldItem("miracle_seed")
|
||||
@JvmField
|
||||
val MYSTIC_WATER = heldItem("mystic_water")
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = heldItem("never_melt_ice")
|
||||
@JvmField
|
||||
val POISON_BARB = heldItem("poison_barb")
|
||||
@JvmField
|
||||
val POWER_ANKLET = heldItem("power_anklet")
|
||||
@@ -1043,11 +1044,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val RED_CARD = heldItem("red_card")
|
||||
@JvmField
|
||||
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
|
||||
val RING_TARGET = heldItem("ring_target")
|
||||
@JvmField
|
||||
val ROOM_SERVICE = heldItem("room_service")
|
||||
@JvmField
|
||||
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
|
||||
val SCOPE_LENS = heldItem("scope_lens")
|
||||
@JvmField
|
||||
val SHARP_BEAK = heldItem("sharp_beak")
|
||||
@JvmField
|
||||
val SHED_SHELL = heldItem("shed_shell")
|
||||
@JvmField
|
||||
@@ -1059,7 +1062,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SOFT_SAND = heldItem("soft_sand")
|
||||
@JvmField
|
||||
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
|
||||
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
|
||||
@JvmField
|
||||
val SMOKE_BALL = heldItem("smoke_ball")
|
||||
@JvmField
|
||||
val SOOTHE_BELL = heldItem("soothe_bell")
|
||||
@JvmField
|
||||
@@ -1067,9 +1072,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = heldItem("terrain_extender")
|
||||
@JvmField
|
||||
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
|
||||
val THROAT_SPRAY = heldItem("throat_spray")
|
||||
@JvmField
|
||||
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
|
||||
val TOXIC_ORB = heldItem("toxic_orb")
|
||||
@JvmField
|
||||
val TWISTED_SPOON = heldItem("twisted_spoon")
|
||||
@JvmField
|
||||
@@ -1077,9 +1082,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
|
||||
@JvmField
|
||||
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
|
||||
val WIDE_LENS = heldItem("wide_lens")
|
||||
@JvmField
|
||||
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
|
||||
val ZOOM_LENS = heldItem("zoom_lens")
|
||||
@JvmField
|
||||
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
|
||||
@JvmField
|
||||
@@ -1091,19 +1096,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = itemNameBlockItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
|
||||
val BRIGHT_POWDER = heldItem("bright_powder")
|
||||
@JvmField
|
||||
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
|
||||
val METAL_POWDER = heldItem("metal_powder")
|
||||
@JvmField
|
||||
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
|
||||
val QUICK_POWDER = heldItem("quick_powder")
|
||||
@JvmField
|
||||
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
|
||||
val DAMP_ROCK = heldItem("damp_rock")
|
||||
@JvmField
|
||||
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
|
||||
val HEAT_ROCK = heldItem("heat_rock")
|
||||
@JvmField
|
||||
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
|
||||
val SMOOTH_ROCK = heldItem("smooth_rock")
|
||||
@JvmField
|
||||
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
|
||||
val ICY_ROCK = heldItem("icy_rock")
|
||||
|
||||
// Mulch
|
||||
@JvmField
|
||||
@@ -1594,16 +1599,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
|
||||
name,
|
||||
WearableHatItem(name).also {
|
||||
wearables.add(it)
|
||||
if (heldItemRemappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
|
||||
}
|
||||
}
|
||||
)
|
||||
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
|
||||
name,
|
||||
WearableBlockItem(name, block).also {
|
||||
WearableItem(name).also {
|
||||
wearables.add(it)
|
||||
if (heldItemRemappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
|
||||
|
||||
@@ -8,6 +8,34 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamPreviewSelectPokemonHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.CloseTeamPreviewHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.TeamPreviewHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleTimerHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleInformationHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleActorTeamHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleActorInformationHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
|
||||
@@ -386,6 +414,12 @@ object CobblemonNetwork {
|
||||
// Battle packets
|
||||
list.add(PacketRegisterInfo(BattleEndPacket.ID, BattleEndPacket::decode, BattleEndHandler))
|
||||
list.add(PacketRegisterInfo(BattleInitializePacket.ID, BattleInitializePacket::decode, BattleInitializeHandler))
|
||||
list.add(PacketRegisterInfo(BattleActorInformationPacket.ID, BattleActorInformationPacket::decode, BattleActorInformationHandler))
|
||||
list.add(PacketRegisterInfo(BattleActorTeamPacket.ID, BattleActorTeamPacket::decode, BattleActorTeamHandler))
|
||||
list.add(PacketRegisterInfo(BattleInformationPacket.ID, BattleInformationPacket::decode, BattleInformationHandler))
|
||||
list.add(PacketRegisterInfo(BattleTimerPacket.ID, BattleTimerPacket::decode, BattleTimerHandler))
|
||||
list.add(PacketRegisterInfo(TeamPreviewPacket.ID, TeamPreviewPacket::decode, TeamPreviewHandler))
|
||||
list.add(PacketRegisterInfo(CloseTeamPreviewPacket.ID, CloseTeamPreviewPacket::decode, CloseTeamPreviewHandler))
|
||||
list.add(PacketRegisterInfo(BattleQueueRequestPacket.ID, BattleQueueRequestPacket::decode, BattleQueueRequestHandler))
|
||||
list.add(PacketRegisterInfo(BattleFaintPacket.ID, BattleFaintPacket::decode, BattleFaintHandler))
|
||||
list.add(PacketRegisterInfo(BattleMakeChoicePacket.ID, BattleMakeChoicePacket::decode, BattleMakeChoiceHandler))
|
||||
@@ -425,7 +459,6 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesEvolutionSyncPacket.ID, SpeciesEvolutionSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
@@ -574,6 +607,7 @@ object CobblemonNetwork {
|
||||
|
||||
// Battle packets
|
||||
list.add(PacketRegisterInfo(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler))
|
||||
list.add(PacketRegisterInfo(TeamPreviewSelectPokemonPacket.ID, TeamPreviewSelectPokemonPacket::decode, TeamPreviewSelectPokemonHandler))
|
||||
list.add(PacketRegisterInfo(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler))
|
||||
list.add(PacketRegisterInfo(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler))
|
||||
|
||||
|
||||
@@ -657,65 +657,5 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
SoundEvents.WOOL_FALL
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_POWDER_BREAK,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_PLACE,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_STEP,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_DICE_BREAK,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_PLACE,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_STEP
|
||||
)
|
||||
|
||||
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.FieldEffect
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
@@ -247,6 +257,38 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
this.turn = newTurnNumber
|
||||
notifyAllOfUpdates()
|
||||
}
|
||||
|
||||
fun notifyAllOfUpdates() {
|
||||
val uuids = this.actors.flatMap { it.getPlayerUUIDs() } + this.spectators
|
||||
notifyOfUpdates(uuids)
|
||||
}
|
||||
|
||||
fun notifyOfUpdates(uuids: List<UUID>) {
|
||||
val weather = this.contextManager.get(BattleContext.Type.WEATHER)?.firstOrNull()
|
||||
val terrain = this.contextManager.get(BattleContext.Type.TERRAIN)?.firstOrNull()
|
||||
val room = this.contextManager.get(BattleContext.Type.ROOM)?.firstOrNull()
|
||||
val side1SidedEffects = getSidedFieldEffects(side1)
|
||||
val side2SidedEffects = getSidedFieldEffects(side2)
|
||||
val updatePacket = BattleInformationPacket(this.battleId, BattleInformationDTO(
|
||||
turn = turn,
|
||||
weather = weather?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
|
||||
terrain = terrain?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
|
||||
room = room?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
|
||||
side1SidedEffects = side1SidedEffects,
|
||||
side2SidedEffects = side2SidedEffects,
|
||||
))
|
||||
uuids.mapNotNull { it.getPlayer() }.forEach {
|
||||
it.sendPacket(updatePacket)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSidedFieldEffects(side: BattleSide): List<FieldEffect> {
|
||||
val hazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
|
||||
val screens = side.contextManager.get(BattleContext.Type.SCREEN)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
|
||||
val tailwind = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) }
|
||||
return hazards + screens + listOfNotNull(tailwind)
|
||||
}
|
||||
|
||||
fun end() {
|
||||
@@ -454,6 +496,7 @@ open class PokemonBattle(
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
if (!ended) actors.filterIsInstance<PlayerBattleActor>().forEach { it.timer?.tick() }
|
||||
try {
|
||||
while (dispatchResult.canProceed()) {
|
||||
val dispatch = dispatches.poll() ?: break
|
||||
|
||||
+18
@@ -8,6 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model.actor
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
|
||||
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
@@ -48,6 +52,20 @@ abstract class BattleActor(
|
||||
var responses = mutableListOf<ShowdownActionResponse>()
|
||||
val expectingPassActions = mutableListOf<ShowdownActionResponse>()
|
||||
var mustChoose = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
if (this is PlayerBattleActor) {
|
||||
timer?.let {
|
||||
if (value) {
|
||||
it.startSelection()
|
||||
sendUpdate(BattleTimerPacket(it.mustChooseBy()))
|
||||
} else {
|
||||
it.endSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var itemsUsed = mutableListOf<BagItem>()
|
||||
/** For when battles start, it's the number of Pokémon that are still in the process of being sent out (animation wise) */
|
||||
|
||||
+1
-12
@@ -11,9 +11,6 @@ package com.cobblemon.mod.common.api.conditional
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
@@ -24,19 +21,11 @@ import java.lang.reflect.Type
|
||||
* @author Hiroku
|
||||
* @since July 17th, 2022
|
||||
*/
|
||||
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>>, JsonSerializer<RegistryLikeCondition<B>> {
|
||||
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>> {
|
||||
val registryLikeConditions: MutableList<(JsonElement) -> RegistryLikeCondition<B>?>
|
||||
|
||||
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): RegistryLikeCondition<B> {
|
||||
return registryLikeConditions.firstNotNullOfOrNull { it(json) }
|
||||
?: throw IllegalArgumentException("Unable to deserialize $json")
|
||||
}
|
||||
|
||||
override fun serialize(src: RegistryLikeCondition<B>, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
|
||||
return when (src) {
|
||||
is RegistryLikeTagCondition<B> -> JsonPrimitive(RegistryLikeTagCondition.PREFIX + src.tag.location())
|
||||
is RegistryLikeIdentifierCondition<B> -> JsonPrimitive(src.identifier.toString())
|
||||
else -> throw IllegalArgumentException("Unable to serialize $src")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +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.api.data
|
||||
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.core.HolderLookup
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.packs.PackType
|
||||
import net.minecraft.server.packs.resources.ResourceManager
|
||||
|
||||
/**
|
||||
* A [DataRegistry] that consumes JSON files.
|
||||
* Every deserialized instance is attached to an [Identifier].
|
||||
* For example a file under data/mymod/[resourcePath]/entry.json would be backed by the identifier modid:entry.
|
||||
*
|
||||
* @param T The type of the data consumed by this registry.
|
||||
*
|
||||
* @author Licious
|
||||
* @since August 5th, 2022
|
||||
*/
|
||||
interface ServerJsonDataRegistry<T> : DataRegistry {
|
||||
|
||||
/**
|
||||
* The [TypeToken] of type [T].
|
||||
*/
|
||||
val typeToken: TypeToken<T>
|
||||
|
||||
/**
|
||||
* The folder location for the data this registry will consume.
|
||||
*/
|
||||
val resourcePath: String
|
||||
|
||||
override val type: PackType
|
||||
get() = PackType.SERVER_DATA
|
||||
|
||||
override fun reload(manager: ResourceManager) {
|
||||
//NO-OP, handled by reload(manager, registry)
|
||||
}
|
||||
|
||||
fun reload(manager: ResourceManager, registry: HolderLookup.Provider) {
|
||||
val gson = setupParser(registry)
|
||||
val data = hashMapOf<ResourceLocation, T>()
|
||||
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
|
||||
if (identifier.namespace == "pixelmon") {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
resource.open().use { stream ->
|
||||
stream.bufferedReader().use { reader ->
|
||||
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
|
||||
data[resolvedIdentifier] = parse(reader, resolvedIdentifier, gson)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reload(data, registry)
|
||||
}
|
||||
|
||||
fun parse(reader: BufferedReader, identifier: ResourceLocation, gson: Gson): T {
|
||||
return try {
|
||||
gson.fromJson(reader, typeToken.type)
|
||||
} catch (exception: Exception) {
|
||||
throw ExecutionException("Error loading JSON for data: $identifier", exception)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the creation of [Gson] instances which have adapters that are registry-aware, e.g. [com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter]
|
||||
*/
|
||||
fun setupParser(registry: HolderLookup.Provider): Gson
|
||||
|
||||
/**
|
||||
* Reloads this registry from the deserialized data.
|
||||
*
|
||||
* @param data A map of the data associating an instance to the respective identifier from the [ResourceManager].
|
||||
*/
|
||||
fun reload(data: Map<ResourceLocation, T>, registry: HolderLookup.Provider)
|
||||
|
||||
companion object {
|
||||
const val JSON_EXTENSION = ".json"
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ object Fossils: JsonDataRegistry<Fossil> {
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.create()
|
||||
|
||||
override val typeToken: TypeToken<Fossil> = TypeToken.get(Fossil::class.java)
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.gui
|
||||
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import net.minecraft.client.gui.components.AbstractWidget
|
||||
import net.minecraft.client.gui.components.Renderable
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.network.chat.Component
|
||||
@@ -54,8 +55,6 @@ abstract class ParentWidget(
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (!active) return false
|
||||
|
||||
return children.any {
|
||||
it.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
@@ -1,116 +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.api.gui
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
interface TreeNode<T> {
|
||||
val children: List<TreeNode<T>>
|
||||
}
|
||||
|
||||
class TreeDrawingNode<T>(
|
||||
val value: T,
|
||||
val children: List<TreeDrawingNode<T>>,
|
||||
) {
|
||||
var x = 0
|
||||
var y = 0
|
||||
var offset = 0
|
||||
}
|
||||
|
||||
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
|
||||
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
|
||||
addMods(root, 0)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> setup(
|
||||
treeNode: TreeNode<T>,
|
||||
depth: Int,
|
||||
nexts: MutableList<Int>,
|
||||
offset: MutableList<Int>,
|
||||
): TreeDrawingNode<T> {
|
||||
val children = treeNode.children.map { child ->
|
||||
setup(child, depth + 1, nexts, offset)
|
||||
}
|
||||
|
||||
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
|
||||
treeDrawingNode.y = depth
|
||||
|
||||
while (nexts.size <= depth) {
|
||||
nexts.add(0)
|
||||
}
|
||||
|
||||
while (offset.size <= depth) {
|
||||
offset.add(0)
|
||||
}
|
||||
|
||||
var place =
|
||||
if (children.isEmpty()) {
|
||||
nexts[depth]
|
||||
} else if (children.size == 1) {
|
||||
// children.first().x - 1
|
||||
children.first().x
|
||||
} else {
|
||||
// (children[0].x + children[1].x) / 2
|
||||
(children.first().x + children.last().x) / 2
|
||||
}
|
||||
|
||||
offset[depth] = max(offset[depth], nexts[depth] - place)
|
||||
|
||||
if (children.isEmpty()) {
|
||||
treeDrawingNode.x = place
|
||||
} else {
|
||||
treeDrawingNode.x = place + offset[depth]
|
||||
}
|
||||
|
||||
nexts[depth] += 2
|
||||
treeDrawingNode.offset = offset[depth]
|
||||
|
||||
return treeDrawingNode
|
||||
}
|
||||
|
||||
private fun <T> addMods(
|
||||
treeNode: TreeDrawingNode<T>,
|
||||
modSum: Int,
|
||||
) {
|
||||
treeNode.x += modSum
|
||||
val newModSum = modSum + treeNode.offset
|
||||
|
||||
treeNode.children.forEach { child ->
|
||||
addMods(child, newModSum)
|
||||
}
|
||||
}
|
||||
|
||||
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
|
||||
val width = maxX - minX
|
||||
val height = maxY - minY
|
||||
val xCenter = (maxX + minX) / 2.0
|
||||
val yCenter = (maxY + minY) / 2.0
|
||||
}
|
||||
|
||||
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
|
||||
var minX = treeDrawingNode.x
|
||||
var maxX = treeDrawingNode.x
|
||||
var minY = treeDrawingNode.y
|
||||
var maxY = treeDrawingNode.y
|
||||
|
||||
for (child in treeDrawingNode.children) {
|
||||
val childBounds = calculateBounds(child)
|
||||
|
||||
minX = min(minX, childBounds.minX)
|
||||
maxX = max(maxX, childBounds.maxX)
|
||||
minY = min(minY, childBounds.minY)
|
||||
maxY = max(maxY, childBounds.maxY)
|
||||
}
|
||||
|
||||
return TreeDrawingBounds(minX, maxX, minY, maxY)
|
||||
}
|
||||
+20
-24
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.api.interaction
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.data.ServerJsonDataRegistry
|
||||
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.Requirement
|
||||
@@ -26,7 +26,6 @@ import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.advancements.critereon.ItemPredicate
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
@@ -37,31 +36,28 @@ import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.levelgen.structure.Structure
|
||||
import net.minecraft.world.level.material.Fluid
|
||||
|
||||
object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
|
||||
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
override val id = cobblemonResource("pokemon_interactions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override fun setupParser(registry: HolderLookup.Provider): Gson {
|
||||
return GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter(registry))
|
||||
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
|
||||
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
}
|
||||
|
||||
override val observable = SimpleObservable<PokemonInteractions>()
|
||||
override val typeToken: TypeToken<PokemonInteractionSet> = TypeToken.get(PokemonInteractionSet::class.java)
|
||||
override val resourcePath = "pokemon_interactions"
|
||||
override val gson: Gson = 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(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val interactions = mutableListOf<PokemonInteractionSet>()
|
||||
|
||||
@@ -69,7 +65,7 @@ object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
|
||||
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>, registry: HolderLookup.Provider) {
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
|
||||
interactions.clear()
|
||||
val split = data.entries
|
||||
.partition {
|
||||
@@ -91,4 +87,4 @@ object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
|
||||
.flatMap { it.interactions }
|
||||
.firstOrNull(interactionCheck)
|
||||
}
|
||||
}
|
||||
}
|
||||
-5
@@ -55,11 +55,6 @@ object GeneralMoLangFunctions {
|
||||
struct.map.remove(variable)
|
||||
DoubleValue.ONE
|
||||
}
|
||||
functions["delete_variables"] = { params ->
|
||||
val struct = params.get<VariableStruct>(0)
|
||||
struct.map.clear()
|
||||
DoubleValue.ONE
|
||||
}
|
||||
functions["get_variable"] = { params ->
|
||||
val struct = params.get<VariableStruct>(0)
|
||||
val variable = params.getString(1)
|
||||
|
||||
-22
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtCount
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
|
||||
import com.cobblemon.mod.common.api.pokedex.Dexes
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.SeenCount
|
||||
@@ -75,27 +74,6 @@ object PokedexMoLangFunctions : AbstractMoLangFunctionHolder<AbstractPokedexMana
|
||||
functions["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
|
||||
functions["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
|
||||
|
||||
functions["dex_caught_count"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtCount))
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_seen_count"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenCount))
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_caught_percent"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtPercent).toDouble())
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_seen_percent"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenPercent).toDouble())
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
|
||||
when (pokedex) {
|
||||
is PokedexManager -> {
|
||||
functions["player_id"] = { StringValue(pokedex.uuid.toString()) }
|
||||
|
||||
+7
-23
@@ -8,10 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.orientation
|
||||
|
||||
import com.cobblemon.mod.common.util.math.QuatSpring
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.math.simpleSpringDamperQuat
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@@ -28,8 +26,6 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
|
||||
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
|
||||
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
|
||||
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
|
||||
private const val RENDER_SMOOTH_HALFLIFE = 0.1f // seconds to close ~half the gap
|
||||
}
|
||||
|
||||
open var active: Boolean = false
|
||||
@@ -37,8 +33,6 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
var orientation: Matrix3f? = null
|
||||
private set
|
||||
|
||||
private val renderSpring = QuatSpring()
|
||||
|
||||
private var renderOrientationO: Matrix3f? = null
|
||||
private var renderOrientation: Matrix3f? = null
|
||||
|
||||
@@ -60,8 +54,6 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
orientation = null
|
||||
renderOrientationO = null
|
||||
renderOrientation = null
|
||||
renderSpring.quat.identity()
|
||||
renderSpring.angVel.set(0f, 0f, 0f)
|
||||
active = false
|
||||
}
|
||||
|
||||
@@ -85,26 +77,18 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
return oldQuat
|
||||
}
|
||||
|
||||
/** Every tick move the current render orientation towards the last known true orientation **/
|
||||
fun tick() {
|
||||
if (!active || orientation == null) return
|
||||
renderOrientationO = renderOrientation
|
||||
val current = orientation!!
|
||||
val targetQuat = Quaternionf().setFromUnnormalized(current)
|
||||
|
||||
if (renderOrientation == null) {
|
||||
// First tick: snap the spring to the target instead of easing in from identity
|
||||
renderSpring.quat.set(targetQuat)
|
||||
renderSpring.angVel.set(0f, 0f, 0f)
|
||||
}
|
||||
|
||||
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
|
||||
// It takes into account the orientations angular velocities and should be much more resilient
|
||||
// to sharp changes in goal orientation or late/missed packets.
|
||||
simpleSpringDamperQuat(renderSpring, targetQuat, 0.15f, TICK_DT)
|
||||
val current = orientation
|
||||
val renderMatrix = this.renderOrientation ?: current
|
||||
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
|
||||
val targetQuat = Quaternionf().setFromUnnormalized(current)
|
||||
val dampingFactor = 0.66f // Smooth interpolation to reduce jitter of orientations recieved from the server.
|
||||
renderQuat.slerp(targetQuat, dampingFactor)
|
||||
|
||||
val newRenderOrientation = Matrix3f()
|
||||
renderSpring.quat.get(newRenderOrientation)
|
||||
renderQuat.get(newRenderOrientation)
|
||||
renderOrientation = newRenderOrientation
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ object CatchRateModifiers {
|
||||
* Used by [PokeBalls.SAFARI_BALL].
|
||||
* Checks if the target is not battling, if true boost by *1.5
|
||||
*/
|
||||
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
|
||||
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
|
||||
|
||||
/**
|
||||
* Used by [PokeBalls.PARK_BALL].
|
||||
|
||||
@@ -13,10 +13,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -76,57 +72,6 @@ abstract class AbstractPokedexManager {
|
||||
} ?: PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
|
||||
if (form == null) return getHighestKnowledgeFor(entry)
|
||||
|
||||
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
|
||||
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
|
||||
if (!hasAllAspects) {
|
||||
return PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
|
||||
val formKnowledge = getHighestKnowledgeFor(entry, form)
|
||||
|
||||
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
|
||||
|
||||
val seenAspects = getSeenAspects(entry)
|
||||
|
||||
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
|
||||
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
|
||||
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
|
||||
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
|
||||
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
|
||||
.mapNotNull { provider ->
|
||||
val allAspects: Collection<String> = when (provider) {
|
||||
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
|
||||
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
|
||||
}
|
||||
.flatten()
|
||||
.toHashSet()
|
||||
|
||||
val allSeenAspects = aspects
|
||||
.filter { aspect ->
|
||||
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
|
||||
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
|
||||
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
|
||||
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
|
||||
|
||||
return@filter true
|
||||
}
|
||||
.all { aspect -> seenAspects.contains(aspect) }
|
||||
|
||||
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
|
||||
|
||||
return formKnowledge
|
||||
}
|
||||
|
||||
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
|
||||
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ package com.cobblemon.mod.common.api.pokedex
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.net.messages.client.data.PokedexDexSyncPacket
|
||||
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
|
||||
@@ -43,17 +40,6 @@ object Dexes : JsonDataRegistry<PokedexDef> {
|
||||
//Maps a dex id to its PokedexDef
|
||||
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
|
||||
|
||||
val equivalentForms = mapOf(
|
||||
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
|
||||
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
|
||||
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
|
||||
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
|
||||
)
|
||||
|
||||
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
|
||||
values.map { it to key }
|
||||
}.toMap()
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
|
||||
dexEntryMap.clear()
|
||||
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
|
||||
@@ -63,41 +49,4 @@ object Dexes : JsonDataRegistry<PokedexDef> {
|
||||
override fun sync(player: ServerPlayer) {
|
||||
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
|
||||
}
|
||||
|
||||
fun findDexEntryBySpeciesId(regionId: ResourceLocation, speciesId: ResourceLocation): PokedexEntry? {
|
||||
return dexEntryMap[regionId]?.getEntries()?.firstOrNull { it -> it.speciesId == speciesId }
|
||||
}
|
||||
|
||||
fun findDexEntryBySpeciesId(speciesId: ResourceLocation): PokedexEntry? {
|
||||
return findDexEntryBySpeciesId(cobblemonResource("national"), speciesId)
|
||||
}
|
||||
|
||||
fun findRootDexEntryBySpeciesId(speciesId: ResourceLocation, formName: String): Pair<PokedexEntry, PokedexForm> {
|
||||
var rootSpecies = PokemonSpecies.getByIdentifier(speciesId)!!
|
||||
val initialFormEquivalents = getFormEquivalents(formName)
|
||||
var rootForm = rootSpecies.forms.firstOrNull { initialFormEquivalents.contains(it.name.lowercase()) }
|
||||
?: rootSpecies.standardForm
|
||||
|
||||
var preEvolution = rootForm.preEvolution
|
||||
while (preEvolution != null) {
|
||||
rootSpecies = preEvolution.species
|
||||
rootForm = preEvolution.resolveForm(rootForm.aspects.toSet())
|
||||
preEvolution = rootForm.preEvolution
|
||||
}
|
||||
|
||||
val entry = findDexEntryBySpeciesId(rootSpecies.resourceIdentifier)
|
||||
?: throw RuntimeException("Root dex entry not found")
|
||||
|
||||
val formEquivalents = getFormEquivalents(rootForm.name)
|
||||
val form =
|
||||
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
|
||||
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
|
||||
|
||||
return entry to form
|
||||
}
|
||||
|
||||
fun getFormEquivalents(formName: String): List<String> {
|
||||
val key = equivalentFormsLookup[formName.lowercase()]
|
||||
return equivalentForms[key] ?: listOf(formName.lowercase())
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,6 @@ class SpeciesDexRecord {
|
||||
|
||||
fun addAspects(addedAspects: Set<String>) {
|
||||
aspects.addAll(addedAspects)
|
||||
pokedexManager.onSpeciesRecordUpdated(this)
|
||||
}
|
||||
|
||||
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
|
||||
|
||||
@@ -44,9 +44,6 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.spawning.TimeRange
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
|
||||
import com.cobblemon.mod.common.net.messages.client.data.FormEvolutionOverride
|
||||
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionEntry
|
||||
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
@@ -131,7 +128,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
|
||||
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
|
||||
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
|
||||
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
|
||||
@@ -246,18 +243,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
SpeciesRegistrySyncPacket(species.toList()).sendToPlayer(player)
|
||||
// Sent after the species themselves so every species is already registered by the time this is decoded.
|
||||
val evolutionEntries = species.map { singleSpecies ->
|
||||
val formOverrides = singleSpecies.forms.mapNotNull { form ->
|
||||
if (form.ownEvolutions == null && form.ownPreEvolution == null) {
|
||||
null
|
||||
} else {
|
||||
FormEvolutionOverride(form.name, form.ownEvolutions, form.ownPreEvolution)
|
||||
}
|
||||
}
|
||||
SpeciesEvolutionEntry(singleSpecies.resourceIdentifier, singleSpecies.evolutions, singleSpecies.preEvolution, formOverrides)
|
||||
}
|
||||
SpeciesEvolutionSyncPacket(evolutionEntries).sendToPlayer(player)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,8 +25,6 @@ interface PreEvolution {
|
||||
|
||||
val form: FormData
|
||||
|
||||
fun resolveForm(extraAspects: Set<String> = emptySet()): FormData = form
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(species: Species, form: FormData = species.standardForm): PreEvolution = StandardPreEvolution(species, form)
|
||||
|
||||
@@ -11,8 +11,6 @@ package com.cobblemon.mod.common.api.pokemon.requirement
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.util.WeakHashMap
|
||||
|
||||
/**
|
||||
* Represents a universal requirement suitable for various checks on pokemon..
|
||||
@@ -34,11 +32,4 @@ fun interface Requirement {
|
||||
*/
|
||||
fun check(pokemon: Pokemon): Boolean
|
||||
|
||||
val pokedexIcon: ResourceLocation? get() = pokedexIconOverrides[this]
|
||||
val pokedexTooltip: String? get() = pokedexTooltipOverrides[this]
|
||||
|
||||
companion object {
|
||||
val pokedexIconOverrides = WeakHashMap<Requirement, ResourceLocation>()
|
||||
val pokedexTooltipOverrides = WeakHashMap<Requirement, String>()
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +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.api.riding.util
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.phys.AABB
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* Walks every block position inside [searchBox] and turns each solid block into one
|
||||
* full-cube [FieldPrimitive].
|
||||
*
|
||||
* @author Jackson
|
||||
* @since July 27th, 2026
|
||||
*/
|
||||
|
||||
fun collectPrimitivesFromBlocks(
|
||||
level: BlockGetter,
|
||||
searchBox: AABB,
|
||||
shape: PrimitiveShape,
|
||||
includeFluids: Boolean = false
|
||||
): List<FieldPrimitive> {
|
||||
val primitives = mutableListOf<FieldPrimitive>()
|
||||
|
||||
val minX = floor(searchBox.minX).toInt()
|
||||
val maxX = floor(searchBox.maxX).toInt()
|
||||
val minY = floor(searchBox.minY).toInt()
|
||||
val maxY = floor(searchBox.maxY).toInt()
|
||||
val minZ = floor(searchBox.minZ).toInt()
|
||||
val maxZ = floor(searchBox.maxZ).toInt()
|
||||
|
||||
val pos = BlockPos.MutableBlockPos()
|
||||
for (x in minX..maxX) {
|
||||
for (y in minY..maxY) {
|
||||
for (z in minZ..maxZ) {
|
||||
pos.set(x, y, z)
|
||||
val state = level.getBlockState(pos)
|
||||
|
||||
val isSolid = !state.getCollisionShape(level, pos).isEmpty
|
||||
val isFluid = includeFluids && !state.fluidState.isEmpty
|
||||
if (!isSolid && !isFluid) continue
|
||||
|
||||
primitives.add(
|
||||
FieldPrimitive(
|
||||
shape = shape,
|
||||
centerX = x + 0.5f, centerY = y + 0.5f, centerZ = z + 0.5f,
|
||||
halfWidth = 0.5f, halfHeight = 0.5f, halfDepth = 0.5f
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return primitives
|
||||
}
|
||||
@@ -1,155 +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.api.riding.util
|
||||
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Helpful Functions when working with signed distance fields/functions.
|
||||
* https://iquilezles.org/articles/distfunctions/
|
||||
*
|
||||
* @author Jackson
|
||||
* @since July 27th, 2026
|
||||
*/
|
||||
|
||||
/** Which distance formula a primitive uses. */
|
||||
enum class PrimitiveShape {
|
||||
BOX,
|
||||
ROUNDED_BOX,
|
||||
ELLIPSOID
|
||||
}
|
||||
|
||||
/** Tunable constants for the field. */
|
||||
data class DistanceFieldTuning(
|
||||
val smoothingRange: Float = 0.5f,
|
||||
val roundingRadius: Float = 0.08f
|
||||
)
|
||||
|
||||
/** A single shape in the field - a center position, half-extents, and which formula to use. */
|
||||
data class FieldPrimitive(
|
||||
val shape: PrimitiveShape,
|
||||
val centerX: Float, val centerY: Float, val centerZ: Float,
|
||||
val halfWidth: Float, val halfHeight: Float, val halfDepth: Float
|
||||
)
|
||||
|
||||
/** Fast approximate 1/sqrt(x) - the Quake III trick, cheaper than a real sqrt + divide. */
|
||||
fun fastInverseSqrt(value: Float): Float {
|
||||
var x = value
|
||||
val halfX = 0.5f * x
|
||||
var bits = java.lang.Float.floatToIntBits(x)
|
||||
bits = 0x5f3759df - (bits shr 1)
|
||||
x = java.lang.Float.intBitsToFloat(bits)
|
||||
x *= (1.5f - halfX * x * x)
|
||||
return x
|
||||
}
|
||||
|
||||
/** Exact distance from a point to an axis-aligned box. Inputs are relative to the box's own center. */
|
||||
fun boxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
|
||||
val qx = abs(relX) - halfWidth
|
||||
val qy = abs(relY) - halfHeight
|
||||
val qz = abs(relZ) - halfDepth
|
||||
val ox = max(qx, 0f)
|
||||
val oy = max(qy, 0f)
|
||||
val oz = max(qz, 0f)
|
||||
val outside = sqrt(ox * ox + oy * oy + oz * oz)
|
||||
val inside = min(max(qx, max(qy, qz)), 0f)
|
||||
return outside + inside
|
||||
}
|
||||
|
||||
/** Same as boxDistance but with rounded corners/edges https://iquilezles.org/articles/distfunctions/ */
|
||||
fun roundedBoxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float, roundingRadius: Float): Float =
|
||||
boxDistance(relX, relY, relZ, halfWidth - roundingRadius, halfHeight - roundingRadius, halfDepth - roundingRadius) - roundingRadius
|
||||
|
||||
/** Approximate (lower-bound) distance to an ellipsoid https://iquilezles.org/articles/ellipsoids/. */
|
||||
fun ellipsoidDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
|
||||
val px = relX / halfWidth
|
||||
val py = relY / halfHeight
|
||||
val pz = relZ / halfDepth
|
||||
val inverseMagnitude = fastInverseSqrt(px * px + py * py + pz * pz)
|
||||
val trueDistance = sqrt(relX * relX + relY * relY + relZ * relZ)
|
||||
return trueDistance * (1.0f - inverseMagnitude)
|
||||
}
|
||||
|
||||
/** Distance from a point to one primitive, dispatched by its shape. */
|
||||
fun distanceToPrimitive(primitive: FieldPrimitive, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
val relX = sampleX - primitive.centerX
|
||||
val relY = sampleY - primitive.centerY
|
||||
val relZ = sampleZ - primitive.centerZ
|
||||
return when (primitive.shape) {
|
||||
PrimitiveShape.BOX -> boxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
|
||||
PrimitiveShape.ROUNDED_BOX -> roundedBoxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth, tuning.roundingRadius)
|
||||
PrimitiveShape.ELLIPSOID -> ellipsoidDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
|
||||
}
|
||||
}
|
||||
|
||||
///** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
|
||||
//fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
|
||||
// if (smoothingRange <= 0.001f) return min(a, b)
|
||||
// val k = smoothingRange * 4.0f
|
||||
// val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
|
||||
// return b + (a - b) * h - k * h * (1f - h)
|
||||
//}
|
||||
//
|
||||
///** Folds every primitive's distance together into one field value at a point. */
|
||||
//fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
// var result = Float.POSITIVE_INFINITY
|
||||
// for (primitive in primitives) {
|
||||
// val distance = distanceToPrimitive(primitive, sampleX, sampleY, sampleZ, tuning)
|
||||
// result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
|
||||
// }
|
||||
// return result
|
||||
//}
|
||||
|
||||
/** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
|
||||
fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
|
||||
if (smoothingRange <= 0.001f) return min(a, b)
|
||||
val k = smoothingRange * 4.0f
|
||||
val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
|
||||
return b + (a - b) * h - k * h * (1f - h)
|
||||
}
|
||||
|
||||
/** Folds nearby primitive distances together into one field value at a point. */
|
||||
fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
if (primitives.isEmpty()) return Float.POSITIVE_INFINITY
|
||||
|
||||
val distances = FloatArray(primitives.size) { distanceToPrimitive(primitives[it], sampleX, sampleY, sampleZ, tuning) }
|
||||
val minDistance = distances.min()
|
||||
|
||||
if (tuning.smoothingRange <= 0.001f) return minDistance
|
||||
|
||||
// Anything more than a few blend-widths past the closest primitive is cut - its
|
||||
// contribution would be negligible in a correct blend anyway, but left in, it still
|
||||
// drags the chained result down the more of them there are.
|
||||
val cutoff = tuning.smoothingRange * 3f
|
||||
|
||||
var result = Float.POSITIVE_INFINITY
|
||||
for (distance in distances) {
|
||||
if (distance - minDistance > cutoff) continue
|
||||
result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Finite-difference gradient of the field at a point - points away from the surface, magnitude ~1 for a clean field. */
|
||||
fun estimateGradient(primitives: List<FieldPrimitive>, point: Vec3, tuning: DistanceFieldTuning, epsilon: Float = 0.05f): Vec3 {
|
||||
val px = point.x.toFloat()
|
||||
val py = point.y.toFloat()
|
||||
val pz = point.z.toFloat()
|
||||
|
||||
val dx = sampleField(primitives, px + epsilon, py, pz, tuning) - sampleField(primitives, px - epsilon, py, pz, tuning)
|
||||
val dy = sampleField(primitives, px, py + epsilon, pz, tuning) - sampleField(primitives, px, py - epsilon, pz, tuning)
|
||||
val dz = sampleField(primitives, px, py, pz + epsilon, tuning) - sampleField(primitives, px, py, pz - epsilon, tuning)
|
||||
|
||||
val gradient = Vec3(dx.toDouble(), dy.toDouble(), dz.toDouble())
|
||||
return if (gradient.lengthSqr() > 1e-8) gradient.normalize() else Vec3(0.0, 1.0, 0.0)
|
||||
}
|
||||
@@ -1,105 +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.api.riding.util
|
||||
|
||||
import net.minecraft.core.particles.DustParticleOptions
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
|
||||
/** One shell to draw - a target field value, a color, a particle size. */
|
||||
data class DistanceFieldLayer(
|
||||
val isoValue: Float,
|
||||
val color: Vector3f,
|
||||
val particleSize: Float = 1.0f
|
||||
)
|
||||
|
||||
/**
|
||||
* Marches a grid of vertical columns through the field around [center] and spawns a
|
||||
* particle everywhere it crosses one of [layers]' target values. Server-side only.
|
||||
*/
|
||||
fun visualizeDistanceField(
|
||||
referenceEntity: Entity,
|
||||
center: Vec3,
|
||||
layers: List<DistanceFieldLayer>,
|
||||
shape: PrimitiveShape = PrimitiveShape.BOX,
|
||||
horizontalRadius: Double = 4.0,
|
||||
verticalRange: Double = 4.0,
|
||||
sampleSpacing: Double = 0.5,
|
||||
marchStep: Double = 0.1,
|
||||
tuning: DistanceFieldTuning = DistanceFieldTuning()
|
||||
) {
|
||||
val level = referenceEntity.level()
|
||||
if (level !is ServerLevel) return
|
||||
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y, center.z + horizontalRadius
|
||||
)
|
||||
|
||||
val primitives = collectPrimitivesFromBlocks(level, searchBox, shape, includeFluids = true)
|
||||
if (primitives.isEmpty()) return
|
||||
|
||||
var x = -horizontalRadius
|
||||
while (x <= horizontalRadius) {
|
||||
var z = -horizontalRadius
|
||||
while (z <= horizontalRadius) {
|
||||
marchColumn(level, primitives, center, x, z, verticalRange, marchStep, layers, tuning)
|
||||
z += sampleSpacing
|
||||
}
|
||||
x += sampleSpacing
|
||||
}
|
||||
}
|
||||
|
||||
private fun marchColumn(
|
||||
level: ServerLevel,
|
||||
primitives: List<FieldPrimitive>,
|
||||
center: Vec3,
|
||||
offsetX: Double,
|
||||
offsetZ: Double,
|
||||
verticalRange: Double,
|
||||
marchStep: Double,
|
||||
layers: List<DistanceFieldLayer>,
|
||||
tuning: DistanceFieldTuning
|
||||
) {
|
||||
val previousValue = FloatArray(layers.size) { Float.NaN }
|
||||
|
||||
var y = verticalRange
|
||||
while (y >= -verticalRange) {
|
||||
val worldX = (center.x + offsetX).toFloat()
|
||||
val worldY = (center.y + y).toFloat()
|
||||
val worldZ = (center.z + offsetZ).toFloat()
|
||||
val fieldValue = sampleField(primitives, worldX, worldY, worldZ, tuning)
|
||||
|
||||
for (layerIndex in layers.indices) {
|
||||
val layer = layers[layerIndex]
|
||||
val previous = previousValue[layerIndex]
|
||||
|
||||
if (!previous.isNaN() && crossedTarget(previous, fieldValue, layer.isoValue)) {
|
||||
val t = (layer.isoValue - previous) / (fieldValue - previous)
|
||||
val crossingY = (y + marchStep) - marchStep * t
|
||||
spawnLayerParticle(level, center.add(offsetX, crossingY, offsetZ), layer)
|
||||
}
|
||||
|
||||
previousValue[layerIndex] = fieldValue
|
||||
}
|
||||
|
||||
y -= marchStep
|
||||
}
|
||||
}
|
||||
|
||||
private fun crossedTarget(previous: Float, current: Float, target: Float): Boolean =
|
||||
(previous - target) * (current - target) < 0f
|
||||
|
||||
private fun spawnLayerParticle(level: ServerLevel, pos: Vec3, layer: DistanceFieldLayer) {
|
||||
val particle = DustParticleOptions(layer.color, layer.particleSize)
|
||||
level.sendParticles(particle, pos.x, pos.y, pos.z, 1, 0.0, 0.0, 0.0, 0.0)
|
||||
}
|
||||
@@ -129,7 +129,12 @@ class ParticleEvent(
|
||||
onDespawn = {},
|
||||
getParticleColor = storm.getParticleColor,
|
||||
targetPos = storm.targetPos,
|
||||
runtime = MoLangRuntime().setup(),
|
||||
runtime = MoLangRuntime().setup().also { runtime ->
|
||||
// Weather sub-emitters share the parent's lifetime condition.
|
||||
storm.runtime.environment.query.functions["is_weather_active"]?.let {
|
||||
runtime.environment.query.addFunction("is_weather_active", it)
|
||||
}
|
||||
},
|
||||
entity = storm.entity
|
||||
)
|
||||
//The reason this doesn't use the newStorms runtime is that the parent storm can add queries AFTER
|
||||
@@ -211,4 +216,4 @@ class EventSoundEffect(
|
||||
).apply(instance, ::EventSoundEffect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.api.spawning
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.TimeRange.Companion.timeRanges
|
||||
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
|
||||
|
||||
/**
|
||||
@@ -51,25 +49,4 @@ class TimeRange : IntRanges {
|
||||
|
||||
constructor() : super()
|
||||
constructor(vararg ranges: IntRange) : super(*ranges)
|
||||
|
||||
fun displayName(): MutableComponent? {
|
||||
val key = timeRanges.entries.firstOrNull { entry -> this == entry.value }?.key ?: return null
|
||||
return lang("ui.time_range.$key")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is TimeRange) return false
|
||||
|
||||
if (other.ranges.size != this.ranges.size) return false
|
||||
other.ranges.forEachIndexed { index, otherRange ->
|
||||
if (this.ranges[index] != otherRange) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return ranges.hashCode()
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning.condition
|
||||
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -44,7 +42,4 @@ enum class MoonPhase {
|
||||
fun ofWorld(world: Level): MoonPhase = VALUES[world.moonPhase]
|
||||
}
|
||||
|
||||
fun displayName(): MutableComponent {
|
||||
return lang("ui.moon_phase.${this.name.lowercase()}")
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,11 @@ data class BattleFormat(
|
||||
var ruleSet: Set<String> = setOf(),
|
||||
val gen: Int = 9,
|
||||
var adjustLevel: Int = -1, // Stop gap rule before a more general system for rules enforced by Cobblemon is implemented.
|
||||
val isOpenTeamSheet: Boolean = false,
|
||||
) {
|
||||
val hasTeamPreview: Boolean
|
||||
get() = ruleSet.any { it.equals(BattleRules.TEAM_PREVIEW, ignoreCase = true) || it.equals("teampreview", ignoreCase = true) }
|
||||
|
||||
companion object {
|
||||
fun setBattleRules(
|
||||
battleFormat: BattleFormat,
|
||||
@@ -121,4 +125,4 @@ data class BattleFormat(
|
||||
}
|
||||
""".trimIndent().replace("\n", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,16 @@ object ShowdownInterpreter {
|
||||
}
|
||||
|
||||
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
|
||||
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
|
||||
@JvmOverloads
|
||||
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon, revealedAbility: Effect = effect) {
|
||||
// Direct -ability messages omit the ability: prefix. A copied ability can also differ
|
||||
// from the effect announced in chat (for example, Trace).
|
||||
if (revealedAbility.type == Effect.Type.ABILITY || revealedAbility.type == Effect.Type.PURE) {
|
||||
battle.dispatchGo {
|
||||
pokemon.revealedAbility = revealedAbility.id
|
||||
pokemon.sendUpdate()
|
||||
}
|
||||
}
|
||||
battle.dispatchWaiting(0.5F) {
|
||||
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
|
||||
battle.broadcastChatMessage(lang)
|
||||
@@ -321,4 +330,4 @@ object ShowdownInterpreter {
|
||||
fun registerSideInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage) -> InterpreterInstruction) {
|
||||
sideInstructionParser[id] = callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
object TeamPreviewManager {
|
||||
private val previews = mutableListOf<TeamPreview>()
|
||||
val TEAM_PREVIEW_TIME_SECONDS = 60
|
||||
|
||||
fun tick() {
|
||||
val expired = previews.filter { Duration.between(it.started, Instant.now()).seconds >= TEAM_PREVIEW_TIME_SECONDS }
|
||||
expired.forEach { cancel(it) }
|
||||
|
||||
val ready = previews.filter {
|
||||
hasEligibleSelections(it.sideA) && hasEligibleSelections(it.sideB)
|
||||
}
|
||||
ready.forEach { start(it) }
|
||||
}
|
||||
|
||||
fun add(sideA: TeamPreviewSide, sideB: TeamPreviewSide, openTeamSheet: Boolean, onStart: (TeamPreview) -> Unit, onCancel: (TeamPreview) -> Unit) {
|
||||
require(sideA.uuid != sideB.uuid) { "A team preview requires two distinct participants" }
|
||||
require(listOf(sideA, sideB).all { it.neededSelections in 1..it.party.count { pokemon -> pokemon != null } }) {
|
||||
"Each side must be able to fill its requested selection"
|
||||
}
|
||||
if (previews.any { it.sideA.uuid == sideA.uuid || it.sideB.uuid == sideA.uuid || it.sideA.uuid == sideB.uuid || it.sideB.uuid == sideB.uuid }) return
|
||||
val now = Instant.now()
|
||||
previews.add(TeamPreview(sideA, sideB, now, onStart, onCancel))
|
||||
if (sideA.isPlayer) {
|
||||
sideA.uuid.getPlayer()?.sendPacket(
|
||||
TeamPreviewPacket(
|
||||
sideA.neededSelections,
|
||||
sideA.party,
|
||||
sideB.party,
|
||||
openTeamSheet,
|
||||
now.plusSeconds(TEAM_PREVIEW_TIME_SECONDS.toLong()),
|
||||
sideB.name
|
||||
)
|
||||
)
|
||||
}
|
||||
if (sideB.isPlayer) {
|
||||
sideB.uuid.getPlayer()?.sendPacket(
|
||||
TeamPreviewPacket(
|
||||
sideB.neededSelections,
|
||||
sideB.party,
|
||||
sideA.party,
|
||||
openTeamSheet,
|
||||
now.plusSeconds(TEAM_PREVIEW_TIME_SECONDS.toLong()),
|
||||
sideA.name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
previews.toList().forEach { cancel(it) }
|
||||
}
|
||||
|
||||
fun remove(player: ServerPlayer) {
|
||||
val preview = previews.firstOrNull { it.sideA.isPlayer && it.sideA.uuid == player.uuid || it.sideB.isPlayer && it.sideB.uuid == player.uuid } ?: return
|
||||
cancel(preview)
|
||||
}
|
||||
|
||||
fun cancel(preview: TeamPreview) {
|
||||
if (!previews.remove(preview)) return
|
||||
if (preview.sideA.isPlayer) {
|
||||
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
if (preview.sideB.isPlayer) {
|
||||
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
preview.onCancel(preview)
|
||||
}
|
||||
|
||||
fun start(preview: TeamPreview) {
|
||||
if (!previews.remove(preview)) return
|
||||
if (preview.sideA.isPlayer) {
|
||||
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
if (preview.sideB.isPlayer) {
|
||||
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
preview.onStart(preview)
|
||||
}
|
||||
|
||||
fun changeSelections(uuid: UUID, selections: Set<Int>) {
|
||||
val preview = previews.firstOrNull { it.sideA.uuid == uuid || it.sideB.uuid == uuid } ?: return
|
||||
val isSideA = preview.sideA.uuid == uuid
|
||||
val side = if (isSideA) preview.sideA else preview.sideB
|
||||
if (!side.isPlayer || !isValidSelection(side.party, selections, side.neededSelections, requireComplete = false)) return
|
||||
previews.remove(preview)
|
||||
if (isSideA) {
|
||||
previews.add(
|
||||
preview.copy(
|
||||
sideA = preview.sideA.copy(selections = selections.toSet()),
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
previews.add(
|
||||
preview.copy(
|
||||
sideB = preview.sideB.copy(selections = selections.toSet()),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasEligibleSelections(side: TeamPreviewSide): Boolean =
|
||||
isValidSelection(side.party, side.selections, side.neededSelections)
|
||||
|
||||
internal fun isValidSelection(party: List<*>, selections: Set<Int>, needed: Int, requireComplete: Boolean = true): Boolean {
|
||||
if (needed < 1 || selections.size > needed || (requireComplete && selections.size != needed)) return false
|
||||
return selections.all { it in party.indices && party[it] != null }
|
||||
}
|
||||
}
|
||||
|
||||
data class TeamPreview(
|
||||
val sideA: TeamPreviewSide,
|
||||
val sideB: TeamPreviewSide,
|
||||
val started: Instant,
|
||||
val onStart: (TeamPreview) -> Unit,
|
||||
val onCancel: (TeamPreview) -> Unit
|
||||
)
|
||||
|
||||
data class TeamPreviewSide(
|
||||
val uuid: UUID,
|
||||
val name: Component,
|
||||
val isPlayer: Boolean,
|
||||
val party: List<Pokemon?>,
|
||||
val selections: Set<Int>,
|
||||
val neededSelections: Int
|
||||
) {
|
||||
constructor(player: ServerPlayer, neededSelections: Int): this(player.uuid, player.name, true, player.party().toGappyList(), emptySet(), neededSelections)
|
||||
constructor(pokemon: Pokemon, name: Component? = null): this(pokemon.uuid, name ?: pokemon.getDisplayName(), false, listOf(null, null, pokemon, null, null, null, null), setOf(2), 1)
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.actor
|
||||
|
||||
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
@@ -33,6 +35,8 @@ class PlayerBattleActor(
|
||||
pokemonList: List<BattlePokemon>,
|
||||
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
|
||||
|
||||
var timer: PlayerBattleTimer? = null
|
||||
|
||||
override val initialPos: Vec3?
|
||||
override val entity: ServerPlayer?
|
||||
get() = this.uuid.getPlayer()
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
|
||||
val optionalPokemon = message.battlePokemonFromOptional(battle)
|
||||
|
||||
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
|
||||
ShowdownInterpreter.broadcastAbility(battle, optionalEffect ?: effect, pokemon)
|
||||
ShowdownInterpreter.broadcastAbility(battle, optionalEffect ?: effect, pokemon, revealedAbility = effect)
|
||||
|
||||
battle.dispatch {
|
||||
val pokemonName = pokemon.getName()
|
||||
@@ -63,4 +63,4 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
|
||||
else return@dispatch GO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -83,6 +83,10 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
|
||||
val changedStat = Stats.getStat(statKey)
|
||||
val delta = if (isBoost) stages else -stages
|
||||
pokemon.statChanges[changedStat] = ((pokemon.statChanges[changedStat] ?: 0) + delta).coerceIn(-6, 6)
|
||||
pokemon.sendUpdate()
|
||||
val boostBucket = if (isBoost) BattleContext.Type.BOOST else BattleContext.Type.UNBOOST
|
||||
val context = ShowdownInterpreter.getContextFromAction(message, boostBucket, battle)
|
||||
// TODO: replace with context that tracks detailed information such as # of stages
|
||||
|
||||
+2
@@ -26,6 +26,8 @@ class ClearAllBoostInstruction(val message: BattleMessage): InterpreterInstructi
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
battle.activePokemon.forEach {
|
||||
it.battlePokemon?.statChanges?.clear()
|
||||
it.battlePokemon?.sendUpdate()
|
||||
it.battlePokemon?.contextManager?.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
}
|
||||
battle.broadcastChatMessage(battleLang("clearallboost"))
|
||||
|
||||
+2
@@ -30,6 +30,8 @@ class ClearBoostInstruction(val message: BattleMessage): InterpreterInstruction
|
||||
val pokemonName = battlePokemon.getName()
|
||||
val lang = battleLang("clearboost", pokemonName)
|
||||
battle.broadcastChatMessage(lang)
|
||||
battlePokemon.statChanges.clear()
|
||||
battlePokemon.sendUpdate()
|
||||
battlePokemon.contextManager.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
}
|
||||
|
||||
+2
@@ -36,6 +36,8 @@ class ClearNegativeBoostInstruction(val message: BattleMessage): InterpreterInst
|
||||
battle.broadcastChatMessage(lang)
|
||||
}
|
||||
|
||||
battlePokemon.statChanges.entries.removeIf { it.value < 0 }
|
||||
battlePokemon.sendUpdate()
|
||||
battlePokemon.contextManager.clear(BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
}
|
||||
|
||||
+4
@@ -32,6 +32,10 @@ class CopyBoostInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val lang = battleLang("copyboost.generic", pokemonName, targetPokemonName)
|
||||
battle.broadcastChatMessage(lang)
|
||||
|
||||
val changes = targetPokemon.statChanges.toMap()
|
||||
pokemon.statChanges.clear()
|
||||
pokemon.statChanges.putAll(changes)
|
||||
pokemon.sendUpdate()
|
||||
pokemon.contextManager.copy(targetPokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
|
||||
+8
-1
@@ -155,6 +155,13 @@ class DamageInstruction(
|
||||
|
||||
val newHealthRatio: Float
|
||||
val remainingHealth = newHealth.split("/")[0].toInt()
|
||||
val previousHealth = battlePokemon.effectedPokemon.currentHealth
|
||||
val damageDealt = (previousHealth - remainingHealth).coerceAtLeast(0)
|
||||
val maxHp = battlePokemon.effectedPokemon.maxHealth
|
||||
if (damageDealt > 0 && maxHp > 0) {
|
||||
val percent = Math.round(damageDealt.toFloat() / maxHp * 100F).coerceAtLeast(1)
|
||||
battle.broadcastChatMessage(battleLang("damage_dealt", battlePokemon.getName(), "$percent%"))
|
||||
}
|
||||
|
||||
if (effect != null) {
|
||||
val lang = when (effect.id) {
|
||||
@@ -222,4 +229,4 @@ class DamageInstruction(
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -35,6 +35,8 @@ class EndItemInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
val itemEffect = message.effectAt(1) ?: return@dispatchWaiting
|
||||
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
|
||||
battlePokemon.revealedHeldItem = net.minecraft.world.item.ItemStack.EMPTY
|
||||
battlePokemon.sendUpdate()
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
battlePokemon.contextManager.remove(itemEffect.id, BattleContext.Type.ITEM)
|
||||
if (message.hasOptionalArgument("eat")) {
|
||||
|
||||
+10
-1
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -58,6 +60,11 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
|
||||
|
||||
battle.actors.forEach { actor ->
|
||||
actor.sendUpdate(BattleSetTeamPokemonPacket(actor.pokemonList.map { it.effectedPokemon }))
|
||||
battle.actors.forEach { viewer ->
|
||||
viewer.sendUpdate(BattleActorTeamPacket(actor.uuid, actor.pokemonList.map {
|
||||
it.toBattleDTO(ally = viewer == actor)
|
||||
}))
|
||||
}
|
||||
val req = actor.request ?: return@forEach
|
||||
actor.sendUpdate(BattleQueueRequestPacket(req))
|
||||
}
|
||||
@@ -68,8 +75,10 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
|
||||
|
||||
battle.dispatchGo {
|
||||
battle.started = true
|
||||
battle.activePokemon.mapNotNull { it.battlePokemon }.forEach { it.sendUpdate() }
|
||||
battle.notifyAllOfUpdates()
|
||||
battle.side1.playCries()
|
||||
afterOnServer(seconds = 1.0F) { battle.side2.playCries() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -28,6 +28,8 @@ class InvertBoostInstruction(val message: BattleMessage): InterpreterInstruction
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
pokemon.statChanges.replaceAll { _, value -> -value }
|
||||
pokemon.sendUpdate()
|
||||
val name = pokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("invertboost", name))
|
||||
|
||||
|
||||
+2
@@ -34,6 +34,8 @@ class ItemInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
|
||||
battlePokemon.revealedHeldItem = battlePokemon.effectedPokemon.heldItem.copy()
|
||||
battlePokemon.sendUpdate()
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
battlePokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.ITEM, battle))
|
||||
}
|
||||
|
||||
+10
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
@@ -74,6 +76,14 @@ class MoveInstruction(
|
||||
battle.dispatch { UntilDispatch { instructionSet.getMostRecentInstruction<MoveInstruction>(this)?.future?.isDone != false } }
|
||||
|
||||
battle.dispatch {
|
||||
val knownMove = userPokemon.revealedMoves.firstOrNull { it?.move == move.displayName }
|
||||
if (knownMove != null) {
|
||||
knownMove.timesUsed++
|
||||
} else {
|
||||
val slot = userPokemon.revealedMoves.indexOfFirst { it == null }
|
||||
if (slot >= 0) userPokemon.revealedMoves[slot] = MoveDTO(move.displayName, 1)
|
||||
}
|
||||
userPokemon.sendUpdate()
|
||||
val pokemonName = userPokemon.getName()
|
||||
ShowdownInterpreter.lastCauser[battle.battleId] = message
|
||||
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
|
||||
|
||||
+8
-1
@@ -19,6 +19,13 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
*/
|
||||
object PostUpdateInstruction : InterpreterInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchGo { battle.actors.forEach { it.postUpdate() } }
|
||||
battle.dispatchGo {
|
||||
battle.actors.forEach { it.postUpdate() }
|
||||
battle.notifyAllOfUpdates()
|
||||
val viewers = battle.actors.flatMap { it.getPlayerUUIDs() } + battle.spectators
|
||||
battle.actors.forEach { actor ->
|
||||
actor.pokemonList.forEach { it.updateBattleActorInformation(viewers) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -27,6 +29,12 @@ class SetBoostInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
val stat = message.argumentAt(1)?.let { Stats.getStat(it) }
|
||||
val amount = message.argumentAt(2)?.toIntOrNull()
|
||||
if (stat != null && amount != null) {
|
||||
pokemon.statChanges[stat] = amount.coerceIn(-6, 6)
|
||||
pokemon.sendUpdate()
|
||||
}
|
||||
val pokemonName = pokemon.getName()
|
||||
val effectID = message.effect()?.id ?: return@dispatchWaiting
|
||||
val lang = battleLang("setboost.$effectID", pokemonName)
|
||||
|
||||
+11
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -29,6 +31,15 @@ class SwapBoostInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val pokemonName = pokemon.getName()
|
||||
val targetPokemon = message.battlePokemon(1, battle) ?: return@dispatchWaiting
|
||||
val targetPokemonName = targetPokemon.getName()
|
||||
val stats = message.argumentAt(2)?.split(',')?.map { Stats.getStat(it.trim()) }
|
||||
?: (pokemon.statChanges.keys + targetPokemon.statChanges.keys).toList()
|
||||
stats.forEach { stat ->
|
||||
val previous = pokemon.statChanges[stat] ?: 0
|
||||
pokemon.statChanges[stat] = targetPokemon.statChanges[stat] ?: 0
|
||||
targetPokemon.statChanges[stat] = previous
|
||||
}
|
||||
pokemon.sendUpdate()
|
||||
targetPokemon.sendUpdate()
|
||||
val effectID = message.effect()?.id ?: return@dispatchWaiting
|
||||
val lang = when (effectID) {
|
||||
"guardswap", "powerswap", "heartswap" -> battleLang("swapboost.$effectID", pokemonName)
|
||||
|
||||
+11
-1
@@ -129,6 +129,14 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
|
||||
activePokemon.battlePokemon?.let { oldPokemon ->
|
||||
if (publicMessage.effect()?.id == "batonpass") oldPokemon.contextManager.swap(pokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
pokemon.statChanges.clear()
|
||||
if (publicMessage.effect()?.id == "batonpass") pokemon.statChanges.putAll(oldPokemon.statChanges)
|
||||
oldPokemon.statChanges.clear()
|
||||
if (oldPokemon.transformed != null) {
|
||||
oldPokemon.revealedMoves.indices.forEach { oldPokemon.revealedMoves[it] = null }
|
||||
oldPokemon.revealedAbility = null
|
||||
}
|
||||
oldPokemon.transformed = null
|
||||
oldPokemon.contextManager.clear(BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.majorBattleActions[oldPokemon.uuid] = publicMessage
|
||||
|
||||
@@ -215,6 +223,8 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
}
|
||||
|
||||
private fun broadcastSwitch(battle: PokemonBattle, actor: BattleActor, newPokemon: BattlePokemon, illusion: BattlePokemon?) {
|
||||
// The active slot and its illusion are now set, so reveal the identity that entered battle.
|
||||
newPokemon.sendUpdate()
|
||||
val publicPokemon = (illusion ?: newPokemon).effectedPokemon
|
||||
val publicLang = publicPokemon.nickname?.let { nickname ->
|
||||
battleLang("switch.other.nickname", actor.getName(), nickname, publicPokemon.species.translatedName)
|
||||
@@ -229,4 +239,4 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -30,6 +30,8 @@ class TerastallizeInstruction(val message: BattleMessage): InterpreterInstructio
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return
|
||||
val type = message.effectAt(1)?.let { TeraTypes.get(it.id) } ?: return
|
||||
battle.dispatchWaiting {
|
||||
battlePokemon.terastallized = type
|
||||
battlePokemon.sendUpdate()
|
||||
val pokemonName = battlePokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
|
||||
CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
|
||||
|
||||
+11
-1
@@ -46,6 +46,16 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
|
||||
}
|
||||
|
||||
battle.dispatchWaiting {
|
||||
pokemon.transformed = targetPokemon
|
||||
pokemon.statChanges.clear()
|
||||
pokemon.statChanges.putAll(targetPokemon.statChanges)
|
||||
for (index in pokemon.revealedMoves.indices) {
|
||||
pokemon.revealedMoves[index] = targetPokemon.moveSet.get(index)?.let {
|
||||
com.cobblemon.mod.common.net.messages.client.battle.MoveDTO(it.displayName, 0)
|
||||
}
|
||||
}
|
||||
pokemon.revealedAbility = targetPokemon.effectedPokemon.ability.name
|
||||
pokemon.sendUpdate()
|
||||
val mock = pokemon.entity?.effects?.mockEffect?.mock
|
||||
val pokemonName = pokemon.getName()
|
||||
val targetPokemonName = targetPokemon.getName()
|
||||
@@ -63,4 +73,4 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,24 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.pokemon
|
||||
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
|
||||
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraType
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
@@ -120,6 +138,13 @@ open class BattlePokemon(
|
||||
|
||||
val contextManager = ContextManager()
|
||||
|
||||
var revealed = false
|
||||
var revealedAbility: String? = null
|
||||
val revealedMoves: MutableList<MoveDTO?> = mutableListOf(null, null, null, null)
|
||||
var revealedHeldItem: ItemStack? = null
|
||||
var transformed: BattlePokemon? = null
|
||||
var terastallized: TeraType? = null
|
||||
|
||||
open fun getName(): MutableComponent {
|
||||
val displayPokemon = getIllusion()?.effectedPokemon ?: effectedPokemon
|
||||
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
|
||||
@@ -129,8 +154,72 @@ open class BattlePokemon(
|
||||
}
|
||||
}
|
||||
|
||||
fun toBattleDTO(ally: Boolean, forceReveal: Boolean = false): BattlePokemonDTO {
|
||||
val moves = (transformed?.effectedPokemon ?: effectedPokemon).moveSet.getMovesWithNulls().map { move ->
|
||||
move?.let { MoveDTO(it.displayName, revealedMoves.firstOrNull { known -> known?.move == it.displayName }?.timesUsed ?: 0) }
|
||||
}
|
||||
|
||||
val boostMultipliers = statChanges.entries.mapNotNull { (stat, stage) ->
|
||||
val standardStat = stat as? Stats ?: return@mapNotNull null
|
||||
val base = if (standardStat == Stats.ACCURACY || standardStat == Stats.EVASION) 3.0 else 2.0
|
||||
val clamped = stage.coerceIn(-6, 6)
|
||||
standardStat to if (clamped >= 0) (base + clamped) / base else base / (base - clamped)
|
||||
}.toMap().filterValues { it != 1.0 }
|
||||
|
||||
val format = if (::actor.isInitialized && actor.isInitialized()) actor.battle.format else null
|
||||
val isTeamSheet = format?.isOpenTeamSheet == true
|
||||
if (ally || isTeamSheet) {
|
||||
val allyDto = BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, ally, getIllusion())
|
||||
return BattlePokemonDTO(
|
||||
this.effectedPokemon.uuid,
|
||||
this.effectedPokemon.isFainted(),
|
||||
(transformed?.effectedPokemon ?: effectedPokemon).ability.name,
|
||||
moves,
|
||||
this.effectedPokemon.heldItem,
|
||||
boostMultipliers,
|
||||
if (ally) effectedPokemon.speed else null,
|
||||
terastallized = terastallized,
|
||||
allyDto
|
||||
)
|
||||
}
|
||||
else {
|
||||
// Team Preview reveals identities, while moves, abilities and items still require discovery.
|
||||
val nonAllyDto = if (forceReveal || revealed || format?.hasTeamPreview == true) {
|
||||
BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, false, getIllusion())
|
||||
} else null
|
||||
return BattlePokemonDTO(
|
||||
this.effectedPokemon.uuid,
|
||||
this.effectedPokemon.isFainted(),
|
||||
revealedAbility,
|
||||
revealedMoves,
|
||||
revealedHeldItem,
|
||||
boostMultipliers,
|
||||
speed = null,
|
||||
terastallized = terastallized,
|
||||
nonAllyDto
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBattleActorInformation(uuids: List<UUID>) {
|
||||
uuids.forEach {
|
||||
if (actor.uuid == it) {
|
||||
it.getPlayer()?.sendPacket(
|
||||
BattleActorInformationPacket(actor.uuid, toBattleDTO(true))
|
||||
)
|
||||
}
|
||||
else {
|
||||
it.getPlayer()?.sendPacket(
|
||||
BattleActorInformationPacket(actor.uuid, toBattleDTO(false))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendUpdate() {
|
||||
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
|
||||
revealed = revealed || isSentOut()
|
||||
updateBattleActorInformation(actor.battle.actors.flatMap { it.getPlayerUUIDs() } + actor.battle.spectators)
|
||||
}
|
||||
|
||||
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
|
||||
@@ -141,5 +230,7 @@ open class BattlePokemon(
|
||||
!isSentOut() && !willBeSwitchedIn && health > 0
|
||||
}
|
||||
|
||||
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
|
||||
}
|
||||
fun getIllusion(): BattlePokemon? = if (::actor.isInitialized) {
|
||||
actor.activePokemon.find { it.battlePokemon == this }?.illusion
|
||||
} else null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.timers
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.ForfeitActionResponse
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import java.time.Instant
|
||||
|
||||
interface PlayerBattleTimer {
|
||||
val actor: PlayerBattleActor
|
||||
val battle: PokemonBattle
|
||||
|
||||
fun startSelection()
|
||||
fun endSelection()
|
||||
fun tick()
|
||||
fun mustChooseBy(): Instant
|
||||
|
||||
fun timeout() {
|
||||
actor.setActionResponses(listOf(ForfeitActionResponse()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.timers
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class ShowdownTimer(override val battle: PokemonBattle, override val actor: PlayerBattleActor): PlayerBattleTimer {
|
||||
private lateinit var turnStart: Instant
|
||||
private var secondsRemaining = 210
|
||||
private var hasSelected = true
|
||||
|
||||
override fun tick() {
|
||||
if (hasSelected || !::turnStart.isInitialized || battle.ended) return
|
||||
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds
|
||||
if (secondsRemaining - secondsSinceStart <= 0) {
|
||||
hasSelected = true
|
||||
timeout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun startSelection() {
|
||||
turnStart = Instant.now()
|
||||
hasSelected = false
|
||||
}
|
||||
|
||||
override fun endSelection() {
|
||||
if (hasSelected || !::turnStart.isInitialized) return
|
||||
hasSelected = true
|
||||
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds.toInt()
|
||||
secondsRemaining = secondsRemaining - secondsSinceStart + 10
|
||||
}
|
||||
|
||||
override fun mustChooseBy(): Instant {
|
||||
return turnStart.plusSeconds(secondsRemaining.toLong())
|
||||
}
|
||||
}
|
||||
@@ -1,137 +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.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class ActivatableDecorationBlock(settings: Properties, val type: ActivatableDecorationBlockType) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
|
||||
enum class ActivatableDecorationBlockType: StringRepresentable {
|
||||
//For basic use, also ignore camelcase errors, it's just dumb
|
||||
CENTERED_8x,
|
||||
//For specific items
|
||||
METRONOME;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
companion object {
|
||||
val CODEC = StringRepresentable.fromEnum(::values)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<ActivatableDecorationBlock> = RecordCodecBuilder.mapCodec { it.group(
|
||||
propertiesCodec(),
|
||||
ActivatableDecorationBlockType.CODEC.fieldOf("type").forGetter(ActivatableDecorationBlock::type)
|
||||
).apply(it, ::ActivatableDecorationBlock) }
|
||||
val ACTIVE = BooleanProperty.create("active")
|
||||
|
||||
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
|
||||
val METRONOME_HITBOX = Shapes.box(0.25, 0.0, 0.375, 0.75, 0.8125, 0.75)
|
||||
|
||||
fun getShape(type: ActivatableDecorationBlockType): VoxelShape {
|
||||
return when (type) {
|
||||
ActivatableDecorationBlockType.METRONOME -> METRONOME_HITBOX
|
||||
else -> CENTERED_8x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(ACTIVE, false)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<ActivatableDecorationBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HORIZONTAL_FACING, ACTIVE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(HORIZONTAL_FACING)
|
||||
return rotateShape(Direction.NORTH, direction, getShape(type))
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getCollisionShape(state, level, pos, context).singleEncompassing()
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(
|
||||
state: BlockState,
|
||||
level: Level,
|
||||
pos: BlockPos,
|
||||
player: Player,
|
||||
hitResult: BlockHitResult
|
||||
): InteractionResult {
|
||||
if (level.isClientSide) {
|
||||
level.setBlockAndUpdate(pos, state.cycle(ACTIVE))
|
||||
return InteractionResult.SUCCESS
|
||||
} else {
|
||||
this.push(state, level, pos, null as Player?)
|
||||
return InteractionResult.CONSUME
|
||||
}
|
||||
}
|
||||
|
||||
fun push(state: BlockState, level: Level, pos: BlockPos?, player: Player?) {
|
||||
var state = state
|
||||
state = state.cycle(ACTIVE)
|
||||
level.setBlock(pos!!, state, 3)
|
||||
level.gameEvent(player, if (state.getValue(ACTIVE)) GameEvent.BLOCK_ACTIVATE else GameEvent.BLOCK_DEACTIVATE, pos)
|
||||
}
|
||||
|
||||
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
|
||||
return true
|
||||
}
|
||||
//TODO: Replace this forced 4 with a value that can be set by the block, only relevant when more blocks of this class are added.
|
||||
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
|
||||
return if (level.getBlockState(pos).getValue(ACTIVE) as Boolean) 4 else 0
|
||||
}
|
||||
}
|
||||
@@ -1,130 +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.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockType): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
|
||||
|
||||
enum class DecorativeItemBlockType: StringRepresentable {
|
||||
//For basic use, also ignore camelcase errors, it's just dumb
|
||||
CENTERED_8x,
|
||||
CENTERED_6x,
|
||||
FLATTENED_8x6,
|
||||
FLATTENED_8x4,
|
||||
FLATTENED_10x4,
|
||||
//For specific items
|
||||
TEACUP,
|
||||
TEAPOT,
|
||||
KNOT,
|
||||
POUCH,
|
||||
NEVERMELT,
|
||||
SCOPE,
|
||||
WIDE,
|
||||
ZOOM;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
companion object {
|
||||
val CODEC = StringRepresentable.fromEnum(::values)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<DecorativeItemBlock> = RecordCodecBuilder.mapCodec { it.group(
|
||||
propertiesCodec(),
|
||||
DecorativeItemBlockType.CODEC.fieldOf("type").forGetter(DecorativeItemBlock::type)
|
||||
).apply(it, ::DecorativeItemBlock) }
|
||||
|
||||
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
|
||||
val CENTERED_6x = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
|
||||
val FLAT6_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
|
||||
val FLAT4_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.25, 0.75)
|
||||
val SMOOSHED4_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.25, 0.8125)
|
||||
val TEACUP_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875)
|
||||
val TEAPOT_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.625, 0.8125)
|
||||
val KNOT_HITBOX = Shapes.box(0.375, 0.0, 0.375, 0.75, 0.375, 0.75)
|
||||
val POUCH_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.3125, 0.6875)
|
||||
val NEVERMELT_HITBOX = Shapes.box(0.25, 0.0, 0.3125, 0.6875, 0.25, 0.75)
|
||||
val SCOPE_HITBOX = Shapes.box(0.625, 0.0, 0.4375, 0.9375, 0.125, 0.75)
|
||||
val WIDE_HITBOX = Shapes.box(0.625, 0.0, 0.125, 0.9375, 0.0625, 0.625)
|
||||
val ZOOM_HITBOX = Shapes.box(0.125, 0.0, 0.1875, 0.375, 0.1875, 0.5)
|
||||
|
||||
fun getShape(type: DecorativeItemBlockType): VoxelShape {
|
||||
return when (type) {
|
||||
DecorativeItemBlockType.TEAPOT -> TEAPOT_HITBOX
|
||||
DecorativeItemBlockType.TEACUP -> TEACUP_HITBOX
|
||||
DecorativeItemBlockType.KNOT -> KNOT_HITBOX
|
||||
DecorativeItemBlockType.POUCH -> POUCH_HITBOX
|
||||
DecorativeItemBlockType.NEVERMELT -> NEVERMELT_HITBOX
|
||||
DecorativeItemBlockType.SCOPE -> SCOPE_HITBOX
|
||||
DecorativeItemBlockType.WIDE -> WIDE_HITBOX
|
||||
DecorativeItemBlockType.ZOOM -> ZOOM_HITBOX
|
||||
DecorativeItemBlockType.FLATTENED_10x4 -> SMOOSHED4_HITBOX
|
||||
DecorativeItemBlockType.FLATTENED_8x4 -> FLAT4_HITBOX
|
||||
DecorativeItemBlockType.FLATTENED_8x6 -> FLAT6_HITBOX
|
||||
DecorativeItemBlockType.CENTERED_6x -> CENTERED_6x
|
||||
else -> CENTERED_8x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(WATERLOGGED, false))
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<DecorativeItemBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HORIZONTAL_FACING, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(HORIZONTAL_FACING)
|
||||
return rotateShape(Direction.NORTH, direction, getShape(type))
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
|
||||
}
|
||||
}
|
||||
@@ -1,139 +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.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.ButtonBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockSetType
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class EjectButtonBlock(properties: Properties, ticksToStayPressed: Int) : ButtonBlock(BlockSetType.IRON, ticksToStayPressed, properties), SimpleWaterloggedBlock {
|
||||
companion object {
|
||||
val FLOOR_UNPOWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
|
||||
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.3125, 0.625) )
|
||||
val FLOOR_POWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
|
||||
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.25, 0.625) )
|
||||
val FLOOR_PANEL = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875)
|
||||
val CEILING_UNPOWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
|
||||
Shapes.box(0.375, 0.6875, 0.375, 0.625, 0.8125, 0.625) )
|
||||
val CEILING_POWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
|
||||
Shapes.box(0.375, 0.75, 0.375, 0.625, 0.8125, 0.625) )
|
||||
val CEILING_PANEL = Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875)
|
||||
val WALL_UNPOWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
|
||||
Shapes.box(0.375, 0.375, 0.6875, 0.625, 0.625, 0.8125) )
|
||||
val WALL_POWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
|
||||
Shapes.box(0.375, 0.375, 0.75, 0.625, 0.625, 0.8125) )
|
||||
val WALL_PANEL = Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(POWERED, false)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING, FACE, POWERED, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
for (direction in context.nearestLookingDirections) {
|
||||
val blockState: BlockState
|
||||
if (direction.getAxis() === Direction.Axis.Y) {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
|
||||
.setValue(FACING, context.horizontalDirection.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
} else {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, AttachFace.WALL) as BlockState)
|
||||
.setValue(FACING, direction.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
}
|
||||
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR -> if (state.getValue(POWERED)) {FLOOR_POWERED} else {FLOOR_UNPOWERED}
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED}
|
||||
else -> rotateShape(Direction.NORTH, direction, if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED})
|
||||
}
|
||||
AttachFace.CEILING -> if (state.getValue(POWERED)) {CEILING_POWERED} else {CEILING_UNPOWERED}
|
||||
}
|
||||
}
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR -> FLOOR_PANEL
|
||||
AttachFace.WALL -> when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_PANEL
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_PANEL)
|
||||
}
|
||||
AttachFace.CEILING -> CEILING_PANEL
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return if (state.getValue(FACE) != AttachFace.WALL) {
|
||||
val direction = getConnectedDirection(state)
|
||||
canSupportCenter(level, pos.relative(direction.opposite), direction)
|
||||
} else {
|
||||
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +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.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class GalaricaWreathBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<GalaricaWreathBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::GalaricaWreathBlock)
|
||||
}
|
||||
|
||||
val FLOOR_NORTH = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.25, 0.875)
|
||||
val WALL_NORTH = Shapes.box(0.125, 0.125, 0.75, 0.875, 0.875, 1.0)
|
||||
val CEILING_NORTH = Shapes.box(0.125, 0.125, 0.375, 0.875, 0.625, 0.5625)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<GalaricaWreathBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
|
||||
} else FLOOR_NORTH
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
|
||||
}
|
||||
AttachFace.CEILING ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +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.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class MagnetBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<MagnetBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::MagnetBlock)
|
||||
}
|
||||
|
||||
val FLOOR_NORTH = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.1875, 0.75)
|
||||
val WALL_NORTH = Shapes.box(0.25, 0.25, 0.8125, 0.75, 0.75, 1.0)
|
||||
val CEILING_NORTH = Shapes.box(0.25, 0.8125, 0.25, 0.75, 1.0, 0.75)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<MagnetBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
|
||||
} else FLOOR_NORTH
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
|
||||
}
|
||||
AttachFace.CEILING ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,68 +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.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class OrbBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<OrbBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::OrbBlock)
|
||||
}
|
||||
|
||||
val FLOOR_NORTH = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
|
||||
val WALL_NORTH = Shapes.box(0.3125, 0.3125, 0.625, 0.6875, 0.6875, 1.0)
|
||||
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.375, 0.6875, 1.0, 0.6875)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<OrbBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
|
||||
} else FLOOR_NORTH
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
|
||||
}
|
||||
AttachFace.CEILING ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,25 @@ import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class PlaqueBlock(properties : Properties) : SixFaceRotationalBlock(properties) {
|
||||
class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
|
||||
companion object {
|
||||
val CODEC: MapCodec<PlaqueBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::PlaqueBlock)
|
||||
@@ -44,6 +54,40 @@ class PlaqueBlock(properties : Properties) : SixFaceRotationalBlock(properties)
|
||||
)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING, FACE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
for (direction in context.nearestLookingDirections) {
|
||||
val blockState: BlockState
|
||||
if (direction.getAxis() === Direction.Axis.Y) {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
|
||||
.setValue(FACING, context.horizontalDirection.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
} else {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, AttachFace.WALL) as BlockState)
|
||||
.setValue(FACING, direction.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
|
||||
}
|
||||
|
||||
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<PlaqueBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
@@ -64,4 +108,21 @@ class PlaqueBlock(properties : Properties) : SixFaceRotationalBlock(properties)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return if (state.getValue(FACE) == AttachFace.CEILING) {
|
||||
val direction = getConnectedDirection(state)
|
||||
canSupportCenter(level, pos.relative(direction.opposite), direction)
|
||||
} else {
|
||||
super.canSurvive(state, level, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +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.block.decor
|
||||
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.block.state.properties.Property
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class RingTargetBlock(settings: Properties) : TargetBlock(settings), SimpleWaterloggedBlock {
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(WATERLOGGED, false)
|
||||
.setValue(OUTPUT_POWER, 0))
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
|
||||
var blockState = defaultBlockState()
|
||||
.setValue(WATERLOGGED, ctx.level.getFluidState(ctx.clickedPos).type == Fluids.WATER)
|
||||
val worldView = ctx.level
|
||||
val blockPos = ctx.clickedPos
|
||||
ctx.nearestLookingDirections.forEach { direction ->
|
||||
if (direction.axis.isHorizontal) {
|
||||
blockState = blockState.setValue(FACING, direction.opposite) as BlockState
|
||||
if (blockState.canSurvive(worldView, blockPos)) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
builder.add(WATERLOGGED)
|
||||
builder.add(*arrayOf<Property<*>>(OUTPUT_POWER))
|
||||
}
|
||||
|
||||
override fun getShape(
|
||||
state: BlockState,
|
||||
blockGetter: BlockGetter,
|
||||
pos: BlockPos,
|
||||
collisionContext: CollisionContext
|
||||
): VoxelShape {
|
||||
return when (state.getValue(FACING)) {
|
||||
Direction.SOUTH -> FULL_PLANE_SOUTH
|
||||
Direction.WEST -> FULL_PLANE_WEST
|
||||
Direction.EAST -> FULL_PLANE_EAST
|
||||
else -> FULL_PLANE_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateShape(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
world: LevelAccessor,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (direction.opposite == state.getValue(FACING) && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
|
||||
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<RingTargetBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(
|
||||
propertiesCodec()
|
||||
).apply(it, ::RingTargetBlock) }
|
||||
|
||||
val OUTPUT_POWER = BlockStateProperties.POWER;
|
||||
|
||||
val FULL_PLANE_NORTH = Shapes.box(0.0, 0.0, 0.925, 1.0, 1.0, 1.0)
|
||||
val FULL_PLANE_WEST = Shapes.box(0.925, 0.0, 0.0, 1.0, 1.0, 1.0)
|
||||
val FULL_PLANE_SOUTH = Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0, 0.075)
|
||||
val FULL_PLANE_EAST = Shapes.box(0.0, 0.0, 0.0, 0.075, 1.0, 1.0)
|
||||
}
|
||||
}
|
||||
@@ -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.block
|
||||
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
|
||||
open class SixFaceRotationalBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
|
||||
companion object {
|
||||
val CODEC = simpleCodec(::SixFaceRotationalBlock)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<out SixFaceRotationalBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING, FACE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
for (direction in context.nearestLookingDirections) {
|
||||
val blockState: BlockState
|
||||
if (direction.getAxis() === Direction.Axis.Y) {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
|
||||
.setValue(FACING, context.horizontalDirection.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
} else {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, AttachFace.WALL) as BlockState)
|
||||
.setValue(FACING, direction.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
|
||||
}
|
||||
|
||||
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return if (state.getValue(FACE) == AttachFace.CEILING) {
|
||||
val direction = getConnectedDirection(state)
|
||||
canSupportCenter(level, pos.relative(direction.opposite), direction)
|
||||
} else if (state.getValue(FACE) == AttachFace.WALL) {
|
||||
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
|
||||
} else {
|
||||
super.canSurvive(state, level, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,7 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
|
||||
ETHER,
|
||||
MAX_POTION,
|
||||
POTION,
|
||||
STATUS_RESTORE,
|
||||
VITAMIN,
|
||||
METAL_COAT,
|
||||
IRON_BALL,
|
||||
THROAT_SPRAY,
|
||||
UPGRADE,
|
||||
DICE;
|
||||
STATUS_RESTORE;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
@@ -63,78 +57,98 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
|
||||
StackableItemBlockType.CODEC.fieldOf("type").forGetter(StackableItemBlock::type)
|
||||
).apply(it, ::StackableItemBlock) }
|
||||
|
||||
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>>
|
||||
get() = mapOf(
|
||||
StackableItemBlockType.BATTLE_ITEM to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
|
||||
Shapes.box(0.09375, 0.0, 0.34375, 0.90625, 0.4375, 0.71875),
|
||||
Shapes.box(0.09375, 0.0, 0.09375, 0.90625, 0.4375, 0.90625),
|
||||
Shapes.box(0.09375, 0.0, 0.09375, 0.96875, 0.4375, 0.90625)
|
||||
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>> = mapOf(
|
||||
StackableItemBlockType.BATTLE_ITEM to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.34375, 0.40625, 0.4375, 0.65625),
|
||||
Shapes.box(0.59375, 0.0, 0.40625, 0.90625, 0.4375, 0.71875)
|
||||
),
|
||||
StackableItemBlockType.ETHER to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.5, 1.0),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.5, 1.0)
|
||||
Shapes.or(
|
||||
Shapes.box(0.59375, 0.0, 0.59375, 0.90625, 0.4375, 0.90625),
|
||||
Shapes.box(0.09375, 0.0, 0.53125, 0.40625, 0.4375, 0.84375),
|
||||
Shapes.box(0.34375, 0.0, 0.09375, 0.65625, 0.4375, 0.40625)
|
||||
),
|
||||
StackableItemBlockType.MAX_POTION to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
|
||||
Shapes.box(0.09375, 0.0, 0.3125, 0.90625, 0.5, 0.75),
|
||||
Shapes.box(0.09375, 0.0, 0.0625, 0.90625, 0.5, 1.0),
|
||||
Shapes.box(0.09375, 0.0, 0.0625, 0.96875, 0.5, 1.0)
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.09375, 0.40625, 0.4375, 0.40625),
|
||||
Shapes.box(0.59375, 0.0, 0.15625, 0.90625, 0.4375, 0.46875),
|
||||
Shapes.box(0.65625, 0.0, 0.59375, 0.96875, 0.4375, 0.90625),
|
||||
Shapes.box(0.15625, 0.0, 0.53125, 0.46875, 0.4375, 0.84375)
|
||||
)
|
||||
),
|
||||
StackableItemBlockType.ETHER to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
|
||||
Shapes.or(
|
||||
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5, 0.6875)
|
||||
),
|
||||
StackableItemBlockType.POTION to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5625, 0.8125),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.5625, 1.0),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.5625, 1.0)
|
||||
Shapes.or(
|
||||
Shapes.box(0.375, 0.0, 0.125, 0.625, 0.5, 0.5),
|
||||
Shapes.box(0.125, 0.0, 0.5625, 0.375, 0.5, 0.9375),
|
||||
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 1.0)
|
||||
),
|
||||
StackableItemBlockType.STATUS_RESTORE to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.4375, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.4375, 0.9375),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.4375, 0.9375)
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.375, 0.5, 0.4375),
|
||||
Shapes.box(0.625, 0.0, 0.125, 0.875, 0.5, 0.5),
|
||||
Shapes.box(0.6875, 0.0, 0.625, 0.9375, 0.5, 1.0),
|
||||
Shapes.box(0.1875, 0.0, 0.5625, 0.4375, 0.5, 0.9375)
|
||||
)
|
||||
),
|
||||
StackableItemBlockType.MAX_POTION to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.3125, 0.40625, 0.5, 0.6875),
|
||||
Shapes.box(0.59375, 0.0, 0.375, 0.90625, 0.5, 0.75)
|
||||
),
|
||||
StackableItemBlockType.METAL_COAT to listOf(
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.375, 0.875),
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375)
|
||||
Shapes.or(
|
||||
Shapes.box(0.34375, 0.0, 0.0625, 0.65625, 0.5, 0.4375),
|
||||
Shapes.box(0.09375, 0.0, 0.5625, 0.40625, 0.5, 0.9375),
|
||||
Shapes.box(0.59375, 0.0, 0.625, 0.90625, 0.5, 1.0)
|
||||
),
|
||||
StackableItemBlockType.VITAMIN to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.4375, 0.625),
|
||||
Shapes.box(0.1875, 0.0, 0.3125, 0.8125, 0.4375, 0.6875),
|
||||
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.4375, 0.8125),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.4375, 0.875)
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.0625, 0.40625, 0.5, 0.4375),
|
||||
Shapes.box(0.15625, 0.0, 0.5625, 0.46875, 0.5, 0.9375),
|
||||
Shapes.box(0.65625, 0.0, 0.625, 0.96875, 0.5, 1.0),
|
||||
Shapes.box(0.59375, 0.0, 0.125, 0.90625, 0.5, 0.5)
|
||||
).singleEncompassing()
|
||||
),
|
||||
StackableItemBlockType.POTION to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5625, 0.75),
|
||||
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5625, 0.8125)
|
||||
),
|
||||
StackableItemBlockType.IRON_BALL to listOf(
|
||||
Shapes.box(0.25, 0.0, 0.25, 0.75, 0.4375, 0.75),
|
||||
Shapes.box(0.0, 0.0, 0.1875, 1.0, 0.4375, 0.8125),
|
||||
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
|
||||
Shapes.or(
|
||||
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
|
||||
Shapes.box(0.25, 0.4375, 0.3125, 0.6875, 0.875, 0.75)
|
||||
)
|
||||
Shapes.or(
|
||||
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.5625, 0.5),
|
||||
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.5625, 1.0),
|
||||
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.5625, 0.9375)
|
||||
),
|
||||
StackableItemBlockType.THROAT_SPRAY to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.1875, 0.625, 0.5625, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.1875, 0.875, 0.5625, 0.8125),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.875, 0.5625, 1.0),
|
||||
Shapes.box(0.075, 0.0, 0.0, 0.9375, 0.5625, 1.0)
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.5625, 0.4375),
|
||||
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.5625, 0.5),
|
||||
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.5625, 1.0),
|
||||
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.5625, 0.9375)
|
||||
)
|
||||
),
|
||||
StackableItemBlockType.STATUS_RESTORE to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.4375, 0.75),
|
||||
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.4375, 0.75)
|
||||
),
|
||||
StackableItemBlockType.UPGRADE to listOf(
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.125, 0.6875),
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875),
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.5, 0.6875)
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.4375, 0.875),
|
||||
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.4375, 0.9375),
|
||||
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.4375, 0.4375)
|
||||
),
|
||||
StackableItemBlockType.DICE to listOf(
|
||||
Shapes.box(0.125, 0.0, 0.6875, 0.3125, 0.1875, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.625, 0.5625, 0.1875, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.1875, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.375, 0.875)
|
||||
Shapes.or(
|
||||
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.4375, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.4375, 0.375),
|
||||
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.4375, 0.4375),
|
||||
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.4375, 0.9375)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
fun getShape(type: StackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
|
||||
fun getMaxAmount(type: StackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
|
||||
@@ -170,7 +184,7 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getCollisionShape(state, level, pos, context)
|
||||
return getCollisionShape(state, level, pos, context).singleEncompassing()
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
|
||||
@@ -128,7 +128,6 @@ class DiscShelfBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobbl
|
||||
|
||||
fun isValidItem(stack: ItemStack): Boolean {
|
||||
return stack.item is TechnicalMachineItem
|
||||
|| stack.item == CobblemonItems.BLANK_TM
|
||||
|| stack.item == CobblemonItems.UPGRADE
|
||||
|| stack.item == CobblemonItems.DUBIOUS_DISC
|
||||
|| stack.has(DataComponents.JUKEBOX_PLAYABLE)
|
||||
|
||||
-2
@@ -31,7 +31,6 @@ import com.cobblemon.mod.common.client.sound.instances.CancellableSoundInstance
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.item.PokeBallItem
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
@@ -236,7 +235,6 @@ class FossilMultiblockStructure (
|
||||
entity.setPos(fixedPosition.center.subtract(0.0, 0.5, 0.0))
|
||||
// TODO: Find a correct way to set the new entity's Yaw rotation. (Face away from the machine)
|
||||
if (world.addFreshEntity(entity)) {
|
||||
pokemon.state = SentOutState(entity)
|
||||
CobblemonEvents.FOSSIL_REVIVED.post(FossilRevivedEvent(pokemon, null))
|
||||
return true
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
@@ -22,7 +24,6 @@ import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.block.entity.TintBlockEntity
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattle
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlay
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
|
||||
@@ -45,7 +46,6 @@ import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
|
||||
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
|
||||
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
|
||||
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
|
||||
import com.cobblemon.mod.common.client.render.layer.PlayerRecallBallRenderer
|
||||
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
|
||||
@@ -126,6 +126,7 @@ object CobblemonClient {
|
||||
storage.onLogout()
|
||||
battle = null
|
||||
battleOverlay.onLogout()
|
||||
ClientBattleInformationRepository.clear()
|
||||
ClientTaskTracker.clear()
|
||||
checkedStarterScreen = false
|
||||
overlay.resetAttachedToast()
|
||||
@@ -240,12 +241,6 @@ object CobblemonClient {
|
||||
CobblemonBlocks.POKE_CAKE
|
||||
)
|
||||
|
||||
this.implementation.registerBlockRenderType(
|
||||
RenderType.translucent(),
|
||||
CobblemonBlocks.UPGRADE,
|
||||
CobblemonBlocks.DUBIOUS_DISC
|
||||
)
|
||||
|
||||
this.implementation.registerBlockRenderType(
|
||||
RenderType.cutout(),
|
||||
CobblemonBlocks.GILDED_CHEST,
|
||||
@@ -391,31 +386,7 @@ object CobblemonClient {
|
||||
CobblemonBlocks.X_SP_ATK,
|
||||
CobblemonBlocks.X_SP_DEF,
|
||||
CobblemonBlocks.X_SPEED,
|
||||
CobblemonBlocks.TM_MACHINE,
|
||||
|
||||
CobblemonBlocks.GALARICA_CUFF,
|
||||
CobblemonBlocks.GALARICA_WREATH,
|
||||
CobblemonBlocks.CHIPPED_POT,
|
||||
CobblemonBlocks.CRACKED_POT,
|
||||
CobblemonBlocks.UNREMARKABLE_TEACUP,
|
||||
CobblemonBlocks.MASTERPIECE_TEACUP,
|
||||
CobblemonBlocks.LUMINOUS_MOSS,
|
||||
CobblemonBlocks.ICY_ROCK,
|
||||
CobblemonBlocks.HEAT_ROCK,
|
||||
CobblemonBlocks.SMOOTH_ROCK,
|
||||
CobblemonBlocks.DAMP_ROCK,
|
||||
CobblemonBlocks.DESTINY_KNOT,
|
||||
CobblemonBlocks.METRONOME,
|
||||
CobblemonBlocks.KINGS_ROCK,
|
||||
CobblemonBlocks.FLAME_ORB,
|
||||
CobblemonBlocks.LIFE_ORB,
|
||||
CobblemonBlocks.TOXIC_ORB,
|
||||
CobblemonBlocks.LIGHT_BALL,
|
||||
CobblemonBlocks.SMOKE_BALL,
|
||||
CobblemonBlocks.NEVER_MELT_ICE,
|
||||
CobblemonBlocks.WIDE_LENS,
|
||||
CobblemonBlocks.ZOOM_LENS,
|
||||
CobblemonBlocks.SCOPE_LENS
|
||||
CobblemonBlocks.TM_MACHINE
|
||||
)
|
||||
|
||||
this.createBoatModelLayers()
|
||||
@@ -437,10 +408,8 @@ object CobblemonClient {
|
||||
var renderer: LivingEntityRenderer<Player, PlayerModel<Player>>? =
|
||||
skinMap?.get(PlayerSkin.Model.WIDE) as LivingEntityRenderer<Player, PlayerModel<Player>>
|
||||
renderer?.addLayer(PokemonOnShoulderRenderer(renderer))
|
||||
renderer?.addLayer(PlayerRecallBallRenderer(renderer))
|
||||
renderer = skinMap[PlayerSkin.Model.SLIM] as LivingEntityRenderer<Player, PlayerModel<Player>>?
|
||||
renderer?.addLayer(PokemonOnShoulderRenderer(renderer))
|
||||
renderer?.addLayer(PlayerRecallBallRenderer(renderer))
|
||||
}
|
||||
|
||||
private fun registerMenuScreens() {
|
||||
@@ -517,6 +486,7 @@ object CobblemonClient {
|
||||
}
|
||||
|
||||
fun endBattle() {
|
||||
ClientBattleInformationRepository.clear()
|
||||
battle = null
|
||||
battleOverlay.lastKnownBattle = null
|
||||
BattleMusicController.endMusic()
|
||||
|
||||
@@ -84,6 +84,7 @@ class ClientBattle(
|
||||
pendingActionRequests.map { it.response!! }
|
||||
)
|
||||
)
|
||||
ClientBattleInformationRepository.mustChooseBy = null
|
||||
mustChoose = false
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
object ClientBattleInformationRepository {
|
||||
val actors = mutableMapOf<UUID, MutableList<BattlePokemonDTO>>()
|
||||
val battles = mutableMapOf<UUID, BattleInformationDTO>()
|
||||
val actorActualSides = mutableMapOf<UUID, Int>()
|
||||
var mustChooseBy: Instant? = null
|
||||
|
||||
fun clear() {
|
||||
actors.clear()
|
||||
battles.clear()
|
||||
actorActualSides.clear()
|
||||
mustChooseBy = null
|
||||
}
|
||||
}
|
||||
+17
-1
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import net.minecraft.util.FormattedCharSequence
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
/**
|
||||
* A wrapper around the client battle message information. This is essentially an appending list of all
|
||||
@@ -21,6 +22,21 @@ import net.minecraft.util.FormattedCharSequence
|
||||
class ClientBattleMessageQueue {
|
||||
var listeners = mutableListOf<(FormattedCharSequence) -> Unit>()
|
||||
private val messages = mutableListOf<FormattedCharSequence>()
|
||||
private val components = mutableListOf<Component>()
|
||||
private val componentListeners = mutableListOf<(Component) -> Unit>()
|
||||
|
||||
fun addComponents(messages: Iterable<Component>) {
|
||||
messages.forEach { message ->
|
||||
components.add(message)
|
||||
componentListeners.toList().forEach { it(message) }
|
||||
}
|
||||
}
|
||||
|
||||
fun subscribeComponents(listener: (Component) -> Unit): () -> Unit {
|
||||
componentListeners.add(listener)
|
||||
components.forEach(listener)
|
||||
return { componentListeners.remove(listener) }
|
||||
}
|
||||
|
||||
fun add(messages: Iterable<FormattedCharSequence>) {
|
||||
this.messages.addAll(messages)
|
||||
@@ -31,4 +47,4 @@ class ClientBattleMessageQueue {
|
||||
this.listeners.add(listener)
|
||||
messages.forEach(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
|
||||
object MoveEffectivenessCalculator {
|
||||
|
||||
fun getMoveEffectiveness(attack: ElementalType, defenderType1: ElementalType, defenderType2: ElementalType?): Effectiveness {
|
||||
val type1Effectiveness = getEffectiveness(attack, defenderType1)
|
||||
val type2Effectiveness = if (defenderType2 == null) 1f else getEffectiveness(attack, defenderType2)
|
||||
val typeEffectiveness = type1Effectiveness * type2Effectiveness
|
||||
|
||||
return when {
|
||||
typeEffectiveness == 0f -> Effectiveness.IMMUNE
|
||||
typeEffectiveness == 0.25f -> Effectiveness.MOSTLY_INEFFECTIVE
|
||||
typeEffectiveness == 0.5f -> Effectiveness.NOT_VERY_EFFECTIVE
|
||||
typeEffectiveness == 1f -> Effectiveness.EFFECTIVE
|
||||
typeEffectiveness == 2f -> Effectiveness.SUPER_EFFECTIVE
|
||||
typeEffectiveness == 4f -> Effectiveness.EXTREMELY_EFFECTIVE
|
||||
else -> Effectiveness.IMMUNE
|
||||
}
|
||||
}
|
||||
|
||||
fun getEffectiveness(attack: ElementalType, defender: ElementalType): Float {
|
||||
if (isImmune(attack, defender)) return 0f
|
||||
if (isSuperEffective(attack, defender)) return 2f
|
||||
if (isNotVeryEffective(attack, defender)) return 0.5f
|
||||
return 1f
|
||||
}
|
||||
|
||||
fun isImmune(attack: ElementalType, defender: ElementalType): Boolean {
|
||||
when (attack) {
|
||||
ElementalTypes.NORMAL -> return defender == ElementalTypes.GHOST
|
||||
ElementalTypes.FIRE -> return false
|
||||
ElementalTypes.WATER -> return false
|
||||
ElementalTypes.GRASS -> return false
|
||||
ElementalTypes.ELECTRIC -> return defender == ElementalTypes.GROUND
|
||||
ElementalTypes.ICE -> return false
|
||||
ElementalTypes.FIGHTING -> return defender == ElementalTypes.GHOST
|
||||
ElementalTypes.POISON -> return defender == ElementalTypes.STEEL
|
||||
ElementalTypes.GROUND -> return defender == ElementalTypes.FLYING
|
||||
ElementalTypes.FLYING -> return false
|
||||
ElementalTypes.PSYCHIC -> return defender == ElementalTypes.DARK
|
||||
ElementalTypes.BUG -> return false
|
||||
ElementalTypes.ROCK -> return false
|
||||
ElementalTypes.GHOST -> return defender == ElementalTypes.NORMAL
|
||||
ElementalTypes.DRAGON -> return defender == ElementalTypes.FAIRY
|
||||
ElementalTypes.DARK -> return false
|
||||
ElementalTypes.STEEL -> return false
|
||||
ElementalTypes.FAIRY -> return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isSuperEffective(attack: ElementalType, defender: ElementalType): Boolean {
|
||||
when (attack) {
|
||||
ElementalTypes.NORMAL -> return false
|
||||
ElementalTypes.FIRE -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.ICE, ElementalTypes.BUG, ElementalTypes.STEEL)
|
||||
ElementalTypes.WATER -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.GROUND, ElementalTypes.ROCK)
|
||||
ElementalTypes.GRASS -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.GROUND, ElementalTypes.ROCK)
|
||||
ElementalTypes.ELECTRIC -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.FLYING)
|
||||
ElementalTypes.ICE -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.GROUND, ElementalTypes.FLYING, ElementalTypes.DRAGON)
|
||||
ElementalTypes.FIGHTING -> return defender in listOf(ElementalTypes.NORMAL, ElementalTypes.ICE, ElementalTypes.ROCK, ElementalTypes.DARK, ElementalTypes.STEEL)
|
||||
ElementalTypes.POISON -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.FAIRY)
|
||||
ElementalTypes.GROUND -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.ELECTRIC, ElementalTypes.POISON, ElementalTypes.ROCK, ElementalTypes.STEEL)
|
||||
ElementalTypes.FLYING -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.FIGHTING, ElementalTypes.BUG)
|
||||
ElementalTypes.PSYCHIC -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.POISON)
|
||||
ElementalTypes.BUG -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.PSYCHIC, ElementalTypes.DARK)
|
||||
ElementalTypes.ROCK -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.ICE, ElementalTypes.FLYING, ElementalTypes.BUG)
|
||||
ElementalTypes.GHOST -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.GHOST)
|
||||
ElementalTypes.DRAGON -> return defender in listOf(ElementalTypes.DRAGON)
|
||||
ElementalTypes.DARK -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.GHOST)
|
||||
ElementalTypes.STEEL -> return defender in listOf(ElementalTypes.ICE, ElementalTypes.ROCK, ElementalTypes.FAIRY)
|
||||
ElementalTypes.FAIRY -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.DRAGON, ElementalTypes.DARK)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isNotVeryEffective(attack: ElementalType, defender: ElementalType): Boolean {
|
||||
when (attack) {
|
||||
ElementalTypes.NORMAL -> return defender in listOf(ElementalTypes.ROCK, ElementalTypes.STEEL)
|
||||
ElementalTypes.FIRE -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ROCK, ElementalTypes.DRAGON)
|
||||
ElementalTypes.WATER -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.GRASS, ElementalTypes.DRAGON)
|
||||
ElementalTypes.GRASS -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.GRASS, ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.BUG, ElementalTypes.DRAGON, ElementalTypes.STEEL)
|
||||
ElementalTypes.ELECTRIC -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.ELECTRIC, ElementalTypes.DRAGON)
|
||||
ElementalTypes.ICE -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ICE, ElementalTypes.STEEL)
|
||||
ElementalTypes.FIGHTING -> return defender in listOf(ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.PSYCHIC, ElementalTypes.BUG, ElementalTypes.FAIRY)
|
||||
ElementalTypes.POISON -> return defender in listOf(ElementalTypes.POISON, ElementalTypes.GROUND, ElementalTypes.ROCK, ElementalTypes.GHOST)
|
||||
ElementalTypes.GROUND -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.BUG)
|
||||
ElementalTypes.FLYING -> return defender in listOf(ElementalTypes.ELECTRIC, ElementalTypes.ROCK, ElementalTypes.STEEL)
|
||||
ElementalTypes.PSYCHIC -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.STEEL)
|
||||
ElementalTypes.BUG -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.FIGHTING, ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.GHOST, ElementalTypes.STEEL, ElementalTypes.FAIRY)
|
||||
ElementalTypes.ROCK -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.GROUND, ElementalTypes.STEEL)
|
||||
ElementalTypes.GHOST -> return defender in listOf(ElementalTypes.DARK)
|
||||
ElementalTypes.DRAGON -> return defender in listOf(ElementalTypes.STEEL)
|
||||
ElementalTypes.DARK -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.DARK, ElementalTypes.FAIRY)
|
||||
ElementalTypes.STEEL -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ELECTRIC, ElementalTypes.STEEL)
|
||||
ElementalTypes.FAIRY -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.POISON, ElementalTypes.STEEL)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class Effectiveness {
|
||||
EXTREMELY_EFFECTIVE,
|
||||
SUPER_EFFECTIVE,
|
||||
EFFECTIVE,
|
||||
NOT_VERY_EFFECTIVE,
|
||||
MOSTLY_INEFFECTIVE,
|
||||
IMMUNE
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.battle.preview
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
|
||||
import net.minecraft.network.chat.Component
|
||||
import java.time.Instant
|
||||
|
||||
class ClientBattleTeamPreview(
|
||||
val selections: Int,
|
||||
val team: List<BattlePokemonDTO?>,
|
||||
val opponent: List<BattlePokemonDTO?>,
|
||||
val mustPickBy: Instant,
|
||||
val opponentName: Component
|
||||
) {
|
||||
val selection = mutableListOf<BattlePokemonDTO>()
|
||||
|
||||
fun select(pokemon: BattlePokemonDTO) {
|
||||
if (pokemon !in team || pokemon in selection || selection.size >= selections) return
|
||||
selection.add(pokemon)
|
||||
sendChangePacket()
|
||||
}
|
||||
|
||||
fun unselect(pokemon: BattlePokemonDTO) {
|
||||
selection.remove(pokemon)
|
||||
sendChangePacket()
|
||||
}
|
||||
|
||||
private fun sendChangePacket() {
|
||||
val indexes = selection.map { team.indexOf(it) }
|
||||
if (indexes.any { it == -1 }) return
|
||||
CobblemonNetwork.sendToServer(TeamPreviewSelectPokemonPacket(indexes.toSet()))
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker
|
||||
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
|
||||
@@ -321,7 +320,6 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
|
||||
ballOffset = 0f
|
||||
ballRotOffset = 0f
|
||||
sendOutPosition = null
|
||||
currentEntity.ownerUUID?.let { RecallArmAnimationTracker.start(it, currentEntity.exposedBall) }
|
||||
afterOnClient(seconds = BEAM_EXTEND_TIME) {
|
||||
activeSendoutScale = 1F
|
||||
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
|
||||
var stackHeight = -scrollAmount.toInt()
|
||||
guiGraphics.enableScissor(x, y, x + width, y + height)
|
||||
children.forEachIndexed { index, entry ->
|
||||
if (stackHeight < height && stackHeight + entry.height > 0) {
|
||||
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
|
||||
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
|
||||
}
|
||||
stackHeight += entry.height
|
||||
|
||||
@@ -20,10 +20,12 @@ import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionS
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.ForfeitConfirmationSelection
|
||||
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
|
||||
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
|
||||
import com.cobblemon.mod.common.client.keybind.boundKey
|
||||
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
@@ -44,15 +46,16 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
val forfeitResource = cobblemonResource("textures/gui/battle/battle_menu_forfeit.png")
|
||||
}
|
||||
|
||||
private lateinit var messagePane: BattleMessagePane
|
||||
lateinit var messagePane: BattleMessagePane
|
||||
var opacity = 0F
|
||||
val actor = CobblemonClient.battle?.side1?.actors?.find { it.uuid == Minecraft.getInstance().player?.uuid }
|
||||
val specBackButton = BattleBackButton(12f, Minecraft.getInstance().window.guiScaledHeight - 32f)
|
||||
val specBackButton = BattleBackButton(OPTION_ROOT_X - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
var queuedActions = mutableListOf<() -> Unit>()
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
if (this::messagePane.isInitialized) messagePane.dispose()
|
||||
messagePane = BattleMessagePane(CobblemonClient.battle!!.messages)
|
||||
messagePane.opacity = CobblemonClient.battleOverlay.opacityRatio.toFloat().coerceAtLeast(0.3F)
|
||||
addRenderableWidget(messagePane)
|
||||
@@ -121,16 +124,17 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
val currentSelection = getCurrentActionSelection()
|
||||
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
|
||||
if (currentSelection == null || currentSelection is BattleGeneralActionSelection) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
|
||||
x = Minecraft.getInstance().window.guiScaledWidth / 2,
|
||||
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
|
||||
y = Minecraft.getInstance().window.guiScaledHeight / 5,
|
||||
opacity = 0.75F * opacity,
|
||||
centered = true
|
||||
)
|
||||
} else if (currentSelection is ForfeitConfirmationSelection) {
|
||||
}
|
||||
if (currentSelection is ForfeitConfirmationSelection) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = battleLang("ui.forfeit_confirmation", PartySendBinding.boundKey().displayName),
|
||||
@@ -144,6 +148,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
|
||||
queuedActions.forEach { it() }
|
||||
queuedActions.clear()
|
||||
CobblemonClient.battleOverlay.mouseX = mouseX
|
||||
CobblemonClient.battleOverlay.mouseY = mouseY
|
||||
// The benched-team portrait columns + active hover panels are queued during the HUD pass and drawn
|
||||
// here, last, so they sit above the switch-menu underlay, our own widgets and the chat.
|
||||
CobblemonClient.battleOverlay.renderDeferredColumns(context)
|
||||
}
|
||||
|
||||
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
@@ -151,8 +160,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
|
||||
|
||||
|
||||
return if (request.forceSwitch) {
|
||||
BattleSwitchPokemonSelection(this, request)
|
||||
} else {
|
||||
@@ -175,11 +182,23 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
CobblemonClient.battle?.minimised = true
|
||||
}
|
||||
|
||||
override fun removed() {
|
||||
if (this::messagePane.isInitialized) messagePane.dispose()
|
||||
super.removed()
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (this::messagePane.isInitialized) messagePane.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
if (CobblemonClient.battleOverlay.columnMouseDragged(mouseX, mouseY)) return true
|
||||
if (this::messagePane.isInitialized && messagePane.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (this::messagePane.isInitialized) messagePane.mouseReleased(mouseX, mouseY, button)
|
||||
if (CobblemonClient.battleOverlay.columnMouseReleased()) return true
|
||||
return super.mouseReleased(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun charTyped(chr: Char, modifiers: Int): Boolean {
|
||||
if (chr.toString().equals(PartySendBinding.boundKey().displayName.string, ignoreCase = true) && CobblemonClient.battleOverlay.opacity == BattleOverlay.MAX_OPACITY && PartySendBinding.canAction()) {
|
||||
return minimizeBattle()
|
||||
@@ -188,14 +207,18 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
// Grab a benched-team portrait column to drag it (left-click). Consumes the click if one was hit.
|
||||
if (button == 0 && CobblemonClient.battleOverlay.columnMouseClicked(mouseX, mouseY)) return true
|
||||
if (button == PartySendBinding.boundKey().value && CobblemonClient.battleOverlay.opacity == BattleOverlay.MAX_OPACITY && PartySendBinding.canAction()) {
|
||||
return minimizeBattle()
|
||||
}
|
||||
|
||||
val battle = CobblemonClient.battle
|
||||
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
|
||||
RemoveSpectatorPacket(battle.battleId).sendToServer()
|
||||
CobblemonClient.endBattle()
|
||||
if (battle?.spectating == true) {
|
||||
if (specBackButton.isHovered(mouseX, mouseY)) {
|
||||
RemoveSpectatorPacket(battle.battleId).sendToServer()
|
||||
CobblemonClient.endBattle()
|
||||
}
|
||||
}
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
@@ -206,4 +229,4 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
PartySendBinding.actioned()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1666
-72
File diff suppressed because it is too large
Load Diff
+682
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.network.chat.TextColor
|
||||
import net.minecraft.network.chat.contents.TranslatableContents
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* The "Info" style expanded stat panel for a single battle Pokémon (form, buffs/debuffs, ability,
|
||||
* moves, speed/speed-tier, types, held item). It renders to the *side* of a tile, with a connector
|
||||
* pointing back at it, and expands inwards (towards the screen centre) on both sides.
|
||||
*
|
||||
* Shared by the team Info screen (the team Info screen)
|
||||
* and the on-field benched-team portrait column ([BattleOverlay.drawPartyColumn]) so the two stay identical.
|
||||
*
|
||||
* Knowledge gating is inherited from [dto]: the team owner gets full data while opponents only carry
|
||||
* what has been revealed, so unrevealed ability/moves show "?" and speed falls back to a tier range.
|
||||
*/
|
||||
object BattlePokemonInfoPanel {
|
||||
private val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
|
||||
// Variant for non-active Pokémon: the left (Buffs/Debuffs) column is cleared for a Current HP gauge.
|
||||
private val expandedPokemonInfoInactive = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center_inactive.png")
|
||||
// Same battle health-bar texture the active Pokémon's tile uses, for the Current HP gauge.
|
||||
private val healthBar = cobblemonResource("textures/gui/battle/health_bar.png")
|
||||
private val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
|
||||
private val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
|
||||
|
||||
private val multiplierFormat = DecimalFormat("0.##")
|
||||
|
||||
// Boost value slots span x=46..65; leave one pixel of padding at the battle text scale.
|
||||
internal const val BOOST_VALUE_CENTER_X = 55.5
|
||||
internal const val BOOST_VALUE_MAX_WIDTH = 36 // 18 GUI pixels at BattleOverlay.SCALE.
|
||||
|
||||
fun renderUnknownTile(context: GuiGraphics, x: Float, y: Float, reversed: Boolean, scale: Float = 1F, opacity: Number = 1F) {
|
||||
val matrices = context.pose()
|
||||
val insetX = if (reversed) 8 else 5
|
||||
matrices.pushPose()
|
||||
matrices.translate(x.toDouble(), y.toDouble(), 0.0)
|
||||
matrices.scale(scale, scale, scale)
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = BattleOverlay.battleInfoUnderlay,
|
||||
x = insetX,
|
||||
y = 4,
|
||||
width = 28,
|
||||
height = 28,
|
||||
alpha = opacity
|
||||
)
|
||||
// Draw the complete 8x10 asset at twice its size, centred in the portrait window.
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = BattleOverlay.questionMarkIcon,
|
||||
x = (insetX + 6) / 2F,
|
||||
y = 4,
|
||||
width = 8,
|
||||
height = 10,
|
||||
scale = 2F,
|
||||
alpha = opacity
|
||||
)
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if (reversed) BattleOverlay.partyColumnTileReversed else BattleOverlay.partyColumnTile,
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 41,
|
||||
height = 39,
|
||||
alpha = opacity
|
||||
)
|
||||
matrices.popPose()
|
||||
}
|
||||
|
||||
fun render(
|
||||
context: GuiGraphics,
|
||||
tileX: Float,
|
||||
tileY: Float,
|
||||
tileWidth: Float,
|
||||
reversed: Boolean,
|
||||
pokemon: ClientBattlePokemon,
|
||||
dto: BattlePokemonDTO,
|
||||
showGap: Boolean = true,
|
||||
showBuffs: Boolean = true
|
||||
) {
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(0.0, 0.0, 300.0)
|
||||
// Panel sits just beside the tile (inwards) with the connector bridging the gap.
|
||||
val startX = if (reversed) tileX - 136 else tileX + tileWidth - 1
|
||||
val startY = tileY
|
||||
val species = pokemon.species
|
||||
val form = species.getForm(pokemon.state.currentAspects)
|
||||
|
||||
// The connector is only shown for the Info screen tiles, not the on-field draggable column.
|
||||
if (showGap) {
|
||||
val gap = if (reversed) hoverGapReversed else hoverGap
|
||||
val xOffset = if (reversed) 136 else -7
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = gap,
|
||||
x = startX + xOffset,
|
||||
y = startY,
|
||||
height = 36,
|
||||
width = 8,
|
||||
textureHeight = 36,
|
||||
textureWidth = 8,
|
||||
)
|
||||
}
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (showBuffs) expandedPokemonInfo else expandedPokemonInfoInactive,
|
||||
x = startX,
|
||||
y = startY,
|
||||
height = 112,
|
||||
width = 137,
|
||||
textureHeight = 112,
|
||||
textureWidth = 137,
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.pokemon_form".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getFormText(species, form),
|
||||
x = startX + 36,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (showBuffs) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.buffs".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.attack.name".asTranslated().statColored(Stats.ATTACK),
|
||||
x = startX + 10,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.ATTACK]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.ATTACK, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.defence.name".asTranslated().statColored(Stats.DEFENCE),
|
||||
x = startX + 10,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.DEFENCE]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.DEFENCE, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.stats.special_attack".asTranslated().statColored(Stats.SPECIAL_ATTACK),
|
||||
x = startX + 10,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.SPECIAL_ATTACK]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.SPECIAL_ATTACK, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.stats.special_defence".asTranslated().statColored(Stats.SPECIAL_DEFENCE),
|
||||
x = startX + 10,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.SPECIAL_DEFENCE]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.SPECIAL_DEFENCE, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated().statColored(Stats.SPEED),
|
||||
x = startX + 10,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.SPEED]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.SPEED, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.accuracy.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.ACCURACY]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.ACCURACY, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.evasion.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.EVASION]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.EVASION, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Non-active Pokémon have no stat stages; the inactive panel asset already clears the
|
||||
// Buffs/Debuffs column, so we just draw a "Current HP" gauge on that clean area.
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.current_hp".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
// Owner sees flat HP (hp/maxHp); opponents carry a 0..1 ratio.
|
||||
val hpRatio = (if (pokemon.isHpFlat) pokemon.hpValue / pokemon.maxHp.toInt() else pokemon.hpValue)
|
||||
.coerceIn(0f, 1f)
|
||||
val (hpRed, hpGreen) = getDepletableRedGreen(hpRatio)
|
||||
|
||||
// Same battle health-bar texture + tint the active Pokémon uses, sized to fill the asset's
|
||||
// bar slot beneath the "Current HP" label (slot ≈ x8..64, y30..36).
|
||||
val barX = startX + 8
|
||||
val barW = hpRatio * 57f
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = healthBar,
|
||||
x = barX,
|
||||
y = startY + 30,
|
||||
width = barW,
|
||||
height = 6,
|
||||
textureWidth = 99,
|
||||
textureHeight = 6,
|
||||
red = hpRed * 0.8f,
|
||||
green = hpGreen * 0.8f,
|
||||
blue = 0.27f
|
||||
)
|
||||
// The texture's right end is angled, so a left-clipped fill has a border on every side except
|
||||
// the right. Re-draw the texture's square left-edge column (texel 0) at the fill's right edge
|
||||
// to close the border into a full rectangle.
|
||||
if (barW >= 1f) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = healthBar,
|
||||
x = barX + barW - 1f,
|
||||
y = startY + 30,
|
||||
width = 1f,
|
||||
height = 6,
|
||||
textureWidth = 99,
|
||||
textureHeight = 6,
|
||||
red = hpRed * 0.8f,
|
||||
green = hpGreen * 0.8f,
|
||||
blue = 0.27f
|
||||
)
|
||||
}
|
||||
|
||||
// Percentage centred inside the bar — and exact values for your own Pokémon — exactly like
|
||||
// the active Pokémon's health bar does in battle.
|
||||
val hpText = if (pokemon.isHpFlat) {
|
||||
"${pokemon.hpValue.toInt()}/${pokemon.maxHp.toInt()} (${ceil(hpRatio * 100).toInt()}%)"
|
||||
} else {
|
||||
"${ceil(hpRatio * 100).toInt()}%"
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = hpText.text(),
|
||||
x = startX + 36,
|
||||
y = startY + 31.0,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.ability".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val ability = dto.ability?.let { Abilities.get(it) }
|
||||
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = abilityName,
|
||||
x = startX + 100,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.moves".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (dto.moves.size != 4) {
|
||||
for (i in 0 until 4) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "?".text(),
|
||||
x = startX + 100,
|
||||
y = startY + 31.5 + i * 8,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val questionMarkText = "?".text()
|
||||
dto.moves.forEachIndexed { index, moveDTO ->
|
||||
val move = moveDTO?.move ?: questionMarkText
|
||||
val moveColor = moveDTO?.move?.let { typeColorFor(it) }
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = move.string.text().let { t -> if (moveColor != null) t.colored(moveColor) else t },
|
||||
x = if (move == questionMarkText) startX + 100 else startX + 74,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = move == questionMarkText
|
||||
)
|
||||
|
||||
if (moveDTO != null) {
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveDTO.timesUsed.toString().text(),
|
||||
x = startX + 127.5,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dtoSpeed = dto.speed
|
||||
if (dtoSpeed != null) {
|
||||
val speedBoost = dto.buffs[Stats.SPEED] ?: 1.0
|
||||
val speedAfterBoost = (dtoSpeed * speedBoost).toInt()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedAfterBoost.toString().text(),
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
else {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.speed_tier".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val speedBoost = dto.buffs[Stats.SPEED] ?: 1.0
|
||||
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedTier,
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.held_item".asTranslated(),
|
||||
x = startX + 40,
|
||||
y = startY + 99.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
TypeIcon(
|
||||
x = startX + 100.5,
|
||||
y = startY + 88,
|
||||
type = form.primaryType,
|
||||
secondaryType = form.secondaryType,
|
||||
small = false,
|
||||
centeredX = true
|
||||
).render(context)
|
||||
|
||||
if (form.secondaryType != null) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
|
||||
x = (startX + 69.0) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 140,
|
||||
textureHeight = 24,
|
||||
textureWidth = 140,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
|
||||
x = (startX + 70.5) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 132,
|
||||
textureHeight = 24,
|
||||
textureWidth = 132,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
|
||||
val heldItem = dto.heldItem
|
||||
if (heldItem != null && !heldItem.isEmpty()) {
|
||||
context.pose().pushPose()
|
||||
context.pose().scale(0.95F, 0.95F, 0.95F)
|
||||
val itemX = (startX + 4) / 0.95
|
||||
val itemY = (startY + 91) / 0.95
|
||||
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
|
||||
context.pose().popPose()
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = BattleOverlay.questionMarkIcon,
|
||||
x = startX + 6,
|
||||
y = startY + 92.5,
|
||||
height = 11,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
context.pose().popPose()
|
||||
}
|
||||
|
||||
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
|
||||
val base = form.baseStats[Stats.SPEED] ?: 0
|
||||
val maxIV = 31
|
||||
val maxEV = 252
|
||||
val minNatureMod = 0.9
|
||||
val maxNatureMod = 1.1
|
||||
|
||||
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
|
||||
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
|
||||
return "deltaclient.ui.battle.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
|
||||
}
|
||||
|
||||
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
|
||||
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
|
||||
}
|
||||
|
||||
internal fun getBoostText(stat: Stats, multiplier: Double): MutableComponent {
|
||||
val stage = stageFromMultiplier(stat, multiplier)
|
||||
val sign = if (stage > 0) "+" else ""
|
||||
val str = "$sign$stage (${multiplierFormat.format(multiplier)}x)"
|
||||
return when {
|
||||
multiplier > 1.0 -> str.green()
|
||||
multiplier < 1.0 -> str.red()
|
||||
else -> str.text()
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a stat-boost multiplier back to its stage. Accuracy/Evasion use a /3 base, others /2.
|
||||
private fun stageFromMultiplier(stat: Stats, multiplier: Double): Int {
|
||||
val base = if (stat == Stats.ACCURACY || stat == Stats.EVASION) 3.0 else 2.0
|
||||
return if (multiplier >= 1.0) Math.round(multiplier * base - base).toInt()
|
||||
else Math.round(base - base / multiplier).toInt()
|
||||
}
|
||||
|
||||
// The type colour for a move from its "cobblemon.move.<name>" display name, or null if unknown.
|
||||
internal fun typeColorFor(move: Component): Int? {
|
||||
val key = (move.contents as? TranslatableContents)?.key ?: return null
|
||||
if (!key.startsWith("cobblemon.move.")) return null
|
||||
val type = Moves.getByName(key.removePrefix("cobblemon.move."))?.elementalType?.name ?: return null
|
||||
return typeColor(type)
|
||||
}
|
||||
|
||||
private fun typeColor(type: String): Int = when (type.lowercase()) {
|
||||
"bug" -> 0xA8B820
|
||||
"dark" -> 0x705848
|
||||
"dragon" -> 0x7038F8
|
||||
"electric" -> 0xF8D030
|
||||
"fairy" -> 0xEE99AC
|
||||
"fighting" -> 0xC03028
|
||||
"fire" -> 0xF08030
|
||||
"flying" -> 0xA890F0
|
||||
"ghost" -> 0x705898
|
||||
"grass" -> 0x78C850
|
||||
"ground" -> 0xE0C068
|
||||
"ice" -> 0x98D8D8
|
||||
"normal" -> 0xA8A878
|
||||
"poison" -> 0xA040A0
|
||||
"psychic" -> 0xF85888
|
||||
"rock" -> 0xB8A038
|
||||
"steel" -> 0xB8B8D0
|
||||
"water" -> 0x6890F0
|
||||
else -> 0xFFFFFF
|
||||
}
|
||||
|
||||
private fun getFormText(species: Species, form: FormData): MutableComponent {
|
||||
val speciesName = species.name
|
||||
val formName = form.name
|
||||
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun MutableComponent.colored(rgb: Int): MutableComponent = this.withStyle { it.withColor(TextColor.fromRgb(rgb)) }
|
||||
|
||||
// Buffs/Debuffs stat names are all white. (Per-stat colours can be re-enabled by returning them here.)
|
||||
internal fun statColor(stat: Stats): Int? = null
|
||||
|
||||
internal fun MutableComponent.statColored(stat: Stats): MutableComponent {
|
||||
val rgb = statColor(stat) ?: return this
|
||||
return this.colored(rgb)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.preview
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
|
||||
class TeamPreviewGUI(val preview: ClientBattleTeamPreview) : Screen(battleLang("gui.team_preview")), CobblemonRenderable {
|
||||
private lateinit var widget: TeamPreviewWidget
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
widget = addRenderableWidget(TeamPreviewWidget(preview))
|
||||
}
|
||||
|
||||
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick)
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc() = false
|
||||
|
||||
override fun isPauseScreen() = false
|
||||
}
|
||||
+973
@@ -0,0 +1,973 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.preview
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.gui.ParentWidget
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.gui.renderSprite
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.questionMarkIcon
|
||||
import com.cobblemon.mod.common.client.render.SpriteType
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toHex
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import com.mojang.blaze3d.platform.Lighting
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.narration.NarratableEntry
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
|
||||
class TeamPreviewWidget(val preview: ClientBattleTeamPreview) : ParentWidget(
|
||||
pX = 0,
|
||||
pY = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
|
||||
pWidth = Minecraft.getInstance().window.guiScaledWidth,
|
||||
pHeight = Minecraft.getInstance().window.guiScaledHeight,
|
||||
"cobblemon.battle.ui.team_info".asTranslated()
|
||||
) {
|
||||
companion object {
|
||||
const val SLOT_HORIZONTAL_SPACING = 4F
|
||||
const val SLOT_VERTICAL_SPACING = 2F
|
||||
|
||||
const val BACKGROUND_HEIGHT = 148
|
||||
val underlayTexture = cobblemonResource("textures/gui/battle/selection_underlay.png")
|
||||
val title = cobblemonResource("textures/gui/battle/team_preview_title.png")
|
||||
val checkmark = cobblemonResource("textures/gui/battle/checkmark.png")
|
||||
var visible = false
|
||||
}
|
||||
|
||||
val teamTiles = mutableListOf<PokemonTile>()
|
||||
val opponentTiles = mutableListOf<PokemonTile>()
|
||||
|
||||
init {
|
||||
addTiles()
|
||||
}
|
||||
|
||||
fun addTiles() {
|
||||
preview.team.forEachIndexed { index, pokemon ->
|
||||
if (pokemon == null) return@forEachIndexed
|
||||
val (slotX, slotY) = getSlotPosition(index, 1)
|
||||
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false))
|
||||
}
|
||||
preview.opponent.forEachIndexed { index, pokemon ->
|
||||
if (pokemon == null) return@forEachIndexed
|
||||
val (slotX, slotY) = getSlotPosition(index, 2)
|
||||
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true))
|
||||
}
|
||||
}
|
||||
|
||||
fun getSlotPosition(index: Int, side: Int): Pair<Float, Float> {
|
||||
val startX = if (side == 1) 10 else width - 10 - (41 + SLOT_HORIZONTAL_SPACING) * 2
|
||||
val startY = y + 15
|
||||
val row = index / 2
|
||||
val column = index % 2
|
||||
val slotX = startX.toFloat() + column * (SLOT_HORIZONTAL_SPACING + 41)
|
||||
val slotY = startY.toFloat() + row * (SLOT_VERTICAL_SPACING + 39)
|
||||
return Pair(slotX, slotY)
|
||||
}
|
||||
|
||||
private fun getHoveredTile(team: Boolean, mouseX: Int, mouseY: Int): Int {
|
||||
val tiles = if (team) teamTiles else opponentTiles
|
||||
return tiles.indexOfFirst { it.isHovered(mouseX.toDouble(), mouseY.toDouble()) }
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val matrixStack = context.pose()
|
||||
val mc = Minecraft.getInstance()
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = underlayTexture,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = BACKGROUND_HEIGHT
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = title,
|
||||
x = (mc.window.guiScaledWidth / 2) - (103 / 2),
|
||||
y = 4,
|
||||
width = 103,
|
||||
height = 22
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.team_preview.title".asTranslated().bold(),
|
||||
x = (mc.window.guiScaledWidth / 2),
|
||||
y = 6,
|
||||
scale = 0.75f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val totalSecondsRemaining = Duration.between(Instant.now(), preview.mustPickBy).seconds
|
||||
val minutesRemaining = max(0, totalSecondsRemaining / 60)
|
||||
val secondsRemaining = max(0, totalSecondsRemaining % 60)
|
||||
val secondsRemainingStr = String.format("%02d", secondsRemaining)
|
||||
val timer = "$minutesRemaining:$secondsRemainingStr"
|
||||
val text = when {
|
||||
totalSecondsRemaining <= 15 -> timer.text().red().bold()
|
||||
totalSecondsRemaining <= 30 -> timer.text().yellow().bold()
|
||||
else -> timer.text().bold()
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = text,
|
||||
x = (mc.window.guiScaledWidth / 2) + 2,
|
||||
y = 15,
|
||||
scale = 0.75f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
|
||||
val ellipsis = ".".repeat(3 - (secondsRemaining % 3).toInt())
|
||||
if (preview.selection.size == preview.selections) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = checkmark,
|
||||
x = (mc.window.guiScaledWidth / 2) - 39,
|
||||
y = 24,
|
||||
width = 8,
|
||||
height = 6
|
||||
)
|
||||
val offset = when (ellipsis.length) {
|
||||
1 -> 0.5
|
||||
2 -> 1.0
|
||||
else -> 2.0
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.team_preview.waiting".asTranslated(ellipsis).bold(),
|
||||
x = (mc.window.guiScaledWidth / 2) + 2.0 + offset,
|
||||
y = 26,
|
||||
scale = 0.5f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/name_plate.png"),
|
||||
x = 10,
|
||||
y = y - 8,
|
||||
width = 128,
|
||||
height = 8
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = mc.player!!.name.string.text(),
|
||||
y = y - 8 + 2,
|
||||
x = x + 14 + 10,
|
||||
scale = SCALE,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/nameplate_reversed.png"),
|
||||
x = mc.window.guiScaledWidth - 14 - 128,
|
||||
y = y - 8,
|
||||
width = 128,
|
||||
height = 8
|
||||
)
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = preview.opponentName.string.text(),
|
||||
y = y - 8 + 2,
|
||||
x = mc.window.guiScaledWidth - 14 - 12,
|
||||
scale = SCALE,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
teamTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(true, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, false)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
opponentTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(false, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, true)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldHide(hoveredTile: Int, currentTile: Int, reversed: Boolean): Boolean {
|
||||
if (hoveredTile != -1) {
|
||||
val hoveredRow = hoveredTile / 2
|
||||
val hoveredColumn = hoveredTile % 2
|
||||
val currentRow = currentTile / 2
|
||||
val currentColumn = currentTile % 2
|
||||
if (!reversed && hoveredColumn < currentColumn && hoveredRow <= currentRow) return true
|
||||
if (reversed && hoveredColumn > currentColumn && hoveredRow <= currentRow) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
val tile = teamTiles.firstOrNull { it.isHovered(pMouseX, pMouseY) }
|
||||
if (tile != null && tile.pokemonDto in preview.team) {
|
||||
if (pButton == InputConstants.MOUSE_BUTTON_LEFT) {
|
||||
if (tile.pokemonDto !in preview.selection && preview.selection.size < preview.selections) {
|
||||
preview.select(tile.pokemonDto)
|
||||
}
|
||||
}
|
||||
else if (pButton == InputConstants.MOUSE_BUTTON_RIGHT) {
|
||||
if (tile.pokemonDto in preview.selection) {
|
||||
preview.unselect(tile.pokemonDto)
|
||||
}
|
||||
}
|
||||
}
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
return super.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun narrationPriority() = NarratableEntry.NarrationPriority.HOVERED
|
||||
|
||||
class PokemonTile(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val pokemonDto: BattlePokemonDTO,
|
||||
val widget: TeamPreviewWidget,
|
||||
val reversed: Boolean
|
||||
) {
|
||||
companion object {
|
||||
const val SELECT_WIDTH = 41
|
||||
const val SELECT_HEIGHT = 39
|
||||
const val SCALE = 0.5F
|
||||
val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
|
||||
|
||||
val pokemonTile = cobblemonResource("textures/gui/battle/pokemon_tile.png")
|
||||
val pokemonTileReversed = cobblemonResource("textures/gui/battle/pokemon_tile_reversed.png")
|
||||
val pokemonTileDisabled = cobblemonResource("textures/gui/battle/pokemon_tile_disabled.png")
|
||||
val pokemonTileDisabledReversed = cobblemonResource("textures/gui/battle/pokemon_tile_disabled_reversed.png")
|
||||
val pokemonTileSelected = cobblemonResource("textures/gui/battle/pokemon_tile_selected.png")
|
||||
val pokemonTileSelectedReversed = cobblemonResource("textures/gui/battle/pokemon_tile_selected_reversed.png")
|
||||
|
||||
val hoverGapSelected = cobblemonResource("textures/gui/battle/hover_gap_selected.png")
|
||||
val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
|
||||
val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
|
||||
|
||||
private val decimalFormat = DecimalFormat("0.00").also {
|
||||
it.roundingMode = RoundingMode.CEILING
|
||||
}
|
||||
}
|
||||
|
||||
val state = FloatingState()
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
|
||||
val pokemon = pokemonDto.activeBattlePokemonDTO ?: return
|
||||
val battlePokemon = ClientBattlePokemon(
|
||||
uuid = pokemon.uuid,
|
||||
properties = pokemon.properties,
|
||||
aspects = pokemon.aspects,
|
||||
displayName = pokemon.displayName,
|
||||
hpValue = pokemon.hpValue,
|
||||
maxHp = pokemon.maxHp,
|
||||
isHpFlat = pokemon.isFlatHp,
|
||||
status = pokemon.status,
|
||||
statChanges = pokemon.statChanges,
|
||||
)
|
||||
state.currentAspects = pokemon.aspects
|
||||
val matrixStack = context.pose()
|
||||
val isFainted = pokemonDto.fainted
|
||||
val isSelected = pokemonDto in widget.preview.selection
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/battle_info_underlay.png"),
|
||||
x = x + 5 + (if (reversed) 3 else 0),
|
||||
y = y + 4,
|
||||
width = 28,
|
||||
height = 28
|
||||
)
|
||||
val speciesToDisplay = battlePokemon.species
|
||||
val stateToDisplay = battlePokemon.state
|
||||
context.enableScissor(
|
||||
(x + 5 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5).toInt(),
|
||||
(x + 5 + 28 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5 + 28).toInt(),
|
||||
)
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(
|
||||
x + (if (reversed) 3 else 0) + 5 + 28 / 2.0,
|
||||
y.toDouble() + 5 - 5.0,
|
||||
0.0
|
||||
)
|
||||
drawCustomPosablePortrait(
|
||||
identifier = speciesToDisplay.resourceIdentifier,
|
||||
matrixStack = matrixStack,
|
||||
scale = 18f,
|
||||
contextScale = speciesToDisplay.getForm(stateToDisplay.currentAspects).baseScale,
|
||||
reversed = reversed,
|
||||
doQuirks = false,
|
||||
state = stateToDisplay,
|
||||
partialTicks = if (Cobblemon.config.animateBattleTiles) deltaTicks else 0F
|
||||
)
|
||||
matrixStack.popPose()
|
||||
context.disableScissor()
|
||||
|
||||
val tile = if (reversed) pokemonTileReversed else pokemonTile
|
||||
val disabled = if (reversed) pokemonTileDisabledReversed else pokemonTileDisabled
|
||||
val selected = if (reversed) pokemonTileSelectedReversed else pokemonTileSelected
|
||||
if (isFainted) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = disabled,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
else if (isSelected) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = selected,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
|
||||
val selectionIndex = widget.preview.selection.indexOf(pokemonDto)
|
||||
if (selectionIndex != -1) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = "${selectionIndex + 1}".text().bold(),
|
||||
x = x + 34,
|
||||
y = y + 29,
|
||||
scale = 1.25f,
|
||||
opacity = 1f,
|
||||
shadow = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = tile,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
|
||||
if (isHovered(mouseX, mouseY)) {
|
||||
renderExpandedInfo(context, isSelected, reversed, battlePokemon, pokemonDto)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderExpandedInfo(context: GuiGraphics, selected: Boolean, reversed: Boolean, pokemon: ClientBattlePokemon, dto: BattlePokemonDTO) {
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(0.0, 0.0, 300.0)
|
||||
val startX = if (reversed) x - 136 else x + 40
|
||||
val startY = y
|
||||
val species = pokemon.species
|
||||
val form = species.getForm(pokemon.state.currentAspects)
|
||||
val expandedInfoTexture = expandedPokemonInfo
|
||||
|
||||
if (selected) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = hoverGapSelected,
|
||||
x = startX - 3,
|
||||
y = startY,
|
||||
height = 30,
|
||||
width = 4,
|
||||
textureHeight = 30,
|
||||
textureWidth = 4,
|
||||
)
|
||||
}
|
||||
else {
|
||||
val gap = if (reversed) hoverGapReversed else hoverGap
|
||||
val xOffset = if (reversed) 136 else -7
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = gap,
|
||||
x = startX + xOffset,
|
||||
y = startY,
|
||||
height = 36,
|
||||
width = 8,
|
||||
textureHeight = 36,
|
||||
textureWidth = 8,
|
||||
)
|
||||
}
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = expandedInfoTexture,
|
||||
x = startX,
|
||||
y = startY,
|
||||
height = 112,
|
||||
width = 137,
|
||||
textureHeight = 112,
|
||||
textureWidth = 137,
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.pokemon_form".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getFormText(species, form),
|
||||
x = startX + 36,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.buffs".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.attack.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.defence.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_attack".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_defence".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPEED)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.accuracy.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ACCURACY)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.evasion.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto?.buffs?.get(Stats.EVASION)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.ability".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val ability = dto.ability?.let { Abilities.get(it) }
|
||||
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = abilityName,
|
||||
x = startX + 100,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.moves".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (dto.moves.size != 4) {
|
||||
for (i in 0 until 4) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "?".text(),
|
||||
x = startX + 100,
|
||||
y = startY + 31.5 + i * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val questionMarkText = "?".text()
|
||||
dto.moves.forEachIndexed { index, moveDTO ->
|
||||
val move = moveDTO?.move ?: questionMarkText
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = move.string.text(),
|
||||
x = if (move == questionMarkText) startX + 100 else startX + 74,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = move == questionMarkText
|
||||
)
|
||||
|
||||
if (moveDTO != null) {
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveDTO.timesUsed.toString().text(),
|
||||
x = startX + 127.5,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.speed != null) {
|
||||
val speed = dto.speed
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedAfterBoost = (speed * speedBoost).toInt()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedAfterBoost.toString().text(),
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
else {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.speed_tier".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedTier,
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.held_item".asTranslated(),
|
||||
x = startX + 40,
|
||||
y = startY + 99.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
TypeIcon(
|
||||
x = startX + 100.5,
|
||||
y = startY + 88,
|
||||
type = form.primaryType,
|
||||
secondaryType = form.secondaryType,
|
||||
small = false,
|
||||
centeredX = true
|
||||
).render(context)
|
||||
|
||||
if (form.secondaryType != null) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
|
||||
x = (startX + 69.0) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 140,
|
||||
textureHeight = 24,
|
||||
textureWidth = 140,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
|
||||
x = (startX + 70.5) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 132,
|
||||
textureHeight = 24,
|
||||
textureWidth = 132,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
|
||||
val heldItem = dto.heldItem
|
||||
if (heldItem != null && !heldItem.isEmpty()) {
|
||||
context.pose().pushPose()
|
||||
context.pose().scale(0.95F, 0.95F, 0.95F)
|
||||
val itemX = (startX + 4) / 0.95
|
||||
val itemY = (startY + 91) / 0.95
|
||||
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
|
||||
context.pose().popPose()
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = questionMarkIcon,
|
||||
x = startX + 6,
|
||||
y = startY + 92.5,
|
||||
height = 11,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
context.pose().popPose()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun drawCustomPosablePortrait(
|
||||
identifier: ResourceLocation,
|
||||
matrixStack: PoseStack,
|
||||
scale: Float = 13F,
|
||||
contextScale: Float = 1F,
|
||||
reversed: Boolean = false,
|
||||
state: PosableState,
|
||||
partialTicks: Float,
|
||||
limbSwing: Float = 0F,
|
||||
limbSwingAmount: Float = 0F,
|
||||
ageInTicks: Float = 0F,
|
||||
headYaw: Float = 0F,
|
||||
headPitch: Float = 0F,
|
||||
r: Float = 1F,
|
||||
g: Float = 1F,
|
||||
b: Float = 1F,
|
||||
a: Float = 1F,
|
||||
doQuirks: Boolean = true
|
||||
) {
|
||||
RenderSystem.applyModelViewMatrix()
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
|
||||
matrixStack.scale(scale, scale, -scale)
|
||||
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
|
||||
|
||||
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
|
||||
|
||||
if (sprite == null) {
|
||||
val model = VaryingModelRepository.getPoser(identifier, state)
|
||||
state.currentModel = model
|
||||
val texture = VaryingModelRepository.getTexture(identifier, state)
|
||||
|
||||
val context = RenderContext()
|
||||
model.context = context
|
||||
VaryingModelRepository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
|
||||
context.put(RenderContext.SCALE, contextScale)
|
||||
context.put(RenderContext.SPECIES, identifier)
|
||||
context.put(RenderContext.ASPECTS, state.currentAspects)
|
||||
context.put(RenderContext.POSABLE_STATE, state)
|
||||
context.put(RenderContext.DO_QUIRKS, doQuirks)
|
||||
|
||||
val renderType = RenderType.entityCutout(texture)
|
||||
|
||||
val quaternion1 = Axis.YP.rotationDegrees(-32F * if (reversed) -1F else 1F)
|
||||
val quaternion2 = Axis.XP.rotationDegrees(5F)
|
||||
|
||||
val originalPose = state.currentPose
|
||||
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
|
||||
state.updatePartialTicks(partialTicks)
|
||||
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
|
||||
originalPose?.let { state.setPose(it) }
|
||||
|
||||
matrixStack.translate(
|
||||
model.portraitTranslation.x * if (reversed) -1F else 1F,
|
||||
model.portraitTranslation.y + 1.5 * model.portraitScale,
|
||||
model.portraitTranslation.z - 4
|
||||
)
|
||||
matrixStack.scale(model.portraitScale, model.portraitScale, 1 / model.portraitScale)
|
||||
matrixStack.mulPose(quaternion1)
|
||||
matrixStack.mulPose(quaternion2)
|
||||
|
||||
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
|
||||
val light2 = Vector3f(0.1F, 0.0F, 8.0F)
|
||||
RenderSystem.setShaderLights(light1, light2)
|
||||
quaternion1.conjugate()
|
||||
|
||||
val immediate = Minecraft.getInstance().renderBuffers().bufferSource()
|
||||
val buffer = immediate.getBuffer(renderType)
|
||||
val packedLight = LightTexture.pack(11, 7)
|
||||
|
||||
val colour = toHex(r, g, b, a)
|
||||
model.withLayerContext(immediate, state, VaryingModelRepository.getLayers(identifier, state)) {
|
||||
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
|
||||
immediate.endBatch()
|
||||
}
|
||||
|
||||
model.setDefault()
|
||||
|
||||
Lighting.setupFor3DItems()
|
||||
} else {
|
||||
renderSprite(matrixStack, sprite)
|
||||
}
|
||||
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
|
||||
val base = form.baseStats[Stats.SPEED] ?: 0
|
||||
val maxIV = 31
|
||||
val maxEV = 252
|
||||
val minNatureMod = 0.9
|
||||
val maxNatureMod = 1.1
|
||||
|
||||
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
|
||||
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
|
||||
return "cobblemon.battle.ui.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
|
||||
}
|
||||
|
||||
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
|
||||
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
|
||||
}
|
||||
|
||||
private fun getMultiplierText(multiplier: Double): MutableComponent {
|
||||
val rounded = decimalFormat.format(multiplier)
|
||||
val str = "x$rounded"
|
||||
return when {
|
||||
multiplier > 1.0 -> str.green()
|
||||
multiplier < 1.0 -> str.red()
|
||||
else -> str.text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFormText(species: Species, form: FormData): MutableComponent {
|
||||
val speciesName = species.name
|
||||
val formName = form.name
|
||||
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
@@ -74,6 +76,12 @@ class BattleGeneralActionSelection(
|
||||
battleGUI.changeActionSelection(ForfeitConfirmationSelection(battleGUI, request))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
if (battle.side2.actors.all { it.type == ActorType.PLAYER }) {
|
||||
addOption(rank++, battleLang("ui.team_info.button"), cobblemonResource("textures/gui/battle/team_info.png")) {
|
||||
battleGUI.changeActionSelection(BattleTeamInfoSelection(battleGUI, request))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,4 +129,4 @@ class BattleGeneralActionSelection(
|
||||
}
|
||||
|
||||
override fun narrationPriority() = NarratableEntry.NarrationPriority.NONE
|
||||
}
|
||||
}
|
||||
|
||||
+294
-7
@@ -10,27 +10,41 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
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.text.bold
|
||||
import com.cobblemon.mod.common.api.text.gold
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.Effectiveness
|
||||
import com.cobblemon.mod.common.client.battle.MoveEffectivenessCalculator
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.math.toRGB
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth.floor
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
||||
class BattleMoveSelection(
|
||||
battleGUI: BattleGUI,
|
||||
@@ -51,7 +65,21 @@ class BattleMoveSelection(
|
||||
const val MOVE_HORIZONTAL_SPACING = 13F
|
||||
|
||||
val moveTexture = cobblemonResource("textures/gui/battle/battle_move.png")
|
||||
val moveOverlayTexture = cobblemonResource("textures/gui/battle/battle_move_overlay.png")
|
||||
val moveOverlayTexture = cobblemonResource("textures/gui/battle/move_outline.png")
|
||||
val moveTooltipIcon = cobblemonResource("textures/gui/battle/move_tooltip_icon.png")
|
||||
val moveTooltip = cobblemonResource("textures/gui/battle/move_tooltip.png")
|
||||
val moveTooltipForStatus = cobblemonResource("textures/gui/battle/move_tooltip_status.png")
|
||||
|
||||
val effectiveIcon = cobblemonResource("textures/gui/battle/effective_icon.png")
|
||||
val extremelyEffectiveIcon = cobblemonResource("textures/gui/battle/extremely_effective_icon.png")
|
||||
val immuneIcon = cobblemonResource("textures/gui/battle/immune_icon.png")
|
||||
val mostlyIneffectiveIcon = cobblemonResource("textures/gui/battle/mostly_ineffective_icon.png")
|
||||
val superEffectiveIcon = cobblemonResource("textures/gui/battle/super_effective_icon.png")
|
||||
val notVeryEffectiveIcon = cobblemonResource("textures/gui/battle/not_very_effective_icon.png")
|
||||
|
||||
private val decimalFormat = DecimalFormat("#.##").also {
|
||||
it.roundingMode = RoundingMode.CEILING
|
||||
}
|
||||
}
|
||||
|
||||
val moveSet = request.moveSet!!
|
||||
@@ -74,7 +102,7 @@ class BattleMoveSelection(
|
||||
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
|
||||
}
|
||||
|
||||
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
|
||||
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
open class MoveTile(
|
||||
val moveSelection: BattleMoveSelection,
|
||||
@@ -138,7 +166,7 @@ class BattleMoveSelection(
|
||||
|
||||
// Move Category
|
||||
MoveCategoryIcon(
|
||||
x = x + 48,
|
||||
x = x + 41,
|
||||
y = y + 14.5,
|
||||
category = moveTemplate.damageCategory,
|
||||
opacity = moveSelection.opacity
|
||||
@@ -168,14 +196,265 @@ class BattleMoveSelection(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = movePPText,
|
||||
x = x + 75,
|
||||
x = x + 67,
|
||||
y = y + 14,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = true
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = moveTooltipIcon,
|
||||
x = x + 82,
|
||||
y = y + 14,
|
||||
width = 9,
|
||||
height = 9,
|
||||
textureHeight = 18,
|
||||
textureWidth = 9,
|
||||
alpha = moveSelection.opacity,
|
||||
vOffset = if (isTooltipHovered(mouseX.toDouble(), mouseY.toDouble())) 9 else 0
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = "i".text().bold(),
|
||||
x = x + 87.375,
|
||||
y = y + 14,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (isTooltipHovered(mouseX.toDouble(), mouseY.toDouble())) {
|
||||
renderTooltip(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
|
||||
fun renderTooltip(context: GuiGraphics) {
|
||||
val tooltipY = Minecraft.getInstance().window.guiScaledHeight - 84 - 59 - 4
|
||||
val tooltipX = 20
|
||||
|
||||
val move = moveTemplate
|
||||
|
||||
val effectiveness = getMoveEffectiveness(move)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (effectiveness == null) moveTooltipForStatus else moveTooltip,
|
||||
x = tooltipX,
|
||||
y = tooltipY,
|
||||
width = 195,
|
||||
height = 56,
|
||||
alpha = moveSelection.opacity
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.power"),
|
||||
x = tooltipX + 18,
|
||||
y = tooltipY + 11,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = MovesWidget.movesPowerIconResource,
|
||||
x = (tooltipX + 8.5) / 0.66f,
|
||||
y = (tooltipY + 9.5) / 0.66f,
|
||||
width = 10,
|
||||
height = 10,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.66f
|
||||
)
|
||||
|
||||
val powerStr = if (move.power <= 0) "—" else move.power.toInt().toString()
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = powerStr.text(),
|
||||
x = tooltipX + 73,
|
||||
y = tooltipY + 11,
|
||||
opacity = moveSelection.opacity,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = MovesWidget.movesAccuracyIconResource,
|
||||
x = (tooltipX + 8.5) / 0.66f,
|
||||
y = (tooltipY + 26) / 0.66f,
|
||||
width = 10,
|
||||
height = 10,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.66f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.accuracy"),
|
||||
x = tooltipX + 18,
|
||||
y = tooltipY + 27,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
val accuracyStr = format(move.accuracy).text()
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = accuracyStr,
|
||||
x = tooltipX + 73,
|
||||
y = tooltipY + 27,
|
||||
opacity = moveSelection.opacity,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = MovesWidget.movesEffectIconResource,
|
||||
x = (tooltipX + 8.5) / 0.66f,
|
||||
y = (tooltipY + 42.5) / 0.66f,
|
||||
width = 10,
|
||||
height = 10,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.66f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.effect"),
|
||||
x = tooltipX + 18,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
val moveEffect = format(move.effectChances.firstOrNull() ?: 0.0).text()
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveEffect,
|
||||
x = tooltipX + 73,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
val moveDescription = move.description
|
||||
val lines = Minecraft.getInstance().font.splitter.splitLines(moveDescription, 200, moveDescription.style)
|
||||
val orderedLines = Language.getInstance().getVisualOrder(lines)
|
||||
val maxLines = if (effectiveness == null) 6 else 4
|
||||
orderedLines.take(maxLines).forEachIndexed { index, line ->
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = line,
|
||||
x = tooltipX + 85,
|
||||
y = tooltipY + 11 + (6 * index),
|
||||
opacity = moveSelection.opacity,
|
||||
scaleX = 0.5f,
|
||||
scaleY = 0.5f,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
if (effectiveness == null) return
|
||||
val effectivenessText = getEffectivenessText(effectiveness)
|
||||
val effectivenessIcon = getEffectivenessIcon(effectiveness)
|
||||
val effectivenessValue = getEffectivenessValue(effectiveness)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = effectivenessIcon,
|
||||
x = (tooltipX + 84) / 0.5f,
|
||||
y = (tooltipY + 43) / 0.5f,
|
||||
width = 12,
|
||||
height = 12,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.5f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = effectivenessText,
|
||||
x = tooltipX + 94,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = effectivenessValue,
|
||||
x = tooltipX + 168,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
}
|
||||
|
||||
private fun getEffectivenessValue(effectiveness: Effectiveness): MutableComponent {
|
||||
return when (effectiveness) {
|
||||
Effectiveness.IMMUNE -> "0.00x".text()
|
||||
Effectiveness.MOSTLY_INEFFECTIVE -> "0.25x".text()
|
||||
Effectiveness.NOT_VERY_EFFECTIVE -> "0.50x".text()
|
||||
Effectiveness.EFFECTIVE -> "1.00x".text()
|
||||
Effectiveness.SUPER_EFFECTIVE -> "2.00x".text()
|
||||
Effectiveness.EXTREMELY_EFFECTIVE -> "4.00x".text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEffectivenessIcon(effectiveness: Effectiveness): ResourceLocation {
|
||||
return when (effectiveness) {
|
||||
Effectiveness.IMMUNE -> immuneIcon
|
||||
Effectiveness.MOSTLY_INEFFECTIVE -> mostlyIneffectiveIcon
|
||||
Effectiveness.NOT_VERY_EFFECTIVE -> notVeryEffectiveIcon
|
||||
Effectiveness.EFFECTIVE -> effectiveIcon
|
||||
Effectiveness.SUPER_EFFECTIVE -> superEffectiveIcon
|
||||
Effectiveness.EXTREMELY_EFFECTIVE -> extremelyEffectiveIcon
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEffectivenessText(effectiveness: Effectiveness): MutableComponent {
|
||||
return when (effectiveness) {
|
||||
Effectiveness.IMMUNE -> "cobblemon.battle.ui.effectiveness.has_no_effect".asTranslated()
|
||||
Effectiveness.MOSTLY_INEFFECTIVE -> "cobblemon.battle.ui.effectiveness.mostly_ineffective".asTranslated()
|
||||
Effectiveness.NOT_VERY_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.not_very_effective".asTranslated()
|
||||
Effectiveness.EFFECTIVE -> "cobblemon.battle.ui.effectiveness.effective".asTranslated()
|
||||
Effectiveness.SUPER_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.super_effective".asTranslated()
|
||||
Effectiveness.EXTREMELY_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.extremely_effective".asTranslated()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMoveEffectiveness(move: MoveTemplate): Effectiveness? {
|
||||
val battle = CobblemonClient.battle ?: return null
|
||||
val opponents = battle.side2.activeClientBattlePokemon.toList()
|
||||
if (opponents.size != 1) return null
|
||||
val opponent = battle.side2.activeClientBattlePokemon.first().battlePokemon ?: return null
|
||||
|
||||
val aspects: Set<String> = opponent.state.currentAspects
|
||||
val opponentForm = opponent.species.getForm(aspects)
|
||||
if (move.damageCategory == DamageCategories.STATUS) return null
|
||||
return MoveEffectivenessCalculator.getMoveEffectiveness(move.elementalType, opponentForm.primaryType, opponentForm.secondaryType)
|
||||
}
|
||||
|
||||
fun format(input: Double): String {
|
||||
if (input <= 0) return "—"
|
||||
return "${decimalFormat.format(input)}%"
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
return !isTooltipHovered(mouseX, mouseY) && mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
|
||||
}
|
||||
|
||||
fun isTooltipHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX >= x + 82 && mouseX <= x + MOVE_WIDTH && mouseY >= y + 14 && mouseY <= y + MOVE_HEIGHT
|
||||
}
|
||||
|
||||
fun onClick() {
|
||||
if (!selectable) return
|
||||
@@ -204,7 +483,15 @@ class BattleMoveSelection(
|
||||
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
|
||||
move.onClick()
|
||||
} else {
|
||||
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
|
||||
battleGUI.changeActionSelection(
|
||||
BattleTargetSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
move.move,
|
||||
move.response.gimmickID,
|
||||
move.move.gimmickMove
|
||||
)
|
||||
)
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
return true
|
||||
@@ -224,4 +511,4 @@ class BattleMoveSelection(
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+880
@@ -0,0 +1,880 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.gui.renderSprite
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleActor
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattlePokemonInfoPanel
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.questionMarkIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.preview.TeamPreviewWidget
|
||||
import com.cobblemon.mod.common.client.render.SpriteType
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.util.toHex
|
||||
import com.mojang.blaze3d.platform.Lighting
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.narration.NarratableEntry
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.collections.get
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
class BattleTeamInfoSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest
|
||||
) : BattleActionSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
x = 0,
|
||||
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
|
||||
width = Minecraft.getInstance().window.guiScaledWidth,
|
||||
height = Minecraft.getInstance().window.guiScaledHeight,
|
||||
"cobblemon.battle.ui.team_info".asTranslated()
|
||||
) {
|
||||
companion object {
|
||||
const val SLOT_HORIZONTAL_SPACING = 4F
|
||||
const val SLOT_VERTICAL_SPACING = 2F
|
||||
|
||||
const val BACKGROUND_HEIGHT = 148
|
||||
val underlayTexture = cobblemonResource("textures/gui/battle/selection_underlay.png")
|
||||
val title = cobblemonResource("textures/gui/battle/turn_counter.png")
|
||||
var visible = false
|
||||
}
|
||||
|
||||
val teamTiles = mutableListOf<PokemonTile>()
|
||||
val opponentTiles = mutableListOf<PokemonTile>()
|
||||
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
init {
|
||||
addTiles()
|
||||
}
|
||||
|
||||
fun addTiles() {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
|
||||
val actors = mutableListOf<ClientBattleActor>()
|
||||
battle.sides.forEach { side ->
|
||||
side.actors.forEach { actors.add(it) }
|
||||
}
|
||||
|
||||
if (actors.size != 2) return
|
||||
val displayOrder = if (actors.last().uuid == Minecraft.getInstance().player?.uuid) actors.reversed() else actors
|
||||
val actorToPokemon = displayOrder.map { it to ClientBattleInformationRepository.actors[it.uuid] }
|
||||
actorToPokemon.first().second?.forEachIndexed { index, pokemon ->
|
||||
val (slotX, slotY) = getSlotPosition(index, 1)
|
||||
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false, actorToPokemon.first().first))
|
||||
}
|
||||
actorToPokemon.last().second?.forEachIndexed { index, pokemon ->
|
||||
val (slotX, slotY) = getSlotPosition(index, 2)
|
||||
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true, actorToPokemon.last().first))
|
||||
}
|
||||
}
|
||||
|
||||
fun getSlotPosition(index: Int, side: Int): Pair<Float, Float> {
|
||||
val startX = if (side == 1) 10 else width - 10 - (41 + TeamPreviewWidget.Companion.SLOT_HORIZONTAL_SPACING) * 2
|
||||
val startY = y + 15
|
||||
val row = index / 2
|
||||
val column = index % 2
|
||||
val slotX = startX.toFloat() + column * (SLOT_HORIZONTAL_SPACING + 41)
|
||||
val slotY = startY.toFloat() + row * (SLOT_VERTICAL_SPACING + 39)
|
||||
return Pair(slotX, slotY)
|
||||
}
|
||||
|
||||
private fun getHoveredTile(team: Boolean, mouseX: Int, mouseY: Int): Int {
|
||||
val tiles = if (team) teamTiles else opponentTiles
|
||||
return tiles.indexOfFirst { it.pokemonDto.activeBattlePokemonDTO != null && it.isHovered(mouseX.toDouble(), mouseY.toDouble()) }
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if (opacity <= 0.05F) return
|
||||
|
||||
val matrixStack = context.pose()
|
||||
val mc = Minecraft.getInstance()
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = underlayTexture,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = BACKGROUND_HEIGHT
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = title,
|
||||
x = (mc.window.guiScaledWidth / 2) - (103 / 2),
|
||||
y = 4,
|
||||
width = 103,
|
||||
height = 22
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.team_info.title".asTranslated().bold(),
|
||||
x = (mc.window.guiScaledWidth / 2),
|
||||
y = 6,
|
||||
scale = 0.75f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
teamTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(true, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, false)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
opponentTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(false, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, true)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
private fun shouldHide(hoveredTile: Int, currentTile: Int, reversed: Boolean): Boolean {
|
||||
if (hoveredTile != -1) {
|
||||
val hoveredRow = hoveredTile / 2
|
||||
val hoveredColumn = hoveredTile % 2
|
||||
val currentRow = currentTile / 2
|
||||
val currentColumn = currentTile % 2
|
||||
if (!reversed && hoveredColumn < currentColumn && hoveredRow <= currentRow) return true
|
||||
if (reversed && hoveredColumn > currentColumn && hoveredRow <= currentRow) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
|
||||
if (backButton.isHovered(mouseX, mouseY)) {
|
||||
battleGUI.changeActionSelection(null)
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
Companion.visible = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun narrationPriority() = NarratableEntry.NarrationPriority.HOVERED
|
||||
|
||||
class PokemonTile(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
private val initialPokemonDto: BattlePokemonDTO,
|
||||
val widget: BattleTeamInfoSelection,
|
||||
val reversed: Boolean,
|
||||
val actor: ClientBattleActor
|
||||
) {
|
||||
companion object {
|
||||
const val SELECT_WIDTH = 41
|
||||
const val SELECT_HEIGHT = 39
|
||||
const val SCALE = 0.5F
|
||||
val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
|
||||
|
||||
val pokemonTile = cobblemonResource("textures/gui/battle/pokemon_tile.png")
|
||||
val pokemonTileReversed = cobblemonResource("textures/gui/battle/pokemon_tile_reversed.png")
|
||||
val pokemonTileDisabled = cobblemonResource("textures/gui/battle/pokemon_tile_disabled.png")
|
||||
val pokemonTileDisabledReversed = cobblemonResource("textures/gui/battle/pokemon_tile_disabled_reversed.png")
|
||||
val pokemonTileSelected = cobblemonResource("textures/gui/battle/pokemon_tile_selected.png")
|
||||
val pokemonTileSelectedReversed = cobblemonResource("textures/gui/battle/pokemon_tile_selected_reversed.png")
|
||||
|
||||
val hoverGapSelected = cobblemonResource("textures/gui/battle/hover_gap_selected.png")
|
||||
val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
|
||||
val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
|
||||
|
||||
private val decimalFormat = DecimalFormat("0.00").also {
|
||||
it.roundingMode = RoundingMode.CEILING
|
||||
}
|
||||
}
|
||||
|
||||
val state = FloatingState()
|
||||
|
||||
val pokemonDto: BattlePokemonDTO
|
||||
get() = ClientBattleInformationRepository.actors[actor.uuid]?.firstOrNull { it.uuid == initialPokemonDto.uuid } ?: initialPokemonDto
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
|
||||
val pokemon = pokemonDto.activeBattlePokemonDTO
|
||||
if (pokemon == null) {
|
||||
BattlePokemonInfoPanel.renderUnknownTile(context, x, y, reversed)
|
||||
return
|
||||
}
|
||||
val battlePokemon = ClientBattlePokemon(
|
||||
uuid = pokemon.uuid,
|
||||
properties = pokemon.properties,
|
||||
aspects = pokemon.aspects,
|
||||
displayName = pokemon.displayName,
|
||||
hpValue = pokemon.hpValue,
|
||||
maxHp = pokemon.maxHp,
|
||||
isHpFlat = pokemon.isFlatHp,
|
||||
status = pokemon.status,
|
||||
statChanges = pokemon.statChanges,
|
||||
)
|
||||
state.currentAspects = pokemon.aspects
|
||||
val matrixStack = context.pose()
|
||||
val isFainted = pokemonDto.fainted
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/battle_info_underlay.png"),
|
||||
x = x + 5 + (if (reversed) 3 else 0),
|
||||
y = y + 4,
|
||||
width = 28,
|
||||
height = 28
|
||||
)
|
||||
val speciesToDisplay = battlePokemon.species
|
||||
val stateToDisplay = battlePokemon.state
|
||||
context.enableScissor(
|
||||
(x + 5 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5).toInt(),
|
||||
(x + 5 + 28 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5 + 28).toInt(),
|
||||
)
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(
|
||||
x + (if (reversed) 3 else 0) + 5 + 28 / 2.0,
|
||||
y.toDouble() + 5 - 5.0,
|
||||
0.0
|
||||
)
|
||||
drawCustomPosablePortrait(
|
||||
identifier = speciesToDisplay.resourceIdentifier,
|
||||
matrixStack = matrixStack,
|
||||
scale = 18f,
|
||||
contextScale = speciesToDisplay.getForm(stateToDisplay.currentAspects).baseScale,
|
||||
reversed = reversed,
|
||||
doQuirks = false,
|
||||
state = stateToDisplay,
|
||||
partialTicks = if (Cobblemon.config.animateBattleTiles) deltaTicks else 0F
|
||||
)
|
||||
matrixStack.popPose()
|
||||
context.disableScissor()
|
||||
|
||||
val tile = if (reversed) pokemonTileReversed else pokemonTile
|
||||
val disabled = if (reversed) pokemonTileDisabledReversed else pokemonTileDisabled
|
||||
val selected = if (reversed) pokemonTileSelectedReversed else pokemonTileSelected
|
||||
if (isFainted) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = disabled,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = tile,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
|
||||
if (isHovered(mouseX, mouseY)) {
|
||||
renderExpandedInfo(context, reversed, battlePokemon, pokemonDto)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderExpandedInfo(context: GuiGraphics, reversed: Boolean, pokemon: ClientBattlePokemon, dto: BattlePokemonDTO) {
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(0.0, 0.0, 300.0)
|
||||
val startX = if (reversed) x - 136 else x + 40
|
||||
val startY = y
|
||||
val species = pokemon.species
|
||||
val form = species.getForm(pokemon.state.currentAspects)
|
||||
val expandedInfoTexture = expandedPokemonInfo
|
||||
|
||||
val gap = if (reversed) hoverGapReversed else hoverGap
|
||||
val xOffset = if (reversed) 136 else -7
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = gap,
|
||||
x = startX + xOffset,
|
||||
y = startY,
|
||||
height = 36,
|
||||
width = 8,
|
||||
textureHeight = 36,
|
||||
textureWidth = 8,
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = expandedInfoTexture,
|
||||
x = startX,
|
||||
y = startY,
|
||||
height = 112,
|
||||
width = 137,
|
||||
textureHeight = 112,
|
||||
textureWidth = 137,
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.pokemon_form".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getFormText(species, form),
|
||||
x = startX + 36,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.buffs".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.attack.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.ATTACK, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.defence.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.DEFENCE, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_attack".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.SPECIAL_ATTACK, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_defence".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.SPECIAL_DEFENCE, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPEED)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.SPEED, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.accuracy.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ACCURACY)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.ACCURACY, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.evasion.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto?.buffs?.get(Stats.EVASION)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = BattlePokemonInfoPanel.getBoostText(Stats.EVASION, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.ability".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val ability = dto.ability?.let { Abilities.get(it) }
|
||||
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = abilityName,
|
||||
x = startX + 100,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.moves".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (dto.moves.size != 4) {
|
||||
for (i in 0 until 4) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "?".text(),
|
||||
x = startX + 100,
|
||||
y = startY + 31.5 + i * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val questionMarkText = "?".text()
|
||||
dto.moves.forEachIndexed { index, moveDTO ->
|
||||
val move = moveDTO?.move ?: questionMarkText
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = move.string.text(),
|
||||
x = if (move == questionMarkText) startX + 100 else startX + 74,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = move == questionMarkText
|
||||
)
|
||||
|
||||
if (moveDTO != null) {
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveDTO.timesUsed.toString().text(),
|
||||
x = startX + 127.5,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.speed != null) {
|
||||
val speed = dto.speed
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedAfterBoost = (speed * speedBoost).toInt()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedAfterBoost.toString().text(),
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
else {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.speed_tier".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedTier,
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.held_item".asTranslated(),
|
||||
x = startX + 40,
|
||||
y = startY + 99.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
TypeIcon(
|
||||
x = startX + 100.5,
|
||||
y = startY + 88,
|
||||
type = form.primaryType,
|
||||
secondaryType = form.secondaryType,
|
||||
small = false,
|
||||
centeredX = true
|
||||
).render(context)
|
||||
|
||||
if (form.secondaryType != null) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
|
||||
x = (startX + 69.0) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 140,
|
||||
textureHeight = 24,
|
||||
textureWidth = 140,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
|
||||
x = (startX + 70.5) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 132,
|
||||
textureHeight = 24,
|
||||
textureWidth = 132,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
|
||||
val heldItem = dto.heldItem
|
||||
if (heldItem != null && !heldItem.isEmpty()) {
|
||||
context.pose().pushPose()
|
||||
context.pose().scale(0.95F, 0.95F, 0.95F)
|
||||
val itemX = (startX + 4) / 0.95
|
||||
val itemY = (startY + 91) / 0.95
|
||||
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
|
||||
context.pose().popPose()
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = questionMarkIcon,
|
||||
x = startX + 6,
|
||||
y = startY + 92.5,
|
||||
height = 11,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
context.pose().popPose()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun drawCustomPosablePortrait(
|
||||
identifier: ResourceLocation,
|
||||
matrixStack: PoseStack,
|
||||
scale: Float = 13F,
|
||||
contextScale: Float = 1F,
|
||||
reversed: Boolean = false,
|
||||
state: PosableState,
|
||||
partialTicks: Float,
|
||||
limbSwing: Float = 0F,
|
||||
limbSwingAmount: Float = 0F,
|
||||
ageInTicks: Float = 0F,
|
||||
headYaw: Float = 0F,
|
||||
headPitch: Float = 0F,
|
||||
r: Float = 1F,
|
||||
g: Float = 1F,
|
||||
b: Float = 1F,
|
||||
a: Float = 1F,
|
||||
doQuirks: Boolean = true
|
||||
) {
|
||||
RenderSystem.applyModelViewMatrix()
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
|
||||
matrixStack.scale(scale, scale, -scale)
|
||||
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
|
||||
|
||||
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
|
||||
|
||||
if (sprite == null) {
|
||||
val model = VaryingModelRepository.getPoser(identifier, state)
|
||||
state.currentModel = model
|
||||
val texture = VaryingModelRepository.getTexture(identifier, state)
|
||||
|
||||
val context = RenderContext()
|
||||
model.context = context
|
||||
VaryingModelRepository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
|
||||
context.put(RenderContext.SCALE, contextScale)
|
||||
context.put(RenderContext.SPECIES, identifier)
|
||||
context.put(RenderContext.ASPECTS, state.currentAspects)
|
||||
context.put(RenderContext.POSABLE_STATE, state)
|
||||
context.put(RenderContext.DO_QUIRKS, doQuirks)
|
||||
|
||||
val renderType = RenderType.entityCutout(texture)
|
||||
|
||||
val quaternion1 = Axis.YP.rotationDegrees(-32F * if (reversed) -1F else 1F)
|
||||
val quaternion2 = Axis.XP.rotationDegrees(5F)
|
||||
|
||||
val originalPose = state.currentPose
|
||||
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
|
||||
state.updatePartialTicks(partialTicks)
|
||||
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
|
||||
originalPose?.let { state.setPose(it) }
|
||||
|
||||
matrixStack.translate(
|
||||
model.portraitTranslation.x * if (reversed) -1F else 1F,
|
||||
model.portraitTranslation.y + 1.5 * model.portraitScale,
|
||||
model.portraitTranslation.z - 4
|
||||
)
|
||||
matrixStack.scale(model.portraitScale, model.portraitScale, 1 / model.portraitScale)
|
||||
matrixStack.mulPose(quaternion1)
|
||||
matrixStack.mulPose(quaternion2)
|
||||
|
||||
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
|
||||
val light2 = Vector3f(0.1F, 0.0F, 8.0F)
|
||||
RenderSystem.setShaderLights(light1, light2)
|
||||
quaternion1.conjugate()
|
||||
|
||||
val immediate = Minecraft.getInstance().renderBuffers().bufferSource()
|
||||
val buffer = immediate.getBuffer(renderType)
|
||||
val packedLight = LightTexture.pack(11, 7)
|
||||
|
||||
val colour = toHex(r, g, b, a)
|
||||
model.withLayerContext(immediate, state, VaryingModelRepository.getLayers(identifier, state)) {
|
||||
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
|
||||
immediate.endBatch()
|
||||
}
|
||||
|
||||
model.setDefault()
|
||||
|
||||
Lighting.setupFor3DItems()
|
||||
} else {
|
||||
renderSprite(matrixStack, sprite)
|
||||
}
|
||||
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
|
||||
val base = form.baseStats[Stats.SPEED] ?: 0
|
||||
val maxIV = 31
|
||||
val maxEV = 252
|
||||
val minNatureMod = 0.9
|
||||
val maxNatureMod = 1.1
|
||||
|
||||
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
|
||||
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
|
||||
return "cobblemon.battle.ui.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
|
||||
}
|
||||
|
||||
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
|
||||
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
|
||||
}
|
||||
|
||||
private fun getMultiplierText(multiplier: Double): MutableComponent {
|
||||
val rounded = decimalFormat.format(multiplier)
|
||||
val str = "x$rounded"
|
||||
return when {
|
||||
multiplier > 1.0 -> str.green()
|
||||
multiplier < 1.0 -> str.red()
|
||||
else -> str.text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFormText(species: Species, form: FormData): MutableComponent {
|
||||
val speciesName = species.name
|
||||
val formName = form.name
|
||||
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
|
||||
}
|
||||
}
|
||||
}
|
||||
+325
-46
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.battle.widgets
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleMessageQueue
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
@@ -17,7 +18,15 @@ import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.ObjectSelectionList
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.Style
|
||||
import net.minecraft.network.chat.TextColor
|
||||
import net.minecraft.network.chat.contents.TranslatableContents
|
||||
import net.minecraft.util.FormattedCharSequence
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.round
|
||||
|
||||
/**
|
||||
* Pane for seeing and interacting with battle messages.
|
||||
@@ -36,29 +45,80 @@ class BattleMessagePane(
|
||||
), CobblemonRenderable {
|
||||
var opacity = 1F
|
||||
private var scrolling = false
|
||||
private var resizing = false
|
||||
private var movingBox = false
|
||||
// Offset from the mouse to the box's top-left when a move-drag begins, so the box tracks the cursor
|
||||
// exactly (positioning absolutely avoids the GUI-scale mismatch that accumulating deltas causes).
|
||||
private var dragOffsetX = 0.0
|
||||
private var dragOffsetY = 0.0
|
||||
|
||||
val appropriateX: Int
|
||||
get() = minecraft.window.guiScaledWidth - (FRAME_WIDTH + 12)
|
||||
val appropriateY: Int
|
||||
get() = minecraft.window.guiScaledHeight - (30 + (if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT))
|
||||
|
||||
val battleMessages = mutableListOf<Component>()
|
||||
private val unsubscribe: () -> Unit
|
||||
|
||||
fun dispose() = unsubscribe()
|
||||
|
||||
init {
|
||||
correctSize()
|
||||
//setRenderBackground(false)
|
||||
|
||||
messageQueue.subscribe {
|
||||
val fullyScrolledDown = maxScroll - scrollAmount < 10
|
||||
addEntry(BattleMessageLine(this, it))
|
||||
if (fullyScrolledDown) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
}
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
unsubscribe = messageQueue.subscribeComponents {
|
||||
battleMessages.add(it)
|
||||
correctBattleText()
|
||||
}
|
||||
}
|
||||
|
||||
private fun correctBattleText() {
|
||||
val isFullyScrolled = maxScroll - scrollAmount < 10
|
||||
clearEntries()
|
||||
val textRenderer = Minecraft.getInstance().font
|
||||
for (message in battleMessages) {
|
||||
// Turn announcements ("It is now turn N") render as a centered "Turn N" divider header.
|
||||
val contents = message.contents
|
||||
if (contents is TranslatableContents && contents.key == "cobblemon.battle.turn") {
|
||||
val turnNum = contents.args.firstOrNull()?.toString() ?: ""
|
||||
addEntry(BattleMessageLine(this, null, Component.translatable("deltaclient.ui.battle.turn", turnNum).string))
|
||||
continue
|
||||
}
|
||||
// Tint certain message categories by their translation key: weather/field/screen → teal,
|
||||
// stat changes → purple, damage → blue. Other messages keep their existing styling.
|
||||
var style = message.style.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE)
|
||||
val color = messageColor((contents as? TranslatableContents)?.key)
|
||||
if (color != null) style = style.withColor(TextColor.fromRgb(color))
|
||||
val line = message.copy().setStyle(style)
|
||||
val wrappedLines = textRenderer.splitter.splitLines(line, battleLogWidth - 27, line.style)
|
||||
val lines = Language.getInstance().getVisualOrder(wrappedLines)
|
||||
for (finalLine in lines) {
|
||||
addEntry(BattleMessageLine(this, finalLine))
|
||||
}
|
||||
}
|
||||
if (isFullyScrolled) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
/** Colour for a battle message based on its translation key, or null to keep its existing styling. */
|
||||
private fun messageColor(key: String?): Int? = when {
|
||||
key == null -> null
|
||||
key == "cobblemon.battle.damage_dealt" -> 0x4298ED // damage % → blue
|
||||
key.startsWith("cobblemon.battle.weather.") -> 0x42EDAF // weather → teal
|
||||
key.startsWith("cobblemon.battle.fieldstart.") -> 0x42EDAF // terrains, rooms, gravity, etc.
|
||||
key.startsWith("cobblemon.battle.fieldend.") -> 0x42EDAF
|
||||
key.startsWith("cobblemon.battle.fieldactivate.") -> 0x42EDAF
|
||||
key.startsWith("cobblemon.battle.sidestart.") -> 0x42EDAF // Reflect, Light Screen, etc.
|
||||
key.startsWith("cobblemon.battle.sideend.") -> 0x42EDAF
|
||||
key.startsWith("cobblemon.battle.boost.") -> 0x8C42ED // stat rose → purple
|
||||
key.startsWith("cobblemon.battle.unboost.") -> 0x8C42ED // stat fell → purple
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun correctSize() {
|
||||
val textBoxHeight = if (expanded) TEXT_BOX_HEIGHT * 2 else TEXT_BOX_HEIGHT
|
||||
setRectangle(TEXT_BOX_WIDTH, textBoxHeight, appropriateY + 6, appropriateY + 6)
|
||||
this.x = appropriateX
|
||||
setRectangle(battleLogWidth - 11, battleLogHeight - 13, battleLogX.toInt(), battleLogY.toInt() + 8)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -71,10 +131,52 @@ class BattleMessagePane(
|
||||
const val TEXT_BOX_HEIGHT = 46
|
||||
const val EXPAND_TOGGLE_SIZE = 5
|
||||
|
||||
private val battleMessagePaneFrameResource = cobblemonResource("textures/gui/battle/battle_log.png")
|
||||
private val battleMessagePaneFrameExpandedResource = cobblemonResource("textures/gui/battle/battle_log_expanded.png")
|
||||
private val battleMessageHighlight = cobblemonResource("textures/gui/battle/battle_log_row_selected_color.png")
|
||||
|
||||
val topTexture = cobblemonResource("textures/gui/battle/log/top.png")
|
||||
val bottomTexture = cobblemonResource("textures/gui/battle/log/bottom.png")
|
||||
val leftTexture = cobblemonResource("textures/gui/battle/log/left.png")
|
||||
val rightTexture = cobblemonResource("textures/gui/battle/log/right.png")
|
||||
val topLeftTexture = cobblemonResource("textures/gui/battle/log/top_left.png")
|
||||
val topRightTexture = cobblemonResource("textures/gui/battle/log/top_right.png")
|
||||
val bottomLeftTexture = cobblemonResource("textures/gui/battle/log/bottom_left.png")
|
||||
val bottomRightTexture = cobblemonResource("textures/gui/battle/log/bottom_right.png")
|
||||
val centerTexture = cobblemonResource("textures/gui/battle/log/center.png")
|
||||
|
||||
private var expanded = false
|
||||
|
||||
var battleLogWidth: Int = 153
|
||||
set(value) {
|
||||
val max = Minecraft.getInstance().window.guiScaledWidth - 20
|
||||
if (battleLogX + value <= max) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
var battleLogHeight: Int = 46
|
||||
set(value) {
|
||||
val max = Minecraft.getInstance().window.guiScaledHeight - 20
|
||||
if (battleLogY + value <= max) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
var battleLogX: Double = defaultLogX
|
||||
get() {
|
||||
val max = Minecraft.getInstance().window.guiScaledWidth - battleLogWidth
|
||||
return max(min(field, max.toDouble() - 20), 4.0)
|
||||
}
|
||||
|
||||
var battleLogY: Double = defaultLogY
|
||||
get() {
|
||||
val max = Minecraft.getInstance().window.guiScaledHeight - battleLogHeight
|
||||
return max(min(field, max.toDouble() - 20), 4.0)
|
||||
}
|
||||
|
||||
val defaultLogX: Double
|
||||
get() = Minecraft.getInstance().window.guiScaledWidth - 181.0
|
||||
val defaultLogY: Double
|
||||
get() = Minecraft.getInstance().window.guiScaledHeight - 85.0
|
||||
}
|
||||
|
||||
override fun addEntry(entry: BattleMessageLine): Int {
|
||||
@@ -82,7 +184,7 @@ class BattleMessagePane(
|
||||
}
|
||||
|
||||
override fun getRowLeft(): Int {
|
||||
return super.getRowLeft() + 4
|
||||
return x + 10
|
||||
}
|
||||
|
||||
override fun renderSelection(guiGraphics: GuiGraphics, top: Int, width: Int, height: Int, outerColor: Int, innerColor: Int) {
|
||||
@@ -102,7 +204,7 @@ class BattleMessagePane(
|
||||
x = x + 6,
|
||||
y = top - 2,
|
||||
height = 1,
|
||||
width = LINE_WIDTH,
|
||||
width = battleLogWidth - 18,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
@@ -112,14 +214,14 @@ class BattleMessagePane(
|
||||
x = x + 6,
|
||||
y = top + 7,
|
||||
height = 1,
|
||||
width = LINE_WIDTH,
|
||||
width = battleLogWidth - 18,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = guiGraphics.pose(),
|
||||
texture = battleMessageHighlight,
|
||||
x = x + 6 + LINE_WIDTH - 1,
|
||||
x = x + 6 + battleLogWidth - 19,
|
||||
y = top - 2,
|
||||
height = 10,
|
||||
width = 1,
|
||||
@@ -128,62 +230,198 @@ class BattleMessagePane(
|
||||
}
|
||||
|
||||
override fun getRowWidth(): Int {
|
||||
return LINE_WIDTH
|
||||
return battleLogWidth - 16
|
||||
}
|
||||
|
||||
override fun getScrollbarPosition(): Int {
|
||||
return this.x + 154
|
||||
return this.x + battleLogWidth - 16
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
|
||||
correctSize()
|
||||
|
||||
val isFullyScrolled = opacity != 1f || maxScroll - scrollAmount < 2
|
||||
if (isFullyScrolled) { scrollAmount = maxScroll.toDouble() }
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (expanded) battleMessagePaneFrameExpandedResource else battleMessagePaneFrameResource,
|
||||
x = this.x,
|
||||
y = appropriateY,
|
||||
height = if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT,
|
||||
width = FRAME_WIDTH,
|
||||
texture = topLeftTexture,
|
||||
x = battleLogX,
|
||||
y = battleLogY,
|
||||
height = 8,
|
||||
width = 7,
|
||||
textureHeight = 8,
|
||||
textureWidth = 7,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = topRightTexture,
|
||||
x = battleLogX + battleLogWidth - 13,
|
||||
y = battleLogY,
|
||||
height = 8,
|
||||
width = 13,
|
||||
textureHeight = 8,
|
||||
textureWidth = 13,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = bottomLeftTexture,
|
||||
x = battleLogX,
|
||||
y = battleLogY + battleLogHeight - 5,
|
||||
height = 5,
|
||||
width = 7,
|
||||
textureHeight = 5,
|
||||
textureWidth = 7,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = bottomRightTexture,
|
||||
x = battleLogX + battleLogWidth - 13,
|
||||
y = battleLogY + battleLogHeight - 10,
|
||||
height = 10,
|
||||
width = 13,
|
||||
textureHeight = 10,
|
||||
textureWidth = 13,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = topTexture,
|
||||
x = battleLogX + 7,
|
||||
y = battleLogY,
|
||||
height = 7,
|
||||
width = battleLogWidth - 7 - 13,
|
||||
textureHeight = 7,
|
||||
textureWidth = 1,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = bottomTexture,
|
||||
x = battleLogX + 7,
|
||||
y = battleLogY + battleLogHeight - 4,
|
||||
height = 4,
|
||||
width = battleLogWidth - 7 - 12,
|
||||
textureHeight = 4,
|
||||
textureWidth = 1,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = leftTexture,
|
||||
x = battleLogX,
|
||||
y = battleLogY + 8,
|
||||
height = battleLogHeight - 8 - 5,
|
||||
width = 7,
|
||||
textureHeight = 1,
|
||||
textureWidth = 7,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = rightTexture,
|
||||
x = battleLogX + battleLogWidth - 13,
|
||||
y = battleLogY + 8,
|
||||
height = battleLogHeight - 18,
|
||||
width = 13,
|
||||
textureHeight = 1,
|
||||
textureWidth = 13,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = centerTexture,
|
||||
x = battleLogX + 7,
|
||||
y = battleLogY + 7,
|
||||
height = battleLogHeight - 11,
|
||||
width = battleLogWidth - 20,
|
||||
textureHeight = battleLogHeight - 11,
|
||||
textureWidth = battleLogWidth - 20,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
val textBoxHeight = if (expanded) TEXT_BOX_HEIGHT * 2 else TEXT_BOX_HEIGHT
|
||||
context.enableScissor(
|
||||
this.x + 5,
|
||||
appropriateY + 6,
|
||||
this.x + 5 + width,
|
||||
appropriateY + 6 + textBoxHeight
|
||||
x + 5,
|
||||
round(battleLogY + 6).toInt(),
|
||||
x + 6 + battleLogWidth,
|
||||
round(battleLogY + 6 + battleLogHeight).toInt()
|
||||
)
|
||||
super.renderWidget(context, mouseX, mouseY, partialTicks)
|
||||
context.disableScissor()
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val toggleOffsetY = if (expanded) 92 else 46
|
||||
if (mouseX > (this.x + 160) && mouseX < (this.x + 160 + EXPAND_TOGGLE_SIZE) && mouseY > (appropriateY + toggleOffsetY) && mouseY < (appropriateY + toggleOffsetY + EXPAND_TOGGLE_SIZE)) {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
if (button != 0) return false
|
||||
// Intentionally NOT calling super.mouseClicked — that selects the clicked log line, which we no
|
||||
// longer want. Instead the press grabs the scrollbar, the bottom-right resize handle, or (anywhere
|
||||
// else on the box) moves the whole box.
|
||||
updateScrollingState(mouseX, mouseY)
|
||||
if (scrolling) {
|
||||
focused = getEntryAtPosition(mouseX, mouseY)
|
||||
isDragging = true
|
||||
if (scrolling) return true
|
||||
if (isOnResizeHandle(mouseX, mouseY)) {
|
||||
resizing = true
|
||||
return true
|
||||
}
|
||||
if (isWithinBox(mouseX, mouseY)) {
|
||||
movingBox = true
|
||||
dragOffsetX = mouseX - battleLogX
|
||||
dragOffsetY = mouseY - battleLogY
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
scrolling = false
|
||||
resizing = false
|
||||
movingBox = false
|
||||
return super.mouseReleased(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (scrolling) {
|
||||
if (mouseY < this.y) {
|
||||
scrollAmount = 0.0
|
||||
} else if (mouseY > bottom) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
} else {
|
||||
scrollAmount += deltaY
|
||||
when {
|
||||
mouseY < this.y -> scrollAmount = 0.0
|
||||
mouseY > bottom -> scrollAmount = maxScroll.toDouble()
|
||||
else -> scrollAmount += deltaY
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
if (resizing) {
|
||||
battleLogHeight = max(mouseY.toInt() + 7 - y, TEXT_BOX_HEIGHT)
|
||||
battleLogWidth = max(mouseX.toInt() - x, TEXT_BOX_WIDTH)
|
||||
correctSize()
|
||||
correctBattleText()
|
||||
return true
|
||||
}
|
||||
if (movingBox) {
|
||||
battleLogX = mouseX - dragOffsetX
|
||||
battleLogY = mouseY - dragOffsetY
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** The whole message box (frame included) — grab it anywhere here to move it. */
|
||||
private fun isWithinBox(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX >= battleLogX && mouseX <= battleLogX + battleLogWidth &&
|
||||
mouseY >= battleLogY && mouseY <= battleLogY + battleLogHeight
|
||||
}
|
||||
|
||||
/** The resize handle in the bottom-right corner (drag to expand); takes priority over moving. */
|
||||
private fun isOnResizeHandle(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX >= battleLogX + battleLogWidth - 13 && mouseX <= battleLogX + battleLogWidth &&
|
||||
mouseY >= battleLogY + battleLogHeight - 10 && mouseY <= battleLogY + battleLogHeight
|
||||
}
|
||||
|
||||
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
|
||||
@@ -193,7 +431,7 @@ class BattleMessagePane(
|
||||
&& mouseY < bottom
|
||||
}
|
||||
|
||||
class BattleMessageLine(val pane: BattleMessagePane, val line: FormattedCharSequence) : Entry<BattleMessageLine>() {
|
||||
class BattleMessageLine(val pane: BattleMessagePane, val line: FormattedCharSequence?, val headerText: String? = null) : Entry<BattleMessageLine>() {
|
||||
override fun getNarration() = "".text()
|
||||
override fun render(
|
||||
context: GuiGraphics,
|
||||
@@ -207,13 +445,54 @@ class BattleMessagePane(
|
||||
isHovered: Boolean,
|
||||
partialTicks: Float
|
||||
) {
|
||||
if (headerText != null) {
|
||||
// "Turn N" centered on the divider's true midpoint, with an equal-length line each side.
|
||||
val comp = headerText.text().setStyle(Style.EMPTY.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE))
|
||||
val textWidth = Minecraft.getInstance().font.width(comp)
|
||||
val dividerLeft = rowLeft - 3
|
||||
val dividerRight = rowLeft + rowWidth - 7
|
||||
val centerX = (dividerLeft + dividerRight) / 2
|
||||
val lineY = rowTop + 2
|
||||
val leftEnd = centerX - textWidth / 2 - 3
|
||||
val rightStart = centerX + textWidth / 2 + 3
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = CobblemonResources.WHITE,
|
||||
x = dividerLeft,
|
||||
y = lineY,
|
||||
width = (leftEnd - dividerLeft).coerceAtLeast(0),
|
||||
height = 1,
|
||||
alpha = pane.opacity * 0.6f
|
||||
)
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = CobblemonResources.WHITE,
|
||||
x = rightStart,
|
||||
y = lineY,
|
||||
width = (dividerRight - rightStart).coerceAtLeast(0),
|
||||
height = 1,
|
||||
alpha = pane.opacity * 0.6f
|
||||
)
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = comp,
|
||||
x = centerX - textWidth / 2,
|
||||
y = rowTop - 2,
|
||||
opacity = pane.opacity
|
||||
)
|
||||
return
|
||||
}
|
||||
drawScaledText(
|
||||
context,
|
||||
line,
|
||||
line ?: return,
|
||||
rowLeft,
|
||||
rowTop - 2,
|
||||
opacity = pane.opacity
|
||||
)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,33 +36,32 @@ import com.cobblemon.mod.common.client.gui.handleProfileDebugKeys
|
||||
import com.cobblemon.mod.common.client.gui.logProfileTranslations
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_ARROW_PADDING_AROUND
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_PADDING_AROUND
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ABILITIES
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DESCRIPTION
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DROPS
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_EVOLUTION_TREE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_SIZE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_MOVES
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_SIZE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_STATS
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.EvolutionTreeWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.debugEvolutionRequirementRendering
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.AbilitiesWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DescriptionWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DropsScrollingWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.EntriesScrollingWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.MovesLearnsetWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonInfoWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SearchWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SizeWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.StatsWidget
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexType
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.isInventoryKeyPressed
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.EditBox
|
||||
@@ -95,10 +94,6 @@ class PokedexGUI private constructor(
|
||||
|
||||
private val categoryBarOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_bar_category.png")
|
||||
|
||||
// TODO: separate into smaller components / widgets?
|
||||
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
|
||||
private val expandedBackgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay_expanded.png")
|
||||
|
||||
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
|
||||
private val tabIcons = arrayOf(
|
||||
cobblemonResource("textures/gui/pokedex/tab_info.png"),
|
||||
@@ -106,8 +101,7 @@ class PokedexGUI private constructor(
|
||||
cobblemonResource("textures/gui/pokedex/tab_size.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_stats.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_drops.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_moves.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_evolution_tree.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_moves.png")
|
||||
)
|
||||
|
||||
val arrowUpIcon = cobblemonResource("textures/gui/pokedex/arrow_up.png")
|
||||
@@ -120,8 +114,6 @@ class PokedexGUI private constructor(
|
||||
TAB_MOVES
|
||||
)
|
||||
|
||||
private val ICONS_PADDING_BETWEEN = (BASE_WIDTH / 2 - ICONS_PADDING_AROUND * 2) / tabIcons.size
|
||||
|
||||
/**
|
||||
* Attempts to open this screen for a client.
|
||||
*/
|
||||
@@ -140,7 +132,6 @@ class PokedexGUI private constructor(
|
||||
|
||||
var oldDragPosX = 0.0
|
||||
var canDragRender = false
|
||||
private var activeDragableElement: GuiEventListener? = null
|
||||
|
||||
private var filteredPokedex: Collection<PokedexDef> = mutableListOf()
|
||||
private var seenCount = "0000"
|
||||
@@ -164,29 +155,13 @@ class PokedexGUI private constructor(
|
||||
private lateinit var scrollScreen: EntriesScrollingWidget
|
||||
private lateinit var pokemonInfoWidget: PokemonInfoWidget
|
||||
private lateinit var searchWidget: SearchWidget
|
||||
private lateinit var pokemonNameWidget: PokemonNameWidget
|
||||
|
||||
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
|
||||
private var selectedCategoryFilter: PokedexCategoryFilterType = PokedexCategoryFilterType.ALL
|
||||
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
|
||||
|
||||
var seenShinyStates = setOf<String>()
|
||||
var shiny = false
|
||||
var maleRatio = -1F
|
||||
var gender = Gender.GENDERLESS
|
||||
var aspects = mutableSetOf<String>()
|
||||
|
||||
lateinit var tabInfoElement: GuiEventListener
|
||||
var tabInfoIndex = TAB_DESCRIPTION
|
||||
var evolutionTreeExpanded = false
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
if (::pokemonInfoWidget.isInitialized) {
|
||||
pokemonInfoWidget.active = !value
|
||||
pokemonInfoWidget.visible = !value
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderBlurredBackground(delta: Float) {}
|
||||
override fun renderMenuBackground(context: GuiGraphics) {}
|
||||
@@ -205,7 +180,7 @@ class PokedexGUI private constructor(
|
||||
|
||||
//Info Widget
|
||||
if (::pokemonInfoWidget.isInitialized) removeWidget(pokemonInfoWidget)
|
||||
pokemonInfoWidget = PokemonInfoWidget(this, x + 180, y + 28) { newForm -> updateSelectedForm(newForm) }
|
||||
pokemonInfoWidget = PokemonInfoWidget(x + 180, y + 28) { newForm -> updateSelectedForm(newForm) }
|
||||
addRenderableWidget(pokemonInfoWidget)
|
||||
|
||||
setUpTabs()
|
||||
@@ -217,10 +192,6 @@ class PokedexGUI private constructor(
|
||||
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
|
||||
addRenderableWidget(searchWidget)
|
||||
|
||||
if (::pokemonNameWidget.isInitialized) removeWidget(pokemonNameWidget)
|
||||
pokemonNameWidget = PokemonNameWidget(this, x + 180, y + 28)
|
||||
addRenderableWidget(pokemonNameWidget)
|
||||
|
||||
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
|
||||
regionSelectWidgetUp = ScaledButton(
|
||||
buttonX = (x + 95).toFloat(),
|
||||
@@ -396,14 +367,6 @@ class PokedexGUI private constructor(
|
||||
shadow = true
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if (evolutionTreeExpanded) expandedBackgroundOverlay else backgroundOverlay,
|
||||
x = pokemonInfoWidget.x, y = pokemonInfoWidget.y,
|
||||
width = HALF_OVERLAY_WIDTH,
|
||||
height = HALF_OVERLAY_HEIGHT
|
||||
)
|
||||
|
||||
// Category filter
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
@@ -439,7 +402,7 @@ class PokedexGUI private constructor(
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = tabSelectArrow,
|
||||
x = (x + BASE_WIDTH / 2 + ICONS_ARROW_PADDING_AROUND + tabInfoIndex * ICONS_PADDING_BETWEEN) / SCALE,
|
||||
x = (x + 191.5F + (22F * tabInfoIndex)) / SCALE,
|
||||
y = (y + 177) / SCALE,
|
||||
width = 12,
|
||||
height = 6,
|
||||
@@ -478,7 +441,6 @@ class PokedexGUI private constructor(
|
||||
val canDisplayEntry = true //selectedForm?.unlockForms
|
||||
|
||||
if (::pokemonInfoWidget.isInitialized
|
||||
&& pokemonInfoWidget.active
|
||||
&& !pokemonInfoWidget.suppressViewport
|
||||
&& pokemonInfoWidget.isWithinPortraitSpace(mouseX, mouseY)
|
||||
&& canDisplayEntry == true
|
||||
@@ -487,12 +449,6 @@ class PokedexGUI private constructor(
|
||||
isDragging = true
|
||||
oldDragPosX = mouseX
|
||||
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
|
||||
activeDragableElement = pokemonInfoWidget
|
||||
} else if (tabInfoIndex == TAB_EVOLUTION_TREE &&
|
||||
::tabInfoElement.isInitialized &&
|
||||
(tabInfoElement as EvolutionTreeWidget).isHovered(mouseX, mouseY)
|
||||
) {
|
||||
activeDragableElement = tabInfoElement
|
||||
}
|
||||
return try {
|
||||
super.mouseClicked(mouseX, mouseY, button)
|
||||
@@ -504,18 +460,14 @@ class PokedexGUI private constructor(
|
||||
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (canDragRender) canDragRender = false
|
||||
if (isDragging) isDragging = false
|
||||
activeDragableElement = null
|
||||
return super.mouseReleased(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (isDragging && canDragRender && activeDragableElement == pokemonInfoWidget) {
|
||||
if (isDragging && canDragRender) {
|
||||
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
|
||||
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
|
||||
} else if (activeDragableElement == tabInfoElement) {
|
||||
tabInfoElement.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
|
||||
oldDragPosX = mouseX
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
@@ -606,8 +558,6 @@ class PokedexGUI private constructor(
|
||||
selectedForm = CobblemonClient.clientPokedexData.getEncounteredForms(newSelectedEntry).firstOrNull()
|
||||
|
||||
pokemonInfoWidget.setDexEntry(selectedEntry!!)
|
||||
pokemonNameWidget.update(selectedEntry!!)
|
||||
|
||||
displaytabInfoElement(tabInfoIndex)
|
||||
}
|
||||
|
||||
@@ -619,7 +569,7 @@ class PokedexGUI private constructor(
|
||||
|
||||
for (i in tabIcons.indices) {
|
||||
tabButtons.add(ScaledButton(
|
||||
x + BASE_WIDTH / 2 + ICONS_PADDING_AROUND + i * ICONS_PADDING_BETWEEN,
|
||||
x + 190.5F + (i * 22F),
|
||||
y + 181.5F,
|
||||
TAB_ICON_SIZE,
|
||||
TAB_ICON_SIZE,
|
||||
@@ -632,8 +582,6 @@ class PokedexGUI private constructor(
|
||||
}
|
||||
|
||||
fun displaytabInfoElement(tabIndex: Int, update: Boolean = true) {
|
||||
if (tabIndex != TAB_EVOLUTION_TREE) evolutionTreeExpanded = false
|
||||
|
||||
val showActiveTab = selectedEntry?.let { selectedForm in getSufficientlyKnownForms(it, tabIndex) } == true
|
||||
var statSubIndex = 0
|
||||
|
||||
@@ -685,9 +633,6 @@ class PokedexGUI private constructor(
|
||||
TAB_MOVES -> {
|
||||
tabInfoElement = MovesLearnsetWidget(x + 180, y + 28)
|
||||
}
|
||||
TAB_EVOLUTION_TREE -> {
|
||||
tabInfoElement = EvolutionTreeWidget(this, x, y)
|
||||
}
|
||||
}
|
||||
val element = tabInfoElement
|
||||
if (element is Renderable && element is NarratableEntry) {
|
||||
@@ -762,9 +707,6 @@ class PokedexGUI private constructor(
|
||||
selectedForm = fallbackForm
|
||||
) { next -> cycleSelectedForm(next) }
|
||||
}
|
||||
TAB_EVOLUTION_TREE -> {
|
||||
(tabInfoElement as EvolutionTreeWidget).setup(selectedEntry, selectedForm)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tabInfoIndex != TAB_DESCRIPTION) displaytabInfoElement(TAB_DESCRIPTION)
|
||||
@@ -828,10 +770,6 @@ class PokedexGUI private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (tabInfoIndex == TAB_EVOLUTION_TREE && keyCode == InputConstants.KEY_SEMICOLON) {
|
||||
debugEvolutionRequirementRendering()
|
||||
}
|
||||
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -16,8 +16,6 @@ object PokedexGUIConstants {
|
||||
const val HEADER_BAR_HEIGHT = 11
|
||||
const val POKEMON_DESCRIPTION_HEIGHT = 42
|
||||
const val POKEMON_DESCRIPTION_PADDING = 9
|
||||
const val ICONS_PADDING_AROUND = 18.5f
|
||||
const val ICONS_ARROW_PADDING_AROUND = ICONS_PADDING_AROUND + 1
|
||||
|
||||
const val TAB_ICON_SIZE = 16
|
||||
|
||||
@@ -38,7 +36,6 @@ object PokedexGUIConstants {
|
||||
const val TAB_STATS = 3
|
||||
const val TAB_DROPS = 4
|
||||
const val TAB_MOVES = 5
|
||||
const val TAB_EVOLUTION_TREE = 6
|
||||
|
||||
// RowScrollingWidget
|
||||
const val SCROLL_BASE_HEIGHT = 141
|
||||
|
||||
+9
-72
@@ -13,101 +13,38 @@ import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
|
||||
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
|
||||
|
||||
private const val TOOLTIP_SLICE_SIZE = 11
|
||||
|
||||
fun renderTooltip(
|
||||
context: GuiGraphics,
|
||||
text: MutableComponent,
|
||||
posX: Int,
|
||||
posY: Int,
|
||||
delta: Float,
|
||||
offsetY: Int = 0,
|
||||
) {
|
||||
renderTooltip(
|
||||
context,
|
||||
listOf(text),
|
||||
posX,
|
||||
posY,
|
||||
delta,
|
||||
offsetY,
|
||||
)
|
||||
}
|
||||
|
||||
fun renderTooltip(
|
||||
context: GuiGraphics,
|
||||
text: List<MutableComponent>,
|
||||
posX: Int,
|
||||
posY: Int,
|
||||
delta: Float,
|
||||
offsetY: Int = 0,
|
||||
) {
|
||||
fun renderTooltip(context: GuiGraphics, text: MutableComponent, posX: Int, posY: Int, delta: Float, offsetY: Int = 0) {
|
||||
val poseStack = context.pose()
|
||||
val textWidth = text.maxOf { Minecraft.getInstance().font.width(it.font(CobblemonResources.DEFAULT_LARGE)) }
|
||||
val tooltipPadding = 6
|
||||
val tooltipWidth = textWidth + tooltipPadding
|
||||
val textWidth = Minecraft.getInstance().font.width(text.font(CobblemonResources.DEFAULT_LARGE))
|
||||
val tooltipWidth = textWidth + 6
|
||||
val tooltipHeight = 11
|
||||
val tooltipTop = posY + offsetY
|
||||
|
||||
poseStack.pushPose()
|
||||
poseStack.translate(0.0, 0.0, 1000.0)
|
||||
|
||||
Minecraft.getInstance().let {
|
||||
it.mainRenderTarget.bindWrite(false)
|
||||
context.enableScissor(
|
||||
posX - (tooltipWidth / 2),
|
||||
tooltipTop + 1,
|
||||
(posX - (tooltipWidth / 2)) + tooltipWidth,
|
||||
tooltipTop + TOOLTIP_SLICE_SIZE * text.size - 1
|
||||
tooltipTop + 10
|
||||
)
|
||||
it.gameRenderer.processBlurEffect(delta)
|
||||
context.disableScissor()
|
||||
it.mainRenderTarget.bindWrite(true)
|
||||
}
|
||||
|
||||
val totalHeight = TOOLTIP_SLICE_SIZE * text.size
|
||||
|
||||
blitSlicedVertical(poseStack, tooltipEdge, posX - (tooltipWidth / 2) - 1, tooltipTop, 1, totalHeight)
|
||||
blitSlicedVertical(poseStack, tooltipBackground, posX - (tooltipWidth / 2), tooltipTop, tooltipWidth, totalHeight)
|
||||
blitSlicedVertical(poseStack, tooltipEdge, posX + (tooltipWidth / 2), tooltipTop, 1, totalHeight)
|
||||
|
||||
val centered = text.size == 1
|
||||
|
||||
text.forEachIndexed { index, it ->
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = it,
|
||||
x = if (centered) posX else posX - tooltipWidth / 2 + tooltipPadding / 2,
|
||||
y = tooltipTop + 1 + TOOLTIP_SLICE_SIZE * index,
|
||||
shadow = true,
|
||||
centered = centered
|
||||
)
|
||||
}
|
||||
|
||||
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX - (tooltipWidth / 2) - 1, y = tooltipTop, width = 1, height = tooltipHeight)
|
||||
blitk(matrixStack = poseStack, texture = tooltipBackground, x = posX - (tooltipWidth / 2), y = tooltipTop, width = tooltipWidth, height = tooltipHeight)
|
||||
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX + (tooltipWidth / 2), y = tooltipTop, width = 1, height = tooltipHeight)
|
||||
drawScaledText(context = context, font = CobblemonResources.DEFAULT_LARGE, text = text, x = posX, y = tooltipTop + 1, shadow = true, centered = true)
|
||||
poseStack.popPose()
|
||||
}
|
||||
|
||||
// Stretching the whole 11px image over an arbitrary height thickens the caps as more lines are added, so
|
||||
// instead the caps are drawn 1:1 and only the flat fill row is stretched to cover the remaining height
|
||||
private fun blitSlicedVertical(
|
||||
poseStack: PoseStack,
|
||||
texture: ResourceLocation,
|
||||
x: Number,
|
||||
y: Int,
|
||||
width: Number,
|
||||
totalHeight: Int,
|
||||
) {
|
||||
val middleHeight = totalHeight - 2
|
||||
|
||||
blitk(matrixStack = poseStack, texture = texture, x = x, y = y, width = width, height = 1, vOffset = 0, textureHeight = TOOLTIP_SLICE_SIZE)
|
||||
blitk(matrixStack = poseStack, texture = texture, x = x, y = y + 1, width = width, height = middleHeight, vOffset = middleHeight, textureHeight = TOOLTIP_SLICE_SIZE * middleHeight)
|
||||
blitk(matrixStack = poseStack, texture = texture, x = x, y = y + 1 + middleHeight, width = width, height = 1, vOffset = TOOLTIP_SLICE_SIZE - 1, textureHeight = TOOLTIP_SLICE_SIZE)
|
||||
}
|
||||
@@ -20,8 +20,8 @@ import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class ScaledButton(
|
||||
buttonX: Float,
|
||||
buttonY: Float,
|
||||
var buttonX: Float,
|
||||
var buttonY: Float,
|
||||
val buttonWidth: Number,
|
||||
val buttonHeight: Number,
|
||||
var resource: ResourceLocation? = null,
|
||||
@@ -30,18 +30,6 @@ class ScaledButton(
|
||||
val clickAction: Button.OnPress
|
||||
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
|
||||
|
||||
var buttonX: Float = buttonX
|
||||
set(value) {
|
||||
field = value
|
||||
this.x = value.toInt()
|
||||
}
|
||||
|
||||
var buttonY: Float = buttonY
|
||||
set(value) {
|
||||
field = value
|
||||
this.y = value.toInt()
|
||||
}
|
||||
|
||||
var isWidgetActive = false
|
||||
var isVisible = true
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user