Compare commits

..
Author SHA1 Message Date
Lily 2f82f381d4 - added custom equip sounds
- tweaked armor material values
- made fireproof
- made trimmable
2023-09-08 15:25:00 -04:00
Lily 4d75103a68 WIP implementations of auspicious and malicious armor, to be completed for 1.5 2023-09-04 16:12:46 -04:00
Paul ff2db8fa11 You saw nothing 2023-09-04 18:49:38 +10:00
Paul 42d330e1c0 Added berry world gen weights to data 2023-09-04 18:45:27 +10:00
Paul 062cee2b9a Merge remote-tracking branch 'origin/main' 2023-09-04 18:16:36 +10:00
Paul 5730d812a0 Randomize berry positions for most berry trees (except for berry swords) 2023-09-04 18:16:28 +10:00
Hiroku cb4ad09a11 Merge branch 'heal-me-up-scotty' into 'main'
Potions are like healing berries

Closes #437

See merge request cable-mc/cobblemon!598
2023-09-04 05:52:07 +00:00
MoeBoy76andHiroku 1d81df91eb Potions are like healing berries 2023-09-04 05:52:06 +00:00
Hiroku d962d30074 Merge branch 'fix-starter-volume' into 'main'
Fixes starter selection sound effect playing twice when clicking the arrow keys to navigate

Closes #427

See merge request cable-mc/cobblemon!599
2023-09-04 05:47:31 +00:00
Hiroku 5096d9a15a Merge branch 'pokemonloot-fix' into 'main'
Fixes pokemon dropping XP when doPokemonLoot gamerule is set to false

See merge request cable-mc/cobblemon!597
2023-09-04 04:20:21 +00:00
Hiroku 24737cf606 Merge branch 'selected-pokemon-pvp-fix' into 'main'
Fix player battle always starting with first pokemon

Closes #389

See merge request cable-mc/cobblemon!579
2023-09-04 04:12:14 +00:00
MiaandHiroku 7bd1c24b57 Fix player battle always starting with first pokemon 2023-09-04 04:12:13 +00:00
miasmus 36b1bec04b Fixes starter selection sound effect playing twice when clicking the arrow keys to navigate 2023-09-03 20:19:52 -04:00
Hiroku 7f14b60efb Merge branch 'neo-1.4-fixes' into 'main'
Various bug fixes

See merge request cable-mc/cobblemon!596
2023-09-03 22:27:52 +00:00
NeovitalismandHiroku 239559cfd3 Various bug fixes 2023-09-03 22:27:52 +00:00
miasmus c4b2a1546a Fixes pokemon dropping XP when doPokemonLoot gamerule is set to false 2023-09-03 13:39:51 -04:00
Paul 278b36234e Put the pre-render call back so pokeballs animate (oopsy) 2023-09-03 14:43:49 +10:00
Paul 1598c8209a Fixed ternary and boolean-not operators not working in Molang which broke berry friendship increase amounts 2023-09-03 14:38:44 +10:00
Hiroku deb7ece946 Merge branch 'berry' into 'main'
Berries

See merge request cable-mc/cobblemon!595
2023-09-02 06:05:40 +00:00
ApionandHiroku d8f9f8926b Berries 2023-09-02 06:05:40 +00:00
Paul 9d8803caf2 Make entity accessible from poseable entity state 2023-09-01 09:01:12 +10:00
Paul 08827a742f Fixes to particle effects in bedrock animations 2023-08-28 18:27:19 +10:00
Paul a6a76052a6 Fixed various items not preventing off-hand interactions from playing 2023-08-27 15:58:57 +10:00
Paul abeb005bbb Improve error logging for bad MoLang expression parsing from String.asExpression 2023-08-27 13:05:03 +10:00
Paul 6835232aa6 Added support for effect looping in non-loop animations 2023-08-27 13:04:47 +10:00
Paul 78ebc9a9eb Added MoLang instructions: s.sound(effect, volume, pitch) and s.random(...) 2023-08-27 13:04:06 +10:00
Paul f5e85971f3 Made Pokémon appear red when hurt 2023-08-27 11:11:53 +10:00
Hiroku e85b2aa138 Merge branch 'battle_bugs_1' into 'main'
Fix Shedinja HP and Faint Softlock.

Closes #341 and #400

See merge request cable-mc/cobblemon!593
2023-08-26 02:33:53 +00:00
Segfault Guy 371e1426a3 Fix Shedinja HP and Faint Softlock. 2023-08-25 22:24:54 -04:00
Hiroku 256e09846d Merge branch 'main' into 'main'
Made Master Balls Fireproof (won't disappear when thrown in lava/fire)

See merge request cable-mc/cobblemon!591
2023-08-26 01:49:13 +00:00
RetroandHiroku dd2d441b0c Made Master Balls Fireproof (won't disappear when thrown in lava/fire) 2023-08-26 01:49:13 +00:00
Nick ddc57ecd8d Merge branch 'fixed-tag-file' into 'main'
Fixed tag file

See merge request cable-mc/cobblemon!592
2023-08-24 19:50:57 +00:00
Deltric 202a6a0496 Fixed tag file 2023-08-24 14:26:32 -05:00
Hiroku fbc639cbf3 Merge branch 'power-items' into 'main'
Added in Power Items

See merge request cable-mc/cobblemon!580
2023-08-24 09:38:51 +00:00
DeltricandHiroku 78faf30b32 Added in Power Items 2023-08-24 09:38:51 +00:00
Hiroku 39cbb448a6 Merge branch 'poke-select' into 'main'
Update Poke Select Interface

See merge request cable-mc/cobblemon!588
2023-08-22 09:21:51 +00:00
TestandHiroku fd10f8efd8 Update Poke Select Interface 2023-08-22 09:21:51 +00:00
Hiroku 9161f61017 Merge branch 'spawn-issue' into 'main'
Bug fixes

Closes #402 and #403

See merge request cable-mc/cobblemon!577
2023-08-22 09:08:40 +00:00
ApionandHiroku 4244680956 Bug fixes 2023-08-22 09:08:40 +00:00
Hiroku 92ad81f8d1 Merge branch 'sound' into 'main'
Various sound updates and volume tweaks

See merge request cable-mc/cobblemon!587
2023-08-22 09:06:17 +00:00
SterrezoandHiroku c1c7106bff Various sound updates and volume tweaks 2023-08-22 09:06:17 +00:00
Hiroku e36b1b22c4 Merge branch 'main' into 'main'
Persistent Data Fix

See merge request cable-mc/cobblemon!589
2023-08-22 09:05:39 +00:00
ItsJustChris85 e21d445e14 Persistent Data Fix 2023-08-22 00:51:00 -04:00
Paul 098ff1f96b Clamp set level within bounds instead of throwing exceptions (means we don't corrupt storages) 2023-08-20 17:21:25 +10:00
Paul f93445f081 JvmOverloads in battle builder, closes #410 2023-08-20 17:06:50 +10:00
Paul 85a962e924 Licenses 2023-08-20 15:13:23 +10:00
Paul f6d0f11c99 Implemented uncatchable, fixed some capture calculation issues, added PokeBallCaptureCalculatedEvent and PokemonCatchRateEvent 2023-08-20 13:51:08 +10:00
Paul e74b838fb2 Fixed lighting of pokemon model items 2023-08-20 10:35:29 +10:00
939 changed files with 19444 additions and 694 deletions
+8 -3
View File
@@ -8,11 +8,13 @@
- Added big roots, generating from cave ceiling dirt and sometimes spreading into energy roots.
- Added trading between players. Press R while looking at another player, you'll figure it out.
- Added pasture blocks, used to let your PC Pokémon roam around your base.
- Added 69 berry trees. Some are found in village farms, some from planting different berries close to each other.
- Added medicine brewing using medicinal leeks and berries in brewing stands.
- Added Pokémon cries when in battles and being sent out.
- Added support for scale in animations.
- Added support for jump keyframes (i.e. pre and post keyframes)
- Made Nosepass point towards world spawn while idle.
- Added AI for Nosepass to point towards world spawn when idle.
- Added structure spawn conditions
- Added Pokémon cry system for battles and sending out.
- Added cries for Gen 1-9 starters and their evolutions.
- Added cries for Chatot, Darmanitan, Darumaka, Lucario, Mimikyu, Quagsire, Riolu, Wooper, Caterpie, Metapod, Butterfree, Weedle, Kakuna, Beedrill, Pidgey, Pidgeotto, Pidgeot, Rattata, Raticate, Cleffa, Clefairy, Clefable, Tauros, Miltank, Buneary, Lopunny, Wooloo, Dubwool.
- Added recipes for Berry Juice, Heal Powder, Remedy, Fine Remedy, Superb Remedy, Revive, Max Revive, HP Up, Protein, Iron, Calcium, Zinc, Carbos, PP Up, PP Max, Pasture, Medicinal Leek to Magenta Dye, Roasted Leek, Leek and Potato Stew, Braised Vivichoke, Vivichoke Dip, Mulch Base, Growth Mulch, Surprise Mulch, Coarse Mulch, Humid Mulch, Rich Mulch, Loamy Mulch, Peat Mulch, Sandy Mulch, Health Feather, Muscle Feather, Resist Feather, Genius Feather, Clever Feather, Genius Feather
@@ -192,6 +194,8 @@
- Updated PC Recipe.
- Improved Pokémon AI and movement.
- Friendship will slowly increase when Pokémon are shoulder-mounted.
- Master balls are now unable to be burned when dropped into fire/lava.
- Pokémon will appear red when hurt, like regular entities, except when they're fainting.
### Fixes
- Fixed spawning moon phase dependent Pokémon only when the moon phase is wrong
@@ -222,10 +226,11 @@
- Fixed particle animations not running when a Pokémon is off-screen.
- Fixed Pokémon variants and layers not rendering correctly when shouldered and playing on a dedicated server, existing shoulders affected will need to be retrieved and shouldered again.
- Shoulder effects will now stay applied through situations that remove potion effects such as drinking milk.
- Fixed Shedninja evolving to use the consumed Pokeball and removed the held item.
- Fixed Shedinja evolving to use the consumed Pokeball and removed the held item.
- Fixed non-existent species in spawn pool file causing random species to spawn.
- Fixed shearing Pokémon dropping 0-2 wool instead of 1-3.
- Fixed some alignment issues in the stat hexagon of the summary menu.
- Fixed capture calculations not applying ball bonuses entirely correctly.
### Developer
- Added SpawnEvent
@@ -1,3 +1,13 @@
/*
*
* * 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/.
*
*/
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val accessWidenerFile = "src/main/resources/cobblemon-common.accesswidener"
@@ -13,6 +23,8 @@ plugins {
repositories {
mavenCentral()
//JEI
maven("https://maven.blamejared.com/")
}
license {
@@ -55,4 +67,4 @@ tasks {
withType<Jar> {
from(rootProject.file("LICENSE"))
}
}
}
+11
View File
@@ -1,3 +1,13 @@
/*
*
* * 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/.
*
*/
plugins {
id("cobblemon.base-conventions")
id("cobblemon.publish-conventions")
@@ -18,6 +28,7 @@ dependencies {
modImplementation(libs.fabricLoader)
modApi(libs.molang)
compileOnlyApi(libs.jeiApi)
// For Showdown
modCompileOnly(libs.graal)
@@ -0,0 +1,16 @@
/*
* 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.bridges;
import net.minecraft.structure.processor.StructureProcessor;
public interface StructureProcessorListBridge {
void append(StructureProcessor processor);
void clear();
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.cobblemonstructures;
public class CobblemonStructureIDs {
//Location of the nbt
//Not sure if this is a good way to store them but doing this meant I wouldnt have to keep typing this out during development
public static final String PLAINS_POKECENTER = "cobblemon:village_plains/village_plains_pokecenter";
public static final String DESERT_POKECENTER = "cobblemon:village_desert/village_desert_pokecenter";
public static final String SAVANNA_POKECENTER = "cobblemon:village_savanna/village_savanna_pokecenter";
public static final String SNOWY_POKECENTER = "cobblemon:village_snowy/village_snowy_pokecenter";
public static final String TAIGA_POKECENTER = "cobblemon:village_taiga/village_taiga_pokecenter";
public static final String PLAINS_LONG_PATH = "cobblemon:village_plains/village_plains_long_path";
public static final String DESERT_LONG_PATH = "cobblemon:village_desert/village_desert_long_path";
public static final String SAVANNA_LONG_PATH = "cobblemon:village_savanna/village_savanna_long_path";
public static final String SNOWY_LONG_PATH = "cobblemon:village_snowy/village_snowy_long_path";
public static final String TAIGA_LONG_PATH = "cobblemon:village_taiga/village_taiga_long_path";
}
@@ -0,0 +1,101 @@
/*
* 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.cobblemonstructures;
import com.cobblemon.mod.common.mixin.StructurePoolAccessor;
import com.mojang.datafixers.util.Pair;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.structure.pool.LegacySinglePoolElement;
import net.minecraft.structure.pool.SinglePoolElement;
import net.minecraft.structure.pool.StructurePool;
import net.minecraft.structure.pool.StructurePoolElement;
import net.minecraft.structure.processor.StructureProcessorList;
import net.minecraft.structure.processor.StructureProcessorLists;
import net.minecraft.util.Identifier;
import java.util.ArrayList;
import java.util.List;
public class CobblemonStructures {
private static final RegistryKey<StructureProcessorList> EMPTY_PROCESSOR_LIST_KEY = RegistryKey.of(RegistryKeys.PROCESSOR_LIST, new Identifier("minecraft", "empty"));
private static final Integer pokecenterWeight = 35;
private static final Integer longPathWeight = 10;
public static void registerJigsaws(MinecraftServer server) {
Registry<StructurePool> templatePoolRegistry = server.getRegistryManager().get(RegistryKeys.TEMPLATE_POOL);
Registry<StructureProcessorList> processorListRegistry = server.getRegistryManager().get(RegistryKeys.PROCESSOR_LIST);
//No pokecenters yet
//addPokecenters(templatePoolRegistry, processorListRegistry);
//addLongPaths(templatePoolRegistry, processorListRegistry);
}
private static void addPokecenters(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry) {
Identifier plainsHousesPoolLocation = new Identifier("minecraft:village/plains/houses");
Identifier desertHousesPoolLocation = new Identifier("minecraft:village/desert/houses");
Identifier savannaHousesPoolLocation = new Identifier("minecraft:village/savanna/houses");
Identifier snowyHousesPoolLocation = new Identifier("minecraft:village/snowy/houses");
Identifier taigaHousesPoolLocation = new Identifier("minecraft:village/taiga/houses");
addBuildingToPool(templatePoolRegistry, processorListRegistry, plainsHousesPoolLocation, CobblemonStructureIDs.PLAINS_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, desertHousesPoolLocation, CobblemonStructureIDs.DESERT_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, savannaHousesPoolLocation, CobblemonStructureIDs.SAVANNA_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, snowyHousesPoolLocation, CobblemonStructureIDs.SNOWY_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, taigaHousesPoolLocation, CobblemonStructureIDs.TAIGA_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, StructureProcessorLists.MOSSIFY_10_PERCENT);
}
private static void addLongPaths(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry) {
Identifier plainsStreetsPoolLocation = new Identifier("minecraft:village/plains/streets");
Identifier desertStreetsPoolLocation = new Identifier("minecraft:village/desert/streets");
Identifier savannaStreetsPoolLocation = new Identifier("minecraft:village/savanna/streets");
Identifier snowyStreetsPoolLocation = new Identifier("minecraft:village/snowy/streets");
Identifier taigaStreetsPoolLocation = new Identifier("minecraft:village/taiga/streets");
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, plainsStreetsPoolLocation, CobblemonStructureIDs.PLAINS_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_PLAINS);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, desertStreetsPoolLocation, CobblemonStructureIDs.DESERT_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, EMPTY_PROCESSOR_LIST_KEY);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, savannaStreetsPoolLocation, CobblemonStructureIDs.SAVANNA_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_SAVANNA);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, snowyStreetsPoolLocation, CobblemonStructureIDs.SNOWY_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_SNOWY_OR_TAIGA);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, taigaStreetsPoolLocation, CobblemonStructureIDs.TAIGA_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_SNOWY_OR_TAIGA);
}
public static void addLegacyBuildingToPool(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry, Identifier poolRL, String nbtPieceRL, int weight, StructurePool.Projection projection, RegistryKey<StructureProcessorList> processorListKey) {
addBuildingToPool(templatePoolRegistry, processorListRegistry, poolRL, nbtPieceRL, weight, projection, processorListKey, true);
}
public static void addBuildingToPool(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry, Identifier poolRL, String nbtPieceRL, int weight, StructurePool.Projection projection, RegistryKey<StructureProcessorList> processorListKey) {
addBuildingToPool(templatePoolRegistry, processorListRegistry, poolRL, nbtPieceRL, weight, projection, processorListKey, false);
}
public static void addBuildingToPool(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry, Identifier poolRL, String nbtPieceRL, int weight, StructurePool.Projection projection, RegistryKey<StructureProcessorList> processorListKey, boolean shouldUseLegacySingePoolElement) {
if (processorListRegistry.getEntry(processorListKey).isEmpty()) return;
RegistryEntry.Reference<StructureProcessorList> processorList = processorListRegistry.getEntry(processorListKey).get();
StructurePool pool = templatePoolRegistry.get(poolRL);
if (pool == null) return;
SinglePoolElement piece;
if (shouldUseLegacySingePoolElement) {
piece = LegacySinglePoolElement.ofProcessedLegacySingle(nbtPieceRL, processorList).apply(projection);
} else {
piece = SinglePoolElement.ofProcessedSingle(nbtPieceRL, processorList).apply(projection);
}
for (int i = 0; i < weight; i++) {
((StructurePoolAccessor) pool).getElements().add(piece);
}
List<Pair<StructurePoolElement, Integer>> listOfPieceEntries = new ArrayList<>(((StructurePoolAccessor) pool).getElementCounts());
listOfPieceEntries.add(new Pair<>(piece, weight));
((StructurePoolAccessor) pool).setElementCounts(listOfPieceEntries);
}
}
@@ -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.mixin;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.structure.pool.StructurePool;
import net.minecraft.structure.pool.StructurePoolElement;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(StructurePool.class)
public interface StructurePoolAccessor {
@Accessor("elementCounts")
List<Pair<StructurePoolElement, Integer>> getElementCounts();
@Mutable
@Accessor("elementCounts")
void setElementCounts(List<Pair<StructurePoolElement, Integer>> elementCounts);
@Accessor("elements")
ObjectArrayList<StructurePoolElement> getElements();
}
@@ -0,0 +1,199 @@
/*
* 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;
import com.cobblemon.mod.common.cobblemonstructures.CobblemonStructureIDs;
import net.minecraft.registry.Registry;
import net.minecraft.structure.PoolStructurePiece;
import net.minecraft.structure.StructureTemplateManager;
import net.minecraft.structure.pool.*;
import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.HeightLimitView;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.noise.NoiseConfig;
import org.apache.commons.lang3.mutable.MutableObject;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.*;
import java.util.stream.Collectors;
@Mixin(StructurePoolBasedGenerator.StructurePoolGenerator.class)
public abstract class StructurePoolGeneratorMixin {
@Final
@Mutable
@Shadow Deque<StructurePoolBasedGenerator.ShapedPoolStructurePiece> structurePieces;
Map<String, Integer> generatedStructureCounts;
private static final Map<String, Integer> structureMaxes;
static {
//Mapped using location string as key
Map<String, Integer> aMap = new HashMap<>();
aMap.put(CobblemonStructureIDs.PLAINS_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.DESERT_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.SAVANNA_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.SNOWY_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.TAIGA_POKECENTER, 1);
// aMap.put(CobblemonStructureIDs.PLAINS_LONG_PATH, 3);
// aMap.put(CobblemonStructureIDs.DESERT_LONG_PATH, 3);
// aMap.put(CobblemonStructureIDs.SAVANNA_LONG_PATH, 3);
// aMap.put(CobblemonStructureIDs.SNOWY_LONG_PATH, 3);
// aMap.put(CobblemonStructureIDs.TAIGA_LONG_PATH, 3);
structureMaxes = Collections.unmodifiableMap(aMap);
}
@Inject(method = "<init>", at = @At("RETURN"))
private void onStructurePoolGeneratorCreation(Registry<StructurePool> registry, int maxSize, ChunkGenerator chunkGenerator, StructureTemplateManager structureTemplateManager, List<? super PoolStructurePiece> children, Random random, CallbackInfo ci) {
generatedStructureCounts = new HashMap<>();
}
@ModifyVariable(method = "generatePiece", at = @At("STORE"), ordinal = 1)
private Iterator<StructurePoolElement> reduceStructurePoolELementIterator(Iterator<StructurePoolElement> iterator) {
List<StructurePoolElement> reducedList = new ArrayList<>();
while (iterator.hasNext()) {
StructurePoolElement structure = iterator.next();
String structurePieceLocationKey = getCobblemonOnlyLocation(structure);
if (structurePieceLocationKey == null) {
reducedList.add(structure);
continue;
}
Integer currentlyPlacedCount = generatedStructureCounts.get(structurePieceLocationKey);
if (currentlyPlacedCount == null) currentlyPlacedCount = 0;
Integer maxAllowed = structureMaxes.get(structurePieceLocationKey);
if (maxAllowed == null) {
reducedList.add(structure);
continue;
}
if (currentlyPlacedCount < maxAllowed) {
reducedList.add(structure);
}
}
for (String maxStructureLocationKey : structureMaxes.keySet()) {
Integer maxAllowed = structureMaxes.get(maxStructureLocationKey);
Integer currentlyGenerated = generatedStructureCounts.get(maxStructureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
Integer allowedInstances = maxAllowed - currentlyGenerated;
reducedList = removeInstanceOfLocationKeyFrom(reducedList, allowedInstances, maxStructureLocationKey);
}
return reducedList.iterator();
}
@ModifyVariable(method = "generatePiece", at = @At("STORE"), ordinal = 1)
private PoolStructurePiece injected(PoolStructurePiece poolStructurePiece) {
String structureLocationKey = getCobblemonOnlyLocation(poolStructurePiece.getPoolElement());
if (structureLocationKey != null) {
Integer currentlyGenerated = generatedStructureCounts.get(structureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
generatedStructureCounts.put(structureLocationKey, currentlyGenerated + 1);
}
return poolStructurePiece;
}
@Inject(method = "generatePiece", at = @At("HEAD"))
private void beforeGeneratePiece(PoolStructurePiece piece, MutableObject<VoxelShape> pieceShape, int minY, boolean modifyBoundingBox, HeightLimitView world, NoiseConfig noiseConfig, CallbackInfo ci) {
String structureLocationKey = getCobblemonOnlyLocation(piece.getPoolElement());
if (structureLocationKey != null && structureLocationKey.equals("cobblemon:village_snowy/village_snowy_pokecenter")) System.out.println("Before Counts" + generatedStructureCounts);
if (structureLocationKey != null) {
Integer currentlyGenerated = generatedStructureCounts.get(structureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
generatedStructureCounts.put(structureLocationKey, currentlyGenerated + 1);
}
List<StructurePoolBasedGenerator.ShapedPoolStructurePiece> reducedStructurePiecesList = structurePieces.stream().toList();
for (String maxStructureLocationKey : structureMaxes.keySet()) {
Integer maxAllowed = structureMaxes.get(maxStructureLocationKey);
Integer currentlyGenerated = generatedStructureCounts.get(maxStructureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
if (currentlyGenerated < maxAllowed) {
continue;
}
//Already have max so need to remove
reducedStructurePiecesList = reducedStructurePiecesList.stream()
.filter(shapedStructurePiece -> {
String locationKey = getCobblemonOnlyLocation(shapedStructurePiece.piece.getPoolElement());
if (locationKey == null) {
return true;
}
return !locationKey.equals(maxStructureLocationKey);
})
.collect(Collectors.toList());
}
structurePieces = new ArrayDeque<>(reducedStructurePiecesList);
}
private static String getCobblemonOnlyLocation(StructurePoolElement structurePoolElement) {
String location = getLocationIfAvailable(structurePoolElement);
if (location == null) return null;
if (!location.contains("cobblemon")) return null;
return location;
}
private static String getLocationIfAvailable(StructurePoolElement structurePoolElement) {
if (structurePoolElement instanceof LegacySinglePoolElement legacySinglePoolElement) {
if (legacySinglePoolElement.location.left().isEmpty()) return null;
return legacySinglePoolElement.location.left().get().toString();
} else if (structurePoolElement instanceof SinglePoolElement singlePoolElement) {
if (singlePoolElement.location.left().isEmpty()) return null;
return singlePoolElement.location.left().get().toString();
} else {
return null;
}
}
private List<StructurePoolElement> removeInstanceOfLocationKeyFrom(List<StructurePoolElement> structureList, Integer allowedNumberOfInstances, String locationKey) {
List<StructurePoolElement> reducedList = new ArrayList<>();
int instancesFound = 0;
for (StructurePoolElement structurePoolElement: structureList) {
String structureKey = getCobblemonOnlyLocation(structurePoolElement);
if (structureKey == null || !structureKey.equals(locationKey)) {
reducedList.add(structurePoolElement);
continue;
}
if (instancesFound >= allowedNumberOfInstances) {
continue;
}
reducedList.add(structurePoolElement);
instancesFound++;
}
return reducedList;
}
}
@@ -0,0 +1,43 @@
/*
* 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;
import com.cobblemon.mod.common.bridges.StructureProcessorListBridge;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.structure.processor.StructureProcessor;
import net.minecraft.structure.processor.StructureProcessorList;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(StructureProcessorList.class)
public class StructureProcessorListMixin implements StructureProcessorListBridge {
@Final
@Mutable
@Shadow
private List<StructureProcessor> list;
@Override
public void append(StructureProcessor processor) {
List<StructureProcessor> mutable = new ArrayList<>(this.list);
mutable.add(processor);
this.list = ImmutableList.copyOf(mutable);
}
@Override
public void clear() {
this.list = ImmutableList.of();
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import com.cobblemon.mod.common.block.entity.PCBlockEntity
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
@@ -27,6 +28,7 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
val HEALING_MACHINE: BlockEntityType<HealingMachineBlockEntity> = this.create("healing_machine", BlockEntityType.Builder.create(::HealingMachineBlockEntity, CobblemonBlocks.HEALING_MACHINE).build(null))
@JvmField
val PC: BlockEntityType<PCBlockEntity> = this.create("pc", BlockEntityType.Builder.create(::PCBlockEntity, CobblemonBlocks.PC).build(null))
val BERRY = this.create("berry", BlockEntityType.Builder.create(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
@JvmField
val PASTURE: BlockEntityType<PokemonPastureBlockEntity> = this.create("pasture", BlockEntityType.Builder.create(::PokemonPastureBlockEntity, CobblemonBlocks.PASTURE).build(null))
@@ -17,6 +17,8 @@ import com.cobblemon.mod.common.mixin.invoker.StairsBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.TrapdoorBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.*
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.block.BerryBlock
import net.minecraft.block.*
import net.minecraft.block.piston.PistonBehavior
import net.minecraft.entity.effect.StatusEffects
@@ -25,6 +27,7 @@ import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.sound.BlockSoundGroup
import net.minecraft.util.Identifier
import net.minecraft.util.math.intprovider.UniformIntProvider
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
@@ -227,6 +230,87 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
private fun apricornBlock(name: String, apricorn: Apricorn): ApricornBlock = this.create(name, ApricornBlock(AbstractBlock.Settings.create().mapColor(apricorn.mapColor()).ticksRandomly().strength(Blocks.OAK_LOG.hardness, Blocks.OAK_LOG.blastResistance).sounds(BlockSoundGroup.WOOD).nonOpaque(), apricorn))
private val berries = mutableMapOf<Identifier, BerryBlock>()
val AGUAV_BERRY = this.berryBlock("aguav")
val APICOT_BERRY = this.berryBlock("apicot")
val ASPEAR_BERRY = this.berryBlock("aspear")
val BABIRI_BERRY = this.berryBlock("babiri")
val BELUE_BERRY = this.berryBlock("belue")
val BLUK_BERRY = this.berryBlock("bluk")
val CHARTI_BERRY = this.berryBlock("charti")
val CHERI_BERRY = this.berryBlock("cheri")
val CHESTO_BERRY = this.berryBlock("chesto")
val CHILAN_BERRY = this.berryBlock("chilan")
val CHOPLE_BERRY = this.berryBlock("chople")
val COBA_BERRY = this.berryBlock("coba")
val COLBUR_BERRY = this.berryBlock("colbur")
val CORNN_BERRY = this.berryBlock("cornn")
val CUSTAP_BERRY = this.berryBlock("custap")
val DURIN_BERRY = this.berryBlock("durin")
val ENIGMA_BERRY = this.berryBlock("enigma")
val FIGY_BERRY = this.berryBlock("figy")
val GANLON_BERRY = this.berryBlock("ganlon")
val GREPA_BERRY = this.berryBlock("grepa")
val HABAN_BERRY = this.berryBlock("haban")
val HONDEW_BERRY = this.berryBlock("hondew")
val HOPO_BERRY = this.berryBlock("hopo")
val IAPAPA_BERRY = this.berryBlock("iapapa")
val JABOCA_BERRY = this.berryBlock("jaboca")
val KASIB_BERRY = this.berryBlock("kasib")
val KEBIA_BERRY = this.berryBlock("kebia")
val KEE_BERRY = this.berryBlock("kee")
val KELPSY_BERRY = this.berryBlock("kelpsy")
val LANSAT_BERRY = this.berryBlock("lansat")
val LEPPA_BERRY = this.berryBlock("leppa")
val LIECHI_BERRY = this.berryBlock("liechi")
val LUM_BERRY = this.berryBlock("lum")
val MAGO_BERRY = this.berryBlock("mago")
val MAGOST_BERRY = this.berryBlock("magost")
val MARANGA_BERRY = this.berryBlock("maranga")
val MICLE_BERRY = this.berryBlock("micle")
val NANAB_BERRY = this.berryBlock("nanab")
val NOMEL_BERRY = this.berryBlock("nomel")
val OCCA_BERRY = this.berryBlock("occa")
val ORAN_BERRY = this.berryBlock("oran")
val PAMTRE_BERRY = this.berryBlock("pamtre")
val PASSHO_BERRY = this.berryBlock("passho")
val PAYAPA_BERRY = this.berryBlock("payapa")
val PECHA_BERRY = this.berryBlock("pecha")
val PERSIM_BERRY = this.berryBlock("persim")
val PETAYA_BERRY = this.berryBlock("petaya")
val PINAP_BERRY = this.berryBlock("pinap")
val POMEG_BERRY = this.berryBlock("pomeg")
val QUALOT_BERRY = this.berryBlock("qualot")
val RABUTA_BERRY = this.berryBlock("rabuta")
val RAWST_BERRY = this.berryBlock("rawst")
val RAZZ_BERRY = this.berryBlock("razz")
val RINDO_BERRY = this.berryBlock("rindo")
val ROSELI_BERRY = this.berryBlock("roseli")
val ROWAP_BERRY = this.berryBlock("rowap")
val SALAC_BERRY = this.berryBlock("salac")
val SHUCA_BERRY = this.berryBlock("shuca")
val SITRUS_BERRY = this.berryBlock("sitrus")
val SPELON_BERRY = this.berryBlock("spelon")
val STARF_BERRY = this.berryBlock("starf")
val TAMATO_BERRY = this.berryBlock("tamato")
val TANGA_BERRY = this.berryBlock("tanga")
val TOUGA_BERRY = this.berryBlock("touga")
val WACAN_BERRY = this.berryBlock("wacan")
val WATMEL_BERRY = this.berryBlock("watmel")
val WEPEAR_BERRY = this.berryBlock("wepear")
val WIKI_BERRY = this.berryBlock("wiki")
val YACHE_BERRY = this.berryBlock("yache")
fun berries() = this.berries.toMap()
private fun berryBlock(name: String): BerryBlock {
val identifier = cobblemonResource("${name}_berry")
val block = this.create(identifier.path, BerryBlock(identifier, AbstractBlock.Settings.copy(Blocks.WHEAT).dynamicBounds().sounds(BlockSoundGroup.CROP)))
this.berries[identifier] = block
return block
}
/**
* Helper method for creating logs
* copied over from Vanilla
@@ -248,4 +332,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
val block = BlocksInvoker.createLeavesBlock(BlockSoundGroup.GRASS)
return this.create(name, block)
}
}
}
@@ -13,18 +13,27 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.item.*
import com.cobblemon.mod.common.item.battle.DireHitItem
import com.cobblemon.mod.common.item.battle.GuardSpecItem
import com.cobblemon.mod.common.item.battle.XStatItem
import com.cobblemon.mod.common.item.berry.FriendshipRaisingBerryItem
import com.cobblemon.mod.common.item.berry.HealingBerryItem
import com.cobblemon.mod.common.item.berry.PPRestoringBerryItem
import com.cobblemon.mod.common.item.berry.PortionHealingBerryItem
import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
import com.cobblemon.mod.common.item.berry.VolatileCuringBerryItem
import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.PotionItem
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.block.Block
import net.minecraft.block.ComposterBlock
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.effect.StatusEffectInstance
import net.minecraft.entity.effect.StatusEffects
import net.minecraft.item.*
@@ -32,6 +41,8 @@ import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.util.Identifier
import net.minecraft.world.World
@Suppress("unused")
object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<Item>>, Item>() {
@@ -220,8 +231,84 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
// ToDo enable me when malicious armor goes in the game
//@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
// Armor
@JvmField val AUSPICIOUS_ARMOR = armorItem("auspicious_armor", AuspiciousArmorMaterial(), ArmorItem.Type.CHESTPLATE, Item.Settings().fireproof())
@JvmField val MALICIOUS_ARMOR = armorItem("malicious_armor", MaliciousArmorMaterial(), ArmorItem.Type.CHESTPLATE, Item.Settings().fireproof())
private val berries = mutableMapOf<Identifier, BerryItem>()
// Plants
@JvmField val ORAN_BERRY = berryItem("oran", HealingBerryItem(CobblemonBlocks.ORAN_BERRY) { CobblemonMechanics.berries.oranRestoreAmount })
@JvmField val CHERI_BERRY = berryItem("cheri", StatusCuringBerryItem(CobblemonBlocks.CHERI_BERRY, Statuses.PARALYSIS))
@JvmField val CHESTO_BERRY = berryItem("chesto", StatusCuringBerryItem(CobblemonBlocks.CHESTO_BERRY, Statuses.SLEEP))
@JvmField val PECHA_BERRY = berryItem("pecha", StatusCuringBerryItem(CobblemonBlocks.PECHA_BERRY, Statuses.POISON, Statuses.POISON_BADLY))
@JvmField val RAWST_BERRY = berryItem("rawst", StatusCuringBerryItem(CobblemonBlocks.RAWST_BERRY, Statuses.BURN))
@JvmField val ASPEAR_BERRY = berryItem("aspear", StatusCuringBerryItem(CobblemonBlocks.ASPEAR_BERRY, Statuses.FROZEN))
@JvmField val PERSIM_BERRY = berryItem("persim", VolatileCuringBerryItem(CobblemonBlocks.PERSIM_BERRY, "confusion"))
@JvmField val RAZZ_BERRY = berryItem("razz", CobblemonBlocks.RAZZ_BERRY)
@JvmField val BLUK_BERRY = berryItem("bluk", CobblemonBlocks.BLUK_BERRY)
@JvmField val NANAB_BERRY = berryItem("nanab", CobblemonBlocks.NANAB_BERRY)
@JvmField val WEPEAR_BERRY = berryItem("wepear", CobblemonBlocks.WEPEAR_BERRY)
@JvmField val PINAP_BERRY = berryItem("pinap", CobblemonBlocks.PINAP_BERRY)
@JvmField val OCCA_BERRY = berryItem("occa", CobblemonBlocks.OCCA_BERRY)
@JvmField val PASSHO_BERRY = berryItem("passho", CobblemonBlocks.PASSHO_BERRY)
@JvmField val WACAN_BERRY = berryItem("wacan", CobblemonBlocks.WACAN_BERRY)
@JvmField val RINDO_BERRY = berryItem("rindo", CobblemonBlocks.RINDO_BERRY)
@JvmField val YACHE_BERRY = berryItem("yache", CobblemonBlocks.YACHE_BERRY)
@JvmField val CHOPLE_BERRY = berryItem("chople", CobblemonBlocks.CHOPLE_BERRY)
@JvmField val KEBIA_BERRY = berryItem("kebia", CobblemonBlocks.KEBIA_BERRY)
@JvmField val SHUCA_BERRY = berryItem("shuca", CobblemonBlocks.SHUCA_BERRY)
@JvmField val COBA_BERRY = berryItem("coba", CobblemonBlocks.COBA_BERRY)
@JvmField val PAYAPA_BERRY = berryItem("payapa", CobblemonBlocks.PAYAPA_BERRY)
@JvmField val TANGA_BERRY = berryItem("tanga", CobblemonBlocks.TANGA_BERRY)
@JvmField val CHARTI_BERRY = berryItem("charti", CobblemonBlocks.CHARTI_BERRY)
@JvmField val KASIB_BERRY = berryItem("kasib", CobblemonBlocks.KASIB_BERRY)
@JvmField val HABAN_BERRY = berryItem("haban", CobblemonBlocks.HABAN_BERRY)
@JvmField val COLBUR_BERRY = berryItem("colbur", CobblemonBlocks.COLBUR_BERRY)
@JvmField val BABIRI_BERRY = berryItem("babiri", CobblemonBlocks.BABIRI_BERRY)
@JvmField val CHILAN_BERRY = berryItem("chilan", CobblemonBlocks.CHILAN_BERRY)
@JvmField val ROSELI_BERRY = berryItem("roseli", CobblemonBlocks.ROSELI_BERRY)
@JvmField val LEPPA_BERRY = berryItem("leppa", PPRestoringBerryItem(CobblemonBlocks.LEPPA_BERRY) { CobblemonMechanics.berries.ppRestoreAmount })
@JvmField val LUM_BERRY = berryItem("lum", StatusCuringBerryItem(CobblemonBlocks.LUM_BERRY))
@JvmField val FIGY_BERRY = berryItem("figy", PortionHealingBerryItem(CobblemonBlocks.FIGY_BERRY, true) { CobblemonMechanics.berries.portionHealRatio })
@JvmField val WIKI_BERRY = berryItem("wiki", PortionHealingBerryItem(CobblemonBlocks.WIKI_BERRY, true) { CobblemonMechanics.berries.portionHealRatio })
@JvmField val MAGO_BERRY = berryItem("mago", PortionHealingBerryItem(CobblemonBlocks.MAGO_BERRY, true) { CobblemonMechanics.berries.portionHealRatio })
@JvmField val AGUAV_BERRY = berryItem("aguav", PortionHealingBerryItem(CobblemonBlocks.AGUAV_BERRY, true) { CobblemonMechanics.berries.portionHealRatio })
@JvmField val IAPAPA_BERRY = berryItem("iapapa", PortionHealingBerryItem(CobblemonBlocks.IAPAPA_BERRY, true) { CobblemonMechanics.berries.portionHealRatio })
@JvmField val SITRUS_BERRY = berryItem("sitrus", HealingBerryItem(CobblemonBlocks.SITRUS_BERRY) { CobblemonMechanics.berries.sitrusHealAmount })
@JvmField val TOUGA_BERRY = berryItem("touga", CobblemonBlocks.TOUGA_BERRY)
@JvmField val CORNN_BERRY = berryItem("cornn", CobblemonBlocks.CORNN_BERRY)
@JvmField val MAGOST_BERRY = berryItem("magost", CobblemonBlocks.MAGOST_BERRY)
@JvmField val RABUTA_BERRY = berryItem("rabuta", CobblemonBlocks.RABUTA_BERRY)
@JvmField val NOMEL_BERRY = berryItem("nomel", CobblemonBlocks.NOMEL_BERRY)
@JvmField val ENIGMA_BERRY = berryItem("enigma", CobblemonBlocks.ENIGMA_BERRY)
@JvmField val POMEG_BERRY = berryItem("pomeg", FriendshipRaisingBerryItem(CobblemonBlocks.POMEG_BERRY, Stats.HP))
@JvmField val KELPSY_BERRY = berryItem("kelpsy", FriendshipRaisingBerryItem(CobblemonBlocks.KELPSY_BERRY, Stats.ATTACK))
@JvmField val QUALOT_BERRY = berryItem("qualot", FriendshipRaisingBerryItem(CobblemonBlocks.QUALOT_BERRY, Stats.DEFENCE))
@JvmField val HONDEW_BERRY = berryItem("hondew", FriendshipRaisingBerryItem(CobblemonBlocks.HONDEW_BERRY, Stats.SPECIAL_ATTACK))
@JvmField val GREPA_BERRY = berryItem("grepa", FriendshipRaisingBerryItem(CobblemonBlocks.GREPA_BERRY, Stats.SPECIAL_DEFENCE))
@JvmField val TAMATO_BERRY = berryItem("tamato", FriendshipRaisingBerryItem(CobblemonBlocks.TAMATO_BERRY, Stats.SPEED))
@JvmField val SPELON_BERRY = berryItem("spelon", CobblemonBlocks.SPELON_BERRY)
@JvmField val PAMTRE_BERRY = berryItem("pamtre", CobblemonBlocks.PAMTRE_BERRY)
@JvmField val WATMEL_BERRY = berryItem("watmel", CobblemonBlocks.WATMEL_BERRY)
@JvmField val DURIN_BERRY = berryItem("durin", CobblemonBlocks.DURIN_BERRY)
@JvmField val BELUE_BERRY = berryItem("belue", CobblemonBlocks.BELUE_BERRY)
@JvmField val KEE_BERRY = berryItem("kee", CobblemonBlocks.KEE_BERRY)
@JvmField val MARANGA_BERRY = berryItem("maranga", CobblemonBlocks.MARANGA_BERRY)
@JvmField val HOPO_BERRY = berryItem("hopo", PPRestoringBerryItem(CobblemonBlocks.HOPO_BERRY) { CobblemonMechanics.berries.ppRestoreAmount })
@JvmField val LIECHI_BERRY = berryItem("liechi", CobblemonBlocks.LIECHI_BERRY)
@JvmField val GANLON_BERRY = berryItem("ganlon", CobblemonBlocks.GANLON_BERRY)
@JvmField val SALAC_BERRY = berryItem("salac", CobblemonBlocks.SALAC_BERRY)
@JvmField val PETAYA_BERRY = berryItem("petaya", CobblemonBlocks.PETAYA_BERRY)
@JvmField val APICOT_BERRY = berryItem("apicot", CobblemonBlocks.APICOT_BERRY)
@JvmField val LANSAT_BERRY = berryItem("lansat", CobblemonBlocks.LANSAT_BERRY)
@JvmField val STARF_BERRY = berryItem("starf", CobblemonBlocks.STARF_BERRY)
@JvmField val MICLE_BERRY = berryItem("micle", CobblemonBlocks.MICLE_BERRY)
@JvmField val CUSTAP_BERRY = berryItem("custap", CobblemonBlocks.CUSTAP_BERRY)
@JvmField val JABOCA_BERRY = berryItem("jaboca", CobblemonBlocks.JABOCA_BERRY)
@JvmField val ROWAP_BERRY = berryItem("rowap", CobblemonBlocks.ROWAP_BERRY)
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
// Medicine
@JvmField
@@ -255,14 +342,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField
val BRAISED_VIVICHOKE = create("braised_vivichoke", Item(Item.Settings().maxCount(1).food(FoodComponent.Builder().hunger(6).saturationModifier(0.6f).build())))
@JvmField
val VIVICHOKE_DIP = create("vivichoke_dip", StewItem(Item.Settings().maxCount(1)
val VIVICHOKE_DIP = create("vivichoke_dip", object : StewItem(Settings().maxCount(1)
.food(FoodComponent.Builder()
.hunger(10)
.saturationModifier(1.2F)
.statusEffect(StatusEffectInstance(StatusEffects.ABSORPTION, 600, 0), 1F)
.build()
)
))
.statusEffect(StatusEffectInstance(StatusEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsing(stack: ItemStack?, world: World?, user: LivingEntity?): ItemStack {
user?.clearStatusEffects()
return super.finishUsing(stack, world, user)
}
})
@JvmField
val ENERGY_ROOT = create("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Item.Settings().food(FoodComponent.Builder().hunger(1).snack().saturationModifier(0.2f).build())))
@JvmField
@@ -544,6 +635,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
val DESTINY_KNOT = heldItem("destiny_knot")
@JvmField
val EVERSTONE = heldItem("everstone")
@JvmField
val POWER_ANKLET = heldItem("power_anklet")
@JvmField
val POWER_BAND = heldItem("power_band")
@JvmField
val POWER_BELT = heldItem("power_belt")
@JvmField
val POWER_BRACER = heldItem("power_bracer")
@JvmField
val POWER_LENS = heldItem("power_lens")
@JvmField
val POWER_WEIGHT = heldItem("power_weight")
// Mulch
@JvmField
@@ -573,6 +676,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
private fun noSettingsItem(name: String): CobblemonItem = this.create(name, CobblemonItem(Item.Settings()))
fun berries() = this.berries.toMap()
private fun mulchItem(name: String, mulchVariant: MulchVariant): MulchItem = this.create(name, MulchItem(mulchVariant))
private fun pokeBallItem(pokeBall: PokeBall): PokeBallItem {
@@ -593,4 +698,23 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
}
)
}
private fun armorItem(name: String, type: ArmorMaterial, slot: ArmorItem.Type, settings: Item.Settings): ArmorItem {
val armor = ArmorItem(type, slot, settings)
val item = create(name, armor)
return item
}
private fun berryItem(name: String, berryBlock: BerryBlock): BerryItem {
val finalName = "${name}_berry"
val item = this.create(finalName, BerryItem(berryBlock))
this.berries[cobblemonResource(finalName)] = item
return item
}
private fun berryItem(name: String, berryItem: BerryItem): BerryItem {
val finalName = "${name}_berry"
val item = this.create(finalName, berryItem)
this.berries[cobblemonResource(finalName)] = item
return item
}
}
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.mechanics.BerriesMechanic
import com.cobblemon.mod.common.mechanics.PotionsMechanic
import com.cobblemon.mod.common.mechanics.RemediesMechanic
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.cobblemonResource
@@ -30,11 +32,19 @@ object CobblemonMechanics : DataRegistry {
.create()
var remedies = RemediesMechanic()
var berries = BerriesMechanic()
var potions = PotionsMechanic()
override fun sync(player: ServerPlayerEntity) {}
override fun reload(manager: ResourceManager) {
manager.getResourceOrThrow(cobblemonResource("mechanics/remedies.json")).inputStream.use {
remedies = gson.fromJson(it.reader(), RemediesMechanic::class.java)
remedies = loadMechanic(manager, "remedies", RemediesMechanic::class.java)
berries = loadMechanic(manager, "berries", BerriesMechanic::class.java)
potions = loadMechanic(manager, "potions", PotionsMechanic::class.java)
}
private fun <T> loadMechanic(manager: ResourceManager, name: String, clazz: Class<T>): T {
manager.getResourceOrThrow(cobblemonResource("mechanics/$name.json")).inputStream.use {
return gson.fromJson(it.reader(), clazz)
}
}
}
@@ -13,14 +13,14 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
import com.cobblemon.mod.common.client.net.battle.*
import com.cobblemon.mod.common.client.net.callback.move.OpenMoveCallbackHandler
import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandler
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.callback.move.OpenMoveCallbackHandler
import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandler
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
@@ -56,6 +56,7 @@ import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPac
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.BerryRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.PropertiesCompletionRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
@@ -273,6 +274,7 @@ object CobblemonNetwork : NetworkManager {
this.createClientBound(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler())
this.createClientBound(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler())
this.createClientBound(UnlockReloadPacket.ID, UnlockReloadPacket::decode, UnlockReloadPacketHandler)
this.createClientBound(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler())
// Effects
this.createClientBound(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler)
@@ -78,6 +78,19 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
@JvmField
val MEDICINE_SPRAY_USE = this.create("medicine_spray.use")
@JvmField
val BERRY_EAT = this.create("berry.eat")
@JvmField
val MULCH_PLACE = this.create("mulch.place")
@JvmField
val MULCH_REMOVE = this.create("mulch.remove")
@JvmField
val AUSPICIOUS_ARMOR_EQUIP = this.create("auspicious_armor.equip")
@JvmField
val MALICIOUS_ARMOR_EQUIP = this.create("malicious_armor.equip")
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.of(cobblemonResource(name)))
}
@@ -34,22 +34,19 @@ import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.entity.Entity
import java.io.File
import java.util.UUID
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.text.Text
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.writeLines
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
/**
* Individual battle instance
@@ -62,7 +59,7 @@ open class PokemonBattle(
val side1: BattleSide,
val side2: BattleSide
) {
/** Whether or not logging will be silenced for this battle. */
/** Whether logging will be silenced for this battle. */
var mute = true
init {
@@ -0,0 +1,108 @@
/*
* 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.berry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.berry.spawncondition.BerrySpawnCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.status.Status
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.BerryRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.adapters.CobblemonStatTypeAdapter
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.awt.Color
import net.minecraft.predicate.NumberRange
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.tag.TagKey
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d
import net.minecraft.world.biome.Biome
/**
* The data registry for [Berry].
*
* @author Licious
* @since November 28th, 2022
*/
object Berries : JsonDataRegistry<Berry> {
override val id: Identifier = cobblemonResource("berries")
override val type: ResourceType = ResourceType.SERVER_DATA
override val observable = SimpleObservable<Berries>()
override val gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.registerTypeAdapter(MulchVariant::class.java, MulchVariantAdapter)
.registerTypeAdapter(NumberRange.FloatRange::class.java, FloatNumberRangeAdapter)
.registerTypeAdapter(Status::class.java, StatusAdapter)
.registerTypeAdapter(Box::class.java, BoxAdapter)
.registerTypeAdapter(Vec3d::class.java, VerboseVec3dAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(GrowthFactor::class.java, CobblemonGrowthFactorAdapter)
.registerTypeAdapter(IntRange::class.java, VerboseIntRangeAdapter)
.registerTypeAdapter(Color::class.java, LiteralHexColorAdapter)
.registerTypeAdapter(Stat::class.java, CobblemonStatTypeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(TagKey::class.java, Biome::class.java).type, TagKeyAdapter(RegistryKeys.BIOME))
.registerTypeAdapter(BerrySpawnCondition::class.java, CobblemonBerrySpawnConditionAdapter)
.create()
override val typeToken: TypeToken<Berry> = TypeToken.get(Berry::class.java)
override val resourcePath = "berries"
private val berries = hashMapOf<Identifier, Berry>()
val PECHA
get() = this.getByName("")
override fun reload(data: Map<Identifier, Berry>) {
this.berries.clear()
data.forEach { (identifier, berry) ->
try {
berry.identifier = identifier
berry.validate()
this.berries[identifier] = berry
} catch (e: Exception) {
Cobblemon.LOGGER.error("Skipped loading the {} berry", identifier, e)
}
}
Cobblemon.LOGGER.info("Loaded {} berries", this.berries.size)
this.observable.emit(this)
}
override fun sync(player: ServerPlayerEntity) {
BerryRegistrySyncPacket(this.all()).sendToPlayer(player)
}
fun all() = this.berries.values.toList()
/**
* Gets a berry if loaded.
*
* @param identifier The identifier of the berry.
* @return The [Berry] if loaded otherwise null.
*/
fun getByIdentifier(identifier: Identifier): Berry? = this.berries[identifier]
/**
* Gets a berry if loaded.
*
* @param name The path of the identifier of the berry under the [Cobblemon.MODID] namespace.
* @return The [Berry] if loaded otherwise null.
*/
fun getByName(name: String): Berry? = this.getByIdentifier(cobblemonResource(name))
}
@@ -0,0 +1,349 @@
/*
* 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.berry
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.berry.spawncondition.BerrySpawnCondition
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.berry.BerryYieldCalculationEvent
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.item.BerryItem
import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.util.readBox
import com.cobblemon.mod.common.util.writeBox
import com.google.gson.annotations.SerializedName
import java.awt.Color
import java.util.EnumSet
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.LivingEntity
import net.minecraft.network.PacketByteBuf
import net.minecraft.registry.tag.TagKey
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.World
import net.minecraft.world.biome.Biome
/**
* Represents the data behind a berry.
*
* @property identifier The [Identifier] of this berry.
* @property baseYield The [IntRange] possible for the berry tree before [bonusYield] is calculated.
* @property preferredBiomeTags The [TagKey]s of the berries preffered biomes. Determines spawning and yield
* @property growthTime The [IntRange] possible in minutes for how long the berry tree takes to grow.
* @property refreshRate The [IntRange] possible in minutes for how long the berry tree takes to regrow its berries once it has been harvested.
* @depreciated @property lifeCycles The [IntRange] possible for the berry to live for between harvests.
* @property favoriteMulches The types of mulches that benefit this berries yield
* @property growthFactors An array of [GrowthFactor]s that will affect this berry. The client is not aware of these.
* @property mutations A map of the partner berry as the key and the value as the resulting mutation with this berry.
* @property growthPoints A collection of [GrowthPoint]s for the berry flowers and fruit.
* @property sproutShapeBoxes A collection of [Box]es that make up the tree [VoxelShape] during the sprouting stages.
* @property matureShapeBoxes A collection of [Box]es that make up the tree [VoxelShape] during the mature stages.
* @property flavors The [Flavor] values.
* @property tintIndexes Determines tints at specific indexes if any.
* @property flowerModelIdentifier The [Identifier] for the model of the berry in flower form.
* @property flowerTexture The [Identifier] for the texture of the berry in flower form. This is resolved into a [ModelPart] on the client.
* @property fruitModelIdentifier The [Identifier] for the model of the berry in flower form. This is resolved into a [ModelPart] on the client.
* @property fruitTexture The [Identifier] for the texture of the berry in flower form.
*
* @throws IllegalArgumentException if the any yield range argument is not a positive range.
*/
class Berry(
identifier: Identifier,
val baseYield: IntRange,
val preferredBiomeTags: List<TagKey<Biome>>,
val growthTime: IntRange,
val refreshRate: IntRange,
//val lifeCycles: IntRange,
val favoriteMulches: EnumSet<MulchVariant>,
val growthFactors: Collection<GrowthFactor>,
val spawnConditions: List<BerrySpawnCondition>,
var growthPoints: Array<GrowthPoint>,
val randomizedGrowthPoints: Boolean = true,
val mutations: Map<Identifier, Identifier>,
@SerializedName("sproutShape")
private val sproutShapeBoxes: Collection<Box>,
@SerializedName("matureShape")
private val matureShapeBoxes: Collection<Box>,
private val flavors: Map<Flavor, Int>,
val tintIndexes: Map<Int, Color>,
@SerializedName("flowerModel")
val flowerModelIdentifier: Identifier,
val flowerTexture: Identifier,
@SerializedName("fruitModel")
val fruitModelIdentifier: Identifier,
val fruitTexture: Identifier,
val weight: Float
) {
@Transient
var identifier: Identifier = identifier
internal set
@Transient
private lateinit var shapedFlower: HashMap<Int, VoxelShape>
@Transient
private lateinit var shapedFruit: HashMap<Int, VoxelShape>
/**
* The [VoxelShape] of the tree during the sprouting stages.
*/
@Transient
lateinit var sproutShape: VoxelShape
private set
/**
* The [VoxelShape] of the tree during the mature stages.
*/
@Transient
lateinit var matureShape: VoxelShape
private set
init {
this.validate()
}
/**
* Finds the [BerryItem] that uses this berry for data.
*
* @return The [BerryItem] if existing.
*/
fun item(): BerryItem? = CobblemonItems.berries()[this.identifier]
/**
* Finds the [BerryBlock] that uses this berry for data.
*
* @return The [BerryBlock] if existing.
*/
fun block(): BerryBlock? = CobblemonBlocks.berries()[this.identifier]
/**
* Query the value of a certain flavor.
*
* @param flavor The [Flavor] being queried.
* @return The value if any or 0.
*/
fun flavor(flavor: Flavor): Int = this.flavors[flavor] ?: 0
fun dislikedBy(nature: Nature): Boolean {
val dislikedFlavor = nature.dislikedFlavor ?: return false
return flavor(dislikedFlavor) > 0
}
/**
* Calculates the yield for a berry tree being planted or replanted after a life cycle.
* Triggers [BerryYieldCalculationEvent].
*
* @param world The [World] the tree is present in.
* @param state The [BlockState] of the tree.
* @param pos The [BlockPos] of the tree.
* @param placer The [LivingEntity] tending to the tree, if any.
* @return The total berry stack count.
*/
fun calculateYield(world: World, state: BlockState, pos: BlockPos, placer: LivingEntity? = null): Int {
val base = this.baseYield.random()
val bonus = this.bonusYield(world, state, pos)
var yield = base + bonus.first
val event = BerryYieldCalculationEvent(this, world, state, pos, placer, yield, bonus.second)
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if (treeEntity.mulchVariant == MulchVariant.RICH) {
yield = yield.times(1.2).coerceAtMost(maxYield().toDouble()).toInt()
treeEntity.mulchDuration--
}
CobblemonEvents.BERRY_YIELD.post(event) { yield = it.yield }
return yield
//Just for testing the renderer
//return growthPoints.size
}
/**
* Calculates and returns the minimum possible yield by summing [baseYield] and [growthFactors] minimum values.
*
* @return The minimum possible yield.
*/
fun minYield() = this.baseYield.first + this.growthFactors.sumOf { it.minYield() }
/**
* Calculates and returns the maximum possible yield by summing [baseYield] and [growthFactors] max values.
*
* @return The maximum possible yield.
*/
fun maxYield() = this.baseYield.last + this.growthFactors.sumOf { it.maxYield() }
/**
* Checks if this berry can mutate with the given [partner].
* For the berry the mutations may produce use [mutationWith].
*
* @param partner The [Berry] being checked as a possible mutation partner.
* @return If a mutation is possible.
*/
fun canMutateWith(partner: Berry) = this.mutationWith(partner) != null
/**
* Query for the possible mutation with the given [partner].
*
* @param partner The [Berry] being mutated with.
* @return The resulting [Berry] if the mutation is possible.
*/
fun mutationWith(partner: Berry): Berry? {
val berryIdentifier = this.mutations[partner.identifier] ?: return null
return Berries.getByIdentifier(berryIdentifier)
}
/**
* Query for a possible partner to mutate into the given [resulting].
*
* @param resulting The [Berry] the mutation should result in.
* @return The [Berry] that mutates with this berry into the given [resulting] if possible.
*/
fun partnerForMutation(resulting: Berry): Berry? {
return this.mutations.firstNotNullOfOrNull { (partner, result) ->
if (result == resulting.identifier)
Berries.getByIdentifier(partner)
else
null
}
}
// A cheat since gson doesn't invoke init block
internal fun validate() {
if (this.baseYield.first < 0 || this.baseYield.last < 0) {
throw IllegalArgumentException("A berry base yield must be a positive range")
}
if (this.growthTime.first < 0 || this.growthTime.last < 0) {
throw IllegalArgumentException("The growth time must be a positive range")
}
if (this.refreshRate.first < 0 || this.refreshRate.last < 0) {
throw IllegalArgumentException("The refresh rate must be a positive range")
}
/*
if (this.lifeCycles.first < 0 || this.lifeCycles.last < 0) {
throw IllegalArgumentException("A berry life cycle must be a positive range")
}
*/
this.growthFactors.forEach { it.validateArguments() }
val maxYield = this.maxYield()
if (this.growthPoints.size < maxYield) {
throw IllegalArgumentException("Anchor points must have enough elements for the max possible yield of $maxYield you've provided ${this.growthPoints.size} points")
}
this.shapedFlower = hashMapOf()
this.shapedFruit = hashMapOf()
this.sproutShape = this.createAndUniteShapes(this.sproutShapeBoxes)
this.matureShape = this.createAndUniteShapes(this.matureShapeBoxes)
}
internal fun encode(buffer: PacketByteBuf) {
buffer.writeIdentifier(this.identifier)
buffer.writeInt(this.baseYield.first)
buffer.writeInt(this.baseYield.last)
buffer.writeEnumSet(favoriteMulches, MulchVariant::class.java)
//buffer.writeInt(this.lifeCycles.first)
//buffer.writeInt(this.lifeCycles.last)
buffer.writeInt(this.growthTime.first)
buffer.writeInt(this.growthTime.last)
buffer.writeInt(this.refreshRate.first)
buffer.writeInt(this.refreshRate.last)
buffer.writeCollection(this.growthPoints.toList()) { writer, value ->
writer.writeDouble(value.position.x)
writer.writeDouble(value.position.y)
writer.writeDouble(value.position.z)
writer.writeDouble(value.rotation.x)
writer.writeDouble(value.rotation.y)
writer.writeDouble(value.rotation.z)
}
buffer.writeBoolean(this.randomizedGrowthPoints)
buffer.writeMap(this.mutations, { writer, key -> writer.writeIdentifier(key) }, { writer, value -> writer.writeIdentifier(value) })
buffer.writeCollection(this.sproutShapeBoxes) { writer, value ->
writer.writeBox(value)
}
buffer.writeCollection(this.matureShapeBoxes) { writer, value ->
writer.writeBox(value)
}
buffer.writeMap(this.flavors, { writer, key -> writer.writeEnumConstant(key) }, { writer, value -> writer.writeInt(value) })
buffer.writeMap(this.tintIndexes, { writer, key -> writer.writeInt(key) }, { writer, value -> writer.writeInt(value.rgb) })
buffer.writeIdentifier(this.flowerModelIdentifier)
buffer.writeIdentifier(this.flowerTexture)
buffer.writeIdentifier(this.fruitModelIdentifier)
buffer.writeIdentifier(this.fruitTexture)
}
/**
* Calculates the bonus yield for the berry tree.
*
* @param world The [World] the tree is present in.
* @param state The [BlockState] of the tree.
* @param pos The [BlockPos] of the tree.
* @return The bonus yield, the growth factors that passed.
*/
private fun bonusYield(world: World, state: BlockState, pos: BlockPos): Pair<Int, Collection<GrowthFactor>> {
var bonus = 0
val passed = arrayListOf<GrowthFactor>()
val entity = world.getBlockEntity(pos) as BerryBlockEntity
val hasBiomeMulch = favoriteMulches.contains(entity.mulchVariant)
this.growthFactors.forEach { factor ->
if (factor.isValid(world, state, pos)) {
bonus += factor.yield()
passed += factor
}
else if (hasBiomeMulch) {
bonus += factor.yield()
}
}
return bonus to passed
}
private fun createAndUniteShapes(boxes: Collection<Box>): VoxelShape {
var shape: VoxelShape? = null
boxes.forEach { box ->
shape = if (shape == null) {
Block.createCuboidShape(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)
} else {
VoxelShapes.union(shape, Block.createCuboidShape(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ))
}
}
return shape ?: VoxelShapes.fullCube()
}
companion object {
internal fun decode(buffer: PacketByteBuf): Berry {
val identifier = buffer.readIdentifier()
val baseYield = IntRange(buffer.readInt(), buffer.readInt())
val favMulchs = buffer.readEnumSet(MulchVariant::class.java)
//val lifeCycles = IntRange(buffer.readInt(), buffer.readInt())
val growthTime = IntRange(buffer.readInt(), buffer.readInt())
val refreshRate = IntRange(buffer.readInt(), buffer.readInt())
val growthPoints = buffer.readList { reader ->
GrowthPoint(Vec3d(reader.readDouble(), reader.readDouble(), reader.readDouble()), Vec3d(reader.readDouble(), reader.readDouble(), reader.readDouble()))
}.toTypedArray()
val randomizedGrowthPoints = buffer.readBoolean()
val mutations = buffer.readMap({ reader -> reader.readIdentifier() }, { reader -> reader.readIdentifier() })
val sproutShapeBoxes = buffer.readList { it.readBox() }
val matureShapeBoxes = buffer.readList { it.readBox() }
val flavors = buffer.readMap({ reader -> reader.readEnumConstant(Flavor::class.java) }, { reader -> reader.readInt() })
val tintIndexes = buffer.readMap({ reader -> reader.readInt() }, { reader -> Color(reader.readInt()) })
val flowerModelIdentifier = buffer.readIdentifier()
val flowerTexture = buffer.readIdentifier()
val fruitModelIdentifier = buffer.readIdentifier()
val fruitTexture = buffer.readIdentifier()
return Berry(identifier, baseYield, emptyList(), growthTime, refreshRate, favMulchs, emptySet(), emptyList(), growthPoints, randomizedGrowthPoints, mutations, sproutShapeBoxes, matureShapeBoxes, flavors, tintIndexes, flowerModelIdentifier, flowerTexture, fruitModelIdentifier, fruitTexture, 0F)
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.berry
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.world.feature.BerryGroveFeature
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.world.biome.Biome
object BerryHelper {
private val CACHE_LOADER = object : CacheLoader<RegistryEntry<Biome>, List<BerryBlock>>() {
override fun load(key: RegistryEntry<Biome>): List<BerryBlock> {
return naturalBerries.filter { berryBlock ->
val berry = berryBlock.berry()
berry?.spawnConditions?.any { it.canSpawn(berry, key) } ?: false
}
}
}
private val naturalBerries = CobblemonBlocks.berries().values.filter {
(it.berry()?.spawnConditions?.size ?: 0) > 0
}
private val validBerryCache: LoadingCache<RegistryEntry<Biome>, List<BerryBlock>> = CacheBuilder.newBuilder()
.maximumSize(4)
.build(CACHE_LOADER)
fun getBerriesForBiome(biome: RegistryEntry<Biome>): List<BerryBlock> {
return validBerryCache.get(biome)
}
fun getNaturallyGeneratingBerries(): List<BerryBlock> {
return naturalBerries
}
}
@@ -0,0 +1,26 @@
/*
* 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.berry
/**
* Represents the different flavors associated with berries and other Pokémon consumables.
* See the [Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Flavor) article for more information.
*
* @author Licious
* @since November 28th, 2022
*/
enum class Flavor {
SPICY,
DRY,
SWEET,
BITTER,
SOUR
}
@@ -0,0 +1,61 @@
/*
* 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.berry
import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
import net.minecraft.world.WorldView
/**
* Represents a condition that impacts extra berry yield.
*
* @author Licious
* @since December 2nd, 2022
*/
interface GrowthFactor {
/**
* Validates the arguments contained in this factor, this is invoked during loading.
*
* @throws IllegalArgumentException If an argument isn't valid.
*/
fun validateArguments()
/**
* Checks if this factor should yield a bonus.
*
* @param world The [WorldView] the berry tree is in.
* @param state The [BlockState] of the berry tree.
* @param pos The [BlockPos] of the berry tree.
* @return If the bonus should activate.
*/
fun isValid(world: WorldView, state: BlockState, pos: BlockPos): Boolean
/**
* Resolves the amount of bonus berries to grow if [isValid] was true.
*
* @return The amount of extra berries to grow.
*/
fun yield(): Int
/**
* Returns the minimum possible yield for this factor.
*
* @return The minimum possible yield for this factor.
*/
fun minYield(): Int
/**
* Returns the maximum possible yield for this factor.
*
* @return The maximum possible yield for this factor.
*/
fun maxYield(): Int
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.berry
import net.minecraft.util.math.Vec3d
/**
* Represents a position in a berry tree where a berry will flower then fruit.
*
* @property position The coordinates of the berry in the tree block.
* @property rotation The degree value for the rotation each axis.
*
* @author Licious
* @since December 19th, 2022
*/
data class GrowthPoint(
val position: Vec3d,
val rotation: Vec3d
)
@@ -0,0 +1,35 @@
/*
* 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.berry.adapter
import com.cobblemon.mod.common.api.berry.GrowthFactor
import com.cobblemon.mod.common.util.adapters.CobblemonGrowthFactorAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import net.minecraft.util.Identifier
import kotlin.reflect.KClass
/**
* A type adapter for [GrowthFactor]s.
* For the default implementation see [CobblemonGrowthFactorAdapter].
*
* @author Licious
* @since December 2nd, 2022
*/
interface GrowthFactorAdapter : JsonDeserializer<GrowthFactor>, JsonSerializer<GrowthFactor> {
/**
* Register a [GrowthFactor] to be used by this adapter.
*
* @param type The [KClass] of the [GrowthFactor].
* @param identifier The expected [Identifier] in the parsed JSON.
*/
fun register(type: KClass<out GrowthFactor>, identifier: Identifier)
}
@@ -0,0 +1,34 @@
/*
* 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.berry.adapter
import com.cobblemon.mod.common.api.berry.GrowthFactor
import com.cobblemon.mod.common.api.berry.spawncondition.BerrySpawnCondition
import com.cobblemon.mod.common.util.adapters.CobblemonBerrySpawnConditionAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import net.minecraft.util.Identifier
import kotlin.reflect.KClass
/**
* A type adapter for [GrowthFactor]s.
* For the default implementation see [CobblemonBerrySpawnConditionAdapter].
*
* @author Apion
* @since August 14, 2023
*/
interface SpawnConditionAdapter : JsonDeserializer<BerrySpawnCondition>, JsonSerializer<BerrySpawnCondition> {
/**
* Register a [BerrySpawnCondition] to be used by this adapter.
*
* @param type The [KClass] of the [BerrySpawnCondition].
* @param identifier The expected [Identifier] in the parsed JSON.
*/
fun register(type: KClass<out BerrySpawnCondition>, identifier: Identifier)
}
@@ -0,0 +1,30 @@
/*
* 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.berry.spawncondition
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.util.math.random.Random
import net.minecraft.world.biome.Biome
class AllBiomeCondition(val minGroveSize: Int, val maxGroveSize: Int) : BerrySpawnCondition{
override fun canSpawn(berry: Berry, biome: RegistryEntry<Biome>): Boolean {
return true
}
override fun getGroveSize(random: Random): Int {
return random.nextBetween(minGroveSize, maxGroveSize)
}
companion object {
val ID = cobblemonResource("all_biome")
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.berry.spawncondition
import com.cobblemon.mod.common.api.berry.Berry
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.util.math.random.Random
import net.minecraft.world.biome.Biome
interface BerrySpawnCondition {
fun getGroveSize(random: Random): Int
fun canSpawn(berry: Berry, biome: RegistryEntry<Biome>): Boolean
}
@@ -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.api.berry.spawncondition
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.util.math.random.Random
import net.minecraft.world.biome.Biome
import kotlin.math.min
class PreferredBiomeCondition(val minGroveSize: Int, val maxGroveSize: Int) : BerrySpawnCondition{
override fun getGroveSize(random: Random): Int {
return random.nextBetween(minGroveSize, maxGroveSize)
}
override fun canSpawn(berry: Berry, biome: RegistryEntry<Biome>): Boolean {
val preferredBiomeTags = berry.preferredBiomeTags
return preferredBiomeTags.any {biome.isIn(it)}
}
companion object {
val ID = cobblemonResource("preferred_biome")
}
}
@@ -13,8 +13,10 @@ import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.lang
import java.util.UUID
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.MutableText
/**
* Used for opening a party select screen for players which routes to move selection when they choose a Pokémon.
@@ -30,6 +32,7 @@ object PartyMoveSelectCallbacks {
@JvmOverloads
fun create(
player: ServerPlayerEntity,
partyTitle: MutableText = lang("ui.party"),
pokemon: List<Pair<PartySelectPokemonDTO, List<MoveSelectDTO>>>,
cancel: (ServerPlayerEntity) -> Unit = {},
handler: (ServerPlayerEntity, pokemonIndex: Int, PartySelectPokemonDTO, moveIndex: Int, MoveSelectDTO) -> Unit
@@ -40,12 +43,13 @@ object PartyMoveSelectCallbacks {
handler = handler
)
callbacks[player.uuid] = callback
player.sendPacket(OpenPartyMoveCallbackPacket(callback.uuid, callback.pokemon))
player.sendPacket(OpenPartyMoveCallbackPacket(callback.uuid, partyTitle, callback.pokemon))
}
@JvmOverloads
fun createFromPokemon(
player: ServerPlayerEntity,
partyTitle: MutableText = lang("ui.party"),
pokemon: List<Pokemon>,
moves: (Pokemon) -> List<Move> = { it.moveSet.getMoves() },
canSelectPokemon: (Pokemon) -> Boolean = { true },
@@ -62,6 +66,7 @@ object PartyMoveSelectCallbacks {
create(
player = player,
partyTitle = partyTitle,
pokemon = pokemonList,
cancel = cancel,
handler = { _, pkIndex, _, moveIndex, _ ->
@@ -12,10 +12,10 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonPropertyExtractor
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.lang
import java.util.UUID
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
@@ -36,7 +36,7 @@ object PartySelectCallbacks {
@JvmOverloads
fun create(
player: ServerPlayerEntity,
title: Text = "".text(),
title: Text = lang("ui.party"),
pokemon: List<PartySelectPokemonDTO>,
cancel: (ServerPlayerEntity) -> Unit = {},
handler: (ServerPlayerEntity, index: Int) -> Unit
@@ -61,12 +61,14 @@ object PartySelectCallbacks {
@JvmOverloads
fun createBattleSelect(
player: ServerPlayerEntity,
title: Text = lang("ui.party"),
pokemon: List<BattlePokemon>,
canSelect: (BattlePokemon) -> Boolean,
cancel: (ServerPlayerEntity) -> Unit = {},
handler: (BattlePokemon) -> Unit
) = create(
player = player,
title = title,
cancel = cancel,
pokemon = pokemon.map { pk -> PartySelectPokemonDTO(pk.effectedPokemon).also { it.enabled = canSelect(pk) } }
) { _, index -> handler(pokemon[index]) }
@@ -74,12 +76,14 @@ object PartySelectCallbacks {
@JvmOverloads
fun createFromPokemon(
player: ServerPlayerEntity,
title: Text = lang("ui.party"),
pokemon: List<Pokemon>,
canSelect: (Pokemon) -> Boolean,
cancel: (ServerPlayerEntity) -> Unit = {},
handler: (Pokemon) -> Unit
) = create(
player = player,
title = title,
cancel = cancel,
pokemon = pokemon.map { pk -> PartySelectPokemonDTO(pk).also { it.enabled = canSelect(pk) } }
) { _, index -> handler(pokemon[index]) }
@@ -118,7 +122,8 @@ class PartySelectCallback(
open class PartySelectPokemonDTO(
val pokemonProperties: PokemonProperties,
val heldItem: ItemStack = ItemStack.EMPTY,
var hpRatio: Float = 1F,
var currentHealth: Int,
var maxHealth: Int,
var enabled: Boolean
) {
@JvmOverloads
@@ -132,21 +137,24 @@ open class PartySelectPokemonDTO(
PokemonPropertyExtractor.STATUS
),
heldItem = pokemon.heldItemNoCopy(),
hpRatio = pokemon.currentHealth / pokemon.hp.toFloat(),
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.hp,
enabled = enabled
)
constructor(buffer: PacketByteBuf): this(
pokemonProperties = PokemonProperties().loadFromNBT(buffer.readNbt() as NbtCompound),
heldItem = buffer.readItemStack(),
hpRatio = buffer.readFloat(),
currentHealth = buffer.readInt(),
maxHealth = buffer.readInt(),
enabled = buffer.readBoolean()
)
fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeNbt(pokemonProperties.saveToNBT())
buffer.writeItemStack(heldItem)
buffer.writeFloat(hpRatio)
buffer.writeInt(currentHealth)
buffer.writeInt(maxHealth)
buffer.writeBoolean(enabled)
}
}
@@ -13,6 +13,8 @@ import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPreEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.berry.*
import com.cobblemon.mod.common.api.events.battles.*
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntityLoadEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveEvent
@@ -20,6 +22,8 @@ import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveToWorldEvent
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.events.farming.ApricornHarvestEvent
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent
import com.cobblemon.mod.common.api.events.pokeball.PokeBallCaptureCalculatedEvent
import com.cobblemon.mod.common.api.events.pokeball.PokemonCatchRateEvent
import com.cobblemon.mod.common.api.events.pokeball.ThrownPokeballHitEvent
import com.cobblemon.mod.common.api.events.pokemon.*
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionAcceptedEvent
@@ -59,6 +63,10 @@ object CobblemonEvents {
@JvmField
val THROWN_POKEBALL_HIT = CancelableObservable<ThrownPokeballHitEvent>()
@JvmField
val POKEMON_CATCH_RATE = EventObservable<PokemonCatchRateEvent>()
@JvmField
val POKE_BALL_CAPTURE_CALCULATED = EventObservable<PokeBallCaptureCalculatedEvent>()
@JvmField
val POKEMON_CAPTURED = EventObservable<PokemonCapturedEvent>()
// @JvmField
// val EGG_HATCH = EventObservable<HatchEggEvent>()
@@ -120,6 +128,15 @@ object CobblemonEvents {
@JvmField
val APRICORN_HARVESTED = EventObservable<ApricornHarvestEvent>()
// Berries
@JvmField
val BERRY_HARVEST = EventObservable<BerryHarvestEvent>()
@JvmField
val BERRY_MUTATION_OFFER = EventObservable<BerryMutationOfferEvent>()
@JvmField
val BERRY_MUTATION_RESULT = EventObservable<BerryMutationResultEvent>()
@JvmField
val BERRY_YIELD = EventObservable<BerryYieldCalculationEvent>()
@JvmField
val LEFTOVERS_CREATED = CancelableObservable<LeftoversCreatedEvent>()
@JvmField
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.berry
import com.cobblemon.mod.common.api.berry.Berry
/**
* The base of all Berry related events.
*
* @author Licious
* @since November 28th, 2022
*/
interface BerryEvent {
/**
* The [Berry] related to the event trigger
*/
val berry: Berry
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.berry
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
/**
* An event fired when [BerryBlockEntity.harvest] is invoked.
*
* @property berry The [Berry] the tree is attached to.
* @property player The [ServerPlayerEntity] harvesting the tree.
* @property world The [World] the tree is in.
* @property pos The [BlockPos] of the tree.
* @property state The [BlockState] of the tree.
* @property blockEntity The backing [BerryBlockEntity]-
* @property drops A collection of [ItemStack]s produced by this harvest.
*
* @author Licious
* @since November 28th, 2022
*/
data class BerryHarvestEvent(
override val berry: Berry,
val player: ServerPlayerEntity,
val world: World,
val pos: BlockPos,
val state: BlockState,
val blockEntity: BerryBlockEntity,
val drops: MutableList<ItemStack>
) : BerryEvent
@@ -0,0 +1,35 @@
/*
* 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.events.berry
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.block.BerryBlock
import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
/**
* The event fired when [BerryBlock.grow] is invoked and the tree passes to [BerryBlock.MATURE_AGE].
* This allows you to modify the possible mutations for the tree, for the event that decides which of these mutations is used see [BerryMutationResultEvent].
*
* @property world The [World] the berry tree is in.
* @property state The [BlockState] of the berry tree.
* @property pos The [BlockPos] of the berry tree.
* @property mutations The possible [Berry] mutations.
*
* @author Licious
* @since January 19th, 2022
*/
data class BerryMutationOfferEvent(
override val berry: Berry,
val world: World,
val state: BlockState,
val pos: BlockPos,
val mutations: MutableSet<Berry>
) : BerryEvent
@@ -0,0 +1,37 @@
/*
* 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.events.berry
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.block.BerryBlock
import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
/**
* The event fired when [BerryBlock.grow] is invoked and the tree passes to [BerryBlock.MATURE_AGE].
* This allows you to modify the possible mutations for the tree, for the event that decides which of these mutations is used see [BerryMutationResultEvent].
*
* @property world The [World] the berry tree is in.
* @property state The [BlockState] of the berry tree.
* @property pos The [BlockPos] of the berry tree.
* @property mutations The possible [Berry] mutations.
* @property pickedMutation The [Berry] chosen to mutate into, if null the mutation will not occur.
*
* @author Licious
* @since January 19th, 2022
*/
data class BerryMutationResultEvent(
override val berry: Berry,
val world: World,
val state: BlockState,
val pos: BlockPos,
val mutations: Set<Berry>,
var pickedMutation: Berry?
) : BerryEvent
@@ -0,0 +1,58 @@
/*
* 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.events.berry
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.api.berry.GrowthFactor
import net.minecraft.block.BlockState
import net.minecraft.entity.LivingEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
/**
* The event fired when [Berry.calculateYield] is invoked.
*
* @property world The [World] the berry tree is in.
* @property state The [BlockState] of the berry tree.
* @property pos The [BlockPos] of the berry tree.
* @property placer The [LivingEntity] triggering the calculation.
* @property yield The current yield of berries.
* @property passedGrowthFactors The [Berry.growthFactors] where [GrowthFactor.isValid] was true.
*
* @author Licious
* @since November 28th, 2022
*/
class BerryYieldCalculationEvent(
override val berry: Berry,
val world: World,
val state: BlockState,
val pos: BlockPos,
val placer: LivingEntity?,
yield: Int,
val passedGrowthFactors: Collection<GrowthFactor>
) : BerryEvent {
/**
* The amount of berries this tree will yield.
*
* @throws IllegalArgumentException If a new value exceeds [Berry.maxYield] or be lesser than 0.
*/
var yield: Int = yield
set(value) {
val max = this.berry.maxYield()
if (value > max) {
throw IllegalArgumentException("Cannot set the berry yield for ${this.berry.identifier} above $max")
}
if (value < 0) {
throw IllegalArgumentException("A berry tree cannot yield a negative amount of berries")
}
field = value
}
}
@@ -0,0 +1,35 @@
/*
* 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.events.pokeball
import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.LivingEntity
import net.minecraft.server.network.ServerPlayerEntity
/**
* Event fired when a Poké Ball has completed its capture calculation and is about to begin shaking
* or breaking free. The result of the capture can be changed by replacing [captureResult].
*
* @author Hiroku
* @since August 20th, 2023
*/
class PokeBallCaptureCalculatedEvent(
val thrower: LivingEntity,
val pokemonEntity: PokemonEntity,
val pokeBallEntity: EmptyPokeBallEntity,
var captureResult: CaptureContext
) {
fun ifPlayer(action: PokeBallCaptureCalculatedEvent.(player: ServerPlayerEntity) -> Unit) {
if (thrower is ServerPlayerEntity) {
action(this, thrower)
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.events.pokeball
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.LivingEntity
/**
* Event fired when a capture calculator is accessing a Pokémon's catch rate. The [catchRate] property can
* be changed to adjust the rate.
*
* If you want to prevent a Pokémon from being caught, you should be handling the [PokeBallCaptureCalculatedEvent]
* event instead.
*
* @author Hiroku
* @since August 20th, 2023
*/
class PokemonCatchRateEvent(
val thrower: LivingEntity,
val pokeBallEntity: EmptyPokeBallEntity,
val pokemonEntity: PokemonEntity,
var catchRate: Float
)
@@ -8,10 +8,12 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.network.ServerPlayerEntity
data class PokemonCapturedEvent (
val pokemon: Pokemon,
val player: ServerPlayerEntity
val player: ServerPlayerEntity,
val pokeBallEntity: EmptyPokeBallEntity
)
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.interaction
import net.minecraft.entity.Entity
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
/**
* An interaction that will affect an [Entity].
* These need to be triggered.
*
* @param T The type of the [Entity] this interaction will affect.
*/
interface EntityInteraction<T : Entity> {
/**
* Fired when a [ServerPlayerEntity] interacts with the target entity.
*
* @param player The [ServerPlayerEntity] interacting with the [entity].
* @param entity The [Entity] of type [T] being interacted.
* @param stack The [ItemStack] used in this interaction.
* @return true if the interaction was successful and no further interactions should be processed
*/
fun onInteraction(player: ServerPlayerEntity, entity: T, stack: ItemStack): Boolean
/**
* Decreases the stack size by a given amount.
* The stack size should be validated beforehand.
* If the [player] is in creative mode the decrement won't be performed.
*
* @param player The [ServerPlayerEntity] that caused the interaction, this is used to check for creative mode.
* @param stack The [ItemStack] being mutated.
* @param amount The amount to deduct from the stack, default is 1.
*/
fun consumeItem(player: ServerPlayerEntity, stack: ItemStack, amount: Int = 1) {
if (!player.isCreative) {
stack.decrement(amount)
}
}
}
@@ -0,0 +1,29 @@
/*
* 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.interaction
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
import net.minecraft.util.Identifier
/**
* A type adapter for [EntityInteraction]s.
* For the default implementation with Pokémon entities see [CobblemonPokemonEntityInteractionTypeAdapter].
*
* @param T The type of [EntityInteraction].
*
* @author Licious
* @since November 30th, 2022
*/
interface EntityInteractionTypeAdapter<T : EntityInteraction<*>> : JsonDeserializer<T>, JsonSerializer<T> {
fun registerInteraction(identifier: Identifier, type: KClass<out T>)
}
@@ -6,23 +6,26 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item.interactive
package com.cobblemon.mod.common.api.interaction
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.interactive.PokemonInteractiveItem.Ownership
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundEvent
/**
* An [InteractiveItem] targeting [PokemonEntity]s.
*
* @param accepted The [Ownership] variants for this interaction to fire [PokemonInteractiveItem.processInteraction].
* An [EntityInteraction] targeting [PokemonEntity]s.
* These need to be manually triggered if not implemented by an item.
*/
interface PokemonInteractiveItem : InteractiveItem<PokemonEntity> {
interface PokemonEntityInteraction : EntityInteraction<PokemonEntity> {
/**
* The accepted [Ownership] for the Pokémon entity in order for the interaction to fire.
*/
val accepted: Set<Ownership>
fun getSound(): SoundEvent? = CobblemonSounds.ITEM_USE
val sound: SoundEvent?
get() = CobblemonSounds.ITEM_USE
override fun onInteraction(player: ServerPlayerEntity, entity: PokemonEntity, stack: ItemStack): Boolean {
val pokemon = entity.pokemon
@@ -40,30 +43,15 @@ interface PokemonInteractiveItem : InteractiveItem<PokemonEntity> {
}
/**
* Fired after [InteractiveItem.onInteraction] the [Ownership] is checked if contained in [accepted].
* Fired after [EntityInteraction.onInteraction] the [Ownership] is checked if contained in [accepted].
*
* @param player The [ServerPlayerEntity] interacting with the [entity].
* @param entity The [PokemonEntity] being interacted with.
* @param stack The [ItemStack] used in this interaction. [ItemStack.getItem] will always be of the same type as this [InteractiveItem].
* @param stack The [ItemStack] used in this interaction.
* @return true if the interaction was successful and no further interactions should be processed.
*/
fun processInteraction(player: ServerPlayerEntity, entity: PokemonEntity, stack: ItemStack): Boolean
/**
* Decreases the stack size by a given amount.
* The stack size should be validated beforehand.
* If the [player] is in creative mode the decrement won't be performed.
*
* @param player The [ServerPlayerEntity] that caused the interaction, this is used to check for creative mode.
* @param stack The [ItemStack] being mutated.
* @param amount The amount to deduct from the stack, default is 1.
*/
fun consumeItem(player: ServerPlayerEntity, stack: ItemStack, amount: Int = 1) {
if (!player.isCreative) {
stack.decrement(amount)
}
}
/**
* Represents the ownership status of a Pokemon relative to a Player.
*
@@ -106,9 +106,14 @@ interface PokemonSelectingItem {
}
fun interactGeneral(player: ServerPlayerEntity, stack: ItemStack): TypedActionResult<ItemStack> {
val party = player.party().toList()
if (party.isEmpty()) {
return TypedActionResult.fail(stack)
}
PartySelectCallbacks.createFromPokemon(
player = player,
pokemon = player.party().toList(),
pokemon = party,
canSelect = ::canUseOnPokemon,
handler = { pk ->
if (stack.isHeld(player)) {
@@ -10,21 +10,22 @@ package com.cobblemon.mod.common.api.mulch
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.item.MulchItem
import net.minecraft.util.StringIdentifiable
import java.time.Duration
/**
* Represents the different types of Mulch implemented in the mod.
*
*/
enum class MulchVariant {
enum class MulchVariant(val isBiomeMulch: Boolean = true, val duration: Int = 0) : StringIdentifiable {
COARSE,
GROWTH,
GROWTH(false, 3),
HUMID,
LOAMY,
PEAT,
RICH,
RICH(false, 3),
SANDY,
SURPRISE;
SURPRISE(false, 3);
/**
* Resolves the item version of the Mulch.
@@ -42,4 +43,7 @@ enum class MulchVariant {
SURPRISE -> CobblemonItems.SURPRISE_MULCH
}
}
override fun asString(): String {
return name.lowercase()
}
}
@@ -8,9 +8,12 @@
package com.cobblemon.mod.common.api.pokeball.catching.calculators
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokeball.PokemonCatchRateEvent
import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.entity.LivingEntity
/**
@@ -24,7 +27,6 @@ import net.minecraft.entity.LivingEntity
* @since November 30, 2021
*/
interface CaptureCalculator {
/**
* The literal ID of this calculator.
* Used when registering the calculator to the registry in order to be used by the game rule.
@@ -37,10 +39,22 @@ interface CaptureCalculator {
* Processes a capture attempt with the given params.
*
* @param thrower The [LivingEntity] that threw the [PokeBall].
* @param pokeBall The [PokeBall] used.
* @param target The target [Pokemon] attempting to be captured.
* @return
* @param pokeBallEntity The [EmptyPokeBallEntity] used.
* @param target The target [PokemonEntity] attempting to be captured.
* @return a [CaptureContext] that is the result of the capture attempt.
*/
fun processCapture(thrower: LivingEntity, pokeBall: PokeBall, target: Pokemon) : CaptureContext
fun processCapture(thrower: LivingEntity, pokeBallEntity: EmptyPokeBallEntity, target: PokemonEntity) : CaptureContext
fun getCatchRate(thrower: LivingEntity, pokeBallEntity: EmptyPokeBallEntity, target: PokemonEntity, catchRate: Float): Float {
val event = PokemonCatchRateEvent(
thrower = thrower,
pokemonEntity = target,
pokeBallEntity = pokeBallEntity,
catchRate = catchRate
)
CobblemonEvents.POKEMON_CATCH_RATE.post(event)
return event.catchRate
}
}
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.api.item.Flavor
import com.cobblemon.mod.common.api.berry.Flavor
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.util.cobblemonResource
@@ -287,12 +287,12 @@ open class PokemonProperties {
customProperties.forEach { it.apply(pokemon) }
ivs?.let { ivs ->
ivs.forEach { stat ->
pokemon.ivs[stat.key] = stat.value
pokemon.setIV(stat.key, stat.value)
}
}
evs?.let { evs ->
evs.forEach { stat ->
pokemon.evs[stat.key] = stat.value
pokemon.setEV(stat.key, stat.value)
}
}
}
@@ -322,12 +322,12 @@ open class PokemonProperties {
customProperties.forEach { it.apply(pokemonEntity) }
ivs?.let { ivs ->
ivs.forEach { stat ->
pokemonEntity.pokemon.ivs[stat.key] = stat.value
pokemonEntity.pokemon.setIV(stat.key, stat.value)
}
}
evs?.let { evs ->
evs.forEach { stat ->
pokemonEntity.pokemon.evs[stat.key] = stat.value
pokemonEntity.pokemon.setEV(stat.key, stat.value)
}
}
}
@@ -238,7 +238,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
val heightm = form?.height ?: species.height
val weightkg = form?.weight ?: species.weight
// This is ugly, but we already have it hardcoded in the mod anyway
val maxHp = if (species.showdownId() == "shedinja") 1 else null
val maxHP = if (species.showdownId() == "shedinja") 1 else null
val canGigantamax: String? = if (form?.gigantamaxMove != null) form.gigantamaxMove.name else null
val cannotDynamax = form?.dynamaxBlocked ?: species.dynamaxBlocked
// ToDo battleOnly
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.pokemon.stats
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
/**
@@ -53,9 +54,24 @@ interface EvCalculator {
object Generation8EvCalculator : EvCalculator {
private val powerItems = mapOf(
Stats.SPEED to CobblemonItemTags.POWER_ANKLET,
Stats.SPECIAL_DEFENCE to CobblemonItemTags.POWER_BAND,
Stats.DEFENCE to CobblemonItemTags.POWER_BELT,
Stats.ATTACK to CobblemonItemTags.POWER_BRACER,
Stats.SPECIAL_ATTACK to CobblemonItemTags.POWER_LENS,
Stats.HP to CobblemonItemTags.POWER_WEIGHT
)
override fun calculate(battlePokemon: BattlePokemon, opponentPokemon: BattlePokemon): Map<Stat, Int> {
// ToDo Once held items and EV related items are implemented update this
return opponentPokemon.originalPokemon.form.evYield
val heldItem = battlePokemon.effectedPokemon.heldItemNoCopy()
val evYield = mutableMapOf<Stat, Int>()
for ((stat, value) in opponentPokemon.originalPokemon.form.evYield) {
val boost = if (!heldItem.isEmpty && heldItem.isIn(powerItems[stat])) 8 else 0
evYield[stat] = evYield.getOrDefault(stat, 0) + value + boost
}
return evYield
}
}
@@ -1,20 +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.spawning.influence
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules.DO_POKEMON_SPAWNING
open class GameRuleInfluence : SpawningInfluence {
override fun affectSpawnable(detail: SpawnDetail, ctx: SpawningContext): Boolean {
return detail !is PokemonSpawnDetail || ctx.world.gameRules.getBoolean(DO_POKEMON_SPAWNING)
}
}
@@ -30,6 +30,7 @@ import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import net.minecraft.world.chunk.Chunk
import net.minecraft.world.chunk.ChunkStatus
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules.DO_POKEMON_SPAWNING
/**
* A type of [TickingSpawner] that operates within some area. When this spawner type
@@ -60,6 +61,9 @@ abstract class AreaSpawner(
override fun run(cause: SpawnCause): Pair<SpawningContext, SpawnDetail>? {
val area = getArea(cause)
if (area?.world?.gameRules?.getBoolean(DO_POKEMON_SPAWNING) == false) {
return null
}
val constrainedArea = if (area != null) constrainArea(area) else null
if (constrainedArea != null) {
val numberNearby = constrainedArea.world.getEntitiesByClass(
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnerManager
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.GameRuleInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import net.minecraft.server.network.ServerPlayerEntity
@@ -26,7 +25,7 @@ import net.minecraft.server.network.ServerPlayerEntity
*/
object PlayerSpawnerFactory {
var spawns: SpawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
var influenceBuilders = mutableListOf<(player: ServerPlayerEntity) -> SpawningInfluence?>({ PlayerLevelRangeInfluence(it, variation = 5) }, { GameRuleInfluence() })
var influenceBuilders = mutableListOf<(player: ServerPlayerEntity) -> SpawningInfluence?>({ PlayerLevelRangeInfluence(it, variation = 5) })
fun create(spawnerManager: SpawnerManager, player: ServerPlayerEntity): PlayerSpawner {
val influences = influenceBuilders.mapNotNull { it(player) }
@@ -34,4 +33,4 @@ object PlayerSpawnerFactory {
it.influences.addAll(influences)
}
}
}
}
@@ -121,8 +121,12 @@ class ReforgedConversion(val base: Path) : CobblemonConverter<NbtCompound> {
evs[Stats.SPECIAL_DEFENCE] = nbt.getInt("EVSpecialDefense")
evs[Stats.SPEED] = nbt.getInt("EVSpeed")
result.evs = evs
result.ivs = ivs
ivs.forEach { stat ->
result.setIV(stat.key, stat.value)
}
evs.forEach { stat ->
result.setEV(stat.key, stat.value)
}
for (move in nbt.getList("Moveset", 10)) {
val compound = move as NbtCompound
@@ -26,6 +26,7 @@ object CobblemonBlockTags {
@JvmField val APRICORN_LOGS = createTag("apricorn_logs")
@JvmField val APRICORN_SAPLINGS = createTag("apricorn_saplings")
@JvmField val APRICORNS = createTag("apricorns")
@JvmField val BERRY_SOIL = createTag("berry_soil")
@JvmField val CROPS = createTag("crops")
@JvmField val DRIPSTONE_GROWABLE = createTag("dripstone_growable")
@JvmField val DRIPSTONE_REPLACEABLES = createTag("dripstone_replaceables")
@@ -38,4 +39,4 @@ object CobblemonBlockTags {
private fun createTag(name: String) = TagKey.of(RegistryKeys.BLOCK, cobblemonResource(name))
}
}
@@ -42,6 +42,18 @@ object CobblemonItemTags {
@JvmField
val EVERSTONE = create("held/everstone")
@JvmField
val POWER_ANKLET = create("held/power_anklet")
@JvmField
val POWER_BAND = create("held/power_band")
@JvmField
val POWER_BELT = create("held/power_belt")
@JvmField
val POWER_BRACER = create("held/power_bracer")
@JvmField
val POWER_LENS = create("held/power_lens")
@JvmField
val POWER_WEIGHT = create("held/power_weight")
@JvmField
val EVOLUTION_STONES = create("evolution_stones")
@JvmField
val EVOLUTION_ITEMS = create("evolution_items")
@@ -21,24 +21,27 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import java.util.Optional
import java.util.UUID
import net.minecraft.entity.Entity
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.MutableText
import net.minecraft.text.Text
import java.util.*
import kotlin.collections.HashMap
object BattleBuilder {
@JvmOverloads
fun pvp1v1(
player1: ServerPlayerEntity,
player2: ServerPlayerEntity,
leadingPokemonPlayer1: UUID? = null,
leadingPokemonPlayer2: UUID? = null,
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
cloneParties: Boolean = false,
healFirst: Boolean = false,
partyAccessor: (ServerPlayerEntity) -> PartyStore = { it.party() }
): BattleStartResult {
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties, checkHealth = !healFirst)
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties, checkHealth = !healFirst)
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemonPlayer1)
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemonPlayer2)
val player1Actor = PlayerBattleActor(player1.uuid, team1)
val player2Actor = PlayerBattleActor(player2.uuid, team2)
@@ -89,6 +92,7 @@ object BattleBuilder {
* @param fleeDistance How far away the player must get to flee the Pokémon. If the value is -1, it cannot be fled.
* @param party The party of the player to use for the battle. This does not need to be their actual party. Defaults to it though.
*/
@JvmOverloads
fun pve(
player: ServerPlayerEntity,
pokemonEntity: PokemonEntity,
@@ -31,6 +31,7 @@ object BattleRegistry {
class BattleChallenge(
val challengeId: UUID,
val challengedPlayerUUID: UUID,
val selectedPokemonId: UUID,
var expiryTimeSeconds: Int = 60
) {
val challengedTime = Instant.now()
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.interpreter.*
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -678,7 +679,7 @@ object ShowdownInterpreter {
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val name = pokemon.getName()
// Move may be null as it's not always given
val moveName = message.argumentAt(2)?.let { Moves.getByName(it.replace(" ",""))?.displayName } ?: "".text()
val moveName = message.argumentAt(2)?.let { Moves.getByName(it)?.displayName } ?: Text.EMPTY
val lang = when (effectID) {
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
@@ -814,7 +815,7 @@ object ShowdownInterpreter {
private fun handleRechargeInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>){
battle.dispatchWaiting(2F){
val pokemon = message.getBattlePokemon(0, battle) ?: return@dispatchWaiting
battle.broadcastChatMessage(battleLang("recharge", pokemon.getName() ?: ""))
battle.broadcastChatMessage(battleLang("recharge", pokemon.getName()))
battle.minorBattleActions[pokemon.uuid] = message
}
}
@@ -949,7 +950,7 @@ object ShowdownInterpreter {
}
// Includes revealed move
"forewarn" -> {
val moveName = message.argumentAt(2)?.let { Moves.getByName(it.replace(" ",""))?.displayName } ?: "".text()
val moveName = message.argumentAt(2)?.let { Moves.getByName(it)?.displayName } ?: Text.EMPTY
battleLang("activate.forewarn", sourceName, moveName)
}
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, message.effectAt(1)!!.typelessData)
@@ -1224,11 +1225,12 @@ object ShowdownInterpreter {
val request = BattleRegistry.gson.fromJson(message.rawMessage.split("|request|")[1], ShowdownActionRequest::class.java)
if (battle.started) {
battle.dispatchGo {
// This request won't be acted on until the start of next turn
battleActor.sendUpdate(BattleQueueRequestPacket(request))
battleActor.request = request
battleActor.responses.clear()
// We need to send this out because 'upkeep' isn't received until the request is handled since the turn won't swap
if (request.forceSwitch.withIndex().any { it.value && battleActor.activePokemon.getOrNull(it.index)?.isGone() == false }) {
if (request.forceSwitch.contains(true)) {
battle.doWhenClear {
battleActor.mustChoose = true
battleActor.sendUpdate(BattleMakeChoicePacket())
@@ -1513,6 +1515,9 @@ object ShowdownInterpreter {
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.remove(item.id, BattleContext.Type.ITEM)
if (message.hasOptionalArgument("eat")) {
battlePokemon.entity?.playSound(CobblemonSounds.BERRY_EAT, 1F, 1F)
}
}
}
@@ -1613,7 +1618,7 @@ object ShowdownInterpreter {
/**
* Format:
* |-clearallboost
* |-clearallboost|
*
* Clears all boosts from all Pokémon on both sides.
*/
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles.pokemon
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
@@ -89,4 +90,8 @@ open class BattlePokemon(
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
fun canBeSentOut() = !isSentOut() && !willBeSwitchedIn && health > 0
fun writeVariables(struct: VariableStruct) {
effectedPokemon.writeVariables(struct)
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.berry
import com.cobblemon.mod.common.api.berry.GrowthFactor
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.block.BlockState
import net.minecraft.predicate.NumberRange
import net.minecraft.util.math.BlockPos
import net.minecraft.world.WorldView
import net.minecraft.world.biome.Biome
/**
* A [GrowthFactor] that is active based on the downfall property of a [Biome].
*
* @property range The range of possible [Biome.getDownfall] values.
* @property bonusYield The range of possible bonus values.
*
* @author Licious
* @since December 2nd, 2022
*/
class BiomeDownfallGrowthFactor(
val range: NumberRange.FloatRange,
val bonusYield: IntRange
) : GrowthFactor {
override fun validateArguments() {
if (this.bonusYield.first < 0 || this.bonusYield.last < 0) {
throw IllegalArgumentException("$ID bonusYield must be a positive range")
}
}
override fun isValid(world: WorldView, state: BlockState, pos: BlockPos): Boolean {
val biome = world.getBiome(pos).value()
return this.range.test(biome.weather.downfall.toDouble())
}
override fun yield() = this.bonusYield.random()
override fun minYield() = this.bonusYield.first
override fun maxYield() = this.bonusYield.last
companion object {
val ID = cobblemonResource("biome_downfall")
}
}
@@ -0,0 +1,52 @@
/*
* 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.berry
import com.cobblemon.mod.common.api.berry.GrowthFactor
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.registry.BiomeIdentifierCondition
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.block.BlockState
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.tag.TagKey
import net.minecraft.util.math.BlockPos
import net.minecraft.world.WorldView
import net.minecraft.world.biome.Biome
class PreferredBiomeGrowthFactor(
val bonusYield: IntRange
) : GrowthFactor{
override fun validateArguments() {
if (this.bonusYield.first < 0 || this.bonusYield.last < 0) {
throw IllegalArgumentException("$ID bonusYield must be a positive range")
}
}
override fun isValid(world: WorldView, state: BlockState, pos: BlockPos): Boolean {
val biome = world.getBiome(pos)
val block = state.block as BerryBlock
val biomeTags = block.berry()?.preferredBiomeTags ?: emptyList()
return biomeTags.any { biome.isIn(it) }
}
override fun yield() = this.bonusYield.random()
override fun minYield() = this.bonusYield.first
override fun maxYield() = this.bonusYield.last
companion object {
val ID = cobblemonResource("preferred_biome")
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.berry
import com.cobblemon.mod.common.api.berry.GrowthFactor
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.block.BlockState
import net.minecraft.predicate.NumberRange
import net.minecraft.util.math.BlockPos
import net.minecraft.world.WorldView
import net.minecraft.world.biome.Biome
/**
* A [GrowthFactor] that is active based on the temperature property of a [Biome].
*
* @property range The range of possible [Biome.getTemperature] values.
* @property bonusYield The range of possible bonus values.
*
* @author Licious
* @since December 2nd, 2022
*/
class BiomeTemperatureGrowthFactor(
val range: NumberRange.FloatRange,
val bonusYield: IntRange
) : GrowthFactor {
override fun validateArguments() {
if (this.bonusYield.first < 0 || this.bonusYield.last < 0) {
throw IllegalArgumentException("$ID bonusYield must be a positive range")
}
}
override fun isValid(world: WorldView, state: BlockState, pos: BlockPos): Boolean {
val biome = world.getBiome(pos).value()
return this.range.test(biome.temperature.toDouble())
}
override fun yield() = this.bonusYield.random()
override fun minYield() = this.bonusYield.first
override fun maxYield() = this.bonusYield.last
companion object {
val ID = cobblemonResource("biome_temperature")
}
}
@@ -0,0 +1,215 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.berry.BerryMutationOfferEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationResultEvent
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.mulch.Mulchable
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.item.ShovelItem
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
import net.minecraft.state.StateManager
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.state.property.IntProperty
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.random.Random
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldView
class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) : BlockWithEntity(settings), Fertilizable, Mulchable {
private val lookupDirections = setOf(Direction.NORTH, Direction.EAST, Direction.WEST, Direction.SOUTH)
/**
* Returns the [Berry] behind this block,
* This will be null if it doesn't exist in the [Berries] registry.
*
* @return The [Berry] if existing.
*/
fun berry(): Berry? = Berries.getByIdentifier(this.berryIdentifier)
override fun createBlockEntity(pos: BlockPos, state: BlockState) = BerryBlockEntity(pos, state, berryIdentifier)
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = !this.isMaxAge(state)
override fun canGrow(world: World, random: Random, pos: BlockPos, state: BlockState) = !this.isMaxAge(state)
override fun <T : BlockEntity> getTicker(world: World, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = checkType(blockWithEntityType, CobblemonBlockEntities.BERRY, BerryBlockEntity.TICKER)
init {
defaultState = this.stateManager.defaultState
.with(WAS_GENERATED, false)
.with(AGE, 0)
}
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
val curAge = state.get(AGE)
val newAge = curAge + 1
if (newAge > FRUIT_AGE) return
var newState = state.with(AGE, newAge)
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if (curAge == MATURE_AGE) {
determineMutation(world, random, pos, state)
}
world.setBlockState(pos, newState, Block.NOTIFY_LISTENERS)
treeEntity.goToNextStageTimer(FRUIT_AGE - curAge)
treeEntity.markDirty()
}
fun determineMutation(world: World, random: Random, pos: BlockPos, state: BlockState) {
val mutations = hashSetOf<Berry>()
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
for (direction in this.lookupDirections) {
val redirectedPos = pos.add(direction.vector)
val redirectedState = world.getBlockState(redirectedPos)
val berryBlock = redirectedState.block as? BerryBlock ?: continue
val berry = berryBlock.berry() ?: continue
val mutation = this.berry()?.mutationWith(berry) ?: continue
mutations += mutation
}
this.berry()?.let { berry ->
CobblemonEvents.BERRY_MUTATION_OFFER.post(BerryMutationOfferEvent(berry, world, state, pos, mutations)) { berryMutationOffer ->
if (berryMutationOffer.mutations.isNotEmpty()) {
var mutateChance = 125
if (treeEntity.mulchVariant == MulchVariant.SURPRISE) {
mutateChance *= 4
treeEntity.mulchDuration--
}
val mutation = if (random.nextInt(1000) < mutateChance) mutations.random() else null
(world.getBlockEntity(pos) as? BerryBlockEntity)?.let { blockEntity ->
CobblemonEvents.BERRY_MUTATION_RESULT.post(BerryMutationResultEvent(berry, world, state, pos, berryMutationOffer.mutations, mutation)) { berryMutationResult ->
berryMutationResult.pickedMutation?.let { mutation -> blockEntity.mutate(mutation) }
}
}
}
}
}
}
override fun canHaveMulchApplied(
world: ServerWorld,
pos: BlockPos,
state: BlockState,
variant: MulchVariant
): Boolean {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
return treeEntity.mulchVariant == null
}
override fun applyMulch(
world: ServerWorld,
random: Random,
pos: BlockPos,
state: BlockState,
variant: MulchVariant
) {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
treeEntity.mulchVariant = variant
treeEntity.mulchDuration = variant.duration
world.playSound(null, pos, CobblemonSounds.MULCH_PLACE, SoundCategory.BLOCKS)
}
@Deprecated("Deprecated in Java")
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if(player.getStackInHand(hand).item is ShovelItem && treeEntity.mulchVariant != null) {
treeEntity.mulchVariant = null
treeEntity.markDirty()
world.playSound(null, pos, CobblemonSounds.MULCH_REMOVE, SoundCategory.BLOCKS)
return ActionResult.SUCCESS
}
if (player.getStackInHand(hand).isOf(Items.BONE_MEAL)) {
return ActionResult.PASS
}
else if (this.isMaxAge(state)) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return ActionResult.PASS
blockEntity.harvest(world, state, pos, player).forEach { drop ->
Block.dropStack(world, pos, drop)
}
return ActionResult.success(world.isClient)
}
return super.onUse(state, world, pos, player, hand, hit)
}
@Deprecated("Deprecated in Java")
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
return state.get(WAS_GENERATED) || world.getBlockState(pos.down()).isIn(CobblemonBlockTags.BERRY_SOIL)
}
@Deprecated("Deprecated in Java")
override fun getStateForNeighborUpdate(state: BlockState, direction: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
return if (state.canPlaceAt(world, pos)) super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos) else Blocks.AIR.defaultState
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, itemStack: ItemStack) {
if (!world.isClient) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
blockEntity.generateGrowthPoints(world, state, pos, placer)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(AGE)
builder.add(WAS_GENERATED)
}
override fun getPickStack(world: BlockView?, pos: BlockPos?, state: BlockState?): ItemStack {
val berryItem = this.berry()?.item() ?: return ItemStack.EMPTY
return ItemStack(berryItem)
}
@Deprecated("Deprecated in Java")
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
val berry = this.berry() ?: return VoxelShapes.fullCube()
return if (state.get(AGE) >= MATURE_AGE) berry.matureShape else berry.sproutShape
}
private fun isMaxAge(state: BlockState) = state.get(AGE) == FRUIT_AGE
@Deprecated("Deprecated in Java")
override fun getRenderType(blockState: BlockState) = BlockRenderType.MODEL
companion object {
const val MATURE_AGE = 3
const val FLOWER_AGE = 4
const val FRUIT_AGE = 5
val AGE: IntProperty = IntProperty.of("age", 0, FRUIT_AGE)
val WAS_GENERATED: BooleanProperty = BooleanProperty.of("generated")
}
}
@@ -260,7 +260,7 @@ class PCBlock(properties: Settings): BlockWithEntity(properties), Waterloggable
world.playSoundServer(
position = blockPos.toVec3d(),
sound = CobblemonSounds.PC_ON,
volume = 1F,
volume = 0.5F,
pitch = 1F
)
return ActionResult.SUCCESS
@@ -246,7 +246,7 @@ class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterlogg
world.playSoundServer(
position = pos.toVec3d(),
sound = CobblemonSounds.PC_ON,
volume = 1F,
volume = 0.5F,
pitch = 1F
)
@@ -51,7 +51,7 @@ class RevivalHerbBlock(settings: Settings) : CropBlock(settings), Mulchable {
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val floor = world.getBlockState(pos.down())
// A bit of a copy pasta but we don't have access to the BlockState being attempted to be placed above on the canPlantOnTop
return (world.getBaseLightLevel(pos, 0) >= 8 || world.isSkyVisible(pos)) && ((state.get(IS_WILD) && floor.isIn(BlockTags.DIRT)) || this.canPlantOnTop(floor, world, pos))
return (world.getBlockState(pos).isIn(BlockTags.REPLACEABLE_BY_TREES)) && ((state.get(IS_WILD) && floor.isIn(BlockTags.DIRT)) || this.canPlantOnTop(floor, world, pos))
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AGE_SHAPES.getOrNull(this.getAge(state)) ?: VoxelShapes.fullCube()
@@ -0,0 +1,306 @@
/*
* 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.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.berry.Berry
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.block.BerryBlock
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtString
import net.minecraft.network.listener.ClientPlayPacketListener
import net.minecraft.network.packet.Packet
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.Identifier
import net.minecraft.util.InvalidIdentifierException
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import net.minecraft.world.event.GameEvent
class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.BERRY, pos, state) {
lateinit var berryIdentifier: Identifier
private val ticksPerMinute = 1200
//The time left for the tree to grow to stage 5
var growthTimer: Int = 72000
set(value) {
if (value < 0) {
throw IllegalArgumentException("You cannot set the growth time to less than zero")
}
if (field != value) {
this.markDirty()
}
field = value
}
//The time left for the tree to grow to the next stage
var stageTimer: Int = growthTimer / BerryBlock.MATURE_AGE
set(value) {
if (value < 0) {
throw IllegalArgumentException("You cannot set the stage growth time to less than zero")
}
if (field != value) {
this.markDirty()
}
field = value
}
private val growthPoints = arrayListOf<Identifier>()
/**
* The idea behind the growth point sequence is it's a 16-long string of
* hexadecimal numbers. They represent the order of the growth points that
* will get used by this tree. If the berry type has randomized growth points,
* this sequence gets scrambled, and then when lining up berries with their
* growth points, it will take the berry index, check the symbol at that
* position in this sequence, and use that as the growth point index from
* reference data.
*
* Kinda confusing but it works without saving an array or something. Protections
* exist for when there are fewer than 16 growth points on a tree (currently always).
*/
private var growthPointSequence = "0123456789ABCDEF"
// Just a cheat to not invoke markDirty unnecessarily
private var wasLoading = false
var mulchVariant: MulchVariant? = null
var mulchDuration = 0
set(value) {
if (value == 0 && !(mulchVariant?.isBiomeMulch ?: false)) {
mulchVariant = null
}
field = value
this.markDirty()
}
constructor(pos: BlockPos, state: BlockState, berryIdentifier: Identifier): this(pos, state) {
this.berryIdentifier = berryIdentifier
resetGrowTimers(pos, state)
if (state.get(BerryBlock.WAS_GENERATED) && state.get(BerryBlock.AGE) >= 4) {
generateSimpleYields()
}
}
fun resetGrowTimers(pos: BlockPos, state: BlockState) {
val curAge = state.get(BerryBlock.AGE)
if (curAge != 5) {
val berry = Berries.getByIdentifier(berryIdentifier)!!
var multiplier = 10
if (mulchVariant == MulchVariant.GROWTH) {
mulchDuration--
multiplier = 15
}
val upperGrowthLimit = ((if (curAge == 0) berry.growthTime.last else berry.refreshRate.last) * multiplier) / 10
val lowerGrowthLimit = ((if (curAge == 0) berry.growthTime.first else berry.refreshRate.first) * multiplier) / 10
val growthRange = lowerGrowthLimit..upperGrowthLimit
growthTimer = growthRange.random() * ticksPerMinute
goToNextStageTimer(BerryBlock.FRUIT_AGE - curAge)
}
}
fun goToNextStageTimer(stagesLeft: Int) {
val avgStageTime = growthTimer / stagesLeft
stageTimer = this.world?.random?.nextBetween((avgStageTime * 8) / 10, avgStageTime) ?:
(((Math.random() * 0.2) + 0.8) * avgStageTime).toInt()
growthTimer -= stageTimer
}
/**
* Returns the [Berry] behind this entity,
* This will be null if it doesn't exist in the [Berries] registry.
*
* @return The [Berry] if existing.
*/
fun berry() = Berries.getByIdentifier(berryIdentifier)
/**
* Generates a random amount of growth points for this tree.
*
* @param world The [World] the tree is in.
* @param state The [BlockState] of the tree.
* @param pos The [BlockPos] of the tree.
* @param placer The [LivingEntity] tending to the tree if any.
*/
fun generateGrowthPoints(world: World, state: BlockState, pos: BlockPos, placer: LivingEntity?) {
val berry = this.berry() ?: return
val yield = berry.calculateYield(world, state, pos, placer)
this.growthPoints.clear()
repeat(yield) {
this.growthPoints += berry.identifier
}
this.growthPointSequence = this.growthPointSequence.toCharArray().also { if (berry.randomizedGrowthPoints) it.shuffle() }.concatToString()
this.markDirty()
}
//Used for naturally spawning berries
fun generateSimpleYields() {
val numBerries = berry()?.baseYield?.random() ?: return
repeat(numBerries) {
growthPoints.add(berryIdentifier)
}
}
/**
* Calculates the berry produce in [ItemStack] form.
*
* @param world The [World] the tree is in.
* @param state The [BlockState] of the tree.
* @param pos The [BlockPos] of the tree.
* @param player The [PlayerEntity] harvesting the tree.
* @return The resulting [ItemStack]s to be dropped.
*/
fun harvest(world: World, state: BlockState, pos: BlockPos, player: PlayerEntity): Collection<ItemStack> {
val drops = arrayListOf<ItemStack>()
val unique = this.growthPoints.groupingBy { it }.eachCount()
unique.forEach { (identifier, amount) ->
val berryItem = Berries.getByIdentifier(identifier)?.item()
if (berryItem != null) {
var remain = amount
while (remain > 0) {
val count = remain.coerceAtMost(berryItem.maxCount)
drops += ItemStack(berryItem, count)
remain -= count
}
}
}
this.berry()?.let { berry ->
if (player is ServerPlayerEntity) {
CobblemonEvents.BERRY_HARVEST.post(BerryHarvestEvent(berry, player, world, pos, state, this, drops))
}
}
refresh(world, pos, state, player)
return drops
}
override fun readNbt(nbt: NbtCompound) {
this.berryIdentifier = Identifier(nbt.getString(BERRY).takeIf { it.isNotBlank() } ?: "cobblemon:pecha")
this.wasLoading = true
this.growthPoints.clear()
this.growthTimer = nbt.getInt(GROWTH_TIMER).coerceAtLeast(0)
this.stageTimer = nbt.getInt(STAGE_TIMER).coerceAtLeast(0)
//this.lifeCycles = nbt.getInt(LIFE_CYCLES).coerceAtLeast(0)
nbt.getList(GROWTH_POINTS, NbtList.STRING_TYPE.toInt()).filterIsInstance<NbtString>().forEach { element ->
// In case some 3rd party mutates the NBT incorrectly
try {
val identifier = Identifier(element.asString())
this.growthPoints += identifier
} catch (ignored: InvalidIdentifierException) {}
}
this.mulchVariant = if (nbt.contains(MULCH_VARIANT)) {
MulchVariant.valueOf(nbt.getString(MULCH_VARIANT))
} else {
null
}
this.mulchDuration = nbt.getInt(MULCH_DURATION)
this.wasLoading = false
if (nbt.contains(GROWTH_POINTS_SEQUENCE)) {
growthPointSequence = nbt.getString(GROWTH_POINTS_SEQUENCE)
}
}
override fun writeNbt(nbt: NbtCompound) {
nbt.putInt(GROWTH_TIMER, this.growthTimer)
nbt.putInt(STAGE_TIMER, this.stageTimer)
val list = NbtList()
list += this.growthPoints.map { NbtString.of(it.toString()) }
nbt.put(GROWTH_POINTS, list)
nbt.putString(BERRY, berryIdentifier.toString())
if (mulchVariant != null) {
nbt.putString(MULCH_VARIANT, mulchVariant.toString())
}
nbt.putInt(MULCH_DURATION, mulchDuration)
nbt.putString(GROWTH_POINTS_SEQUENCE, growthPointSequence)
}
override fun markDirty() {
if (!this.wasLoading) {
super.markDirty()
}
}
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
return BlockEntityUpdateS2CPacket.create(this)
}
override fun toInitialChunkDataNbt(): NbtCompound {
return this.createNbt()
}
/**
* Collects each [Berry] and [GrowthPoint].
*
* @return Collection of the [Berry] and [GrowthPoint].
*/
internal fun berryAndGrowthPoint(): List<Pair<Berry, GrowthPoint>> {
val baseBerry = this.berry() ?: return emptyList()
val berryPoints = arrayListOf<Pair<Berry, GrowthPoint>>()
val sequenceIndices = growthPointSequence.toCharArray().filter { it.digitToInt(16) < baseBerry.growthPoints.size }
for ((index, identifier) in this.growthPoints.withIndex()) {
val berry = Berries.getByIdentifier(identifier) ?: continue
val sequenceIndexHex = sequenceIndices[index].digitToInt(16)
berryPoints.add(berry to baseBerry.growthPoints[sequenceIndexHex])
}
return berryPoints
}
/**
* Inserts the given [berry] as a mutation in a random [growthPoints].
*
* @param berry The [Berry] being mutated.
*/
internal fun mutate(berry: Berry) {
if (this.growthPoints.isEmpty()) {
return
}
val index = this.growthPoints.indices.random()
this.growthPoints[index] = berry.identifier
this.markDirty()
}
private fun refresh(world: World, pos: BlockPos, state:BlockState, player: PlayerEntity) {
val newState = state.with(BerryBlock.AGE, 3)
world.setBlockState(pos, newState, Block.NOTIFY_LISTENERS)
world.emitGameEvent(player, GameEvent.BLOCK_CHANGE, pos)
this.generateGrowthPoints(world, newState, pos, player)
resetGrowTimers(pos, newState)
return
}
companion object {
internal val TICKER = BlockEntityTicker<BerryBlockEntity> { world, pos, state, blockEntity ->
if (world.isClient) return@BlockEntityTicker
if (blockEntity.stageTimer > 0) {
blockEntity.stageTimer--
}
if (blockEntity.stageTimer == 0) {
(state.block as BerryBlock).grow(world as ServerWorld, world.random, pos, state)
}
}
//private const val LIFE_CYCLES = "life_cycles"
private const val GROWTH_POINTS = "GrowthPoints"
private const val GROWTH_POINTS_SEQUENCE = "GrowthPointsSequence"
private const val GROWTH_TIMER = "GrowthTimer"
private const val STAGE_TIMER = "StageTimer"
private const val BERRY = "Berry"
private const val MULCH_VARIANT = "MulchVariant"
private const val MULCH_DURATION = "MulchDuration"
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.brewing
import com.cobblemon.mod.common.CobblemonItems
import net.minecraft.item.Item
import net.minecraft.item.Items
import net.minecraft.potion.Potion
import net.minecraft.potion.Potions
import net.minecraft.recipe.BrewingRecipeRegistry
@@ -21,7 +20,21 @@ object BrewingRecipes {
@JvmField
val brewableItems = mutableListOf(
CobblemonItems.MEDICINAL_BREW,
CobblemonItems.ZINC
CobblemonItems.POTION,
CobblemonItems.SUPER_POTION,
CobblemonItems.HYPER_POTION,
CobblemonItems.MAX_POTION,
CobblemonItems.FULL_RESTORE,
CobblemonItems.FULL_HEAL,
CobblemonItems.ANTIDOTE,
CobblemonItems.AWAKENING,
CobblemonItems.BURN_HEAL,
CobblemonItems.ICE_HEAL,
CobblemonItems.PARALYZE_HEAL,
CobblemonItems.ETHER,
CobblemonItems.MAX_ETHER,
CobblemonItems.ELIXIR,
CobblemonItems.MAX_ELIXIR
)
fun registerPotionTypes() {
@@ -32,14 +45,38 @@ object BrewingRecipes {
fun getPotionRecipes(): List<Triple<Potion, Ingredient, Potion>> {
return listOf(
Triple(Potions.WATER, Ingredient.ofItems(CobblemonItems.MEDICINAL_LEEK), Potions.WATER) // This one is Mixin'd to result in brew rather than water
Triple(Potions.WATER, Ingredient.ofItems(CobblemonItems.MEDICINAL_LEEK), Potions.WATER), // This one is Mixin'd to result in brew rather than water
)
}
fun getItemRecipes(): List<Triple<Item, Ingredient, Item>> {
// This is where the majority of our recipes go. First arg is thing down the bottom, second is thing up the top, third is results.
return listOf(
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(Items.GOLD_INGOT), CobblemonItems.ZINC)
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.LEPPA_BERRY), CobblemonItems.ETHER),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.HOPO_BERRY), CobblemonItems.ELIXIR),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.ORAN_BERRY), CobblemonItems.POTION),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.SITRUS_BERRY), CobblemonItems.HYPER_POTION),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_HEAL),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.PECHA_BERRY), CobblemonItems.ANTIDOTE),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.CHESTO_BERRY), CobblemonItems.AWAKENING),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.RAWST_BERRY), CobblemonItems.BURN_HEAL),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.ASPEAR_BERRY), CobblemonItems.ICE_HEAL),
Triple(CobblemonItems.MEDICINAL_BREW, Ingredient.ofItems(CobblemonItems.CHERI_BERRY), CobblemonItems.PARALYZE_HEAL),
Triple(CobblemonItems.BURN_HEAL, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_HEAL),
Triple(CobblemonItems.ANTIDOTE, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_HEAL),
Triple(CobblemonItems.AWAKENING, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_HEAL),
Triple(CobblemonItems.ICE_HEAL, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_HEAL),
Triple(CobblemonItems.PARALYZE_HEAL, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_HEAL),
Triple(CobblemonItems.ETHER, Ingredient.ofItems(CobblemonItems.PEP_UP_FLOWER), CobblemonItems.MAX_ETHER),
Triple(CobblemonItems.ELIXIR, Ingredient.ofItems(CobblemonItems.PEP_UP_FLOWER), CobblemonItems.MAX_ELIXIR),
Triple(CobblemonItems.POTION, Ingredient.ofItems(CobblemonItems.ENERGY_ROOT), CobblemonItems.SUPER_POTION),
Triple(CobblemonItems.SUPER_POTION, Ingredient.ofItems(CobblemonItems.FIGY_BERRY), CobblemonItems.HYPER_POTION),
Triple(CobblemonItems.SUPER_POTION, Ingredient.ofItems(CobblemonItems.WIKI_BERRY), CobblemonItems.HYPER_POTION),
Triple(CobblemonItems.SUPER_POTION, Ingredient.ofItems(CobblemonItems.MAGO_BERRY), CobblemonItems.HYPER_POTION),
Triple(CobblemonItems.SUPER_POTION, Ingredient.ofItems(CobblemonItems.AGUAV_BERRY), CobblemonItems.HYPER_POTION),
Triple(CobblemonItems.SUPER_POTION, Ingredient.ofItems(CobblemonItems.IAPAPA_BERRY), CobblemonItems.HYPER_POTION),
Triple(CobblemonItems.HYPER_POTION, Ingredient.ofItems(CobblemonItems.VIVICHOKE), CobblemonItems.MAX_POTION),
Triple(CobblemonItems.MAX_POTION, Ingredient.ofItems(CobblemonItems.LUM_BERRY), CobblemonItems.FULL_RESTORE)
)
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.texture.SpriteAtlasHolder
import net.minecraft.client.texture.TextureManager
class CobblemonBerryAtlas(textureManager: TextureManager) : SpriteAtlasHolder(
textureManager,
cobblemonResource("textures/atlas/berries.png"),
cobblemonResource("berries")
) {
}
@@ -8,17 +8,14 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonClientImplementation
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.scheduling.ScheduledTaskTracker
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
@@ -34,11 +31,16 @@ import com.cobblemon.mod.common.client.storage.ClientStorageManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated
import java.awt.Color
import net.minecraft.client.color.block.BlockColorProvider
import net.minecraft.client.color.block.BlockColors
import net.minecraft.client.color.item.ItemColorProvider
import net.minecraft.client.color.world.BiomeColors
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.entity.EntityRenderer
@@ -88,6 +90,7 @@ object CobblemonClient {
PlatformEvents.CLIENT_PLAYER_LOGOUT.subscribe { onLogout() }
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.HEALING_MACHINE, ::HealingMachineRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.BERRY, ::BerryBlockRenderer)
registerBlockRenderTypes()
registerColors()
@@ -164,7 +167,9 @@ object CobblemonClient {
CobblemonBlocks.REVIVAL_HERB,
CobblemonBlocks.VIVICHOKE_SEEDS,
CobblemonBlocks.PEP_UP_FLOWER,
CobblemonBlocks.POTTED_PEP_UP_FLOWER
CobblemonBlocks.POTTED_PEP_UP_FLOWER,
CobblemonBlocks.REVIVAL_HERB,
*CobblemonBlocks.berries().values.toTypedArray()
)
}
@@ -203,6 +208,7 @@ object CobblemonClient {
)
PokemonModelRepository.reload(resourceManager)
PokeBallModelRepository.reload(resourceManager)
BerryModelRepository.reload(resourceManager)
LOGGER.info("Loaded assets")
// PokeBallModelRepository.reload(resourceManager)
}
@@ -26,6 +26,7 @@ class ClientBallDisplay(val pokeBall: PokeBall, val aspects: Set<String>) : Poke
override val stateEmitter = SettableObservable(EmptyPokeBallEntity.CaptureState.FALL)
override val shakeEmitter = SimpleObservable<Unit>()
override fun getEntity() = null
override fun updatePartialTicks(partialTicks: Float) {
this.currentPartialTicks += partialTicks
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.client.entity
import com.cobblemon.mod.common.api.entity.EntitySideDelegate
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.client.render.pokeball.PokeBallPoseableState
@@ -21,11 +20,16 @@ class EmptyPokeBallClientDelegate : PokeBallPoseableState(), EntitySideDelegate<
override val stateEmitter: SettableObservable<CaptureState> = SettableObservable(NOT)
override val shakeEmitter = SimpleObservable<Unit>()
lateinit var currentEntity: EmptyPokeBallEntity
override fun getEntity() = currentEntity
override fun updatePartialTicks(partialTicks: Float) {
this.currentPartialTicks = partialTicks
}
override fun initialize(entity: EmptyPokeBallEntity) {
this.currentEntity = entity
age = entity.age
initSubscriptions()
entity.captureState.subscribe {
@@ -28,10 +28,12 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
const val BEAM_EXTEND_TIME = 0.2F
}
lateinit var entity: PokemonEntity
lateinit var currentEntity: PokemonEntity
var phaseTarget: Entity? = null
var entityScaleModifier = 1F
override fun getEntity() = currentEntity
override fun updatePartialTicks(partialTicks: Float) {
this.currentPartialTicks = partialTicks
}
@@ -46,26 +48,26 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
private val intensityVelocityCap = -0.5F
override fun changePokemon(pokemon: Pokemon) {
pokemon.isClient = true
entity.subscriptions.add(entity.species.subscribeIncludingCurrent {
currentEntity.subscriptions.add(currentEntity.species.subscribeIncludingCurrent {
currentPose = null
entity.pokemon.species = PokemonSpecies.getByIdentifier(Identifier(it))!! // TODO exception handling
currentEntity.pokemon.species = PokemonSpecies.getByIdentifier(Identifier(it))!! // TODO exception handling
})
// entity.subscriptions.add(entity.nickname.subscribeIncludingCurrent {
// entity.pokemon.nickname = it?.copy()
// currentEntity.subscriptions.add(currentEntity.nickname.subscribeIncludingCurrent {
// currentEntity.pokemon.nickname = it?.copy()
// })
entity.subscriptions.add(entity.deathEffectsStarted.subscribe {
currentEntity.subscriptions.add(currentEntity.deathEffectsStarted.subscribe {
if (it) {
val model = (currentModel ?: return@subscribe) as PokemonPoseableModel
val animation = try { model.getFaintAnimation(entity, this) } catch (e: Exception) { e.printStackTrace(); null } ?: return@subscribe
val animation = try { model.getFaintAnimation(currentEntity, this) } catch (e: Exception) { e.printStackTrace(); null } ?: return@subscribe
statefulAnimations.add(animation)
}
})
entity.subscriptions.add(entity.labelLevel.subscribeIncludingCurrent { if (it > 0) entity.pokemon.level = it })
currentEntity.subscriptions.add(currentEntity.labelLevel.subscribeIncludingCurrent { if (it > 0) currentEntity.pokemon.level = it })
entity.subscriptions.add(entity.phasingTargetId.subscribe {
currentEntity.subscriptions.add(currentEntity.phasingTargetId.subscribe {
if (it != -1) {
setPhaseTarget(it)
} else {
@@ -73,22 +75,22 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
})
// pokemon.aspects = entity.aspects.get()
// entity.aspects.pipe(emitWhile { pokemon == entity.pokemon }).subscribe {
// pokemon.aspects = currentEntity.aspects.get()
// currentEntity.aspects.pipe(emitWhile { pokemon == currentEntity.pokemon }).subscribe {
// pokemon.aspects = it
// }
entity.subscriptions.add(entity.beamModeEmitter.subscribeIncludingCurrent {
currentEntity.subscriptions.add(currentEntity.beamModeEmitter.subscribeIncludingCurrent {
if (it == 0.toByte()) {
// Do nothing
} else if (it == 1.toByte()) {
// Scaling up out of pokeball
entityScaleModifier = 0F
beamStartTime = System.currentTimeMillis()
entity.isInvisible = true
currentEntity.isInvisible = true
after(seconds = BEAM_EXTEND_TIME) {
lerp(BEAM_SHRINK_TIME) { entityScaleModifier = it }
entity.isInvisible = false
currentEntity.isInvisible = false
}
} else {
// Scaling down into pokeball
@@ -104,7 +106,7 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
override fun initialize(entity: PokemonEntity) {
this.entity = entity
this.currentEntity = entity
this.age = entity.age
}
@@ -128,25 +130,25 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
fun setPhaseTarget(targetId: Int) {
this.phaseTarget = entity.world.getEntityById(targetId)
this.phaseTarget = currentEntity.world.getEntityById(targetId)
}
override fun handleStatus(status: Byte) {
if (status == 10.toByte()) {
val model = (currentModel ?: return) as PokemonPoseableModel
val animation = model.getEatAnimation(entity, this) ?: return
val animation = model.getEatAnimation(currentEntity, this) ?: return
statefulAnimations.add(animation)
}
}
override fun updatePostDeath() {
++entity.deathTime
++currentEntity.deathTime
}
fun cry() {
val model = currentModel ?: return
if (model is PokemonPoseableModel) {
val animation = model.cryAnimation(entity, this) ?: return
val animation = model.cryAnimation(currentEntity, this) ?: return
statefulAnimations.add(animation)
}
}
@@ -400,7 +400,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
model.setupAnimStateful(null, state, 0F, 0F, 0F, 0F, 0F)
matrixStack.scale(scale, scale, -scale)
matrixStack.translate(0.0, -2.0, -4.0)
matrixStack.translate(0.0, 5.5, -4.0)
matrixStack.push()
matrixStack.scale(scale * state.scale, scale * state.scale, 0.1F)
@@ -408,8 +408,8 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
matrixStack.multiply(quaternion1)
matrixStack.multiply(quaternion2)
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
val light2 = Vector3f(0.1F, -1.0F, 2.0F)
val light1 = Vector3f(2.2F, 4.0F, -4.0F)
val light2 = Vector3f(1.1F, -4.0F, 7.0F)
RenderSystem.setShaderLights(light1, light2)
quaternion1.conjugate()
@@ -131,6 +131,7 @@ class MoveSelectGUI(
}
override fun shouldCloseOnEsc() = true
override fun shouldPause() = false
fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
@@ -10,13 +10,12 @@ package com.cobblemon.mod.common.client.gui.interact.partyselect
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.callback.MoveSelectDTO
import com.cobblemon.mod.common.api.callback.PartySelectPokemonDTO
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectGUI
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -40,10 +39,12 @@ class PartySelectGUI(
val config: PartySelectConfiguration
) : Screen(Text.translatable("cobblemon.ui.interact.moveselect")) {
companion object {
const val WIDTH = 102
const val HEIGHT = 115
const val WIDTH = 163
const val HEIGHT = 132
const val SCALE = 0.5F
private val baseBackgroundResource = cobblemonResource("textures/gui/interact/party_select.png")
private val spacerResource = cobblemonResource("textures/gui/interact/party_select_spacer.png")
}
var closed = false
@@ -75,8 +76,19 @@ class PartySelectGUI(
val y = (height - HEIGHT) / 2
config.pokemon.forEachIndexed { index, pokemon ->
val slotX = x + 5 + (index % 2) * (PartySlotButton.WIDTH + 2)
val slotY = y + 5 + (index / 2) * (PartySlotButton.HEIGHT + 2)
var slotX = x + 11
var slotY = y + 23
if (index > 0) {
val isEven = index % 2 == 0
val offsetIndex = (index - (if (isEven) 0 else 1)) / 2
val offsetX = if (isEven) 0 else 74
val offsetY = if (isEven) 0 else -8
slotX += offsetX
slotY += (31 * offsetIndex) + offsetY
}
addDrawableChild(
PartySlotButton(
parent = this,
@@ -84,7 +96,8 @@ class PartySelectGUI(
y = slotY,
pokemon = pokemon.pokemonProperties,
heldItem = pokemon.heldItem,
hpRatio = pokemon.hpRatio,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled
) { onPress(pokemon) }
)
@@ -93,8 +106,8 @@ class PartySelectGUI(
// Add Exit Button
addDrawableChild(
ExitButton(
pX = x + 72,
pY = y + 96
pX = x + 134,
pY = y + 116
) {
playSound(CobblemonSounds.GUI_CLICK)
config.onBack(this)
@@ -117,6 +130,26 @@ class PartySelectGUI(
height = HEIGHT
)
// Label
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = config.title.bold(),
x = x + 37,
y = y + 1.5,
centered = true
)
blitk(
matrixStack = context.matrices,
texture = spacerResource,
x = (x + 86.5) / SCALE,
y = (y + 111) / SCALE,
width = 79,
height = 12,
scale = SCALE
)
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
}
@@ -137,6 +170,7 @@ class PartySelectGUI(
}
override fun shouldCloseOnEsc() = true
override fun shouldPause() = false
fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.interact.partyselect
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
@@ -34,7 +35,8 @@ import org.joml.Vector3f
class PartySlotButton(
x: Int, y: Int,
val pokemon: PokemonProperties,
val hpRatio: Float,
val currentHealth: Int,
val maxHealth: Int,
val heldItem: ItemStack,
val enabled: Boolean = true,
val parent: PartySelectGUI,
@@ -42,16 +44,14 @@ class PartySlotButton(
) : ButtonWidget(x, y, WIDTH, HEIGHT, Text.literal("Pokemon"), onPress, NarrationSupplier { "".text() }) {
companion object {
private val slotResource = cobblemonResource("textures/gui/summary/summary_party_slot.png")
private val slotFaintedResource = cobblemonResource("textures/gui/summary/summary_party_slot_fainted.png")
private val slotEmptyResource = cobblemonResource("textures/gui/summary/summary_party_slot_empty.png")
private val slotResource = cobblemonResource("textures/gui/interact/party_select_slot.png")
private val slotFaintedResource = cobblemonResource("textures/gui/interact/party_select_slot_fainted.png")
const val WIDTH = 46
const val WIDTH = 69
const val HEIGHT = 27
const val SCALE = 0.5F
}
var animationSeconds = 0F
var lastTime = System.currentTimeMillis()
val state = PokemonFloatingState()
private val renderablePokemon = pokemon.asRenderablePokemon()
@@ -63,7 +63,7 @@ class PartySlotButton(
blitk(
matrixStack = matrices,
texture = if (hpRatio <= 0F) slotFaintedResource else slotResource,
texture = if (currentHealth <= 0F) slotFaintedResource else slotResource,
x = x,
y = y,
width = width,
@@ -90,41 +90,61 @@ class PartySlotButton(
)
context.matrices.pop()
val halfScale = 0.5F
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.pokeball!!.asIdentifierDefaultingNamespace().path + ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
texture = ballIcon,
x = (x - 2) / halfScale,
y = (y - 3) / halfScale,
x = (x - 2) / SCALE,
y = (y - 3) / SCALE,
height = ballHeight,
width = 18,
textureHeight = ballHeight * 2,
scale = halfScale
scale = SCALE
)
val status = pokemon.status
if (hpRatio > 0F && status != null) {
drawScaledText(
context = context,
text = lang("ui.lv.number", pokemon.level!!),
x = x + 24,
y = y + 6.5,
shadow = true,
scale = SCALE
)
// Pokémon Name
val displayName = pokemon.nickname ?: renderablePokemon.species.translatedName
drawScaledText(
context = context,
text = displayName.copy(),
x = x + 24,
y = y + 12.5,
scale = SCALE
)
if ("male" in pokemon.aspects || "female" in pokemon.aspects) {
blitk(
matrixStack = matrices,
texture = cobblemonResource("textures/gui/party/status_$status.png"),
x = x + 42,
y = y + 5,
height = 14,
width = 4
texture = if ("male" in pokemon.aspects) PartySlotWidget.genderIconMale else PartySlotWidget.genderIconFemale,
x = (x + 60.5) / SCALE,
y = (y + 12.5) / SCALE,
height = 7,
width = 5,
scale = SCALE
)
}
val barWidthMax = 37
// HP
val hpRatio = currentHealth / maxHealth.toFloat()
val barWidthMax = 65
val barWidth = hpRatio * barWidthMax
val (red, green) = getDepletableRedGreen(hpRatio)
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = x + 4,
y = y + 25,
x = x + 1,
y = y + 20,
width = barWidth,
height = 1,
textureWidth = barWidth / hpRatio,
@@ -134,28 +154,36 @@ class PartySlotButton(
blue = 0.27F
)
if ("male" in pokemon.aspects || "female" in pokemon.aspects) {
blitk(
matrixStack = matrices,
texture = if ("male" in pokemon.aspects) PartySlotWidget.genderIconMale else PartySlotWidget.genderIconFemale,
x = (x + 40) / halfScale,
y = (y + 20) / halfScale,
height = 7,
width = 5,
scale = halfScale
)
}
drawScaledText(
context = context,
text = lang("ui.lv.number", pokemon.level!!),
x = x + 31,
y = y + 13,
centered = true,
shadow = true,
scale = halfScale
text = "$currentHealth/$maxHealth".text(),
x = x + 14,
y = y + 22.5,
scale = SCALE,
centered = true
)
val status = pokemon.status
if (hpRatio > 0F && status != null) {
blitk(
matrixStack = matrices,
texture = cobblemonResource("textures/gui/interact/party_select_status_$status.png"),
x = x + 27,
y = y + 22,
height = 5,
width = 37
)
drawScaledText(
context = context,
text = lang("ui.status.$status").bold(),
x = x + 32.5,
y = y + 22.5,
shadow = true,
scale = SCALE
)
}
// Held Item
if (!heldItem.isEmpty) {
renderScaledGuiItemIcon(
@@ -166,16 +194,6 @@ class PartySlotButton(
matrixStack = matrices
)
}
// Pokémon Name
val displayName = pokemon.nickname ?: renderablePokemon.species.translatedName
drawScaledText(
context = context,
text = displayName.copy(),
x = x + 4,
y = y + HEIGHT - 7,
scale = halfScale
)
}
override fun playDownSound(soundManager: SoundManager) {}
@@ -37,7 +37,7 @@ class ArrowButton(
}
override fun playDownSound(soundManager: SoundManager?) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
return
}
override fun renderButton(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
@@ -145,62 +145,62 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Evolve Button
addDrawableChild(
SummaryButton(
buttonX = x + 12F,
buttonY = y + 145F,
buttonWidth = 54,
buttonHeight = 15,
clickAction = {
after(ticks = 0, serverThread = false) {
displaySideScreen(if (sideScreenIndex == EVOLVE) PARTY else EVOLVE)
}
},
text = lang("ui.evolve"),
resource = evolveButtonResource,
renderRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() },
clickRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() }
)
SummaryButton(
buttonX = x + 12F,
buttonY = y + 145F,
buttonWidth = 54,
buttonHeight = 15,
clickAction = {
after(ticks = 0, serverThread = false) {
displaySideScreen(if (sideScreenIndex == EVOLVE) PARTY else EVOLVE)
}
},
text = lang("ui.evolve"),
resource = evolveButtonResource,
renderRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() },
clickRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() }
)
)
// Init Tabs
summaryTabs.clear()
summaryTabs.add(
SummaryTab(
pX = x + 78,
pY = y - 1,
label = lang("ui.info")
) {
if (mainScreenIndex != INFO) {
displayMainScreen(INFO)
playSound(CobblemonSounds.GUI_CLICK)
SummaryTab(
pX = x + 78,
pY = y - 1,
label = lang("ui.info")
) {
if (mainScreenIndex != INFO) {
displayMainScreen(INFO)
playSound(CobblemonSounds.GUI_CLICK)
}
}
}
)
summaryTabs.add(
SummaryTab(
pX = x + 119,
pY = y - 1,
label = lang("ui.moves")
) {
if (mainScreenIndex != MOVES) {
displayMainScreen(MOVES)
playSound(CobblemonSounds.GUI_CLICK)
SummaryTab(
pX = x + 119,
pY = y - 1,
label = lang("ui.moves")
) {
if (mainScreenIndex != MOVES) {
displayMainScreen(MOVES)
playSound(CobblemonSounds.GUI_CLICK)
}
}
}
)
summaryTabs.add(
SummaryTab(
pX = x + 160,
pY = y - 1,
label = lang("ui.stats")
) {
if (mainScreenIndex != STATS) {
displayMainScreen(STATS)
playSound(CobblemonSounds.GUI_CLICK)
SummaryTab(
pX = x + 160,
pY = y - 1,
label = lang("ui.stats")
) {
if (mainScreenIndex != STATS) {
displayMainScreen(STATS)
playSound(CobblemonSounds.GUI_CLICK)
}
}
}
)
summaryTabs[mainScreenIndex].toggleTab()
@@ -208,24 +208,24 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Add Exit Button
addDrawableChild(
ExitButton(
pX = x + 302,
pY = y + 145
) {
playSound(CobblemonSounds.GUI_CLICK)
MinecraftClient.getInstance().setScreen(null)
}
ExitButton(
pX = x + 302,
pY = y + 145
) {
playSound(CobblemonSounds.GUI_CLICK)
MinecraftClient.getInstance().setScreen(null)
}
)
// Add Nickname Entry
nicknameEntryWidget = NicknameEntryWidget(
selectedPokemon,
x = x + 12,
y = (y + 14.5).toInt(),
width = 50,
height = 10,
isParty = true,
lang("ui.nickname")
selectedPokemon,
x = x + 12,
y = (y + 14.5).toInt(),
width = 50,
height = 10,
isParty = true,
lang("ui.nickname")
)
focused = nicknameEntryWidget
nicknameEntryWidget.isFocused = false
@@ -233,14 +233,14 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Add Model Preview
modelWidget = ModelWidget(
pX = x + 6,
pY = y + 32,
pWidth = PORTRAIT_SIZE,
pHeight = PORTRAIT_SIZE,
pokemon = selectedPokemon.asRenderablePokemon(),
baseScale = 2F,
rotationY = 325F,
offsetY = -10.0
pX = x + 6,
pY = y + 32,
pWidth = PORTRAIT_SIZE,
pHeight = PORTRAIT_SIZE,
pokemon = selectedPokemon.asRenderablePokemon(),
baseScale = 2F,
rotationY = 325F,
offsetY = -10.0
)
}
@@ -258,7 +258,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
val targetPosition = PartyPosition(targetIndex)
val packet = targetPokemon?.let { SwapPartyPokemonPacket(it.uuid, targetPosition, sourcePokemon.uuid, sourcePosition) }
?: MovePartyPokemonPacket(sourcePokemon.uuid, sourcePosition, targetPosition)
?: MovePartyPokemonPacket(sourcePokemon.uuid, sourcePosition, targetPosition)
packet.sendToServer()
// Update change in UI
@@ -289,11 +289,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
*/
private fun listenToMoveSet() {
moveSetSubscription = selectedPokemon.moveSet.observable
.pipe(emitWhile { isOpen() })
.subscribe {
if (mainScreen is MovesWidget)
displayMainScreen(MOVES)
}
.pipe(emitWhile { isOpen() })
.subscribe {
if (mainScreen is MovesWidget)
displayMainScreen(MOVES)
}
}
/**
@@ -312,7 +312,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
if (::mainScreen.isInitialized) remove(mainScreen)
if (sideScreenIndex == MOVE_SWAP) displaySideScreen(PARTY)
summaryTabs.forEachIndexed {index, item ->
summaryTabs.forEachIndexed { index, item ->
if (index == screen) item.toggleTab() else item.toggleTab(false)
}
@@ -322,24 +322,26 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
when (screen) {
INFO -> {
mainScreen = InfoWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
)
}
MOVES -> {
mainScreen = MovesWidget(
pX = x + 77,
pY = y + 12,
summary = this
pX = x + 77,
pY = y + 12,
summary = this
)
}
STATS -> {
mainScreen = StatWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
tabIndex = subIndex
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
tabIndex = subIndex
)
}
}
@@ -359,38 +361,41 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
when (screen) {
PARTY -> {
sideScreen = PartyWidget(
pX = x + 216,
pY = y + 24,
isParty = selectedPokemon in CobblemonClient.storage.myParty,
summary = this,
partyList = this.party
pX = x + 216,
pY = y + 24,
isParty = selectedPokemon in CobblemonClient.storage.myParty,
summary = this,
partyList = this.party
)
}
MOVE_SWAP -> {
val movesWidget = mainScreen
if (movesWidget is MovesWidget && move != null) {
sideScreen = MoveSwapScreen(
x + 216,
y + 22,
movesWidget = movesWidget,
replacedMove = move
x + 216,
y + 22,
movesWidget = movesWidget,
replacedMove = move
).also { switchPane ->
val pokemon = selectedPokemon
pokemon.allAccessibleMoves
.filter { template -> pokemon.moveSet.none { it.template == template } }
.map { template ->
val benched = pokemon.benchedMoves.find { it.moveTemplate == template }
MoveSwapScreen.MoveSlot(switchPane, template, benched?.ppRaisedStages ?: 0)
}
.forEach { switchPane.addEntry(it) }
.filter { template -> pokemon.moveSet.none { it.template == template } }
.map { template ->
val benched = pokemon.benchedMoves.find { it.moveTemplate == template }
MoveSwapScreen.MoveSlot(switchPane, template, benched?.ppRaisedStages
?: 0)
}
.forEach { switchPane.addEntry(it) }
}
}
}
EVOLVE -> {
sideScreen = EvolutionSelectScreen(
x + 216,
y + 22,
pokemon = selectedPokemon
x + 216,
y + 22,
pokemon = selectedPokemon
)
}
}
@@ -409,24 +414,24 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Render Portrait Background
blitk(
matrixStack = matrices,
texture = portraitBackgroundResource,
x = x + 6,
y = y + 32,
width = PORTRAIT_SIZE,
height = PORTRAIT_SIZE
matrixStack = matrices,
texture = portraitBackgroundResource,
x = x + 6,
y = y + 32,
width = PORTRAIT_SIZE,
height = PORTRAIT_SIZE
)
modelWidget.render(context, pMouseX, pMouseY, pPartialTicks)
// Render Base Resource
blitk(
matrixStack = matrices,
texture = baseResource,
x = x,
y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
matrixStack = matrices,
texture = baseResource,
x = x,
y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
)
// Status
@@ -434,100 +439,100 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
if (selectedPokemon.isFainted() || status != null) {
val statusName = if (selectedPokemon.isFainted()) "fnt" else status?.showdownName
blitk(
matrixStack = matrices,
texture = cobblemonResource("textures/gui/battle/battle_status_$statusName.png"),
x = x + 34,
y = y + 4,
height = 7,
width = 39,
uOffset = 35,
textureWidth = 74
matrixStack = matrices,
texture = cobblemonResource("textures/gui/battle/battle_status_$statusName.png"),
x = x + 34,
y = y + 4,
height = 7,
width = 39,
uOffset = 35,
textureWidth = 74
)
blitk(
matrixStack = matrices,
texture = cobblemonResource("textures/gui/summary/status_trim.png"),
x = x + 34,
y = y + 5,
height = 6,
width = 3
matrixStack = matrices,
texture = cobblemonResource("textures/gui/summary/status_trim.png"),
x = x + 34,
y = y + 5,
height = 6,
width = 3
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.status.$statusName").bold(),
x = x + 39,
y = y + 3
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.status.$statusName").bold(),
x = x + 39,
y = y + 3
)
}
// Poké Ball
val ballResource = cobblemonResource("textures/item/poke_balls/" + selectedPokemon.caughtBall.name.path + ".png")
blitk(
matrixStack = matrices,
texture = ballResource,
x = (x + 3.5) / SCALE,
y = (y + 15) / SCALE,
width = 16,
height = 16,
scale = SCALE
matrixStack = matrices,
texture = ballResource,
x = (x + 3.5) / SCALE,
y = (y + 15) / SCALE,
width = 16,
height = 16,
scale = SCALE
)
if (selectedPokemon.gender != Gender.GENDERLESS) {
val isMale = selectedPokemon.gender == Gender.MALE
val textSymbol = if (isMale) "♂".text().bold() else "♀".text().bold()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = x + 69, // 64 when tag icon is implemented
y = y + 14.5,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = x + 69, // 64 when tag icon is implemented
y = y + 14.5,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
)
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv").bold(),
x = x + 6,
y = y + 4.5,
shadow = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv").bold(),
x = x + 6,
y = y + 4.5,
shadow = true
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = selectedPokemon.level.toString().text().bold(),
x = x + 19,
y = y + 4.5,
shadow = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = selectedPokemon.level.toString().text().bold(),
x = x + 19,
y = y + 4.5,
shadow = true
)
// Shiny Icon
if (selectedPokemon.shiny) {
blitk(
matrixStack = matrices,
texture = iconShinyResource,
x = (x + 63.5) / SCALE,
y = (y + 33.5) / SCALE,
width = 14,
height = 14,
scale = SCALE
matrixStack = matrices,
texture = iconShinyResource,
x = (x + 63.5) / SCALE,
y = (y + 33.5) / SCALE,
width = 14,
height = 14,
scale = SCALE
)
}
// Type Icon(s)
blitk(
matrixStack = matrices,
texture = if (selectedPokemon.secondaryType != null) typeSpacerDoubleResource else typeSpacerResource,
x = (x + 5.5) / SCALE,
y = (y + 126) / SCALE,
width = 134,
height = 24,
scale = SCALE
matrixStack = matrices,
texture = if (selectedPokemon.secondaryType != null) typeSpacerDoubleResource else typeSpacerResource,
x = (x + 5.5) / SCALE,
y = (y + 126) / SCALE,
width = 134,
height = 24,
scale = SCALE
)
// Held Item
@@ -540,29 +545,29 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
drawScaledText(
context = context,
text = lang("held_item"),
x = x + 27,
y = y + 114.5,
scale = SCALE
context = context,
text = lang("held_item"),
x = x + 27,
y = y + 114.5,
scale = SCALE
)
TypeIcon(
x = x + 39,
y = y + 123,
type = selectedPokemon.primaryType,
secondaryType = selectedPokemon.secondaryType,
centeredX = true
x = x + 39,
y = y + 123,
type = selectedPokemon.primaryType,
secondaryType = selectedPokemon.secondaryType,
centeredX = true
).render(context)
blitk(
matrixStack = matrices,
texture = sideSpacerResource,
x = (x + 217) / SCALE,
y = (y + 141) / SCALE,
width = 144,
height = 14,
scale = SCALE
matrixStack = matrices,
texture = sideSpacerResource,
x = (x + 217) / SCALE,
y = (y + 141) / SCALE,
width = 144,
height = 14,
scale = SCALE
)
// Render all added Widgets
@@ -609,4 +614,4 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
}
}
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.InputUtil
import net.minecraft.text.Text
class NicknameEntryWidget(
@@ -64,16 +65,20 @@ class NicknameEntryWidget(
text = text.trim().ifBlank { pokemonName }
if (!focused) {
val newNickname = if (text == pokemonName) null else text
if (oldText != lastSavedName && !(newNickname == null && pokemon.nickname == null)) {
lastSavedName = text
CobblemonNetwork.sendToServer(
updateNickname(oldText, newNickname)
}
}
private fun updateNickname(oldText: String, newNickname: String?) {
if (oldText != lastSavedName && !(newNickname == null && pokemon.nickname == null)) {
lastSavedName = text
CobblemonNetwork.sendToServer(
SetNicknamePacket(
pokemonUUID = pokemon.uuid,
nickname = newNickname,
isParty = isParty
pokemonUUID = pokemon.uuid,
nickname = newNickname,
isParty = isParty
)
)
}
)
}
}
@@ -89,4 +94,14 @@ class NicknameEntryWidget(
shadow = true
)
}
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (keyCode == InputUtil.GLFW_KEY_ESCAPE) {
val oldText = text.trim()
val pokemonName = pokemon.species.translatedName.string
val newNickname = if (text == pokemonName) null else text
updateNickname(oldText, newNickname)
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
}
@@ -52,7 +52,7 @@ object OpenPartyMoveCallbackHandler : ClientNetworkPacketHandler<OpenPartyMoveCa
}
partySelectConfiguration = PartySelectConfiguration(
title = "".text(),
title = packet.partyTitle,
pokemon = pokemonToMoves.keys.toList(),
onCancel = cancel,
onBack = cancel,
@@ -15,9 +15,7 @@ import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.cobblemon.mod.common.particle.SnowstormParticleEffect
import com.cobblemon.mod.common.util.math.geometry.getOrigin
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import com.cobblemon.mod.common.util.math.geometry.transformPosition
import kotlin.random.Random
import net.minecraft.client.MinecraftClient
import net.minecraft.client.particle.NoRenderParticle
@@ -38,6 +36,7 @@ class ParticleStorm(
val sourceVelocity: () -> Vec3d = { Vec3d.ZERO },
val sourceAlive: () -> Boolean = { true },
val sourceVisible: () -> Boolean = { true },
val onDespawn: () -> Unit = {},
val runtime: MoLangRuntime = MoLangRuntime()
): NoRenderParticle(world, matrixWrapper.getOrigin().x, matrixWrapper.getOrigin().y, matrixWrapper.getOrigin().z) {
fun spawn() {
@@ -55,6 +54,9 @@ class ParticleStorm(
val particles = mutableListOf<SnowstormParticle>()
var started = false
var stopped = false
var despawned = false
// The idea is that some instantaneous particle effects could teeeechnically be over before they start.
var hasPlayedOnce = false
var entity: Entity? = null
@@ -72,11 +74,24 @@ class ParticleStorm(
return if (stopped) 0 else Int.MAX_VALUE
}
override fun markDead() {
super.markDead()
if (!despawned) {
despawned = true
onDespawn()
}
}
override fun tick() {
setMaxAge(getMaxAge())
super.tick()
if (!sourceAlive()) {
if (!hasPlayedOnce) {
age = 0
hasPlayedOnce = true
}
if (!sourceAlive() && !stopped) {
stopped = true
markDead()
}
@@ -99,7 +99,7 @@ class VaryingRenderableResolver<E : Entity, M : PoseableEntityModel<E>>(
posers.clear()
getAllModels().forEach { identifier ->
try {
models[identifier] = repository.texturedModels[identifier]!!.invoke()
models[identifier] = repository.texturedModels[identifier]!!.invoke(repository.isForLivingEntityRenderer)
} catch (e: Exception) {
throw IllegalStateException("Unable to load model $identifier for $name", e)
}
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.setPosition
import com.cobblemon.mod.common.util.math.geometry.Axis
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.block.entity.BlockEntityRenderer
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d
class BerryBlockRenderer(private val context: BlockEntityRendererFactory.Context) : BlockEntityRenderer<BerryBlockEntity> {
override fun isInRenderDistance(blockEntity: BerryBlockEntity, pos: Vec3d): Boolean {
return super.isInRenderDistance(blockEntity, pos)
&& MinecraftClient.getInstance().worldRenderer.frustum.isVisible(Box.of(pos, 2.0, 1.5, 2.0))
}
override fun render(entity: BerryBlockEntity, tickDelta: Float, matrices: MatrixStack, vertexConsumers: VertexConsumerProvider, light: Int, overlay: Int) {
if (!isInRenderDistance(entity, entity.pos.toVec3d())) return
val blockState = entity.cachedState
val age = blockState.get(BerryBlock.AGE)
if (age <= BerryBlock.MATURE_AGE) {
return
}
val isFlower = age == BerryBlock.FLOWER_AGE
for ((berry, growthPoint) in entity.berryAndGrowthPoint()) {
val model = (if (isFlower) BerryModelRepository.modelOf(berry.flowerModelIdentifier) else BerryModelRepository.modelOf(berry.fruitModelIdentifier)) ?: continue
val texture = if (isFlower) berry.flowerTexture else berry.fruitTexture
val layer = RenderLayer.getEntityCutoutNoCull(texture)
model.setAngles(
Math.toRadians(180.0 - growthPoint.rotation.x).toFloat(),
Math.toRadians(180.0 + growthPoint.rotation.y).toFloat(),
Math.toRadians(growthPoint.rotation.z).toFloat()
)
model.setPosition(Axis.X_AXIS.ordinal, growthPoint.position.x.toFloat())
model.setPosition(Axis.Y_AXIS.ordinal, growthPoint.position.y.toFloat())
model.setPosition(Axis.Z_AXIS.ordinal, growthPoint.position.z.toFloat())
model.render(matrices, vertexConsumers.getBuffer(layer), light, overlay)
}
}
}
@@ -12,13 +12,11 @@ import com.cobblemon.mod.common.client.render.models.blockbench.repository.Pokem
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.item.PokemonItem
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.DiffuseLighting
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.model.json.ModelTransformation
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.item.ItemStack
@@ -36,37 +34,31 @@ class PokemonItemRenderer : CobblemonBuiltinItemRenderer {
val transformations = positions[mode]!!
RenderSystem.applyModelViewMatrix()
DiffuseLighting.enableGuiDepthLighting()
matrices.scale(transformations.scale.x, transformations.scale.y, transformations.scale.z)
matrices.translate(transformations.translation.x, transformations.translation.y, transformations.translation.z)
model.setupAnimStateless(PoseType.PROFILE)
matrices.translate(model.profileTranslation.x, model.profileTranslation.y, model.profileTranslation.z - 4.0)
matrices.scale(model.profileScale, model.profileScale, 0.1F)
matrices.scale(model.profileScale, model.profileScale, 0.15F)
val rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(transformations.rotation.x, transformations.rotation.y, transformations.rotation.z))
matrices.multiply(rotation)
rotation.conjugate()
val oldRotation = MinecraftClient.getInstance().entityRenderDispatcher.rotation
MinecraftClient.getInstance().entityRenderDispatcher.rotation = rotation
val light1 = Vector3f(-1F, 1F, 1.0F)
val light2 = Vector3f(1.3F, -1F, 1.0F)
val previous = RenderSystem.shaderLightDirections.let { it[0] to it[1] }
RenderSystem.setShaderLights(light1, light2)
// val packedLight = LightmapTextureManager.pack(12, 12)
val vertexConsumer: VertexConsumer = vertexConsumers.getBuffer(renderLayer)
matrices.push()
val packedLight = if (mode == ModelTransformationMode.GUI) {
LightmapTextureManager.pack(13, 13)
} else {
light
}
val packedLight = LightmapTextureManager.pack(11, 7)
model.withLayerContext(vertexConsumers, null, PokemonModelRepository.getLayers(species.resourceIdentifier, aspects)) {
model.render(matrices, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
}
matrices.pop()
matrices.pop()
RenderSystem.setShaderLights(previous.first, previous.second)
MinecraftClient.getInstance().entityRenderDispatcher.rotation = oldRotation
DiffuseLighting.disableGuiDepthLighting()
}
companion object {
@@ -10,7 +10,11 @@ package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.ModelLayer
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseTransitionAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.RotationFunctionStatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.TranslationFunctionStatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockStatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockStatelessAnimation
@@ -26,10 +30,17 @@ import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.client.model.ModelPart
import net.minecraft.client.render.*
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.render.entity.model.EntityModel
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.util.Identifier
import net.minecraft.util.math.RotationAxis
@@ -46,6 +57,9 @@ abstract class PoseableEntityModel<T : Entity>(
) : EntityModel<T>(renderTypeFunc), ModelFrame {
var currentEntity: T? = null
/** Whether the renderer that will process this is going to do the weird -1.5 Y offset bullshit that the living entity renderer does. */
abstract val isForLivingEntityRenderer: Boolean
val poses = mutableMapOf<String, Pose<T, out ModelFrame>>()
lateinit var locatorAccess: LocatorAccess
@@ -102,6 +116,19 @@ abstract class PoseableEntityModel<T : Entity>(
currentState = null
}
open fun getOverlayTexture(entity: T?): Int? {
return if (entity is LivingEntity) {
OverlayTexture.packUv(
OverlayTexture.getU(0F),
OverlayTexture.getV(entity.hurtTime > 0 || entity.deathTime > 0)
)
} else if (entity != null) {
OverlayTexture.DEFAULT_UV
} else {
null
}
}
/**
* Registers a pose for this model.
*
@@ -249,7 +276,7 @@ abstract class PoseableEntityModel<T : Entity>(
stack,
buffer,
packedLight,
OverlayTexture.DEFAULT_UV,
getOverlayTexture(currentEntity) ?: packedOverlay,
red * r,
green * g,
blue * b,
@@ -268,7 +295,7 @@ abstract class PoseableEntityModel<T : Entity>(
stack,
consumer,
packedLight,
OverlayTexture.DEFAULT_UV,
getOverlayTexture(currentEntity) ?: packedOverlay,
layer.tint.x,
layer.tint.y,
layer.tint.z,
@@ -295,7 +322,14 @@ abstract class PoseableEntityModel<T : Entity>(
fun makeLayer(texture: Identifier, emissive: Boolean, translucent: Boolean): RenderLayer {
val multiPhaseParameters: RenderLayer.MultiPhaseParameters = RenderLayer.MultiPhaseParameters.builder()
.program(if (emissive) RenderPhase.ENTITY_TRANSLUCENT_EMISSIVE_PROGRAM else RenderPhase.ENTITY_TRANSLUCENT_PROGRAM)
.program(
when {
emissive && translucent -> RenderPhase.ENTITY_TRANSLUCENT_EMISSIVE_PROGRAM
!emissive && translucent -> RenderPhase.ENTITY_TRANSLUCENT_PROGRAM
!emissive && !translucent -> RenderPhase.ENTITY_CUTOUT_PROGRAM
else -> RenderPhase.ENTITY_TRANSLUCENT_EMISSIVE_PROGRAM // This one should be changed to maybe a custom shader? Translucent stuffs with things
}
)
.texture(RenderPhase.Texture(texture, false, false))
.transparency(if (translucent) RenderPhase.TRANSLUCENT_TRANSPARENCY else RenderPhase.NO_TRANSPARENCY)
.cull(RenderPhase.ENABLE_CULLING)
@@ -492,8 +526,11 @@ abstract class PoseableEntityModel<T : Entity>(
matrixStack.scale(1F, -1F, -1F)
matrixStack.scale(0.7F, 0.7F, 0.7F)
}
// Standard living entity offset, only God knows why Mojang did this.
matrixStack.translate(0.0, -1.5, 0.0)
if (isForLivingEntityRenderer) {
// Standard living entity offset, only God knows why Mojang did this.
matrixStack.translate(0.0, -1.5, 0.0)
}
locatorAccess.update(matrixStack, state.locatorStates)
}
@@ -46,6 +46,7 @@ abstract class PoseableEntityState<T : Entity> {
protected var age = 0
protected var currentPartialTicks = 0F
abstract fun getEntity(): T?
fun getPartialTicks() = currentPartialTicks
open fun updateAge(age: Int) {
this.age = age
@@ -93,8 +94,16 @@ abstract class PoseableEntityState<T : Entity> {
val model = currentModel
if (model != null) {
val poseImpl = model.getPose(pose) ?: return
poseParticles.removeIf { it !in poseImpl.idleAnimations.filterIsInstance<BedrockStatelessAnimation<*>>().flatMap { it.particleKeyFrames } }
poseParticles.removeIf { particle -> poseImpl.idleAnimations.filterIsInstance<BedrockStatelessAnimation<*>>().flatMap { it.particleKeyFrames }.none(particle::isSameAs) }
poseImpl.onTransitionedInto(this)
val entity = getEntity()
if (entity != null) {
poseImpl.idleAnimations
.filterIsInstance<BedrockStatelessAnimation<*>>()
.flatMap { it.particleKeyFrames }
.filter { particle -> particle.seconds == 0F && poseParticles.none(particle::isSameAs) }
.forEach { it.run(entity, this) }
}
}
}
@@ -20,7 +20,7 @@ class TexturedModel {
@SerializedName("minecraft:geometry")
val geometry: List<ModelGeometry>? = null
fun create() : TexturedModelData {
fun create(isForLivingEntityRenderer: Boolean) : TexturedModelData {
val modelData = ModelData()
val parts = HashMap<String, ModelPartData>()
val bones = HashMap<String, ModelBone>()
@@ -58,7 +58,7 @@ class TexturedModel {
when {
bone.parent == null -> {
// The root part always has a 24 Y offset. One of life's great mysteries.
modelTransform = ModelTransform.pivot(0F, 24F, 0F)
modelTransform = ModelTransform.pivot(0F, if (isForLivingEntityRenderer) 24F else 0F, 0F)
}
boneRotation != null -> {
modelTransform = ModelTransform.of(
@@ -14,12 +14,15 @@ import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.MoScope
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleEffect
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.resolveDouble
import java.util.SortedMap
@@ -49,6 +52,20 @@ class BedrockParticleKeyframe(
val locator: String,
val scripts: List<Expression>
) : BedrockEffectKeyframe(seconds) {
fun isSameAs(other: BedrockParticleKeyframe): Boolean {
return if (seconds != other.seconds) {
false
} else if (effect != other.effect) {
false
} else if (locator != other.locator) {
false
} else if (scripts.map { it.getString() }.toSet() != other.scripts.map { it.getString() }.toSet()) {
false
} else {
true
}
}
override fun <T : Entity> run(entity: T, state: PoseableEntityState<T>) {
val world = entity.world as ClientWorld
val matrixWrapper = state.locatorStates[locator] ?: state.locatorStates["root"]!!
@@ -58,16 +75,18 @@ class BedrockParticleKeyframe(
return
}
state.poseParticles.add(this)
val storm = ParticleStorm(
effect = effect,
matrixWrapper = matrixWrapper,
world = world,
sourceVelocity = { entity.velocity },
sourceAlive = { !entity.isRemoved && this in state.poseParticles },
sourceVisible = { !entity.isInvisible }
sourceVisible = { !entity.isInvisible },
onDespawn = { state.poseParticles.remove(this) }
)
state.poseParticles.add(this)
storm.runtime.execute(this.scripts)
storm.spawn()
}
@@ -149,6 +168,27 @@ data class BedrockAnimation(
MinecraftClient.getInstance().player?.sendMessage(params.getString(0).text())
Unit
}
registerInstruction<Entity>("sound") { entity, _, params ->
// Means we don't need to setup a sound registry entry for every single thing
val soundEvent = SoundEvent.of(params.getString(0).asIdentifierDefaultingNamespace())
if (soundEvent != null) {
val volume = if (params.contains(1)) params.getDouble(1).toFloat() else 1F
val pitch = if (params.contains(2)) params.getDouble(2).toFloat() else 1F
MinecraftClient.getInstance().soundManager.play(
PositionedSoundInstance(soundEvent, SoundCategory.NEUTRAL, volume, pitch, entity.world.random, entity.x, entity.y, entity.z)
)
}
Unit
}
registerInstruction<Entity>("random") { entity, _, params ->
val options = mutableListOf<MoValue>()
var index = 0
while (params.contains(index)) {
options.add(params.get(index))
index++
}
return@registerInstruction options.random() // Can throw an exception if they specified no args. They'd be idiots though.
}
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityMo
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1.PikachuModel
import net.minecraft.entity.Entity
/**
@@ -34,6 +33,12 @@ class BedrockStatelessAnimation<T: Entity>(frame: ModelFrame, val animation: Bed
}
override fun applyEffects(entity: T, state: PoseableEntityState<T>, previousSeconds: Float, newSeconds: Float) {
animation.applyEffects(entity, state, (previousSeconds % animation.animationLength).toFloat(), (newSeconds % animation.animationLength).toFloat())
val effectiveAnimationLength = animation.animationLength.takeUnless { it <= 0 }?.toFloat() ?: animation.effects.maxOfOrNull { it.seconds }?.takeIf { it != 0F }
val (loopedPreviousSeconds, loopedNewSeconds) = if (effectiveAnimationLength != null) {
(previousSeconds % effectiveAnimationLength) to (newSeconds % effectiveAnimationLength)
} else {
previousSeconds to newSeconds
}
animation.applyEffects(entity, state, loopedPreviousSeconds, loopedNewSeconds)
}
}
@@ -21,7 +21,7 @@ class PokeBallModel(root: ModelPart) : PoseableEntityModel<EmptyPokeBallEntity>(
override val rootPart = root.registerChildWithAllChildren("poke_ball")
override val base = getPart("base")
override val lid = getPart("lid")
override val isForLivingEntityRenderer = false
lateinit var shut: PokeBallPose
lateinit var open: PokeBallPose
@@ -25,6 +25,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
* @since May 1st, 2022
*/
class PokemonFloatingState : PoseableEntityState<PokemonEntity>() {
override fun getEntity() = null
override fun updatePartialTicks(partialTicks: Float) {
this.currentPartialTicks += partialTicks
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.pose.TransformedModelPart
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.client.render.OverlayTexture
import net.minecraft.util.math.Vec3d
/**
@@ -27,7 +28,7 @@ import net.minecraft.util.math.Vec3d
* @since December 4th, 2021
*/
abstract class PokemonPoseableModel : PoseableEntityModel<PokemonEntity>() {
override val isForLivingEntityRenderer = true
override fun getState(entity: PokemonEntity) = entity.delegate as PokemonClientDelegate
/** Registers the same configuration for both left and right shoulder poses. */
@@ -71,6 +72,17 @@ abstract class PokemonPoseableModel : PoseableEntityModel<PokemonEntity>() {
state: PoseableEntityState<PokemonEntity>
): StatefulAnimation<PokemonEntity, ModelFrame>? = null
override fun getOverlayTexture(entity: PokemonEntity?): Int {
return if (entity != null) {
OverlayTexture.packUv(
OverlayTexture.getU(0F),
OverlayTexture.getV(entity.hurtTime > 0)
)
} else {
OverlayTexture.DEFAULT_UV
}
}
open val cryAnimation: CryProvider = CryProvider { _, _ -> null }
//
// open fun getCryAnimation(
@@ -35,6 +35,7 @@ class PikachuModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
lateinit var standing: PokemonPose
lateinit var walk: PokemonPose
lateinit var battleidle: PokemonPose
lateinit var sleep: PokemonPose
val shoulderOffset = 1.5
override fun registerPoses() {
@@ -51,13 +52,17 @@ class PikachuModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
idleAnimations = arrayOf(bedrock("pikachu", "ground_sleep"))
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
idleAnimations = arrayOf(
singleBoneLook(),
bedrock("pikachu", "ground_walk")
)
)
@@ -93,7 +98,6 @@ class PikachuModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
quirks = arrayOf(blink),
condition = { it.isBattling },
idleAnimations = arrayOf(
singleBoneLook(),
bedrock("pikachu", "battle_idle")
)
@@ -59,7 +59,6 @@ class RaichuModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
quirks = arrayOf(blink),
condition = { !it.isBattling },
idleAnimations = arrayOf(
singleBoneLook(),
bedrock("raichu", "ground_walk")
)
)
@@ -71,7 +70,6 @@ class RaichuModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
quirks = arrayOf(blink),
condition = { it.isBattling },
idleAnimations = arrayOf(
singleBoneLook(),
bedrock("raichu", "battle_idle")
)
)
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.repository
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.client.render.models.blockbench.TexturedModel
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.client.model.ModelPart
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* The data registry responsible for berry fruit and flower models.
* This is only present on the client.
*/
object BerryModelRepository : JsonDataRegistry<TexturedModel> {
override val id = cobblemonResource("berry_models")
override val type = ResourceType.CLIENT_RESOURCES
override val observable = SimpleObservable<BerryModelRepository>()
override val gson: Gson = TexturedModel.GSON
override val typeToken: TypeToken<TexturedModel> = TypeToken.get(TexturedModel::class.java)
override val resourcePath = "bedrock/berries"
private val models = hashMapOf<Identifier, ModelPart>()
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, TexturedModel>) {
this.models.clear()
data.forEach { (identifier, model) ->
this.models[identifier] = model.create(isForLivingEntityRenderer = false).createModel()
}
Cobblemon.LOGGER.info("Loaded {} berry models", this.models.size)
}
fun modelOf(identifier: Identifier) = this.models[identifier]
}
@@ -20,6 +20,7 @@ object PokeBallModelRepository : VaryingModelRepository<EmptyPokeBallEntity, Pok
override val poserDirectories: List<String> = listOf("bedrock/$type/posers")
override val modelDirectories: List<String> = listOf("bedrock/$type/models")
override val animationDirectories: List<String> = listOf("bedrock/$type/animations")
override val isForLivingEntityRenderer = false
override val fallback = PokeBalls.POKE_BALL.name
@@ -30,6 +30,7 @@ object PokemonModelRepository : VaryingModelRepository<PokemonEntity, PokemonPos
override val poserDirectories = listOf("bedrock/$type/posers", "bedrock/posers")
override val modelDirectories = listOf("bedrock/$type/models", "bedrock/models")
override val animationDirectories: List<String> = listOf("bedrock/animations", "bedrock/$type/animations")
override val isForLivingEntityRenderer = true
override val fallback = cobblemonResource("substitute")
@@ -29,13 +29,14 @@ import net.minecraft.util.Pair
import java.io.File
import java.nio.charset.StandardCharsets
import java.util.function.BiFunction
import java.util.function.Function
import java.util.function.Supplier
abstract class VaryingModelRepository<E : Entity, M : PoseableEntityModel<E>> {
val posers = mutableMapOf<Identifier, (Bone) -> M>()
val variations = mutableMapOf<Identifier, VaryingRenderableResolver<E, M>>()
val texturedModels = mutableMapOf<Identifier, () -> Bone>()
val texturedModels = mutableMapOf<Identifier, (isForLivingEntityRenderer: Boolean) -> Bone>()
abstract val title: String
abstract val type: String
@@ -44,6 +45,8 @@ abstract class VaryingModelRepository<E : Entity, M : PoseableEntityModel<E>> {
abstract val modelDirectories: List<String>
abstract val animationDirectories: List<String>
abstract val fallback: Identifier
/** When using the living entity renderer in Java Edition, a root joint 24F (1.5) Y offset is necessary. I've no fucking idea why. */
abstract val isForLivingEntityRenderer: Boolean
abstract fun loadJsonPoser(json: String): (Bone) -> M
@@ -98,13 +101,11 @@ abstract class VaryingModelRepository<E : Entity, M : PoseableEntityModel<E>> {
fun registerModels(resourceManager: ResourceManager) {
var models = 0
for (directory in modelDirectories) {
MODEL_FACTORIES.forEach { entry ->
resourceManager.findResources(directory) { path ->
path.endsWith(entry.key) }
.map {
entry.value.apply(it.key, it.value) }
MODEL_FACTORIES.forEach { (key, func) ->
resourceManager.findResources(directory) { path -> path.endsWith(key) }
.map { func.apply(it.key, it.value) }
.forEach {
texturedModels[it.left] = it.right::get
texturedModels[it.left] = { isForLivingEntityRenderer -> it.right.apply(isForLivingEntityRenderer) }
models++
}
}
@@ -156,7 +157,7 @@ abstract class VaryingModelRepository<E : Entity, M : PoseableEntityModel<E>> {
}
companion object {
fun registerFactory(id: String, factory: BiFunction<Identifier, Resource, Pair<Identifier, Supplier<Bone>>>) {
fun registerFactory(id: String, factory: BiFunction<Identifier, Resource, Pair<Identifier, Function<Boolean, Bone>>>) {
MODEL_FACTORIES[id] = factory
}
@@ -164,15 +165,15 @@ abstract class VaryingModelRepository<E : Entity, M : PoseableEntityModel<E>> {
Needs to be java function to work with non kotlin sidemods.
- Waterpicker
*/
private var MODEL_FACTORIES = mutableMapOf<String, BiFunction<Identifier, Resource, Pair<Identifier, Supplier<Bone>>>>().also {
it[".geo.json"] = BiFunction<Identifier, Resource, Pair<Identifier, Supplier<Bone>>> { identifier: Identifier, resource: Resource ->
private var MODEL_FACTORIES = mutableMapOf<String, BiFunction<Identifier, Resource, Pair<Identifier, Function<Boolean, Bone>>>>().also {
it[".geo.json"] = BiFunction<Identifier, Resource, Pair<Identifier, Function<Boolean, Bone>>> { identifier: Identifier, resource: Resource ->
resource.inputStream.use { stream ->
val json = String(stream.readAllBytes(), StandardCharsets.UTF_8)
val resolvedIdentifier = Identifier(identifier.namespace, File(identifier.path).nameWithoutExtension)
val texturedModel = TexturedModel.from(json).create()
val supplier = Supplier<Bone>{texturedModel.createModel()}
Pair(resolvedIdentifier, supplier)
val texturedModel = TexturedModel.from(json)
val boneCreator: Function<Boolean, Bone> = Function { texturedModel.create(it).createModel() }
Pair(resolvedIdentifier, boneCreator)
}
}
}
@@ -30,7 +30,6 @@ class PokeBallRenderer(context: EntityRendererFactory.Context) : EntityRenderer<
override fun render(entity: EmptyPokeBallEntity, yaw: Float, partialTicks: Float, poseStack: MatrixStack, buffer: VertexConsumerProvider, packedLight: Int) {
val model = PokeBallModelRepository.getPoser(entity.pokeBall.name, entity.aspects.get())
poseStack.push()
poseStack.translate(0.0, 1.5 * 0.7F, 0.0)
poseStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw))
poseStack.scale(0.7F, -0.7F, -0.7F)
val vertexConsumer = ItemRenderer.getDirectItemGlintConsumer(buffer, model.getLayer(getTexture(entity)), false, false)
@@ -45,7 +45,7 @@ object PcCommand {
context.source.world.playSoundServer(
position = context.source.player!!.pos,
sound = CobblemonSounds.PC_ON,
volume = 1F,
volume = 0.5F,
pitch = 1F
)
return Command.SINGLE_SUCCESS

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