Compare commits

..
Author SHA1 Message Date
whatsy 8cb77a54d5 System reworked to be simpler, since a provider is no longer necessary 2026-11-02 15:12:03 -05:00
whatsy 04553d658f Merge branch 'main' into berry-tree-scanning
# Conflicts:
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2026-11-02 11:54:19 -05:00
Polymeta 99e0a26d7b Merge branch 'compat-improvements' into 'main'
teeny fixes to compat

See merge request cable-mc/cobblemon!2520
2026-11-02 09:17:39 +00:00
dhi_holo 71753df513 Leftovers
Also a slight fix to the order in changelog
2026-11-02 13:30:23 +10:00
dhi_holo 729f080f6e Merge branch 'main' into compat-improvements 2026-11-02 13:26:43 +10:00
dhi_holo 13eb57afb1 category fix
Fixing one recipe category I missed teehee
2026-11-02 13:23:42 +10:00
Jackson Bowes 72b6cb06ff Merge branch 'soon-to-be-implemented-steps' into 'main'
Implementing some steps

See merge request cable-mc/cobblemon!2519
2026-11-01 08:15:06 -06:00
Jackson Bowes 74a57eb972 Merge branch 'sterr/move-sound-implementation' into 'main'
Added sounds to Gigaton Hammer and Pyro Ball

See merge request cable-mc/cobblemon!2518
2026-11-01 08:14:14 -06:00
Sterrezo 7246c7f8e6 Update CHANGELOG.md 2026-11-01 13:22:32 +01:00
Sterrezo cd05f741ae Merge branch 'main' into soon-to-be-implemented-steps 2026-11-01 13:21:23 +01:00
Sterrezo 00222d929d Added sounds to Gigaton Hammer and Pyro Ball 2026-11-01 13:18:22 +01:00
Polymeta b8f4637cb3 Merge branch 'feat/power-ride-training' into 'main'
power bracer ride stat training

See merge request cable-mc/cobblemon!2503
2026-10-31 12:33:22 +00:00
Ronak Chavva 9f572250f5 fix(*): modularization 2026-10-31 06:59:37 -05:00
Ronak Chavva 0927d6fa43 fix(*): enum change, configurable item stat 2026-10-31 06:26:45 -05:00
Ronak Chavva a293b3d71f changelog additions 2026-10-31 05:09:36 -05:00
Ronak Chavva 162b1cc68f Merge branch 'main' of https://gitlab.com/ronakch8/cobblemon into feat/power-ride-training 2026-10-31 05:07:29 -05:00
Ronak Chavva 066a6b56f7 Merge branch 'main' of https://gitlab.com/ronakch8/cobblemon into feat/power-ride-training 2026-10-23 18:13:32 -05:00
Ronak Chavva 7687833add mapping rewrite 2026-10-22 21:35:48 -05:00
Ronak Chavva cb9ba82fcc holding power bracer while riding, 1 to all 5 stats 2026-10-22 03:11:14 -05:00
Sterrezo 4221993f7d Added stepsounds to Mr. Mime 2026-10-03 14:39:21 +02:00
Sterrezo a616c25c8c Added stepsounds to Parasect 2026-09-30 14:45:00 +02:00
Sterrezo 6ce4bf5673 Added stepsounds to Rhyhorn 2026-09-30 14:34:07 +02:00
Sterrezo 1b652ba99f Added stepsounds to Rhydron 2026-09-30 14:01:50 +02:00
Sterrezo 11b59df386 Added stepsounds to Dewgong 2026-09-28 20:20:16 +02:00
Sterrezo ac2b068bf3 Added stepsounds to Blastoise 2026-09-28 14:06:42 +02:00
Sterrezo 8e8f346e52 Added stepsounds keyframes to Venusaur 2026-09-28 13:47:47 +02:00
Plastered_Crab ee8133356e Remove test logging 2026-06-08 13:54:11 -07:00
Plastered_Crab d78e114b3b Revision to move to only code only providers for pokedex scanning of blocks and block entities 2026-06-08 13:23:22 -07:00
Plastered_Crab 14ab259eee Merge branch 'main' into berry-tree-scanning
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/pokedex/PokedexScannerRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokedex/scanner/PokedexUsageContext.kt
2026-06-08 10:38:19 -07:00
Plastered_Crab a8f78e3fd6 Merge branch 'main' into berry-tree-scanning 2026-03-02 12:54:16 -08:00
Plastered_Crab 96ae054aae Changelog 2026-01-19 16:06:48 -08:00
Plastered_Crab 80666530db Clean Up 2026-01-19 16:05:56 -08:00
Plastered_Crab b5e912a13f Fix closing animation and implement datapackable block entity scanning 2026-01-19 15:34:22 -08:00
Plastered_Crab 945f6b5f84 So close to getting Pokedex Scannable block entities datapackable 2025-12-19 16:07:16 -08:00
Plastered_Crab bf6c668850 WIP system for scanning Berry Bushes for info. Might expand it into a generalized BlockEntity scanning system 2025-12-19 11:43:43 -08:00
47 changed files with 1532 additions and 1355 deletions

No files matched your search

+145 -12
View File
@@ -2,28 +2,161 @@
## [1.8.5 (MONTH xth, 2026)](#1-8-5)
### Additions
- You can now drag to select multiple Pokémon inside the PC menu.
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
- Added brewing recipe for Throat Spray.
- Added Berry Tree scanning for Pokedex
- Added Datapackable Pokedex Scanning for Block Entities
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`)
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
- Added a new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Added missing crops to Botany Pots integration
- Added a way to obtain Porygon by inserting the Upgrade or Dubious Disc into a data monitor
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
- Added Configs for Infinite Use TMs and Unlocking all TMs
- Added Type Gem blocks
- Added Type Gem Clusters
- Added TMs
- Added TM Machine
- Added Deepslate Core Block
- Added /givetm Command
- Added advancement triggers for learning TMs: `cobblemon:has_learn_specific_tm` (per-TM), `cobblemon:has_learn_all_tm` (all TMs learned), and `cobblemon:tms_learned` (count-based), backed by a new `totalTMLearnedCount` on player advancement data.
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` tracking for capturing Alpha Pokémon.
- Added Move Dex to Pokédex
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
- Added intrinsic scaling to all mons (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added scaling to "baby" Pokémon (smaller (-20%) at level 1 → normally sized at level 10)
- Added conditional seats for riding. Seats can be gated by molang such as "is_alpha".
- Added Alpha Pokémon
- Added alpha scaling (alphas are larger than their normal counterparts)
- Alphas will always lead herds and will spawn in herds
- Alphas spawn with two random TM moves from their learnset
- Alpha Pokémon will always defend themselves from players that attack them
- Added alpha level matching and stat boost (based on level)
- Added pitch-down and reverb to alpha cries
- Added alpha Pokémon bait effect
- Added alpha eye rendering including eye overlay, bloom, eye trail, and particles.
- Added cobweb block slowness immunity for spider Pokémon
- Added 49 new habitat structures. 34 as typical Minecraft structures and 15 as structures that get placed as biome features to allow for more natural placement in generation. Structures placed as biome features will not be able to be located with the `/locate structure` command.
- Vanilla village house template pools have been adjusted to account for the new abandoned village structure habitat. Weights have changed within the JSON file for every piece to ensure too many habitats don't generate in a single village.
- Added 10 new ruin structures.
- rooted_arch_ruins & crumbling_arch_ruins have been transferred over to biome features, allowing for better placement in generation.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- All fossil structures have been transferred over to biome features, allowing for better placement in generation.
- The prehistoric_underwater_fissure & prehistoric_submerged_impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- A new Biome tag called `#cobblemon:is_stony_beach` has been added.
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
- 1 New shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves easier.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- The lush shipwreck cove structure itself has been slightly cleaned up to be less noisy with the blocks it is built out of.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma cove gets blast burn, lush cove gets frenzy plant, and submerged cove gets hydro cannon)
- Added 7 new advancements
### New rideable Pokémon
- Drampa
- Archeops
### Pokémon Added
#### Generation 4
#### Generation 5
#### Generation 6
#### Generation 7
#### Generation 8
#### Generation 9
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
- Skitty
- Delcatty
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Audino
- Rockruff
- Lycanroc
- Snom
- Frosmoth
- Applin
- Flapple
- Appletun
- Dipplin
- Hydrapple
- Sandshrew (Alolan)
- Sandslash (Alolan)
- Seviper
- Zangoose
- Stunky
- Skuntank
- Pawniard
- Bisharp
- Kingambit
### Model updates for the following Pokémon
- Goomy
- Sliggoo
- Goodra
- Porygon
- Porygon2
- Porygon-Z
- Zubat
- Golbat
- Crobat
- Pidgey
- Pidgeotto
- Pidgeot
- Sandslash
- Nidoking
- Sableye
- Mawile
- Nacli
- Naclestack
- Garganacl
- Skarmory
### Animation updates for the following Pokémon
- Drampa
- Archeops
- Skarmory
- Riolu
- Lucario
- Shellos
- Skitty
- Joltik
- Purrloin
- Liepard
- Flygon
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
- Fire Punch
- Fire Spin
- Flame Charge
- Flame Wheel
- Giga Drain
- Ice Punch
- Leech Life
- Magical Leaf
- Mega Drain
- Minimize
- Poison Gas
- Rock Throw
- Scary Face
- Seed bomb
- Smokescreen
- String Shot
- Tail Whip
- Added the `healersSetSpawn` gamerule. When enabled, using a healing machine will set your spawnpoint.
- A Pokémon holding a Power item now gains 1 point in the corresponding riding stat every two minutes while being ridden. The interval is configurable, and can be disabled with `-1`.
- Added sounds to Gigaton Hammer and Pyro Ball
- Added stepsounds to Venusaur, Blastoise, Dewgong, Rhydon, Rhyhorn, Parasect and Mr. Mime.
### Changes
- Amethyst now grows from Typegem core blocks like typegems do
- Updated the recipe category for the Create compatibility EXP Candy recipe
- Added No Man's Lands' Honeyed Apple to the Leaves Leftovers tag
### Fixes
- Fixed Ponigiri food values for AppleSkin integration
@@ -566,7 +566,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SwapPartyPokemonPacket.ID, SwapPartyPokemonPacket::decode, SwapPartyPokemonHandler))
list.add(PacketRegisterInfo(MovePCPokemonPacket.ID, MovePCPokemonPacket::decode, MovePCPokemonHandler))
list.add(PacketRegisterInfo(MovePCPokemonBatchPacket.ID, MovePCPokemonBatchPacket::decode, MovePCPokemonBatchHandler))
list.add(PacketRegisterInfo(MovePartyPokemonPacket.ID, MovePartyPokemonPacket::decode, MovePartyPokemonHandler))
list.add(PacketRegisterInfo(SwapPCPartyPokemonPacket.ID, SwapPCPartyPokemonPacket::decode, SwapPCPartyPokemonHandler))
@@ -9,9 +9,12 @@
package com.cobblemon.mod.common.api.riding.stats
import com.cobblemon.mod.common.api.cooking.Flavour
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.tags.TagKey
import net.minecraft.world.item.Item
/**
* A stat that is used for riding Pokémon. The exact implementation of each of these depends on the [com.cobblemon.mod.common.api.riding.controller.RideController].
@@ -20,13 +23,14 @@ import net.minecraft.network.chat.MutableComponent
* @since February 17th, 2025
*/
enum class RidingStat(
val flavour: Flavour
val flavour: Flavour,
val powerItem: TagKey<Item>
) {
ACCELERATION(Flavour.SPICY),
SKILL(Flavour.DRY),
SPEED(Flavour.SWEET),
STAMINA(Flavour.SOUR),
JUMP(Flavour.BITTER);
ACCELERATION(Flavour.SPICY, CobblemonItemTags.POWER_BRACER),
SKILL(Flavour.DRY, CobblemonItemTags.POWER_BELT),
SPEED(Flavour.SWEET, CobblemonItemTags.POWER_ANKLET),
STAMINA(Flavour.SOUR, CobblemonItemTags.POWER_WEIGHT),
JUMP(Flavour.BITTER, CobblemonItemTags.POWER_BAND);
companion object {
fun getByName(name: String) = RidingStat.entries.firstOrNull { it.name.equals(name, ignoreCase = true) }
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacke
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated
@@ -271,34 +270,6 @@ open class PCStore(
}
}
fun moveBatch(moves: List<Triple<UUID, PCPosition, PCPosition>>): Boolean {
if (moves.isEmpty() || moves.size > MovePCPokemonBatchPacket.MAX_MOVES) return false
if (moves.map { it.second }.toSet().size != moves.size ||
moves.map { it.third }.toSet().size != moves.size ||
moves.map { it.first }.toSet().size != moves.size
) return false
if (moves.any { (_, source, target) -> !isValidPosition(source) || !isValidPosition(target) }) return false
val sourcePositions = moves.mapTo(mutableSetOf()) { it.second }
val pokemonMoves = moves.map { (pokemonID, source, target) ->
val pokemon = get(source)?.takeIf { it.uuid == pokemonID } ?: return false
Triple(source, target, pokemon)
}
if (pokemonMoves.any { (_, target, _) -> get(target) != null && target !in sourcePositions }) return false
pokemonMoves.forEach { (source, _, _) -> setAtPosition(source, null) }
pokemonMoves.forEach { (_, target, pokemon) -> setAtPosition(target, pokemon) }
pokemonMoves.forEach { (_, _, pokemon) ->
sendPacketToObservers(RemoveClientPokemonPacket(this, pokemon.uuid))
}
pokemonMoves.forEach { (_, target, pokemon) ->
sendPacketToObservers(SetPCPokemonPacket(uuid, target) { pokemon })
}
pcChangeObservable.emit(Unit)
return true
}
override fun loadPositionFromNBT(nbt: CompoundTag): StoreCoordinates<PCPosition> {
return StoreCoordinates(this, PCPosition(nbt.getShort(DataKeys.STORE_BOX).toInt(), nbt.getByte(DataKeys.STORE_SLOT).toInt()))
}
@@ -15,13 +15,19 @@ import com.cobblemon.mod.common.api.berry.GrowthPoint
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.berry.BerryHarvestEvent
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.pokedex.scanner.BlockScanData
import com.cobblemon.mod.common.pokedex.scanner.Frame
import com.cobblemon.mod.common.pokedex.scanner.ScannableBlockEntity
import com.cobblemon.mod.common.util.lang
import net.minecraft.ResourceLocationException
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
@@ -35,11 +41,10 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.CropBlock.UPDATE_CLIENTS
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.BERRY, pos, state) {
class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.BERRY, pos, state), ScannableBlockEntity {
lateinit var berryIdentifier: ResourceLocation
private val ticksPerMinute = 1200
var renderState: RenderState? = null
@@ -375,6 +380,22 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
return stageTimer <= 0
}
override fun resolveBlockScan(): BlockScanData {
val berryName = berry()?.item()?.let { ItemStack(it).hoverName.copy() }
?: berryIdentifier.toString().text()
val frames = mutableSetOf<Frame>(
Frame(1, lang("ui.pokedex.scan.berry.stage", blockState.getValue(BerryBlock.AGE))),
Frame(2, berryName)
)
if (mulchVariant != MulchVariant.NONE) {
frames.add(Frame(3, Component.translatable("item.cobblemon.${mulchVariant.serializedName}_mulch")))
}
return BlockScanData(frames)
}
//Using a similar approach to ComputerCraft, implement this client side
interface RenderState : AutoCloseable {
var needsRebuild: Boolean
@@ -111,7 +111,6 @@ object CobblemonClient {
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
var pcHealthToggle: Boolean = false
fun onLogin() {
clientPlayerData = ClientGeneralPlayerData()
@@ -29,8 +29,7 @@ class BoxStorageSlot(
val grabbedSlot = parent.grabbedSlot
return if (grabbedSlot == null) {
val pokemon = getPokemon()
pokemon == null || !parent.isInMultiSelection(pokemon, position)
true
} else {
grabbedSlot.getPokemon() != getPokemon()
}
@@ -50,7 +50,6 @@ import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import kotlin.isInitialized
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
class PCGUI(
val pc: ClientPC,
@@ -99,8 +98,6 @@ class PCGUI(
private val buttonWallpaperResource = cobblemonResource("textures/gui/pc/pc_button_set_wallpaper.png")
private val buttonInfoArrow = cobblemonResource("textures/gui/pc/info_arrow.png")
private val buttonHealthToggleResource = cobblemonResource("textures/gui/pc/pc_button_toggle_health.png")
private val topSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_top.png")
private val bottomSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_bottom.png")
private val rightSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_right.png")
@@ -132,55 +129,6 @@ class PCGUI(
var selectPointerOffsetIncrement = false
var displayOptions = false
private var dragStartX: Int? = null
private var dragStartY: Int? = null
private var dragEndX: Int? = null
private var dragEndY: Int? = null
private var selectionDragActive = false
data class SelectionArea(val left: Int, val top: Int, val right: Int, val bottom: Int)
private data class BoxGridBounds(val left: Int, val top: Int, val right: Int, val bottom: Int) {
fun contains(x: Int, y: Int) = x in left..< right && y >= top && y < bottom
}
private fun getBoxGridBounds(): BoxGridBounds {
val left = storageWidget.x
val top = storageWidget.y
return BoxGridBounds(
left,
top,
left + StorageWidget.SCREEN_WIDTH,
top + StorageWidget.SCREEN_HEIGHT
)
}
fun getSelectionArea(): SelectionArea? {
if (!selectionDragActive) return null
val startX = dragStartX ?: return null
val startY = dragStartY ?: return null
val endX = dragEndX ?: return null
val endY = dragEndY ?: return null
val bounds = getBoxGridBounds()
if (!bounds.contains(startX, startY)) return null
val columns = 6
val rows = 5
val slotPitch = StorageSlot.SIZE + StorageWidget.BOX_SLOT_PADDING
val gridLeft = bounds.left + StorageWidget.BOX_SLOT_START_OFFSET_X
val gridTop = bounds.top + StorageWidget.BOX_SLOT_START_OFFSET_Y + if (displayOptions) 5 else 0
val startColumn = ((startX - gridLeft) / slotPitch).coerceIn(0, columns - 1)
val endColumn = (((endX.coerceIn(bounds.left, bounds.right - 1)) - gridLeft) / slotPitch).coerceIn(0, columns - 1)
val startRow = ((startY - gridTop) / slotPitch).coerceIn(0, rows - 1)
val endRow = (((endY.coerceIn(bounds.top, bounds.bottom - 1)) - gridTop) / slotPitch).coerceIn(0, rows - 1)
return SelectionArea(
gridLeft + minOf(startColumn, endColumn) * slotPitch, // left
gridTop + minOf(startRow, endRow) * slotPitch, // top
gridLeft + (maxOf(startColumn, endColumn) * slotPitch) + StorageSlot.SIZE, // right
gridTop + (maxOf(startRow, endRow) * slotPitch) + StorageSlot.SIZE // bottom
)
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -271,6 +219,7 @@ class PCGUI(
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
)
@@ -301,28 +250,12 @@ class PCGUI(
configuration.showParty = isVisible
wallpaperWidget.visible = !isVisible
(it as IconButton).highlighted = !isVisible
if (unseenWallpapers.isNotEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
val healthButton = this.addRenderableWidget(IconButton(
pX = x + 230,
pY = y + 31,
buttonWidth = 20,
buttonHeight = 20,
resource = buttonHealthToggleResource,
label = "toggle_health_bars",
) {
CobblemonClient.pcHealthToggle = !CobblemonClient.pcHealthToggle
(it as IconButton).highlighted = CobblemonClient.pcHealthToggle
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
healthButton.highlighted = CobblemonClient.pcHealthToggle
PokemonSortMode.entries.forEachIndexed { index, sortType ->
val typeName = sortType.name.lowercase()
this.addRenderableWidget(IconButton(
@@ -382,20 +315,6 @@ class PCGUI(
super.init()
}
private fun renderSelectionBox(context: GuiGraphics, area: SelectionArea) {
context.pose().pushPose()
context.pose().translate(0.0, 0.0, 1500.0)
val borderColor = FastColor.ARGB32.color(255, 205, 212, 255)
val bgColor = FastColor.ARGB32.color(75, 133, 183, 230)
val thickness = 1
context.fill(area.left, area.top, area.right, area.bottom, bgColor)
context.fill(area.left, area.top, area.right, area.top + thickness, borderColor)
context.fill(area.left, area.bottom - thickness, area.right, area.bottom, borderColor)
context.fill(area.left, area.top, area.left + thickness, area.bottom, borderColor)
context.fill(area.right - thickness, area.top, area.right, area.bottom, borderColor)
context.pose().popPose()
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
renderBackground(context, mouseX, mouseY, delta)
@@ -536,20 +455,6 @@ class PCGUI(
shadow = true
)
// var dexNo = pokemon.species.nationalPokedexNumber.toString()
// while (dexNo.length < 4) {
// dexNo = "0$dexNo"
// }
// drawScaledText(
// context = context,
// font = CobblemonResources.DEFAULT_LARGE,
// text = Component.literal(dexNo).bold(),
// x = x + 38,
// y = y + 1.5,
// shadow = true,
// scale = 0.85f
// )
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
@@ -780,14 +685,12 @@ class PCGUI(
scale = SCALE
)
if (optionButtons.isNotEmpty() && displayOptions) {
if (!optionButtons.isEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown()
}
super.render(context, mouseX, mouseY, delta)
getSelectionArea()?.let { renderSelectionBox(context, it) }
// Item Tooltip
if (pokemon != null) {
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
@@ -809,20 +712,6 @@ class PCGUI(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
storageWidget.clearPendingStorageSlotClick()
selectionDragActive = false
val clickX = mouseX.toInt()
val clickY = mouseY.toInt()
if (storageWidget.multiSelection == null && button == 0 &&
getBoxGridBounds().contains(clickX, clickY) &&
storageWidget.pastureWidget == null) {
dragStartX = clickX
dragStartY = clickY
} else {
dragStartX = null
dragStartY = null
}
// Trigger child component function as they will need to check the entire screen click area
if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button)
return super.mouseClicked(mouseX, mouseY, button)
@@ -851,43 +740,9 @@ class PCGUI(
storageWidget.pastureWidget?.let { pasture ->
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
if (button == 0 && storageWidget.pastureWidget == null) {
val startX = dragStartX
val startY = dragStartY
if (startX != null && startY != null) {
val offsetX = mouseX - startX
val offsetY = mouseY - startY
if (offsetX * offsetX + offsetY * offsetY >= 25.0) {
selectionDragActive = true
storageWidget.clearPendingStorageSlotClick()
dragEndX = mouseX.toInt()
dragEndY = mouseY.toInt()
}
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
val handled = super.mouseReleased(mouseX, mouseY, button)
if (button == 0) {
if (selectionDragActive) {
dragEndX = mouseX.toInt()
dragEndY = mouseY.toInt()
storageWidget.updateSelection()
storageWidget.clearPendingStorageSlotClick()
} else {
storageWidget.completePendingStorageSlotClick(mouseX, mouseY)
}
}
dragStartX = null
dragStartY = null
dragEndX = null
dragEndY = null
selectionDragActive = false
return handled
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.isFocused
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
@@ -1,37 +0,0 @@
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.pokemon.Pokemon
data class PCMultiSelection(
private val grid: MutableList<MutableList<Pokemon?>> = mutableListOf(),
val sourceBox: Int = 0,
val sourceColumn: Int = 0,
val sourceRow: Int = 0
) {
fun getHorizontalSize(): Int = grid.firstOrNull()?.size ?: 0
fun getVerticalSize(): Int = grid.size
constructor(horizontalSize: Int, verticalSize: Int, sourceBox: Int, sourceColumn: Int, sourceRow: Int) : this(
MutableList(verticalSize) { MutableList<Pokemon?>(horizontalSize) { null } },
sourceBox,
sourceColumn,
sourceRow
)
fun getPokemon(x: Int, y: Int): Pokemon? {
return grid[y][x]
}
fun setPokemon(x: Int, y: Int, pokemon: Pokemon?) {
grid[y][x] = pokemon
}
fun pokemonEntries(): List<Triple<Int, Int, Pokemon>> {
return grid.flatMapIndexed { y, row ->
row.mapIndexedNotNull { x, pokemon -> pokemon?.let { Triple(x, y, it) } }
}
}
fun getPokemonCount(): Int = pokemonEntries().size
}
@@ -45,13 +45,11 @@ class ReleaseButton(
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
val pokemonCount = parent.multiSelection?.getPokemonCount() ?: 0
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = if (pokemonCount > 1)
{ lang("ui.pc.release_all").bold() } else { lang("ui.pc.release").bold() },
text = lang("ui.pc.release").bold(),
x = x + (WIDTH / 2),
y = y + 3.5,
centered = true,
@@ -10,11 +10,10 @@ package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
@@ -23,19 +22,13 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import juuxel.adorn.util.Colors
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
open class StorageSlot(
x: Int, y: Int,
@@ -61,17 +54,9 @@ open class StorageSlot(
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectionArea = parent.pcGui.getSelectionArea()
if (selectionArea != null && parent.getSelectedPokemon(selectionArea).contains(this)) {
val highlightColor = 0x40FFFFFF
context.fill(x + 1, y, x + (SIZE - 1), y + SIZE, highlightColor)
context.fill(x, y + 1, x + 1, y + (SIZE - 1), highlightColor)
context.fill(x + SIZE - 1, y + 1, x + SIZE, y + (SIZE - 1), highlightColor)
}
if (shouldRender()) {
renderSlot(context, x, y, delta)
}
}
fun renderSlot(context: GuiGraphics, posX: Int, posY: Int, partialTicks: Float) {
@@ -145,23 +130,6 @@ open class StorageSlot(
matrixStack = matrices
)
}
// Pokemon health
// Taken from vanilla durability rendering
val currentHealth = pokemon.currentHealth
val maxHealth = pokemon.maxHealth.toFloat()
if (currentHealth < maxHealth && CobblemonClient.pcHealthToggle) {
val damageValue = maxHealth - currentHealth
val f = max(0.0f, (maxHealth - (maxHealth - currentHealth)) / maxHealth)
val color = Mth.hsvToRgb(f / 3.0f, 1.0f, 1.0f)
val totalWidth = 12
val width = (totalWidth - damageValue * totalWidth / pokemon.maxHealth.toFloat()).roundToInt();
val k = posX + 1
val l = posY + 6
val bgColor = if (this is PartyStorageSlot) Colors.BLACK else 0xFF3E3E3E.toInt()
context.fill(RenderType.guiOverlay(), k, l, k + totalWidth, l + 1, bgColor)
context.fill(RenderType.guiOverlay(), k, l, k + width, l + 1, color or bgColor)
}
matrices.popPose()
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.text.bold
@@ -38,9 +39,8 @@ import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.util.UUID
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket.PCPokemonMove
class StorageWidget(
pX: Int, pY: Int,
@@ -77,14 +77,9 @@ class StorageWidget(
var displayConfirmRelease = false
var screenLoaded = false
var selectedPosition: PartyPosition? = null
var selectedPosition: StorePosition? = null
var grabbedSlot: GrabbedStorageSlot? = null
var multiSelection: PCMultiSelection? = null
private val multiGrabbedSlots = mutableMapOf<UUID, GrabbedStorageSlot>()
private var pendingStorageSlotClick: Button? = null
private var movePending = false
var box = 0
set(value) {
field =
@@ -118,22 +113,16 @@ class StorageWidget(
subKey = "ui.generic.yes",
onPress = {
if (canDeleteSelected() && displayConfirmRelease) {
val selection = multiSelection
if (selection != null) {
selection.pokemonEntries().forEach { (column, row, pokemon) ->
val position = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
CobblemonNetwork.sendToServer(ReleasePCPokemonPacket(pokemon.uuid, position))
}
} else {
val position = selectedPosition ?: return@ReleaseConfirmButton
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
val position = selectedPosition ?: return@ReleaseConfirmButton
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
CobblemonNetwork.sendToServer(ReleasePartyPokemonPacket(pokemon.uuid, position))
val packet = when (position) {
is PartyPosition -> ReleasePartyPokemonPacket(pokemon.uuid, position)
is PCPosition -> ReleasePCPokemonPacket(pokemon.uuid, position)
else -> return@ReleaseConfirmButton
}
CobblemonNetwork.sendToServer(packet)
playSound(CobblemonSounds.PC_RELEASE)
resetSelected()
displayConfirmRelease = false
@@ -160,8 +149,9 @@ class StorageWidget(
}
fun canDeleteSelected(): Boolean {
multiSelection?.let { return it.pokemonEntries().isNotEmpty() }
return party.filterNotNull().size > 1 && selectedPosition != null && grabbedSlot != null
return !(selectedPosition is PartyPosition && party.filterNotNull().size <= 1)
&& selectedPosition != null
&& grabbedSlot != null
}
fun setupStorageSlots() {
@@ -180,7 +170,7 @@ class StorageWidget(
parent = this,
pc = pc,
position = PCPosition(box, index),
onPress = { pendingStorageSlotClick = it }
onPress = { this.onStorageSlotClicked(it) }
).also { widget ->
this.addWidget(widget)
this.boxSlots.add(widget)
@@ -210,7 +200,7 @@ class StorageWidget(
parent = this,
party = party,
position = PartyPosition(partyIndex),
onPress = { pendingStorageSlotClick = it }
onPress = { this.onStorageSlotClicked(it) }
).also { widget ->
this.addWidget(widget)
this.partySlots.add(widget)
@@ -220,117 +210,14 @@ class StorageWidget(
private fun getSelectedPokemon(): Pokemon? {
val selectedPosition = this.selectedPosition ?: return null
return party.get(selectedPosition)
}
fun getSelectedPokemon(area: PCGUI.SelectionArea): List<BoxStorageSlot> {
return boxSlots.filter { slot ->
slot.x < area.right && slot.x + StorageSlot.SIZE > area.left &&
slot.y < area.bottom && slot.y + StorageSlot.SIZE > area.top &&
slot.getPokemon()?.let(pcGui.search::passes) == true
return when (selectedPosition) {
is PCPosition -> pc.get(selectedPosition)
is PartyPosition -> party.get(selectedPosition)
else -> null
}
}
fun clearPendingStorageSlotClick() {
pendingStorageSlotClick = null
}
fun completePendingStorageSlotClick(mouseX: Double, mouseY: Double) {
val button = pendingStorageSlotClick ?: return
pendingStorageSlotClick = null
if (button.isMouseOver(mouseX, mouseY)) {
onStorageSlotClicked(button)
}
}
fun updateSelection() {
val area = pcGui.getSelectionArea()
if (area == null) {
multiSelection = null
return
}
selectedPosition = null
grabbedSlot = null
pcGui.setPreviewPokemon(null)
val selectedSlots = getSelectedPokemon(area)
if (selectedSlots.isEmpty()) {
multiSelection = null
return
}
val minColumn = selectedSlots.minOf { it.position.slot % 6 }
val maxColumn = selectedSlots.maxOf { it.position.slot % 6 }
val minRow = selectedSlots.minOf { it.position.slot / 6 }
val maxRow = selectedSlots.maxOf { it.position.slot / 6 }
val selection = PCMultiSelection(maxColumn - minColumn + 1, maxRow - minRow + 1, box, minColumn, minRow)
selectedSlots.forEach { slot ->
val pokemon = slot.getPokemon() ?: return@forEach
val slotIndex = slot.position.slot
val column = slotIndex % 6
val row = slotIndex / 6
if (column in minColumn..maxColumn && row in minRow..maxRow) {
selection.setPokemon(column - minColumn, row - minRow, pokemon)
}
}
playSound(CobblemonSounds.PC_GRAB)
multiSelection = selection
}
private fun moveMultiSelectionTo(destination: PCPosition): Boolean {
val selection = multiSelection ?: return false
val width = selection.getHorizontalSize()
val height = selection.getVerticalSize()
val destinationColumn = destination.slot % 6
val destinationRow = destination.slot / 6
if (destinationColumn + width > 6 || destinationRow + height > 5) return false
val moves = mutableListOf<PCPokemonMove>()
val selectedSources = mutableSetOf<PCPosition>()
for (row in 0 until height) {
for (column in 0 until width) {
val pokemon = selection.getPokemon(column, row) ?: continue
val source = PCPosition(selection.sourceBox, (selection.sourceRow + row) * 6 + selection.sourceColumn + column)
val target = PCPosition(destination.box, (destinationRow + row) * 6 + destinationColumn + column)
selectedSources.add(source)
moves.add(PCPokemonMove(pokemon.uuid, source, target))
}
}
if (moves.isEmpty()) return false
for (row in 0 until height) {
for (column in 0 until width) {
if (selection.getPokemon(column, row) == null) continue
val target = PCPosition(destination.box, (destinationRow + row) * 6 + destinationColumn + column)
val displacedPokemon = pc.get(target) ?: continue
if (target in selectedSources) continue
val displacedSource = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
if (pc.get(displacedSource) != null && displacedSource !in selectedSources) return false
moves.add(PCPokemonMove(displacedPokemon.uuid, target, displacedSource))
}
}
if (moves.map { it.newPosition }.toSet().size != moves.size) return false
MovePCPokemonBatchPacket(moves).sendToServer()
return true
}
fun isInMultiSelection(pokemon: Pokemon, position: PCPosition): Boolean {
val selection = multiSelection ?: return false
return selection.sourceBox == position.box && selection.pokemonEntries().any { it.third == pokemon }
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
updatePendingMove()
val matrices = context.pose()
// Party Label
if (pcGui.configuration.showParty) {
@@ -353,14 +240,11 @@ class StorageWidget(
shadow = true
)
val pokemonCount = multiSelection?.getPokemonCount() ?: 0
if (canDeleteSelected() && displayConfirmRelease) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = if (pokemonCount > 1)
{ lang("ui.pc.release_all").bold() } else { lang("ui.pc.release").bold() },
text = lang("ui.pc.release").bold(),
x = x + 223,
y = y + 119,
centered = true
@@ -424,7 +308,6 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& multiSelection == null
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -441,7 +324,6 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& multiSelection == null
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -453,32 +335,10 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
renderSelectedPokemon(context, mouseX, mouseY, delta)
grabbedSlot?.render(context, mouseX, mouseY, delta)
matrices.popPose()
}
private fun renderSelectedPokemon(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (movePending) return
val selection = multiSelection
if (selection == null) {
grabbedSlot?.render(context, mouseX, mouseY, delta)
return
}
val slotOffset = StorageSlot.SIZE + BOX_SLOT_PADDING
selection.pokemonEntries().forEach { (column, row, pokemon) ->
val grabbed = multiGrabbedSlots.getOrPut(pokemon.uuid) {
GrabbedStorageSlot(0, 0, this, pokemon)
}
grabbed.render(
context,
mouseX + column * slotOffset,
mouseY + row * slotOffset,
delta
)
}
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (displayConfirmRelease) {
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
@@ -511,55 +371,23 @@ class StorageWidget(
else -> return
}
val clickedPokemon = when (clickedPosition) {
is PCPosition -> pc.get(clickedPosition)
is PartyPosition -> party.get(clickedPosition)
else -> null
}
multiSelection?.let { selection ->
when (clickedPosition) {
is PCPosition -> {
if (clickedPosition.box == selection.sourceBox &&
clickedPosition.slot == selection.sourceRow * 6 + selection.sourceColumn
) {
resetSelected()
} else if (moveMultiSelectionTo(clickedPosition)) {
playSound(CobblemonSounds.PC_DROP)
movePending = true
}
}
is PartyPosition -> {
val selectedPokemon = selection.pokemonEntries().singleOrNull() ?: return
val (column, row, pokemon) = selectedPokemon
val source = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
if (pc.get(source) !== pokemon) return
val packet = clickedPokemon?.let {
SwapPCPartyPokemonPacket(it.uuid, clickedPosition, pokemon.uuid, source)
} ?: MovePCPokemonToPartyPacket(pokemon.uuid, source, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
movePending = true
}
else -> Unit
}
return
}
// Reset release confirmation
displayConfirmRelease = false
// Clicking on itself, so unselect position
if (selectedPosition == clickedPosition) {
if (this.selectedPosition != null && this.selectedPosition == clickedPosition) {
if (grabbedSlot != null) playSound(CobblemonSounds.PC_DROP)
resetSelected()
return
}
// Check for selecting a Pokémon
val clickedPokemon = when(button) {
is BoxStorageSlot -> pc.get(clickedPosition as PCPosition)
is PartyStorageSlot -> party.get(clickedPosition as PartyPosition)
else -> null
}
val selectOverride = pcGui.configuration.selectOverride
if (selectOverride != null) {
selectOverride(pcGui, clickedPosition, clickedPokemon)
@@ -592,47 +420,59 @@ class StorageWidget(
}
}
if (clickedPosition is PCPosition) {
val column = clickedPosition.slot % 6
val row = clickedPosition.slot / 6
multiSelection = PCMultiSelection(1, 1, clickedPosition.box, column, row).also {
it.setPokemon(0, 0, clickedPokemon)
}
playSound(CobblemonSounds.PC_GRAB)
return
} else if (clickedPosition is PartyPosition) {
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
parent = this,
pokemon = clickedPokemon
)
playSound(CobblemonSounds.PC_GRAB)
}
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
parent = this,
pokemon = clickedPokemon
)
playSound(CobblemonSounds.PC_GRAB)
}
} else {
val sourcePosition = selectedPosition ?: return
val selectedPokemon = party.get(sourcePosition) ?: return
// Handle movement within the Box
val selectedPokemon = when(this.selectedPosition) {
is PCPosition -> pc.get(this.selectedPosition as PCPosition)
is PartyPosition -> party.get(this.selectedPosition as PartyPosition)
else -> null
} ?: return
// Box to Box
if (this.selectedPosition is PCPosition && clickedPosition is PCPosition) {
val packet = clickedPokemon?.let { SwapPCPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonPacket(selectedPokemon.uuid, selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Box to Party
else if (this.selectedPosition is PCPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(clickedPokemon.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonToPartyPacket(selectedPokemon.uuid, this.selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Party to Box
if (clickedPosition is PCPosition) {
else if (this.selectedPosition is PartyPosition && clickedPosition is PCPosition) {
if (ServerSettings.preventCompletePartyDeposit && this.party.filterNotNull().size == 1 && clickedPokemon == null) {
return
}
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
movePending = true
resetSelected()
}
// Party to Party
else if (clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, sourcePosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
else if (this.selectedPosition is PartyPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PartyPosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
movePending = true
resetSelected()
}
}
}
@@ -640,36 +480,6 @@ class StorageWidget(
fun resetSelected() {
selectedPosition = null
grabbedSlot = null
multiSelection = null
multiGrabbedSlots.clear()
pcGui.setPreviewPokemon(null)
}
// annoyingly complex solution, but server/client desync causes flashing when moving pokemon. this should fix it
// if you can think of a simpler solution, feel free to implement it
private fun updatePendingMove() {
if (!movePending) return
val selection = multiSelection
if (selection != null) {
val updated = selection.pokemonEntries().any { (column, row, pokemon) ->
val source = PCPosition(selection.sourceBox, (selection.sourceRow + row) * 6 + selection.sourceColumn + column)
pc.get(source)?.uuid != pokemon.uuid
}
if (updated) {
movePending = false
resetSelected()
}
return
}
val source = selectedPosition
val pokemon = grabbedSlot?.getPokemon()
if (source != null && pokemon != null) {
if (party.get(source)?.uuid != pokemon.uuid) {
movePending = false
resetSelected()
}
}
}
}
@@ -19,11 +19,16 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIG
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokedex.scanner.BlockScanData
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.pokedex.scanner.ScannableBlockEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.PokemonSizeCategory
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.world.level.block.entity.BlockEntity
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.DeltaTracker
@@ -82,13 +87,45 @@ class PokedexScannerRenderer {
fun infoFrameResource(isLeft: Boolean, tier: Int): ResourceLocation = cobblemonResource("textures/gui/pokedex/scan/scan_info_frame_${if (isLeft) "left" else "right"}_$tier.png")
}
private var lastFocusedBlockTypeId: ResourceLocation? = null
private fun getBlockEntityInfo(entity: BlockEntity): BlockScanData? {
val scannable = entity as? ScannableBlockEntity ?: return null
return scannable.resolveBlockScan()
}
fun renderInfoFrames(graphics: GuiGraphics, poseStack: PoseStack, usageContext: PokedexUsageContext, centerX: Int, centerY: Int, opacity: Float) {
if (usageContext.focusIntervals > 0) {
var infoDisplayedCounter = 0
usageContext.availableInfoFrames.forEachIndexed {index, isLeftSide ->
if (isLeftSide !== null) {
if (infoDisplayedCounter > 2 && !usageContext.isPokemonInFocusOwned) return@forEachIndexed
val isPokemonFocus = usageContext.scannableEntityInFocus != null
if (isPokemonFocus && infoDisplayedCounter > 1 && !usageContext.isPokemonInFocusOwned) return@forEachIndexed
infoDisplayedCounter++
val blockEntityInFocus: BlockEntity? = usageContext.blockEntityInFocus
val pokedexEntityData = usageContext.scannableEntityInFocus?.resolvePokemonScan()
// store the block entity typeId so our close animation knows what to show
if (pokedexEntityData != null) {
lastFocusedBlockTypeId = null
} else if (blockEntityInFocus != null) {
lastFocusedBlockTypeId = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntityInFocus.type)
} else if (usageContext.focusIntervals <= 0f) {
lastFocusedBlockTypeId = null
}
val scanInfo = blockEntityInFocus?.let { getBlockEntityInfo(it) }
// if a block entity is being focused on make sure we only show the frames needed
if (pokedexEntityData == null && scanInfo != null) {
val hasFrameForSlot = scanInfo.frames.any { it.slot == infoDisplayedCounter }
if (!hasFrameForSlot) {
return@forEachIndexed
}
}
// Frames
val isInnerFrame = index == 1 || index == 2
val frameHeight = if (isInnerFrame) INNER_INFO_FRAME_HEIGHT else OUTER_INFO_FRAME_HEIGHT
@@ -110,77 +147,79 @@ class PokedexScannerRenderer {
val yOffsetText = when(index) { 0 -> 5; 1 -> 4; 2 -> 8; 3 -> 42; else -> 0 }
// Text
if (usageContext.focusIntervals == PokedexUsageContext.FOCUS_INTERVALS && usageContext.scannableEntityInFocus != null) {
val pokedexEntityData = usageContext.scannableEntityInFocus?.resolvePokemonScan() ?: return
if (infoDisplayedCounter == 1) {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv.number", pokedexEntityData.pokemon.level).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
)
}
if (infoDisplayedCounter == 2) {
val hasTrainer = (usageContext.scannableEntityInFocus?.resolveEntityScan() as? PokemonEntity)?.ownerUUID !== null
val speciesName = pokedexEntityData.getApparentSpecies().translatedName.bold()
var yOffsetName = if (hasTrainer) 2 else 0
if (hasTrainer) {
drawScaledText(
context = graphics,
text = lang("ui.pokedex.scan.trainer_owned"),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - yOffsetName,
shadow = true,
centered = true,
scale = 0.5F
)
}
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesName,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + yOffsetName,
shadow = true,
centered = true
)
val gender = pokedexEntityData.pokemon.gender
val speciesNameWidth = Minecraft.getInstance().font.width(speciesName.font(CobblemonResources.DEFAULT_LARGE))
if (gender != Gender.GENDERLESS) {
val isMale = gender == Gender.MALE
val textSymbol = if (isMale) "♂".text().bold() else "♀".text().bold()
if (usageContext.focusIntervals == PokedexUsageContext.FOCUS_INTERVALS && (pokedexEntityData != null || blockEntityInFocus != null)) {
if (pokedexEntityData != null) {
if (infoDisplayedCounter == 1) {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = centerX + xOffset + xOffsetText + 2 + (speciesNameWidth / 2),
text = lang("ui.lv.number", pokedexEntityData.pokemon.level).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
)
}
if (infoDisplayedCounter == 2) {
val hasTrainer =
(usageContext.scannableEntityInFocus?.resolveEntityScan() as? PokemonEntity)?.ownerUUID !== null
val speciesName = pokedexEntityData.getApparentSpecies().translatedName.bold()
var yOffsetName = if (hasTrainer) 2 else 0
if (hasTrainer) {
drawScaledText(
context = graphics,
text = lang("ui.pokedex.scan.trainer_owned"),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - yOffsetName,
shadow = true,
centered = true,
scale = 0.5F
)
}
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesName,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + yOffsetName,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
shadow = true,
centered = true
)
val gender = pokedexEntityData.pokemon.gender
val speciesNameWidth =
Minecraft.getInstance().font.width(speciesName.font(CobblemonResources.DEFAULT_LARGE))
if (gender != Gender.GENDERLESS) {
val isMale = gender == Gender.MALE
val textSymbol = if (isMale) "♂".text().bold() else "♀".text().bold()
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = centerX + xOffset + xOffsetText + 2 + (speciesNameWidth / 2),
y = centerY + yOffset + yOffsetText + yOffsetName,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
)
}
if (usageContext.isPokemonInFocusOwned) {
blitk(
matrixStack = poseStack,
texture = caughtIndicator,
x = (centerX + xOffset + xOffsetText - 7 - (speciesNameWidth / 2)) / SCALE,
y = (centerY + yOffset + yOffsetText + yOffsetName + 2) / SCALE,
height = 10,
width = 10,
scale = SCALE
)
}
}
if (usageContext.isPokemonInFocusOwned) {
blitk(
matrixStack = poseStack,
texture = caughtIndicator,
x = (centerX + xOffset + xOffsetText - 7 - (speciesNameWidth / 2)) / SCALE,
y = (centerY + yOffset + yOffsetText + yOffsetName + 2) / SCALE,
height = 10,
width = 10,
scale = SCALE
)
}
}
if (infoDisplayedCounter == 3) {
val pokemon = pokedexEntityData.pokemon
if (infoDisplayedCounter == 3) {
val pokemon = pokedexEntityData.pokemon
val entityAlpha = (usageContext.scannableEntityInFocus?.resolveEntityScan() as? PokemonEntity)
?.entityData
?.get(PokemonEntity.IS_ALPHA)
@@ -203,35 +242,66 @@ class PokedexScannerRenderer {
}
if (infoDisplayedCounter == 4) {
val typeText = lang("type.suffix", pokedexEntityData.getApparentForm().types.map { it.displayName.copy() }.reduce { acc, next -> acc.plus("/").plus(next) }).bold()
val typeWidth = Minecraft.getInstance().font.width(typeText.font(CobblemonResources.DEFAULT_LARGE))
// Split into 2 lines if text width is too long
if (typeWidth > (OUTER_INFO_FRAME_WIDTH - 8) && pokedexEntityData.getApparentForm().secondaryType !== null) {
val splitLines = typeText.string.split("/")
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[0], "/").bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - 4,
shadow = true,
centered = true
)
val typeText = lang(
"type.suffix",
pokedexEntityData.getApparentForm().types.map { it.displayName.copy() }
.reduce { acc, next -> acc.plus("/").plus(next) }).bold()
val typeWidth =
Minecraft.getInstance().font.width(typeText.font(CobblemonResources.DEFAULT_LARGE))
// Split into 2 lines if text width is too long
if (typeWidth > (OUTER_INFO_FRAME_WIDTH - 8) && pokedexEntityData.getApparentForm().secondaryType !== null) {
val splitLines = typeText.string.split("/")
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[0], "/").bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - 4,
shadow = true,
centered = true
)
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[1]).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + 3,
shadow = true,
centered = true
)
} else {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = typeText,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
)
}
}
} else if (blockEntityInFocus != null && scanInfo != null) {
val frame = scanInfo.frames.firstOrNull { it.slot == infoDisplayedCounter } ?: return@forEachIndexed
val scale = frame.scale
if (scale != null) {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[1]).bold(),
text = frame.text.bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + 3,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
centered = true,
scale = scale
)
} else {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = typeText,
text = frame.text.bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
@@ -462,4 +532,13 @@ class PokedexScannerRenderer {
renderScanOverlay(graphics, tickDelta)
}
}
private fun anyToComponent(value: Any?): MutableComponent {
return when (value) {
null -> text("")
is MutableComponent -> value
is Component -> value.copy()
else -> text(value.toString())
}
}
}
@@ -47,6 +47,10 @@ class CobblemonConfig {
@IntConstraint(min = 0, max = 1000)
var maxPokemonFriendship = 255
@CobblemonConfigField(Category.Pokemon, lang = "power_item_ride_stat_gain_interval_seconds", SERVER)
@IntConstraint(min = -1, max = Int.MAX_VALUE)
var powerItemRideStatGainIntervalSeconds = 120
@CobblemonConfigField(Category.Pokemon, lang = "announce_drop_items", SERVER)
var announceDropItems = true
@CobblemonConfigField(Category.Pokemon, lang = "default_drop_item_method", SERVER)
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.feature.TickingSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause.Companion.DROPS_REROLL_ASPECT
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.BattleRegistry
@@ -46,10 +47,15 @@ import kotlin.math.*
/** Handles purely server logic for a Pokémon */
class PokemonServerDelegate : PokemonSideDelegate {
companion object {
private const val RIDE_STAT_GAIN_AMOUNT = 1F
}
lateinit var entity: PokemonEntity
var acknowledgedHPValue = -1
var acknowledgedLevel = -1
val passengerOffsets = mutableMapOf<Int, Vec3>()
private var rideStatGainAnchor: Vec3? = null
/** Mocked properties exposed to the client [PokemonEntity]. */
private val mock: PokemonProperties?
@@ -355,9 +361,30 @@ class PokemonServerDelegate : PokemonSideDelegate {
}
}
val rideStatGainIntervalSeconds = Cobblemon.config.powerItemRideStatGainIntervalSeconds
if (rideStatGainIntervalSeconds > 0) {
val rideStatGainIntervalTicks = rideStatGainIntervalSeconds * 20L
if (entity.level().gameTime % rideStatGainIntervalTicks == 0L) {
trainRideStats()
}
}
updateTrackedValues()
}
private fun trainRideStats() {
val position = entity.position()
val anchor = rideStatGainAnchor
rideStatGainAnchor = position
if (anchor == null || anchor == position || entity.passengers.isEmpty()) return
val heldItem = entity.pokemon.heldItemNoCopy()
if (heldItem.isEmpty) return
val stat = RidingStat.entries.firstOrNull { heldItem.`is`(it.powerItem) } ?: return
entity.pokemon.addRideBoost(stat, RIDE_STAT_GAIN_AMOUNT)
}
fun updateShownItem() {
val trackedShownItem = when {
// Show Hand Item if Held item is hidden
@@ -1,55 +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://www.mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.storage.pc
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readPCPosition
import com.cobblemon.mod.common.util.writePCPosition
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
class MovePCPokemonBatchPacket(val moves: List<PCPokemonMove>) : NetworkPacket<MovePCPokemonBatchPacket> {
override val id = ID
data class PCPokemonMove(
val pokemonID: UUID,
val oldPosition: PCPosition,
val newPosition: PCPosition
)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(moves.size)
moves.forEach { move ->
buffer.writeUUID(move.pokemonID)
buffer.writePCPosition(move.oldPosition)
buffer.writePCPosition(move.newPosition)
}
}
companion object {
val ID = cobblemonResource("move_pc_pokemon_batch")
const val MAX_MOVES = 60
fun decode(buffer: RegistryFriendlyByteBuf): MovePCPokemonBatchPacket {
val size = buffer.readVarInt()
require(size in 1..MAX_MOVES) { "Invalid batch PC move size: $size" }
return MovePCPokemonBatchPacket(
List(size) {
PCPokemonMove(
pokemonID = buffer.readUUID(),
oldPosition = buffer.readPCPosition(),
newPosition = buffer.readPCPosition()
)
}
)
}
}
}
@@ -1,23 +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://www.mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.storage.pc
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object MovePCPokemonBatchHandler : ServerNetworkPacketHandler<MovePCPokemonBatchPacket> {
override fun handle(packet: MovePCPokemonBatchPacket, server: MinecraftServer, player: ServerPlayer) {
val pc = PCLinkManager.getPC(player) ?: return run { ClosePCPacket(null).sendToPlayer(player) }
pc.moveBatch(packet.moves.map { Triple(it.pokemonID, it.oldPosition, it.newPosition) })
}
}
@@ -0,0 +1,13 @@
package com.cobblemon.mod.common.pokedex.scanner
import net.minecraft.network.chat.MutableComponent
data class BlockScanData(
val frames: MutableSet<Frame> = mutableSetOf()
)
data class Frame(
val slot: Int,
val text: MutableComponent,
val scale: Float? = null
)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.pokedex.scanner
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexLearnedInformation
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.pokedex.PokedexScannerRenderer
@@ -18,6 +19,8 @@ import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.level.block.entity.BlockEntity
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random
@@ -26,6 +29,7 @@ import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.core.BlockPos
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import kotlin.math.exp
@@ -51,6 +55,8 @@ class PokedexUsageContext {
var infoGuiOpen: Boolean = false
var scanningGuiOpen: Boolean = false
var isPokemonInFocusOwned: Boolean = false
var blockEntityInFocus: BlockEntity? = null
var blockPosInFocus: BlockPos? = null
var registerCompleted: Boolean = false
var scannedSpecies: ResourceLocation? = null
var scannableEntityInFocus: ScannableEntity? = null
@@ -68,6 +74,8 @@ class PokedexUsageContext {
var availableInfoFrames: MutableList<Boolean?> = mutableListOf(null, null, null, null)
val renderer: PokedexScannerRenderer = PokedexScannerRenderer()
private fun hasAnyFocus() = (scannableEntityInFocus != null) || (blockEntityInFocus != null)
fun stopUsing(ticksInUse: Int, speciesId: ResourceLocation? = null) {
if (ticksInUse < OPEN_SCANNER_BUFFER_TICKS) {
openPokedexGUI(type, speciesId)
@@ -89,7 +97,9 @@ class PokedexUsageContext {
if (transitionIntervals == TRANSITION_INTERVALS) playSound(CobblemonSounds.POKEDEX_SCAN_CLOSE)
transitionIntervals = max(transitionIntervals - updateInterval, 0F)
if (transitionIntervals <= 0) {
if (viewInfoTicks >= VIEW_INFO_BUFFER_TICKS) openPokedexGUI(type, scannableEntityInFocus!!.resolvePokemonScan()?.getApparentSpecies()?.resourceIdentifier)
if (viewInfoTicks >= VIEW_INFO_BUFFER_TICKS && scannableEntityInFocus != null) {
openPokedexGUI(type, scannableEntityInFocus!!.resolvePokemonScan()?.getApparentSpecies()?.resourceIdentifier)
}
resetState()
}
}
@@ -102,7 +112,7 @@ class PokedexUsageContext {
if (scanningProgress >= MAX_SCAN_PROGRESS) targetId?.let { FinishScanningPacket(it, zoomLevel.toInt()).sendToServer() }
if (focusIntervals > 0) focusIntervals = max(0F, focusIntervals - updateInterval)
} else {
if (scannableEntityInFocus != null) {
if (hasAnyFocus()) {
if (focusIntervals < FOCUS_INTERVALS) focusIntervals = min(focusIntervals + updateInterval, FOCUS_INTERVALS)
} else {
if (focusIntervals > 0) focusIntervals = max(0F, focusIntervals - updateInterval)
@@ -137,8 +147,9 @@ class PokedexUsageContext {
playSound(CobblemonSounds.POKEDEX_OPEN)
}
// todo display info of scanned berry to the Berry Dex later
fun attackKeyHeld(isHeld: Boolean) {
if (isHeld && scannableEntityInFocus !== null && viewInfoTicks < VIEW_INFO_BUFFER_TICKS && scanningProgress == 0F) {
if (isHeld && hasAnyFocus() && viewInfoTicks < VIEW_INFO_BUFFER_TICKS && scanningProgress == 0F) {
viewInfoTicks++
if (viewInfoTicks % 2 == 0) playSound(CobblemonSounds.POKEDEX_SCAN_LOOP)
} else if (viewInfoTicks > 0 && viewInfoTicks < VIEW_INFO_BUFFER_TICKS) {
@@ -147,26 +158,30 @@ class PokedexUsageContext {
}
fun tryScanPokemon(user: LocalPlayer) {
// Pokémon/entity scan
val targetScannableEntity = PokemonScanner.findScannableEntity(user, zoomLevel.toInt())
if (targetScannableEntity != null) {
if (targetScannableEntity != scannableEntityInFocus) {
resetFocusedPokemonState()
scannableEntityInFocus = targetScannableEntity
val resolvedPokemon = scannableEntityInFocus?.resolvePokemonScan()
if(resolvedPokemon == null) {
if (resolvedPokemon == null) {
resetFocusedPokemonState()
return
}
// Check if Pokémon in focus is owned
isPokemonInFocusOwned = CobblemonClient.clientPokedexData.getHighestKnowledgeForSpecies(resolvedPokemon.getApparentSpecies().resourceIdentifier) == PokedexEntryProgress.OWNED
isPokemonInFocusOwned =
CobblemonClient.clientPokedexData.getHighestKnowledgeForSpecies(resolvedPokemon.getApparentSpecies().resourceIdentifier) == PokedexEntryProgress.OWNED
// Randomize info frames for render
if (focusIntervals == 0F) {
availableInfoFrames = mutableListOf(null, null, null, null)
for (i in 0..3) {
var randomIndex = Random.nextInt(availableInfoFrames.size)
if (availableInfoFrames[randomIndex] !== null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
if (availableInfoFrames[randomIndex] != null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
availableInfoFrames.set(randomIndex, Random.nextBoolean())
}
}
@@ -176,9 +191,31 @@ class PokedexUsageContext {
if (newPokemonInfo == PokedexLearnedInformation.NONE) playSound(CobblemonSounds.POKEDEX_SCAN_DETAIL)
else scannedSpecies = resolvedPokemon.getApparentSpecies().resourceIdentifier
}
} else {
resetFocusedPokemonState()
return
}
// BlockEntity scan
val blockHit = PokemonScanner.findScannableBlockEntity(user, zoomLevel.toInt())
if (blockHit != null) {
val (pos, be) = blockHit
if (be != blockEntityInFocus || pos != blockPosInFocus) {
resetFocusedPokemonState()
blockEntityInFocus = be
blockPosInFocus = pos
if (focusIntervals == 0F) {
availableInfoFrames = mutableListOf(null, null, null, null)
for (i in 0..3) {
var randomIndex = Random.nextInt(availableInfoFrames.size)
if (availableInfoFrames[randomIndex] != null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
availableInfoFrames[randomIndex] = Random.nextBoolean()
}
}
}
return
}
resetFocusedPokemonState()
}
fun onServerConfirmedRegister(packet: ServerConfirmedRegisterPacket) {
@@ -196,6 +233,8 @@ class PokedexUsageContext {
fun resetFocusedPokemonState() {
scannableEntityInFocus = null
blockEntityInFocus = null
blockPosInFocus = null
scannedSpecies = null
viewInfoTicks = 0
scanningProgress = 0F
@@ -10,10 +10,14 @@ package com.cobblemon.mod.common.pokedex.scanner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext.Companion.BLOCK_LENGTH_PER_ZOOM_STAGE
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.HitResult
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.level.block.entity.BlockEntity
//Handles the actual raycasting to figure out what pokemon we are looking at
object PokemonScanner {
@@ -100,4 +104,34 @@ object PokemonScanner {
fun isEntityInRange(castingEntity: Entity, targetEntity: Entity, zoomLevel: Int): Boolean {
return targetEntity.position().distanceTo(castingEntity.position()) <= Cobblemon.config.maxPokedexScanningDetectionRange + (zoomLevel * BLOCK_LENGTH_PER_ZOOM_STAGE)
}
fun findScannableBlockEntity(castingEntity: Entity, zoomLevel: Int): Pair<BlockPos, BlockEntity>? {
val eyePos = castingEntity.getEyePosition(1.0F)
val lookVec = castingEntity.getViewVector(1.0F)
val maxDistance =
Cobblemon.config.maxPokedexScanningDetectionRange + (zoomLevel * BLOCK_LENGTH_PER_ZOOM_STAGE)
val end = eyePos.add(lookVec.scale(maxDistance.toDouble()))
val hit = castingEntity.level().clip(
ClipContext(
eyePos,
end,
ClipContext.Block.OUTLINE,
ClipContext.Fluid.NONE,
castingEntity
)
)
if (hit.type != HitResult.Type.BLOCK) return null
val pos = (hit as BlockHitResult).blockPos
val be = castingEntity.level().getBlockEntity(pos) ?: return null
val typeId = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(be.type) ?: return null
// if (BlockScanProviderRegistry.get(typeId) == null) return null
return pos to be
}
}
@@ -0,0 +1,5 @@
package com.cobblemon.mod.common.pokedex.scanner
interface ScannableBlockEntity {
fun resolveBlockScan(): BlockScanData
}
@@ -456,6 +456,12 @@
"cannon_left": {
"position": [0, 0, 3]
}
},
"timeline": {
"0.25": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.25": "q.run_molang('cobblemon:stepsound_standard_large');",
"2.25": "q.run_molang('cobblemon:stepsound_standard_large');",
"3.25": "q.run_molang('cobblemon:stepsound_standard_large');"
}
},
"animation.blastoise.ground_run": {
@@ -83,6 +83,7 @@
},
"animation.parasect.ground_walk": {
"loop": true,
"animation_length": 1,
"bones": {
"body": {
"rotation": [0, 0, 0],
@@ -134,6 +135,12 @@
"locator_seat_1": {
"rotation": [7.5, 0, 0]
}
},
"timeline": {
"0.0": "q.run_molang('cobblemon:stepsound_minecraft');",
"0.25": "q.run_molang('cobblemon:stepsound_minecraft');",
"0.5": "q.run_molang('cobblemon:stepsound_minecraft');",
"0.75": "q.run_molang('cobblemon:stepsound_minecraft');"
}
},
"animation.parasect.ground_run": {
@@ -258,6 +265,10 @@
"rotation": ["5+Math.cos((query.anim_time) * 90 * 16 - 90) * 2", 0, 0],
"position": [0, "0+Math.cos((query.anim_time) * 90 * 32) * -0.2", -0.25]
}
},
"timeline": {
"0.0": "q.run_molang('cobblemon:stepsound_minecraft');",
"0.125": "q.run_molang('cobblemon:stepsound_minecraft');"
}
},
"animation.parasect.ride_ground_run": {
@@ -391,6 +391,10 @@
"0.7083": [0, 0, 0]
}
}
},
"timeline": {
"0.0833": "q.run_molang('cobblemon:stepsound_standard_medium');",
"0.2917": "q.run_molang('cobblemon:stepsound_standard_medium');"
}
},
"animation.dewgong.water_idle": {
@@ -1071,6 +1071,16 @@
"head": {
"rotation": ["-0.8*math.cos(q.anim_time*90*8-120)", 0, "3*math.sin(q.anim_time*90*4+70)"]
}
},
"timeline": {
"0.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"0.5": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.5": "q.run_molang('cobblemon:stepsound_standard_large');",
"2.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"2.5": "q.run_molang('cobblemon:stepsound_standard_large');",
"3.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"3.5": "q.run_molang('cobblemon:stepsound_standard_large');"
}
},
"animation.rhyhorn.ride_ground_walk": {
@@ -1917,6 +1927,16 @@
],
"position": [0, "0.4+0.6*math.cos(q.anim_time*90*8-120)", 0]
}
},
"timeline": {
"0.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"0.375": "q.run_molang('cobblemon:stepsound_standard_medium');",
"0.5": "q.run_molang('cobblemon:stepsound_standard_large');",
"0.875": "q.run_molang('cobblemon:stepsound_standard_medium');",
"1.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.375": "q.run_molang('cobblemon:stepsound_standard_medium');",
"1.5": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.875": "q.run_molang('cobblemon:stepsound_standard_medium');"
}
},
"animation.rhyhorn.ride_ground_run": {
@@ -329,6 +329,12 @@
"locator_seat_1": {
"rotation": [-15, 0, 0]
}
},
"timeline": {
"0.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"2.0": "q.run_molang('cobblemon:stepsound_standard_large');",
"3.0": "q.run_molang('cobblemon:stepsound_standard_large');"
}
},
"animation.rhydon.ride_ground_walk": {
@@ -807,6 +813,12 @@
"locator_seat_1": {
"rotation": [-15, 0, 0]
}
},
"timeline": {
"0.3333": "q.run_molang('cobblemon:stepsound_standard_large');",
"0.8333": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.3333": "q.run_molang('cobblemon:stepsound_standard_large');",
"1.8333": "q.run_molang('cobblemon:stepsound_standard_large');"
}
},
"animation.rhydon.ride_ground_run": {
@@ -391,6 +391,10 @@
"1.0": [0, 0, 0]
}
}
},
"timeline": {
"0.0833": "q.run_molang('cobblemon:stepsound_minecraft');",
"0.5833": "q.run_molang('cobblemon:stepsound_minecraft');"
}
},
"animation.mr_mime.ground_run": {
@@ -1299,7 +1299,6 @@
"cobblemon.ui.pc.filter.tooltip": "Format: pikachu shiny held_item lvl=1...",
"cobblemon.ui.pc.options": "Options",
"cobblemon.ui.pc.release": "Release",
"cobblemon.ui.pc.release_all": "Release All",
"cobblemon.ui.sort.name": "Sort by name",
"cobblemon.ui.sort.level": "Sort by level",
"cobblemon.ui.sort.pokedex_number": "Sort by Pokédex number",
@@ -1474,6 +1473,9 @@
"cobblemon.ui.pokedex.weight": "Weight: %1$skg",
"cobblemon.ui.pokedex.height": "Height: %1$sm",
"cobblemon.ui.pokedex.scan.berry.stage": "Stage: %s",
"cobblemon.ui.pokedex.scan.berry.mulch_none": "No Mulch",
"cobblemon.ui.pokedex.evolution.item_interact.tooltip": "Needs %1$s item to evolve",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_species": "Evolves when traded with %1$s",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_any_species": "Evolves when traded",
@@ -2435,6 +2437,8 @@
"cobblemon.config.ui.max_pokemon_level.tooltip": "The maximum level Pokémon can get to.",
"cobblemon.config.ui.max_pokemon_friendship": "Max Pokémon Friendship",
"cobblemon.config.ui.max_pokemon_friendship.tooltip": "The maximum friendship value.",
"cobblemon.config.ui.power_item_ride_stat_gain_interval_seconds": "Power Item Riding Stat Training Interval (Seconds)",
"cobblemon.config.ui.power_item_ride_stat_gain_interval_seconds.tooltip": "Seconds between riding stat gains while riding a Pokémon holding a Power item. Defaults to 120 seconds. Set to -1 or 0 to disable; values below -1 are clamped to -1.",
"cobblemon.config.ui.announce_drop_items": "Announce Drop Items",
"cobblemon.config.ui.announce_drop_items.tooltip": "Whether to notify the Player of dropped items from defeating wild Pokémon.",
"cobblemon.config.ui.default_drop_item_method": "Default Drop Item Method",
@@ -1653,6 +1653,18 @@
"move.horndrill.target_2": {
"sounds": [ { "name": "cobblemon:move/horndrill/horndrill_target_2", "volume": 1.0 } ]
},
"move.gigatonhammer.actor": {
"sounds": [ { "name": "cobblemon:move/gigatonhammer/gigatonhammer_actor", "volume": 1.0 } ]
},
"move.gigatonhammer.target": {
"sounds": [ { "name": "cobblemon:move/gigatonhammer/gigatonhammer_target", "volume": 1.0 } ]
},
"move.pyroball.actor": {
"sounds": [ { "name": "cobblemon:move/pyroball/pyroball_actor", "volume": 1.0 } ]
},
"move.pyroball.target": {
"sounds": [ { "name": "cobblemon:move/pyroball/pyroball_target", "volume": 1.0 } ]
},
"impact.normal": {
"sounds": [
{ "name": "cobblemon:move/impact_generic/impact_generic_fighting_1", "volume": 0.8 },
@@ -8672,6 +8684,9 @@
"pokemon.cinderace.cry": {
"sounds": [ { "name": "cobblemon:pokemon/cinderace/cinderace_cry", "volume": 0.8 } ]
},
"pokemon.cinderace.pyroball": {
"sounds": [ { "name": "cobblemon:pokemon/cinderace/cinderace_pyroball", "volume": 0.5 } ]
},
"pokemon.sobble.cry": {
"sounds": [ { "name": "cobblemon:pokemon/sobble/sobble_cry", "volume": 0.8 } ]
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

@@ -0,0 +1,35 @@
{
"timeline": [
{
"type": "add_holds",
"holds": ["effects"]
},
{
"type": "animation",
"animation": ["gigatonhammer"],
"delay": 0.3
},
{
"type": "entity_molang",
"entityCondition": "q.entity.is_user == true",
"expressions": ["q.sound('move.gigatonhammer.actor');"]
},
{
"type": "pause",
"pause": 1.1
},
{
"type": "entity_molang",
"entityCondition": "q.entity.is_user == false && q.missed(q.entity.uuid) == false",
"expressions": ["q.sound('move.gigatonhammer.target');"]
},
{
"type": "remove_holds",
"holds": ["effects"]
},
{
"type": "pause",
"pause": 1.5
}
]
}
@@ -3,11 +3,38 @@
{
"type": "add_holds",
"holds": ["effects"]
},
{
"type": "animation",
"animation": ["pyroball"],
"delay": 2.05
"animation": ["pyroball"]
},
{
"type": "pause",
"pause" : 0.1
},
{
"type": "entity_molang",
"entityCondition": "q.entity.is_user == true",
"expressions": ["q.sound('move.pyroball.actor');"]
},
{
"type": "entity_molang",
"entityCondition": "q.entity.is_user == true",
"expressions": ["q.sound('pokemon.cinderace.pyroball');"]
},
{
"type": "pause",
"pause" : 1.875
},
{
"type": "entity_molang",
"entityCondition": "q.entity.is_user == false && q.missed(q.entity.uuid) == false",
"expressions": ["q.sound('move.pyroball.target');"]
},
{
"type": "pause",
"pause" : 0.175
},
{
"type": "entity_particles",
@@ -14,7 +14,7 @@
}
],
"type": "cobblemon:cooking_pot_shapeless",
"category": "medicines",
"category": "misc",
"ingredients": [
{
"item": "create:experience_nugget"
@@ -6,6 +6,7 @@
"maleRatio": 0.875,
"height": 16,
"weight": 855,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.blastoise.desc"
],
@@ -7,6 +7,7 @@
"maleRatio": 0.5,
"height": 17,
"weight": 1200,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.dewgong.desc"
],
@@ -7,6 +7,7 @@
"maleRatio": 0.5,
"height": 13,
"weight": 545,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.mrmime.desc"
],
@@ -7,6 +7,7 @@
"maleRatio": 0.5,
"height": 10,
"weight": 295,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.parasect.desc"
],
@@ -7,6 +7,7 @@
"maleRatio": 0.5,
"height": 19,
"weight": 1200,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.rhydon.desc"
],
@@ -7,6 +7,7 @@
"maleRatio": 0.5,
"height": 10,
"weight": 1150,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.rhyhorn.desc"
],
@@ -7,6 +7,7 @@
"maleRatio": 0.875,
"height": 20,
"weight": 1000,
"silenceStepSounds": true,
"pokedex": [
"cobblemon.species.venusaur.desc"
],
@@ -6,6 +6,7 @@
"cobblemon:tart_apple",
"cobblemon:syrupy_apple",
"cobblemon:candied_apple",
{ "id": "create:honeyed_apple","required": false }
{ "id": "create:honeyed_apple","required": false },
{ "id": "nomansland:honeyed_apple","required": false }
]
}