Compare commits

..
Author SHA1 Message Date
Plastered_Crab 8a7145ae4d Code to get observers to detect top section of berry trees when transitioning between states 2025-05-13 01:26:23 -07:00
Plastered_Crab 709e6f4a63 Allow for Food and Mob effects to be optional 2025-05-12 17:13:56 -07:00
Nick 73b72ff19d Merge branch 'ponigiri' into 'main'
Adds Ponigiri, Poke Puffs, Sinister Tea, and berry data changes and tool tip changes

See merge request cable-mc/cobblemon!1658
2025-05-12 15:22:58 -07:00
Plastered_CrabandNick 15aac5e20a Adds Ponigiri, Poke Puffs, Sinister Tea, and berry data changes and tool tip changes 2025-05-12 15:22:58 -07:00
Polymeta 6916cd51f7 Merge branch 'feature/datapack-starters' into 'main'
Add datapack-driven starter menu categories

See merge request cable-mc/cobblemon!1653
2025-05-12 12:11:37 +00:00
Winter b2ebfcc3b0 Simplify category validation 2025-05-12 12:15:13 +01:00
Hiroku 3b73493801 Merge branch 'spawning-upgrades' into 'main'
Major spawning revamp with herds and rapid spawning and refactored spawn terms

See merge request cable-mc/cobblemon!1634
2025-05-11 10:29:42 +00:00
Polymeta bfb48e8583 Merge branch 'particle-fixes' into 'main'
Particle fixes

See merge request cable-mc/cobblemon!1660
2025-05-11 09:14:23 +00:00
Apion 01c44537d0 Fix naked Gastly 2025-05-10 22:56:16 -04:00
Hiroku e414dd94c3 Merge branch 'main' into 'spawning-upgrades'
# Conflicts:
#   common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/condition/SpawningCondition.kt
2025-05-11 02:34:59 +00:00
Apion 2d4800a733 Fix issue where locator X axis was not aligned with blockbench. 2025-05-10 21:50:52 -04:00
Paul 90e193e5a0 Added more information about what was renamed in spawning system 2025-05-11 10:33:18 +10:00
Hiroku c7e86cf767 Merge branch 'fix-dimension-condition' into 'main'
Fixes dimension condition using the wrong ResourceLocation.

See merge request cable-mc/cobblemon!1659
2025-05-11 00:20:00 +00:00
Paul d8906992a3 Scraper for spawns now exports 'spawnablePositionType' instead of 'context' which is the modern name 2025-05-11 10:18:33 +10:00
Neovitalism 972c259f6d changelog so hiro doesn't send a guy to break my legs 2025-05-10 19:17:46 -05:00
Paul 8228424191 Merge remote-tracking branch 'origin/spawning-upgrades' into spawning-upgrades 2025-05-11 10:14:03 +10:00
Paul 9bb67087b3 Merge remote-tracking branch 'origin/main' into spawning-upgrades 2025-05-11 10:11:25 +10:00
Neovitalism 0f08446732 Moves the imports back 2025-05-10 19:03:53 -05:00
Neovitalism af09a6a0c7 Fixes dimension condition using the wrong ResourceLocation 2025-05-10 18:55:14 -05:00
Winter b4d2f39c48 Remove unused StarterDataLoader import 2025-05-10 16:16:16 +01:00
Winter 1a12bbd493 Ignore unused exception 2025-05-10 00:09:45 +01:00
Winter 8a24f635cf Undo reformat 2025-05-10 00:05:43 +01:00
Winter d6e066ddcd Validate starter categories 2025-05-10 00:03:35 +01:00
Polymeta 50335135c9 Merge branch '603.5' into 'main'
Half resolved 1207, hence the branch name

See merge request cable-mc/cobblemon!1652
2025-05-09 22:48:28 +00:00
AramandPolymeta 1ee6f83a5b Half resolved 1207, hence the branch name 2025-05-09 22:48:28 +00:00
Landon e8b747a432 Merge branch 'gibber-no-crash' into 'main'
Fix dialogue gibber not being read as nullable for "artificial" and null speaker faces

See merge request cable-mc/cobblemon!1657
2025-05-09 21:44:17 +00:00
Dani Voort 8f8804f3f4 Fix dialogue gibber not being read as nullable for "artificial" and null speaker faces 2025-05-09 22:59:42 +02:00
Hiroku ef826158ce Merge branch 'spicy-spicy-molang' into 'main'
Added `labels` and `has_label` as available MoLang functions

See merge request cable-mc/cobblemon!1656
2025-05-09 03:29:17 +00:00
SpicyandHiroku d32314742d Added labels and has_label as available MoLang functions 2025-05-09 03:29:16 +00:00
Hiroku e8e0ca47dd Merge branch 'issue-#1543-fix' into 'main'
Fixed typo while saving/loading NPCEntity causes data loss

Closes #1543

See merge request cable-mc/cobblemon!1655
2025-05-08 23:23:49 +00:00
Bluedude ebcac6e782 Edit CHANGELOG.md 2025-05-08 17:28:28 +00:00
Dan 63bfa634ae Fixed typo while saving/loading NPCEntity causes data loss 2025-05-08 18:26:05 +02:00
Winter 7ea85dbd1e Update CHANGELOG.md 2025-05-08 15:10:05 +01:00
Winter 6cdb14d2f8 Rely on JsonDataRegistry to auto register 2025-05-08 15:07:59 +01:00
Winter f041049576 Register StarterDataLoader 2025-05-08 15:07:23 +01:00
Winter e1aa6445fa Switch to JsonDataRegistry<StarterCategory> to be consistent with other registries 2025-05-08 15:06:51 +01:00
Winter f1b7ee5b9c Revert to original implementation 2025-05-08 15:03:35 +01:00
Polymeta 9ea0f7e4f8 Merge branch 'aprijuice-visual-tweaks' into 'main'
Aprijuice visual tweaks

See merge request cable-mc/cobblemon!1654
2025-05-08 08:46:51 +00:00
Plastered_CrabandPolymeta d0edd6ecbc Aprijuice visual tweaks 2025-05-08 08:46:51 +00:00
Winter 1372505486 Add license header 2025-05-07 22:46:18 +01:00
Winter a4ce4bf99b Add datapack starter categories with fallback support 2025-05-07 20:33:26 +01:00
Polymeta d72dad355c Merge branch '1544' into 'main'
1544, Add Pokemon Despawner to Config!

See merge request cable-mc/cobblemon!1650
2025-05-06 16:10:27 +00:00
AramandPolymeta 40b5726c5b 1544, Add Pokemon Despawner to Config! 2025-05-06 16:10:27 +00:00
Polymeta d32aa46832 init PokemonProperties with basefriendship is none is explicitly set
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-06 00:00:24 +01:00
Polymeta 5068e66fa7 Merge branch 'advancing' into 'main'
corrected logic that was incorrectly checking the ResourceLocation of the species

See merge request cable-mc/cobblemon!1649
2025-05-05 09:53:56 +00:00
MoeBoy76 482b682595 corrected logic that was incorrectly checking the ResourceLocation of the species 2025-05-05 12:31:57 +10:00
Paul ffecc58979 Fixed gibber click interactions not being as intended 2025-05-04 13:17:39 +10:00
Paul eaaf2ca05c Added unset_interaction to NPC structs 2025-05-04 12:16:16 +10:00
Paul e91651553b Added set_dialogue_interaction to NPC structs 2025-05-04 12:14:32 +10:00
Test a2094677cd update pot cook button 2025-05-03 00:12:26 -04:00
Polymeta eae4eb5121 Merge branch 'E' into 'main'
🇪

See merge request cable-mc/cobblemon!1646
2025-05-02 08:33:44 +00:00
TestandPolymeta ed3a008515 🇪 2025-05-02 08:33:43 +00:00
Polymeta 462074ce43 Merge branch 'sketch-copy-move-fix' into 'main'
Sketch copy move fix

Closes #1503

See merge request cable-mc/cobblemon!1645
2025-05-02 08:32:33 +00:00
Polymeta d54a85aa68 Merge branch 'aprijuice-visual-tweaks' into 'main'
Aprijuice visual tweaks

See merge request cable-mc/cobblemon!1647
2025-05-02 08:32:14 +00:00
Plastered_CrabandPolymeta bf8433f6e9 Aprijuice visual tweaks 2025-05-02 08:32:13 +00:00
CeeJay 51b0f768a0 looks better capitalized 2025-05-01 21:52:10 -04:00
CeeJay 106e487035 changelog for sketch fix 2025-05-01 21:51:08 -04:00
CeeJay 2ecbe28c87 remove extra line Fixes #1503 2025-04-30 21:48:12 -04:00
Polymeta 80112e0b70 pokemon can use flying pose again
Closes #1553
2025-05-01 00:05:38 +00:00
CeeJay f776542365 regex to capture special characters along with spaces in sketch copying move 2025-04-29 23:39:41 -04:00
Polymeta 045ca3a07f Merge branch 'sterr/silence' into 'main'
Pokemon no longer make sounds as they're being sent out and now respect silent nbt

See merge request cable-mc/cobblemon!1644
2025-04-29 16:04:25 +00:00
Sterrezo 2085f12fef Pokemon no longer make sounds as they're being sent out and now respect silent nbt 2025-04-29 02:08:17 +02:00
Hiroku 863b43f902 Debug code that you never saw shush 2025-04-28 10:25:32 +00:00
Paul 474918e414 Reimplemented lure cake nomnoms 2025-04-26 20:14:52 +10:00
Paul 0e93383c90 Fixed up saccharine honey tree spawning stuff 2025-04-26 17:41:04 +10:00
Paul abb44cf696 Testo fixo 2025-04-26 12:22:55 +10:00
Paul e6bd7cad72 Brought fishing in line with new spawning bucket normalizing 2025-04-26 11:41:19 +10:00
Paul e2db59c837 Oop fixed zone influences not being packed into contexts 2025-04-25 18:10:54 +10:00
Paul 7d06f1e2cd Major spawning revamp with herds and rapid spawning and refactored spawn terms 2025-04-25 14:39:33 +10:00
588 changed files with 7313 additions and 2063 deletions
+16
View File
@@ -0,0 +1,16 @@
### Additions
- Pokémon can now spawn in herds, either as distinct herd groupings or simply several Pokémon spawning at once.
### Fixes
- Fixed the "enabled" property in spawn files not actually being respected. Where do they find these devs?
### Developer
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
- Renamed heaps of things in the spawning system to make more sense.
- SpawningContext is now SpawnablePosition
- WorldSlice is SpawningZone
- SpawningProspector is now SpawningZoneGenerator
- Renamed things in Spawn Rules to go with the other renames:
- contextSelector is now spawnablePositionSelector
- context is now spawnable_position
+13
View File
@@ -21,6 +21,8 @@
- Fixed Moon Ball moon phase logic to actually work correctly
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon.
- Added modification to Minecraft Creative Inventory search to account for item names that contain `poké` when input contains `poke`.
- Added Campfire Pot as well as loads of new food items (Poke Puffs, Ponigiri, Sinister Tea, etc)
### Pokémon Added
@@ -136,6 +138,8 @@
- A number of Pokemon that float above the ground visually (Gastly, Klingklang, etc.) are no longer considered to be touching the ground
- Renamed `pokemonId` and `type` to `species` in relevant Advancement triggers for conformity, this is a breaking change.
- Added a separate `species` argument to the `pick_starter`, `pasture_use` and `resurrect_pokemon` Advancement triggers.
- Made `CobblemonAgingDespawner` thresholds configurable via the config file.
- Tweaked Berry flavor data to balance cooking pot mechanics
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -169,6 +173,13 @@
- Fixed dragon's breath not being usable on the restoration tank when it should be
- Fixed Moon Stones not interacting properly with dripstone blocks.
- Fixed some effects like particles from fishing rods appearing for players in the same coordinates in another world.
- Fixed an issue with Sketch where the Pokémon using Sketch would not properly learn moves with special characters in their name (e.g. King's Shield, Baby-Doll Eyes, etc.)
- Fixed wild Pokémon sometimes spawning with incorrect friendship values
- Fixed typo while saving/loading NPCEntity causes data loss
- Fixed an issue where catching a Pokémon while it was leashed to a fence would not update the fence.
- Fixed the `dimensions` spawning condition using the incorrect ResourceLocation, causing it to not function properly.
- Fix issue where locator X axis was not aligned with blockbench.
- Fix issue where particle effects that play on frame 1 on sendout would never play (Gastly)
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -214,6 +225,8 @@
- Fixed `heldItem` property inside spawn files not working and causing crashes
- Fixed `spawn_bedrock_particles` MoLang causing crashes when used in a server environment
- The Pokédex form lang key definition now follows `cobblemon.ui.pokedex.info.form.{species}-{formname}` instead of `cobblemon.ui.pokedex.info.form.{formname}`.
- Added `labels` & `has_label` as available Molang functions for the `speciesFunctions`
- Added datapack-defined starter categories via `data/<namespace>/starters/*.json`, with built-in fallback and `useConfigStarters` merge option.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import net.minecraft.client.searchtree.IdSearchTree;
import net.minecraft.client.searchtree.SearchTree;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@Mixin(IdSearchTree.class)
public abstract class SearchTreeMixin {
@Unique
private static final ThreadLocal<Boolean> reentryFlag = ThreadLocal.withInitial(() -> false);
/**
* Runs an additional search using the original search term where every instance of 'e' is replaced with 'é'
* Appends additional search results to original search results.
*/
@Inject(method = "search(Ljava/lang/String;)Ljava/util/List;", at = @At("RETURN"), cancellable = true)
private void onSearch(String string, CallbackInfoReturnable<List> cir) {
// If flag is true, skip to avoid infinite loop
if (reentryFlag.get()) {
return;
}
try {
reentryFlag.set(true);
List originalResults = cir.getReturnValue();
if (!string.contains("poke")) {
return;
}
String modifiedString = string.replace("poke", "poké");
List additionalResults = ((SearchTree)this).search(modifiedString);
Set combinedSet = new LinkedHashSet<>();
combinedSet.addAll(originalResults);
combinedSet.addAll(additionalResults);
cir.setReturnValue(new ArrayList<>(combinedSet));
} finally {
reentryFlag.set(false);
}
}
}
@@ -40,9 +40,9 @@ import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningProspector
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
import com.cobblemon.mod.common.api.storage.adapter.conversions.ReforgedConversion
@@ -160,8 +160,8 @@ object Cobblemon {
var isDedicatedServer = false
val showdownThread = ShowdownThread()
lateinit var config: CobblemonConfig
var prospector: SpawningProspector = CobblemonSpawningProspector
var areaContextResolver: AreaContextResolver = object : AreaContextResolver {}
var spawningZoneGenerator: SpawningZoneGenerator = CobblemonSpawningZoneGenerator
var areaSpawnablePositionResolver: AreaSpawnablePositionResolver = object : AreaSpawnablePositionResolver {}
val bestSpawner = BestSpawner
val battleRegistry = BattleRegistry
var storage = PokemonStoreManager()
@@ -9,12 +9,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.PotComponent
import com.cobblemon.mod.common.item.components.BaitEffectsComponent
import com.cobblemon.mod.common.item.components.FlavourComponent
import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.item.components.HeldItemCapableComponent
import com.cobblemon.mod.common.item.components.PokemonItemComponent
import com.cobblemon.mod.common.item.components.RodBaitComponent
import com.cobblemon.mod.common.item.components.*
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.component.DataComponentType
@@ -60,6 +55,20 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
.build())
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
.persistent(IngredientComponent.CODEC)
.networkSynchronized(IngredientComponent.PACKET_CODEC)
.build())
val FOOD: DataComponentType<FoodComponent> = create("food", DataComponentType.builder<FoodComponent>()
.persistent(FoodComponent.CODEC)
.networkSynchronized(FoodComponent.PACKET_CODEC)
.build())
val MOB_EFFECTS: DataComponentType<MobEffectsComponent> = create("mob_effects", DataComponentType.builder<MobEffectsComponent>()
.persistent(MobEffectsComponent.CODEC)
.networkSynchronized(MobEffectsComponent.PACKET_CODEC)
.build())
fun register() {
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:pokemon_item"), POKEMON_ITEM)
@@ -68,6 +77,9 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait_effects"), BAIT_EFFECTS)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
}
override val registry = BuiltInRegistries.DATA_COMPONENT_TYPE
@@ -34,6 +34,8 @@ 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.food.PonigiriItem
import com.cobblemon.mod.common.item.food.SinisterTeaItem
import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
import com.cobblemon.mod.common.platform.PlatformRegistry
@@ -363,123 +365,97 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val APRIJUICE_PINK = aprijuiceItem(Apricorn.PINK)
@JvmField
val POKE_PUFF = noSettingsItem("poke_puff") // todo make a PokePuffItem class for friendship boosting purposes
val PONIGIRI = create("ponigiri", PonigiriItem())
@JvmField
val SINISTER_TEA = create("sinister_tea", SinisterTeaItem())
@JvmField
val POKE_PUFF = pokepuffItem("poke_puff") // todo make a PokePuffItem class for friendship boosting purposes
// FOODS
@JvmField
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = create("tasty_tail", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(8)
.saturationModifier(12.8F)
.alwaysEdible()
.build())) {})
@JvmField
val PEWTER_CRUNCHIES = create("pewter_crunchies", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val RAGE_CANDY_BAR = create("rage_candy_bar", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val LAVA_COOKIE = create("lava_cookie", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val OLD_GATEAU = create("old_gateau", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val CASTELIACONE = create("casteliacone", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val SHALOR_SABLE = create("shalor_sable", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
val SHALOUR_SABLE = create("shalour_sable", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val LUMIOSE_GALETTE = create("lumiose_galette", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val BIG_MALASADA = create("big_malasada", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
/*@JvmField // todo see what we are doing for these (are there different kinds?)
val CURRY = create("curry", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
@@ -499,16 +475,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val JUBILIFE_MUFFIN = create("jubilife_muffin", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
/*@JvmField // todo see what we are doing for these (are there different kinds?)
val SANDWICH = create("sandwich", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
@@ -536,70 +507,31 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POTATO_MOCHI = create("potato_mochi", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val FRIED_RICE = create("fried_rice", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val CANDIED_APPLE = create("candied_apple", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
@JvmField
val CANDIED_BERRY = create("candied_berry", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val JELLY_DOUGHNUT = create("jelly_doughnut", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
.build())) {})
// todo etc?
@@ -1642,6 +1574,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun pokepuffItem(name: String): PokePuffItem {
val item = create("poke_puff", PokePuffItem())
return item
}
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
name,
CobblemonItem(Item.Properties()).also {
@@ -36,7 +36,7 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
@JvmField
val SACCHARINE_HONEY_LOG = create(SACCHARINE_HONEY_LOG_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.SACCHARINE_HONEY_LOG), 0, 1))
@JvmField
val INCENSE_SWEET_KEY: ResourceKey<PoiType> = createKey("saccharine_honey_log")
val INCENSE_SWEET_KEY: ResourceKey<PoiType> = createKey("incense_sweet")
@JvmField
val INCENSE_SWEET = create(INCENSE_SWEET_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.INCENSE_SWEET), 0, 1))
@@ -32,6 +32,6 @@ class PokemonCriterion(
}
override fun matches(player: ServerPlayer, context: Pokemon): Boolean {
return (species.equals(context.species.resourceIdentifier.path) || species.equals(context.species.resourceIdentifier)) && properties.matches(context)
return (species.equals(context.species.resourceIdentifier.path) || species.equals(context.species.resourceIdentifier.toString())) && properties.matches(context)
}
}
@@ -1,7 +1,7 @@
# Task Configuration
A task configuration is responsible for producing one or multiple tasks as part of a
[BrainConfiguration](../README.md). Tasks are the smallest unit of behaviour in a brain and are executed
[Behaviour Configuration](../README.md). Tasks are the smallest unit of behaviour in a brain and are executed
as part of an activity.
A task configuration can be a simple string that represents the type of task to create, or a JSON object
@@ -23,7 +23,8 @@ enum class Flavour(val colour: Int, val chatFormatting: ChatFormatting): StringR
DRY(0xFF0000, ChatFormatting.DARK_AQUA),
SWEET(0x0000FF, ChatFormatting.LIGHT_PURPLE),
BITTER(0xFFFF00, ChatFormatting.GREEN),
SOUR(0xFF00FF, ChatFormatting.YELLOW);
SOUR(0xFF00FF, ChatFormatting.YELLOW),
MILD(0xFF00FF, ChatFormatting.DARK_PURPLE);
override fun getSerializedName() = name
companion object {
@@ -54,6 +54,25 @@ fun getColourMixFromFlavours(dominantFlavours: List<Flavour>, forBubbles: Boolea
acc
}
return FastColor.ARGB32.color(
alphaSum / colors.size,
redSum / colors.size,
greenSum / colors.size,
blueSum / colors.size
)
}
fun getColourMixFromColors(colors: List<Int>): Int? {
if (colors.isEmpty()) return null
val (alphaSum, redSum, greenSum, blueSum) = colors.fold(IntArray(4)) { acc, color ->
acc[0] += FastColor.ARGB32.alpha(color)
acc[1] += FastColor.ARGB32.red(color)
acc[2] += FastColor.ARGB32.green(color)
acc[3] += FastColor.ARGB32.blue(color)
acc
}
return FastColor.ARGB32.color(
alphaSum / colors.size,
redSum / colors.size,
@@ -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.cooking
import com.cobblemon.mod.common.item.components.FoodComponent
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
data class Food(
val hunger: Int = 0,
val saturation: Float = 0f,
) {
companion object {
val CODEC: Codec<Food> = RecordCodecBuilder.create { builder ->
builder.group(
Codec.INT.optionalFieldOf("hunger", 0).forGetter { it.hunger },
Codec.FLOAT.optionalFieldOf("saturation", 0f).forGetter { it.saturation },
).apply(builder, ::Food)
}
}
fun toComponent(): FoodComponent = FoodComponent(hunger, saturation)
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.item.components.FoodComponent
import kotlin.math.ceil
import kotlin.math.round
object FoodUtils {
fun merge(components: List<FoodComponent>, extraHunger: Int = 0, extraSaturation: Float = 0f): FoodComponent {
if (components.isEmpty()) return FoodComponent(0, 0f)
val count = components.size
val totalHunger = components.sumOf { it.hunger.toDouble() } + extraHunger
val totalSaturation = components.sumOf { it.saturation.toDouble() } + extraSaturation
val multiplier = when (count) {
1 -> 1.0
2 -> 0.8
3 -> 0.6
else -> 0.4
}
val adjustedHunger = ceil(totalHunger * multiplier)
val adjustedSaturation = totalSaturation * multiplier
return FoodComponent(
hunger = adjustedHunger.toInt(),
saturation = (round(adjustedSaturation * 100) / 100f).toFloat()
)
}
}
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
data class MobCookingEffects(
val appliedEffects: List<SerializableMobEffectInstance> = emptyList()
) {
companion object {
val CODEC: Codec<MobCookingEffects> = RecordCodecBuilder.create { builder ->
builder.group(
SerializableMobEffectInstance.CODEC.listOf().optionalFieldOf("appliedEffects", emptyList()).forGetter { it.appliedEffects }
).apply(builder, ::MobCookingEffects)
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import net.minecraft.world.effect.MobEffectInstance
import kotlin.math.ceil
import kotlin.math.max
object MobEffectUtils {
fun mergeEffects(effects: List<SerializableMobEffectInstance>): List<SerializableMobEffectInstance> {
val grouped = effects.groupBy { it.effect }
return grouped.map { (effectId, group) ->
val amplifier = mergeAmplifiers(group.map { it.amplifier })
val duration = mergeDurations(group.map { it.duration })
val first = group.first()
SerializableMobEffectInstance(
effect = effectId,
duration = duration,
amplifier = amplifier,
ambient = first.ambient,
visible = first.visible,
showIcon = first.showIcon
)
}
}
private fun mergeDurations(durations: List<Int>): Int {
if (durations.isEmpty()) return 0
val sorted = durations.sortedDescending()
var result = 0.0
for ((index, value) in sorted.withIndex()) {
val multiplier = when (index) {
0 -> 1.0
1 -> 0.75
2 -> 0.5
else -> 0.25
}
result += value * multiplier
}
return ceil(result).toInt()
}
private fun mergeAmplifiers(amplifiers: List<Int>): Int {
if (amplifiers.isEmpty()) return 0
val sorted = amplifiers.sortedDescending()
var result = 0.0
for ((index, value) in sorted.withIndex()) {
val multiplier = when (index) {
0 -> 1.0
1 -> 0.75
2 -> 0.5
else -> 0.25
}
result += (value + 1) * multiplier
}
return ceil(result).toInt() - 1
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.PokePuffItem
import com.cobblemon.mod.common.item.PokePuffItem.Companion.DISLIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.item.PokePuffItem.Companion.LIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.core.registries.BuiltInRegistries
object PokePuffUtils {
fun calculateFriendshipChange(stack: ItemStack, nature: Nature): Int {
val dominantFlavour = getDominantFlavour(stack) ?: return 0
val isNeutralNature = nature.favouriteFlavour == nature.dislikedFlavour
val (baseFriendship) = calculateBaseFriendship(stack)
return when {
dominantFlavour == nature.favouriteFlavour -> (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
dominantFlavour == Flavour.MILD && isNeutralNature -> (baseFriendship / 2.0).coerceAtLeast(1.0).toInt()
else -> (-baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
}
fun calculateBaseFriendship(stack: ItemStack): Pair<Double, Double> {
val ingredientIds = stack.get(CobblemonItemComponents.INGREDIENT)?.ingredientIds ?: emptyList()
var baseFriendship = 0.0
for (id in ingredientIds) {
val item = BuiltInRegistries.ITEM.get(id)
val itemStack = ItemStack(item)
val seasoning = Seasonings.getFromItemStack(itemStack) ?: continue
val dominantValue = seasoning.flavours?.values?.maxOrNull() ?: 0
baseFriendship += dominantValue
}
val ingredientStrings = ingredientIds.map { it.toString() }
val hasSugar = "minecraft:sugar" in ingredientStrings
val hasSweet = ingredientStrings.any { it.startsWith("cobblemon:") && it.endsWith("_sweet") }
val multiplier = when {
hasSugar && hasSweet -> 5.0
hasSweet -> 2.25
hasSugar -> 1.75
else -> 1.0
}
val finalBaseFriendship = baseFriendship * multiplier
return finalBaseFriendship to multiplier
}
fun getDominantFlavour(stack: ItemStack): Flavour? {
val flavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours ?: return null
val max = flavours.values.maxOrNull() ?: return null
val dominant = flavours.filterValues { it == max }.keys
return when {
dominant.isEmpty() -> null
dominant.size > 1 -> Flavour.MILD
else -> dominant.first()
}
}
fun getFlavorNatureLabel(flavour: Flavour): String {
return when (flavour) {
Flavour.SPICY -> "attack"
Flavour.DRY -> "special_attack"
Flavour.SWEET -> "speed"
Flavour.BITTER -> "special_defense"
Flavour.SOUR -> "defense"
Flavour.MILD -> "neutral"
}
}
fun getFriendshipChangeLiked(stack: ItemStack): Int {
val (baseFriendship) = calculateBaseFriendship(stack)
return (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
}
fun getFriendshipChangeDisliked(stack: ItemStack): Int {
val (baseFriendship) = calculateBaseFriendship(stack)
return (baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
}
@@ -25,11 +25,15 @@ import net.minecraft.world.item.Item
data class Seasoning(
val ingredient: RegistryLikeCondition<Item>,
@SerializedName("flavours", alternate = ["flavors"])
val flavours: Map<Flavour, Int>,
val flavours: Map<Flavour, Int>? = null,
@SerializedName("colour", alternate = ["color"])
val colour: DyeColor,
@SerializedName("baitEffects")
val baitEffects: List<SpawnBait.Effect> = emptyList()
val baitEffects: List<SpawnBait.Effect> = emptyList(),
@SerializedName("food")
val food: Food? = null,
@SerializedName("mobEffects")
val mobEffects: MobCookingEffects? = null
) {
companion object {
val CODEC: Codec<Seasoning> = RecordCodecBuilder.create { builder ->
@@ -37,7 +41,9 @@ data class Seasoning(
ITEM_REGISTRY_LIKE_CODEC.fieldOf("ingredient").forGetter { it.ingredient },
Codec.unboundedMap(Flavour.CODEC, Codec.INT).fieldOf("flavours").forGetter { it.flavours }, // Use map codec
DyeColor.CODEC.fieldOf("colour").forGetter { it.colour },
SpawnBait.Effect.CODEC.listOf().optionalFieldOf("baitEffects", emptyList()).forGetter { it.baitEffects }
SpawnBait.Effect.CODEC.listOf().optionalFieldOf("baitEffects", emptyList()).forGetter { it.baitEffects },
Food.CODEC.optionalFieldOf("food", Food(0, 0f)).forGetter { it.food ?: Food(0, 0f) },
MobCookingEffects.CODEC.optionalFieldOf("mobEffects", MobCookingEffects()).forGetter { it.mobEffects ?: MobCookingEffects() }
).apply(builder, ::Seasoning)
}
@@ -47,7 +53,9 @@ data class Seasoning(
ingredient = RegistryLikeIdentifierCondition<Item>(cobblemonResource("blank")),
flavours = Flavour.entries.associate { it to 0 },
colour = DyeColor.WHITE,
baitEffects = emptyList()
baitEffects = emptyList(),
food = Food(),
mobEffects = MobCookingEffects()
)
}
}
@@ -46,7 +46,9 @@ object Seasonings : JsonDataRegistry<Seasoning> {
val BLANK_SEASONING = Seasoning(
ingredient = RegistryLikeIdentifierCondition<Item>(cobblemonResource("blank")),
flavours = emptyMap(),
colour = DyeColor.WHITE
colour = DyeColor.WHITE,
food = Food(),
mobEffects = MobCookingEffects()
)
override fun sync(player: ServerPlayer) {
@@ -59,7 +61,10 @@ object Seasonings : JsonDataRegistry<Seasoning> {
it.identifier to Seasoning(
ingredient = RegistryLikeIdentifierCondition(it.identifier),
flavours = it.flavours.toMap(),
colour = it.colour
colour = it.colour,
baitEffects = emptyList(),
food = Food(),
mobEffects = MobCookingEffects()
)
}.toMutableMap()
finalData.putAll(data)
@@ -75,8 +80,29 @@ object Seasonings : JsonDataRegistry<Seasoning> {
val holder = stack.itemHolder
val seasoning = seasonings.find { it.ingredient.fits(holder) } ?: BLANK_SEASONING
val inherentFlavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours ?: emptyMap()
val seasoningFlavours = seasoning.flavours ?: emptyMap()
return seasoning.flavours.mapValues { (flavour, value) -> inherentFlavours.getOrDefault(flavour, 0) + value }
return seasoningFlavours.mapValues { (flavour, value) ->
inherentFlavours.getOrDefault(flavour, 0) + value
}
}
fun getFoodFromItemStack(stack: ItemStack): Food? {
return getFromItemStack(stack)?.food
}
fun hasFood(stack: ItemStack): Boolean {
val effects = getFromItemStack(stack)?.food ?: return false
return effects.hunger > 0 || effects.saturation > 0f
}
fun getMobEffectFromItemStack(stack: ItemStack): MobCookingEffects? {
return getFromItemStack(stack)?.mobEffects
}
fun hasMobEffect(stack: ItemStack): Boolean {
val effects = getFromItemStack(stack)?.mobEffects ?: return false
return effects.appliedEffects.isNotEmpty()
}
fun getBaitEffectsFromItemStack(stack: ItemStack): List<SpawnBait.Effect> {
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.Holder
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectInstance
data class SerializableMobEffectInstance(
val effect: ResourceLocation,
val duration: Int,
val amplifier: Int = 0,
val ambient: Boolean = false,
val visible: Boolean = true,
val showIcon: Boolean = true
) {
fun toInstance(): MobEffectInstance {
val holder = BuiltInRegistries.MOB_EFFECT.getHolder(effect)
.orElseThrow { IllegalArgumentException("Unknown MobEffect: $effect") }
return MobEffectInstance(holder, duration, amplifier, ambient, visible, showIcon)
}
companion object {
val CODEC: Codec<SerializableMobEffectInstance> = RecordCodecBuilder.create { builder ->
builder.group(
ResourceLocation.CODEC.fieldOf("effect").forGetter { it.effect },
Codec.INT.fieldOf("duration").forGetter { it.duration },
Codec.INT.fieldOf("amplifier").forGetter { it.amplifier },
Codec.BOOL.optionalFieldOf("ambient", false).forGetter { it.ambient },
Codec.BOOL.optionalFieldOf("visible", true).forGetter { it.visible },
Codec.BOOL.optionalFieldOf("showIcon", true).forGetter { it.showIcon }
).apply(builder, ::SerializableMobEffectInstance)
}
}
}
@@ -25,6 +25,7 @@ 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
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveToWorldEvent
import com.cobblemon.mod.common.api.events.entity.SpawnBucketChosenEvent
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.fishing.*
@@ -245,8 +246,6 @@ object CobblemonEvents {
@JvmField
val POKEROD_REEL = CancelableObservable<PokerodReelEvent>()
@JvmField
val BOBBER_BUCKET_CHOSEN = EventObservable<BobberBucketChosenEvent>()
@JvmField
val BOBBER_SPAWN_POKEMON_PRE = CancelableObservable<BobberSpawnPokemonEvent.Pre>()
@JvmField
val BOBBER_SPAWN_POKEMON_MODIFY = EventObservable<BobberSpawnPokemonEvent.Modify>()
@@ -255,6 +254,9 @@ object CobblemonEvents {
@JvmField
val BAIT_EFFECT_REGISTRATION = EventObservable<BaitEffectFunctionRegistryEvent>()
@JvmField
val SPAWN_BUCKET_CHOSEN = EventObservable<SpawnBucketChosenEvent>()
// Baits and Lures
@JvmField
val BAIT_SPAWN_POKEMON_MODIFY = EventObservable<BaitSpawnPokemonEvent.Modify>()
@@ -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.entity
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
/**
* Event fired when the spawning system makes a decision on which bucket to spawn from.
*/
class SpawnBucketChosenEvent(
/** The spawner this is happening within. */
val spawner: Spawner,
/** The cause of the spawn. */
val spawnCause: SpawnCause,
/** The bucket that was chosen. You can alter this. */
var bucket: SpawnBucket,
/** The final weights that were used to make the choice. */
val bucketWeights: Map<SpawnBucket, Float>
)
@@ -10,15 +10,15 @@ package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import net.minecraft.world.entity.Entity
/**
* Event fired when an entity is spawned using the [BestSpawner]. The entity that was spawned
* and the [SpawningContext] of the spawn are provided. Cancelling this event will prevent the
* and the [SpawnablePosition] of the spawn are provided. Cancelling this event will prevent the
* entity from spawning.
*
* @author Hiroku
* @since April 22nd, 2023
*/
class SpawnEvent<T : Entity>(val entity: T, val ctx: SpawningContext) : Cancelable()
class SpawnEvent<T : Entity>(val entity: T, val spawnablePosition: SpawnablePosition) : Cancelable()
@@ -8,12 +8,12 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.itemRegistry
@@ -28,23 +28,23 @@ interface BobberSpawnPokemonEvent {
/**
* Event that is fired before a Pokemon is spawned by a bobber.
* @param bobber The PokeRodFishingBobberEntity that is spawning the Pokemon.
* @param chosenBucket The bucket that is chosen.
* @param spawnAction The spawn that is planned.
* @param rod The ItemStack of the rod that the bobber is attached to.
*/
data class Pre(
val bobber: PokeRodFishingBobberEntity,
val chosenBucket: SpawnBucket,
val spawnAction: SpawnAction<*>,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
/**
* Event that is fired when a Pokemon is modified before it is spawned by a bobber.
* @param chosenBucket The bucket that is chosen.
* @param spawnAction The [SpawnAction] that is motivating the spawn.
* @param rod The ItemStack of the rod that the bobber is attached to.
* @param pokemon The Pokemon that is modified.
*/
data class Modify(
val chosenBucket: SpawnBucket,
val spawnAction: SpawnAction<*>,
val rod: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -58,12 +58,12 @@ interface BobberSpawnPokemonEvent {
*/
data class Post(
val bobber: PokeRodFishingBobberEntity,
val chosenBucket: SpawnBucket,
val spawnAction: SpawnAction<*>,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(chosenBucket, { it.name }),
"chosen_bucket" to ObjectValue(spawnAction.bucket, { it.name }),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.api.fishing
import kotlin.math.ceil
object SpawnBaitUtils {
fun mergeEffects(effects: List<SpawnBait.Effect>): List<SpawnBait.Effect> {
val grouped = effects.groupBy { Pair(it.type, it.subcategory) }
@@ -25,12 +27,12 @@ object SpawnBaitUtils {
val multiplier = when (count) {
2 -> 0.8
3 -> 0.9
3 -> 0.6
else -> 1.0
}
val adjustedChance = (totalChance * multiplier).coerceAtMost(1.0)
val adjustedValue = totalValue * multiplier
val adjustedValue = ceil(totalValue * multiplier)
SpawnBait.Effect(type, subcategory, adjustedChance, adjustedValue)
}
@@ -32,6 +32,7 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
@@ -45,7 +46,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.api.storage.party.PartyStore
@@ -811,6 +812,17 @@ object MoLangFunctions {
npc.hideNameTag = !nameTagVisible
return@put DoubleValue.ONE
}
map.put("unset_interaction") {
npc.interaction = null
return@put DoubleValue.ONE
}
map.put("set_dialogue_interaction") { params ->
val dialogue = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction = DialogueNPCInteractionConfiguration().also {
it.dialogue = dialogue
}
return@put DoubleValue.ONE
}
map.put("set_script_interaction") { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction = ScriptNPCInteractionConfiguration().also {
@@ -1184,7 +1196,7 @@ object MoLangFunctions {
}
)
val spawningContextFunctions = mutableListOf<(SpawningContext) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val spawningContextFunctions = mutableListOf<(SpawnablePosition) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ spawningContext ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(spawningContext.world).asWorldMoLangValue()
@@ -1198,7 +1210,6 @@ object MoLangFunctions {
map.put("moon_phase") { _ -> DoubleValue(spawningContext.moonPhase.toDouble()) }
map.put("can_see_sky") { _ -> DoubleValue(spawningContext.canSeeSky) }
map.put("sky_light") { _ -> DoubleValue(spawningContext.skyLight.toDouble()) }
map.put("bucket") { _ -> StringValue(spawningContext.cause.bucket.name) }
map.put("player") { _ ->
val causeEntity = spawningContext.cause.entity ?: return@put DoubleValue.ZERO
if (causeEntity is ServerPlayer) {
@@ -1335,6 +1346,8 @@ object MoLangFunctions {
map.put("hitbox_height") { DoubleValue(species.hitbox.height) }
map.put("hitbox_fixed") { DoubleValue(species.hitbox.fixed) }
map.put("catch_rate") { DoubleValue(species.catchRate) }
map.put("labels") { return@put species.labels.asArrayValue { StringValue(it) } }
map.put("has_label") { params -> DoubleValue(species.labels.contains(params.getString(0))) }
map
}
)
@@ -1536,7 +1549,7 @@ object MoLangFunctions {
return value
}
fun SpawningContext.asMoLangValue(): ObjectValue<SpawningContext> {
fun SpawnablePosition.asMoLangValue(): ObjectValue<SpawnablePosition> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.events.CobblemonEvents
@@ -385,7 +386,13 @@ open class PokemonProperties {
shiny?.let { pokemon.shiny = it }
gender?.let { pokemon.gender = it }
level?.let { pokemon.level = it }
friendship?.let { pokemon.setFriendship(it) }
friendship.also {
if (it != null) {
pokemon.setFriendship(it)
} else {
pokemon.setFriendship(pokemon.form.baseFriendship)
}
}
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
ability?.let { this.createAbility(it, pokemon.form)?.let(pokemon::updateAbility) }
@@ -798,4 +805,19 @@ open class PokemonProperties {
return potentialAbility.template.create(false, potentialAbility.priority)
}
/**
* Shared logic for figuring out what range of levels is possible for a Pokémon, given that some
* kind of optional level range has been requested. Used mainly as a thing for spawning to avoid
* copy-pasting code.
*/
fun deriveLevelRange(levelRange: IntRange?): IntRange {
return levelRange.let { levelRange ->
val pokemonLevel = level
levelRange
?: pokemonLevel?.until(pokemonLevel)
?: IntRange(1, config.maxPokemonLevel)
}
}
fun hasSpecies() = species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) } != null
}
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.api.snowstorm
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.cobblemon.mod.common.util.codec.optionalFieldOfWithDefault
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
@@ -34,7 +32,11 @@ class EmitterSpace(
}
fun initializeEmitterMatrix(rootMatrix: MatrixWrapper, locatorMatrix: MatrixWrapper): MatrixWrapper {
val rootRotation = rootMatrix.matrix.getRotation(AxisAngle4f())
var rootRotation = rootMatrix.matrix.getRotation(AxisAngle4f())
//When the locator hasn't yet been initialized, we start getting NaNs, so default to no rotation instead
if (rootRotation.x.isNaN() || rootRotation.y.isNaN() || rootRotation.z.isNaN() || rootRotation.angle.isNaN()) {
rootRotation = AxisAngle4f()
}
val scale = Vector3f()
if (scaling == ScalingMode.ENTITY) {
@@ -14,11 +14,16 @@ import com.bedrockk.molang.ast.NumberExpression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.math.convertSphericalToCartesian
import com.cobblemon.mod.common.util.math.getRotationMatrix
import com.cobblemon.mod.common.util.math.times
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveVec3d
import com.cobblemon.mod.common.util.writeString
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
@@ -27,10 +32,10 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
import net.minecraft.world.phys.Vec3
interface ParticleEmitterShape : CodecMapped {
companion object : ArbitrarilyMappedSerializableCompanion<ParticleEmitterShape, ParticleEmitterShapeType>(
@@ -261,9 +266,9 @@ class BoxParticleEmitterShape(
}
} else {
Vec3(
Random.nextDouble(sizes.x) - sizes.x / 2,
Random.nextDouble(sizes.y) - sizes.y / 2,
Random.nextDouble(sizes.z) - sizes.z / 2
Random.nextDouble(sizes.x) - sizes.x / 2.0,
Random.nextDouble(sizes.y) - sizes.y / 2.0,
Random.nextDouble(sizes.z) - sizes.z / 2.0
)
}
@@ -18,29 +18,30 @@ import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.LavafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.GroundedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.LavafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SeafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SubmergedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SurfaceSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.GroundedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.LavafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SeafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SubmergedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SurfaceSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.detail.NPCSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonHerdSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawner
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.GroundedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.LavafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SeafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SurfaceSpawnablePosition
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.api.spawning.spawner.AreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
@@ -57,12 +58,12 @@ import net.minecraft.server.MinecraftServer
* the configuration and many other properties used by the spawner.
*
* The Best Spawner (in world spawning) works in distinct stages that are:
* - Prospecting (see: [SpawningProspector])
* - Context resolving (see: [AreaContextResolver])
* - Spawning zone generation (see: [SpawningZoneGenerator])
* - Spawnable position resolving (see: [AreaSpawnablePositionResolver])
* - Spawn selection (see: [SpawningSelector])
* - Spawn action (see: [SpawnAction])
*
* In the case of more specialized use, the creation of a [SpawningContext] that motivates most of the spawn
* In the case of more specialized use, the creation of a [SpawnablePosition] that motivates most of the spawn
* process can be created manually, skipping the first two steps.
*
* An individually spawnable entity is defined as a [SpawnDetail]. A processor handling this process is a [Spawner].
@@ -74,10 +75,10 @@ import net.minecraft.server.MinecraftServer
* Spawning is coordinated and ticked using a [SpawnerManager], and all the current managers are accessible from
* [BestSpawner.spawnerManagers].
*
* Spawners and contexts are often put under the effects of [SpawningInfluence]s which can be used to make temporary
* or lasting changes to spawning for whatever component they are attached to (whether that is a spawner or a context).
* This pairs strongly with edits to the influence builders inside the [PlayerSpawnerFactory]. The range of effects
* an influence can have is significant.
* Spawners and spawnable positions are often put under the effects of [SpawningInfluence]s which can be used to make
* temporary or lasting changes to spawning for whatever component they are attached to (whether that is a spawner or a
* spawnable position). This pairs strongly with edits to the influence builders inside the [PlayerSpawnerFactory]. The
* range of effects an influence can exert is very broad.
*
* Broad configuration of this spawning system is found in [BestSpawner.config].
*
@@ -87,7 +88,7 @@ import net.minecraft.server.MinecraftServer
object BestSpawner {
var config = BestSpawnerConfig()
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
var defaultPokemonDespawner: Despawner<PokemonEntity> = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
lateinit var defaultPokemonDespawner: Despawner<PokemonEntity>
lateinit var fishingSpawner: FishingSpawner
fun init() {
@@ -103,23 +104,24 @@ object BestSpawner {
LOGGER.info("Loaded ${SpawningCondition.conditionTypes.size} spawning condition types.")
SpawningContextCalculator.register(GroundedSpawningContextCalculator)
SpawningContextCalculator.register(SeafloorSpawningContextCalculator)
SpawningContextCalculator.register(LavafloorSpawningContextCalculator)
SpawningContextCalculator.register(SubmergedSpawningContextCalculator)
SpawningContextCalculator.register(SurfaceSpawningContextCalculator)
SpawnablePositionCalculator.register(GroundedSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SeafloorSpawnablePositionCalculator)
SpawnablePositionCalculator.register(LavafloorSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SubmergedSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SurfaceSpawnablePositionCalculator)
SpawningContext.register(name = "grounded", clazz = GroundedSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "seafloor", clazz = SeafloorSpawningContext::class.java, defaultCondition = SeafloorSpawningCondition.NAME)
SpawningContext.register(name = "lavafloor", clazz = LavafloorSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "submerged", clazz = SubmergedSpawningContext::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
SpawningContext.register(name = "surface", clazz = SurfaceSpawningContext::class.java, defaultCondition = SurfaceSpawningCondition.NAME)
SpawningContext.register(name = "fishing", clazz = FishingSpawningContext::class.java, defaultCondition = FishingSpawningCondition.NAME)
SpawnablePosition.register(name = "grounded", clazz = GroundedSpawnablePosition::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawnablePosition.register(name = "seafloor", clazz = SeafloorSpawnablePosition::class.java, defaultCondition = SeafloorSpawningCondition.NAME)
SpawnablePosition.register(name = "lavafloor", clazz = LavafloorSpawnablePosition::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawnablePosition.register(name = "submerged", clazz = SubmergedSpawnablePosition::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
SpawnablePosition.register(name = "surface", clazz = SurfaceSpawnablePosition::class.java, defaultCondition = SurfaceSpawningCondition.NAME)
SpawnablePosition.register(name = "fishing", clazz = FishingSpawnablePosition::class.java, defaultCondition = FishingSpawningCondition.NAME)
LOGGER.info("Loaded ${SpawningContext.contexts.size} spawning context types.")
LOGGER.info("Loaded ${SpawnablePosition.spawnablePositionTypes.size} spawnable position types.")
SpawnDetail.registerSpawnType(name = PokemonSpawnDetail.TYPE, PokemonSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = NPCSpawnDetail.TYPE, NPCSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = PokemonHerdSpawnDetail.TYPE, PokemonHerdSpawnDetail::class.java)
LOGGER.info("Loaded ${SpawnDetail.spawnDetailTypes.size} spawn detail types.")
loadConfig()
@@ -129,6 +131,7 @@ object BestSpawner {
}
fun loadConfig() {
defaultPokemonDespawner = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
config = BestSpawnerConfig.load()
}
@@ -142,4 +145,4 @@ object BestSpawner {
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
}
}
}
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.condition.BiomePrecalculation
import com.cobblemon.mod.common.api.spawning.condition.BucketPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.ContextPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.SpawnablePositionTypePrecalculation
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.data.CobblemonDataProvider
import net.minecraft.server.MinecraftServer
@@ -31,7 +31,7 @@ object CobblemonSpawnPools {
fun load() {
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world")
.addPrecalculators(
ContextPrecalculation,
SpawnablePositionTypePrecalculation,
BucketPrecalculation,
BiomePrecalculation
)
@@ -39,7 +39,7 @@ object CobblemonSpawnRules : JsonDataRegistry<SpawnRule> {
override val gson = GsonBuilder()
.registerTypeAdapter(SpawnRuleComponent::class.java, SpawnRuleComponentAdapter)
.registerTypeAdapter(SpawnDetailSelector::class.java, SpawnDetailSelectorAdapter)
.registerTypeAdapter(SpawningContextSelector::class.java, SpawningContextSelectorAdapter)
.registerTypeAdapter(SpawnablePositionSelector::class.java, SpawnablePositionSelectorAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
@@ -56,8 +56,8 @@ object CobblemonSpawnRules : JsonDataRegistry<SpawnRule> {
SpawnDetailSelector.register<ExpressionSpawnDetailSelector>("expression")
SpawningContextSelector.register<ExpressionSpawningContextSelector>("expression")
SpawningContextSelector.register<ConditionalSpawningContextSelector>("conditional")
SpawnablePositionSelector.register<ExpressionSpawnablePositionSelector>("expression")
SpawnablePositionSelector.register<ConditionalSpawnablePositionSelector>("conditional")
}
val rules = mutableMapOf<ResourceLocation, SpawnRule>()
@@ -10,11 +10,10 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningInfluenceProspector
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos.blockToSectionCoord
@@ -27,21 +26,21 @@ import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
/**
* A spawning prospector that takes a straightforward approach
* in slicing out a [WorldSlice]. If you want to replace this,
* change over the value of [Cobblemon.prospector].
* A spawning zone generator that takes a straightforward approach
* in slicing out a [SpawningZone]. If you want to replace this,
* change over the value of [Cobblemon.spawningZoneGenerator].
*
* @author Hiroku
* @since February 5th, 2022
*/
object CobblemonSpawningProspector : SpawningProspector {
override fun prospect(
object CobblemonSpawningZoneGenerator : SpawningZoneGenerator {
override fun generate(
spawner: Spawner,
area: SpawningArea
): WorldSlice {
val world = area.world
var baseY = area.baseY
var height = area.height
input: SpawningZoneInput
): SpawningZone {
val world = input.world
var baseY = input.baseY
var height = input.height
if (baseY < world.minBuildHeight) {
val difference = world.minBuildHeight - baseY
baseY += difference
@@ -60,34 +59,34 @@ object CobblemonSpawningProspector : SpawningProspector {
}
val minimumDistanceBetweenEntities = config.minimumDistanceBetweenEntities
val nearbyEntityPositions = area.world.getEntities(
area.cause.entity,
val nearbyEntityPositions = input.world.getEntities(
input.cause.entity,
AABB.ofSize(
Vec3(
area.baseX + area.length / 2.0,
input.baseX + input.length / 2.0,
baseY + height / 2.0,
area.baseZ + area.width / 2.0
input.baseZ + input.width / 2.0
),
area.length + minimumDistanceBetweenEntities,
input.length + minimumDistanceBetweenEntities,
height + minimumDistanceBetweenEntities,
area.width + minimumDistanceBetweenEntities
input.width + minimumDistanceBetweenEntities
)
).filterIsInstance<LivingEntity>()
.map { it.position() }
val defaultState = Blocks.STONE.defaultBlockState()
val defaultBlockData = WorldSlice.BlockData(defaultState, 0, 0)
val defaultBlockData = SpawningZone.BlockData(defaultState, 0, 0)
val blocks = Array(area.length) { Array(height) { Array(area.width) { defaultBlockData } } }
val skyLevel = Array(area.length) { Array(area.width) { world.maxBuildHeight } }
val blocks = Array(input.length) { Array(height) { Array(input.width) { defaultBlockData } } }
val skyLevel = Array(input.length) { Array(input.width) { world.maxBuildHeight } }
val pos = BlockPos.MutableBlockPos()
val worldSlicedSpawningInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
val spawningZoneInfluences = mutableListOf<SpawningZoneInfluence>()
val chunks = mutableMapOf<Pair<Int, Int>, ChunkAccess?>()
val yRange = (baseY until baseY + height).reversed()
val lightingProvider = world.lightEngine
for (x in area.baseX until area.baseX + area.length) {
for (z in area.baseZ until area.baseZ + area.width) {
for (x in input.baseX until input.baseX + input.length) {
for (z in input.baseZ until input.baseZ + input.width) {
val query = chunks.computeIfAbsent(Pair(blockToSectionCoord(x), blockToSectionCoord(z))) {
world.getChunk(it.first, it.second, ChunkStatus.FULL, false)
} ?: continue
@@ -96,14 +95,14 @@ object CobblemonSpawningProspector : SpawningProspector {
for (y in yRange) {
val skyLight = lightingProvider.getLayerListener(LightLayer.SKY).getLightValue(pos.set(x, y, z))
val state = query.getBlockState(pos.set(x, y, z))
blocks[x - area.baseX][y - baseY][z - area.baseZ] = WorldSlice.BlockData(
blocks[x - input.baseX][y - baseY][z - input.baseZ] = SpawningZone.BlockData(
state = state,
light = world.getMaxLocalRawBrightness(pos),
skyLight = skyLight
)
worldSlicedSpawningInfluences.addAll(SpawningInfluenceProspector.prospectors.mapNotNull { it.prospectBlock(world, pos, state) })
spawningZoneInfluences.addAll(SpawningInfluenceDetector.detectors.flatMap { it.detectFromBlock(world, pos, state) })
if (canSeeSky) {
skyLevel[x - area.baseX][z - area.baseZ] = y
skyLevel[x - input.baseX][z - input.baseZ] = y
}
if (state.fluidState.isEmpty && !state.`is`(CobblemonBlockTags.SEES_SKY)) {
canSeeSky = false
@@ -112,20 +111,20 @@ object CobblemonSpawningProspector : SpawningProspector {
}
}
for (prospector in SpawningInfluenceProspector.prospectors) {
worldSlicedSpawningInfluences.addAll(prospector.prospect(spawner, area))
for (spawningZoneGenerator in SpawningInfluenceDetector.detectors) {
spawningZoneInfluences.addAll(spawningZoneGenerator.detectFromInput(spawner, input))
}
return WorldSlice(
cause = area.cause,
return SpawningZone(
cause = input.cause,
world = world,
baseX = area.baseX,
baseX = input.baseX,
baseY = baseY,
baseZ = area.baseZ,
baseZ = input.baseZ,
blocks = blocks,
skyLevel = skyLevel,
nearbyEntityPositions = nearbyEntityPositions,
influences = worldSlicedSpawningInfluences
influences = spawningZoneInfluences
)
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.server.level.ServerLevel
import java.util.UUID
@@ -9,8 +9,8 @@
package com.cobblemon.mod.common.api.spawning
/**
* A spawn bucket is a simple referenceable object by spawn files. The bucket is used early in the spawn
* process to decide which set of spawns will be used for spawning on that attempt. Weights are used to
* A spawn bucket is a simple referenceable object by spawn files. The bucket is used at the beginning of
* the selection process to decide which set of spawns will be used for spawning on that attempt. Weights are used to
* make entire buckets more or less likely, and then weighted selections will occur within whichever bucket
* was chosen.
*
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.util.weightedSelection
object SpawnBucketUtils {
fun chooseAdjustedSpawnBucket(buckets: List<SpawnBucket>, bucketLureStrength: Int): SpawnBucket {
val baseValues = listOf(88.5F, 8.5F, 2.4F, 0.6F)
val adjustments = listOf(-6.9F, 3.6F, 2.3F, 1F)
val adjustedWeights = buckets.mapIndexed { index, bucket ->
if (index >= baseValues.size) {
bucket to bucket.weight
} else {
val base = baseValues[index]
val adjustment = adjustments[index]
val adjusted = base + adjustment * bucketLureStrength
bucket to adjusted.coerceAtLeast(0f) // we do not want the weights to be negative
}
}.toMap()
return buckets.weightedSelection { adjustedWeights[it]!! }!!
}
}
@@ -16,7 +16,6 @@ import net.minecraft.world.entity.EntityType
open class SpawnCause(
val spawner: Spawner,
val bucket: SpawnBucket,
entity: Entity? = null
): SpawningInfluence {
val entityWorldId = entity?.level()?.dimension()
@@ -25,5 +24,9 @@ open class SpawnCause(
val entityType = entity?.type
val entity: Entity?
get() = if (entityType == EntityType.PLAYER) server()?.playerList?.getPlayer(entityUUID) else server()?.getLevel(entityWorldId)?.getEntity(entityId!!)
get() = if (entityType == EntityType.PLAYER) {
server()?.playerList?.getPlayer(entityUUID!!)
} else {
server()?.getLevel(entityWorldId!!)?.getEntity(entityId!!)
}
}
@@ -17,7 +17,7 @@ import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.detail.PossibleHeldItem
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.util.adapters.*
@@ -50,7 +50,7 @@ object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
@@ -16,7 +16,7 @@ import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.detail.PossibleHeldItem
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.util.adapters.*
@@ -58,7 +58,7 @@ object SpawnLoader {
).type,
EitherIdentifierOrTagAdapter(Registries.STRUCTURE)
)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawnDetail::class.java, SpawnDetailAdapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
@@ -8,28 +8,29 @@
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.influence.ConditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import kotlin.math.max
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import kotlin.math.max
/**
* A slice of the world that can be accessed safely from an async thread. This includes all of the information
* A zone of the world that can be accessed quickly from later steps. This includes all the information
* that might be unsafe to access async from the world itself for the purposes of spawning.
*
* This is generated by a [SpawningProspector].
* This is generated by a [SpawningZoneGenerator].
*
* @author Hiroku
* @since January 31st, 2022
*/
class WorldSlice(
class SpawningZone(
val cause: SpawnCause,
val world: ServerLevel,
val baseX: Int,
@@ -38,7 +39,7 @@ class WorldSlice(
val blocks: Array<Array<Array<BlockData>>>,
val skyLevel: Array<Array<Int>>,
var nearbyEntityPositions: List<Vec3>,
val influences: List<WorldSlicedSpawningInfluence>
influences: List<SpawningZoneInfluence>
) {
class BlockData(
val state: BlockState,
@@ -46,14 +47,17 @@ class WorldSlice(
val skyLight: Int
)
val unconditionalInfluences = influences.filterIsInstance<UnconditionalSpawningZoneInfluence>().map { it.influence }
val conditionalInfluences = influences.filterIsInstance<ConditionalSpawningZoneInfluence>()
val length = blocks.size
val height = blocks[0].size
val width = blocks[0][0].size
private val structureChunkCaches = mutableMapOf<ChunkPos, SpawningContext.StructureChunkCache>()
private val structureChunkCaches = mutableMapOf<ChunkPos, SpawnablePosition.StructureChunkCache>()
fun getStructureCache(pos: BlockPos): SpawningContext.StructureChunkCache {
return structureChunkCaches.getOrPut(ChunkPos(pos), SpawningContext::StructureChunkCache)
fun getStructureCache(pos: BlockPos): SpawnablePosition.StructureChunkCache {
return structureChunkCaches.getOrPut(ChunkPos(pos), SpawnablePosition::StructureChunkCache)
}
companion object {
@@ -228,4 +232,8 @@ class WorldSlice(
return space
}
fun getInfluences(spawnablePosition: SpawnablePosition): List<SpawningInfluence> {
return unconditionalInfluences + conditionalInfluences.filter { it.appliesTo(spawnablePosition) }.map { it.influence }
}
}
@@ -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.spawning
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
/**
* Interface responsible for slicing out an async-save [SpawningZone] that can be used for generating
* [SpawnablePosition]s, specifically [AreaSpawnablePosition]s.
*
* @author Hiroku
* @since January 29th, 2022
*/
interface SpawningZoneGenerator {
fun generate(spawner: Spawner, input: SpawningZoneInput): SpawningZone
}
@@ -8,8 +8,7 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
/**
@@ -17,7 +16,7 @@ import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
* and is then stowed in [SpawningCondition.appendages].
*
* As an example, you could add an appendage which has a single field called 'weather'. The [fits]
* implementation would check the [SpawningContext] using your own logic. Inside the JSON, you
* implementation would check the [SpawnablePosition] using your own logic. Inside the JSON, you
* would specify the 'weather' property as if it was a built-in field inside [SpawningCondition]. During
* loading, it will deserialize the [SpawningCondition]'s JSON several times targeting the various
* appendage classes, and then add those appendages to [SpawningCondition.appendages] so that when
@@ -55,5 +54,5 @@ interface AppendageCondition {
fun getAppendages(spawningCondition: SpawningCondition<*>) = appendages.filter { it.spawningConditionFits(spawningCondition) }.map { it.clazz }
}
fun fits(ctx: SpawningContext): Boolean
fun fits(spawnablePosition: SpawnablePosition): Boolean
}
@@ -9,30 +9,30 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.AreaSpawningContext
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePosition
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [AreaSpawningContext]. This
* can be extended for subclasses of [AreaSpawningContext].
* Base type for a spawning condition that applies to some kind of [AreaSpawnablePosition]. This
* can be extended for subclasses of [AreaSpawnablePosition].
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCondition<T>() {
abstract class AreaTypeSpawningCondition<T : AreaSpawnablePosition> : SpawningCondition<T>() {
var minHeight: Int? = null
var maxHeight: Int? = null
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(ctx: T): Boolean {
if (!super.fits(ctx)) {
override fun fits(spawnablePosition: T): Boolean {
if (!super.fits(spawnablePosition)) {
return false
} else if (minHeight != null && ctx.height < minHeight!!) {
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
return false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockHolders.any { cond.fits(it) } }) {
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> spawnablePosition.nearbyBlockHolders.any { cond.fits(it) } }) {
return false
} else {
return true
@@ -49,19 +49,19 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCond
}
override fun isValid(): Boolean {
val containsNullValues = neededNearbyBlocks != null && neededNearbyBlocks!!.any {it == null}
val containsNullValues = neededNearbyBlocks != null && neededNearbyBlocks!!.any { it == null }
return super.isValid() && !containsNullValues
}
}
/**
* A spawning condition for an [AreaSpawningContext].
* A spawning condition for an [AreaSpawnablePosition].
*
* @author Hiroku
* @since February 7th, 2022
*/
class AreaSpawningCondition : AreaTypeSpawningCondition<AreaSpawningContext>() {
override fun contextClass(): Class<out AreaSpawningContext> = AreaSpawningContext::class.java
class AreaSpawningCondition : AreaTypeSpawningCondition<AreaSpawnablePosition>() {
override fun spawnablePositionClass(): Class<out AreaSpawnablePosition> = AreaSpawnablePosition::class.java
companion object {
const val NAME = "area"
}
@@ -8,16 +8,16 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
/**
* A basic spawning condition that works for any type of spawning context.
* A basic spawning condition that works for any type of spawnable position.
*
* @author Hiroku
* @since February 7th, 2022
*/
class BasicSpawningCondition : SpawningCondition<SpawningContext>() {
override fun contextClass(): Class<out SpawningContext> = SpawningContext::class.java
class BasicSpawningCondition : SpawningCondition<SpawnablePosition>() {
override fun spawnablePositionClass(): Class<out SpawnablePosition> = SpawnablePosition::class.java
companion object {
const val NAME = "basic"
}
@@ -8,8 +8,7 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import net.minecraft.core.Holder
import net.minecraft.world.level.biome.Biome
@@ -41,11 +40,11 @@ class CompositeSpawningCondition {
return false
}
fun satisfiedBy(ctx: SpawningContext): Boolean {
return if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(ctx) }) {
fun satisfiedBy(spawnablePosition: SpawnablePosition): Boolean {
return if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(spawnablePosition) }) {
false
} else {
!(anticonditions.isNotEmpty() && anticonditions.any { it.isSatisfiedBy(ctx) })
!(anticonditions.isNotEmpty() && anticonditions.any { it.isSatisfiedBy(spawnablePosition) })
}
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
@@ -18,13 +18,13 @@ import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
/**
* A spawning condition that applies to [FishingSpawningContext]s.
* A spawning condition that applies to [FishingSpawnablePosition]s.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
override fun contextClass() = FishingSpawningContext::class.java
class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
override fun spawnablePositionClass() = FishingSpawnablePosition::class.java
var rod: RegistryLikeCondition<Item>? = null
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
@@ -33,19 +33,19 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
var bait: ResourceLocation? = null
var rodType: ResourceLocation? = null
override fun fits(ctx: FishingSpawningContext): Boolean {
if (!super.fits(ctx)) {
override fun fits(spawnablePosition: FishingSpawnablePosition): Boolean {
if (!super.fits(spawnablePosition)) {
return false
} else if (rod != null && !rod!!.fits(ctx.rodItem ?: return false, ctx.world.itemRegistry)) {
} else if (rod != null && !rod!!.fits(spawnablePosition.rodItem ?: return false, spawnablePosition.world.itemRegistry)) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> spawnablePosition.nearbyBlockTypes.any { cond.fits(it, spawnablePosition.blockRegistry) } }) {
return false
}
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = ctx.rodStack
val pokerodStack = spawnablePosition.rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
ctx.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
if (lureLevel < minLureLevel!!) {
@@ -55,13 +55,13 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
}
}
if (bait != null) { // check for the bait on the bobber
val pokerodBait = ctx.baitStack.itemHolder.unwrapKey().orElse(null)?.location()
val pokerodBait = spawnablePosition.baitStack.itemHolder.unwrapKey().orElse(null)?.location()
if (pokerodBait != bait) {
return false
}
}
if (rodType != null) { // check for the type of pokerod being used
val pokerodItem = ctx.rodItem
val pokerodItem = spawnablePosition.rodItem
if (pokerodItem?.pokeRodId != rodType) {
return false
}
@@ -9,28 +9,28 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.position.GroundedSpawnablePosition
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [GroundedSpawningContext]. This
* can be extended for subclasses of [GroundedSpawningContext].
* Base type for a spawning condition that applies to some kind of [GroundedSpawnablePosition]. This
* can be extended for subclasses of [GroundedSpawnablePosition].
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class GroundedTypeSpawningCondition<T : GroundedSpawningContext> : AreaTypeSpawningCondition<T>() {
abstract class GroundedTypeSpawningCondition<T : GroundedSpawnablePosition> : AreaTypeSpawningCondition<T>() {
var neededBaseBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
false
} else if (minHeight != null && ctx.height < minHeight!!) {
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
return false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
return false
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlockHolder) }) {
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(spawnablePosition.baseBlockHolder) }) {
return false
} else {
return true
@@ -51,13 +51,13 @@ abstract class GroundedTypeSpawningCondition<T : GroundedSpawningContext> : Area
}
/**
* A spawning condition for a [GroundedSpawningContext].
* A spawning condition for a [GroundedSpawnablePosition].
*
* @author Hiroku
* @since February 7th, 2022
*/
open class GroundedSpawningCondition : GroundedTypeSpawningCondition<GroundedSpawningContext>() {
override fun contextClass() = GroundedSpawningContext::class.java
open class GroundedSpawningCondition : GroundedTypeSpawningCondition<GroundedSpawnablePosition>() {
override fun spawnablePositionClass() = GroundedSpawnablePosition::class.java
companion object {
const val NAME = "grounded"
}
@@ -9,31 +9,31 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.position.SeafloorSpawnablePosition
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [SeafloorSpawningContext]. This
* can be extended for subclasses of [SeafloorSpawningContext].
* Base type for a spawning condition that applies to some kind of [SeafloorSpawnablePosition]. This
* can be extended for subclasses of [SeafloorSpawnablePosition].
*
* Borrowed the Grounded context code since it was unintentionally spawning Pokémon on the sea floor
* Borrowed the Grounded spawnable position code since it was unintentionally spawning Pokémon on the sea floor
* already. Seems to be working fine /shrug
*
* @author FrankTheFarmer
* @since May 22nd, 2024
*/
abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawningContext> : AreaTypeSpawningCondition<T>() {
abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawnablePosition> : AreaTypeSpawningCondition<T>() {
var neededBaseBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
false
} else if (minHeight != null && ctx.height < minHeight!!) {
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
false
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlockHolder) }) {
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(spawnablePosition.baseBlockHolder) }) {
false
} else {
true
@@ -54,13 +54,13 @@ abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawningContext> : Area
}
/**
* A spawning condition for a [SeafloorSpawningContext].
* A spawning condition for a [SeafloorSpawnablePosition].
*
* @author FrankTheFarmer
* @since May 22nd, 2024
*/
open class SeafloorSpawningCondition : SeafloorTypeSpawningCondition<SeafloorSpawningContext>() {
override fun contextClass() = SeafloorSpawningContext::class.java
open class SeafloorSpawningCondition : SeafloorTypeSpawningCondition<SeafloorSpawnablePosition>() {
override fun spawnablePositionClass() = SeafloorSpawnablePosition::class.java
companion object {
const val NAME = "seafloor"
}
@@ -11,32 +11,29 @@ package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.util.Merger
import com.cobblemon.mod.common.util.math.orMax
import com.cobblemon.mod.common.util.math.orMin
import com.mojang.datafixers.util.Either
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.WorldgenRandom
import net.minecraft.world.level.levelgen.structure.Structure
/**
* The root of spawning conditions that can be applied to a spawning context. What type
* of spawning context it can be applied to is relevant for any subclasses.
* The root of spawning conditions that can be applied to a spawnable position. What type
* of spawnable position it can be applied to is relevant for any subclasses.
*
* @author Hiroku
* @since January 24th, 2022
*/
abstract class SpawningCondition<T : SpawningContext> {
abstract class SpawningCondition<T : SpawnablePosition> {
companion object {
val conditionTypes = mutableMapOf<String, Class<out SpawningCondition<*>>>()
fun getByName(name: String) = conditionTypes[name]
fun <T : SpawningContext, C : SpawningCondition<T>> register(name: String, clazz: Class<C>) {
fun <T : SpawnablePosition, C : SpawningCondition<T>> register(name: String, clazz: Class<C>) {
conditionTypes[name] = clazz
}
}
@@ -61,69 +58,72 @@ abstract class SpawningCondition<T : SpawningContext> {
var timeRange: TimeRange? = null
var structures: MutableList<Either<ResourceLocation, TagKey<Structure>>>? = null
var isSlimeChunk: Boolean? = null
var markers: MutableList<String>? = null
@Transient
var appendages = mutableListOf<AppendageCondition>()
abstract fun contextClass(): Class<out T>
fun contextMatches(ctx: SpawningContext) = contextClass().isAssignableFrom(ctx::class.java)
abstract fun spawnablePositionClass(): Class<out T>
fun spawnablePositionMatches(spawnablePosition: SpawnablePosition) = spawnablePositionClass().isAssignableFrom(spawnablePosition::class.java)
fun isSatisfiedBy(ctx: SpawningContext): Boolean {
return if (contextMatches(ctx)) {
fits(ctx as T)
fun isSatisfiedBy(spawnablePosition: SpawnablePosition): Boolean {
return if (spawnablePositionMatches(spawnablePosition)) {
fits(spawnablePosition as T)
} else {
false
}
}
protected open fun fits(ctx: T): Boolean {
if (ctx.position.x < minX.orMin() || ctx.position.x > maxX.orMax()) {
protected open fun fits(spawnablePosition: T): Boolean {
if (spawnablePosition.position.x < minX.orMin() || spawnablePosition.position.x > maxX.orMax()) {
return false
} else if (ctx.position.y < minY.orMin() || ctx.position.y > maxY.orMax()) {
} else if (spawnablePosition.position.y < minY.orMin() || spawnablePosition.position.y > maxY.orMax()) {
return false
} else if (ctx.position.z < minZ.orMin() || ctx.position.z > maxZ.orMax()) {
} else if (spawnablePosition.position.z < minZ.orMin() || spawnablePosition.position.z > maxZ.orMax()) {
return false
} else if (moonPhase != null && ctx.moonPhase !in moonPhase!!) {
} else if (moonPhase != null && spawnablePosition.moonPhase !in moonPhase!!) {
return false
} else if (ctx.light > maxLight.orMax() || ctx.light < minLight.orMin()) {
} else if (spawnablePosition.light > maxLight.orMax() || spawnablePosition.light < minLight.orMin()) {
return false
} else if (ctx.skyLight > maxSkyLight.orMax() || ctx.skyLight < minSkyLight.orMin()) {
} else if (spawnablePosition.skyLight > maxSkyLight.orMax() || spawnablePosition.skyLight < minSkyLight.orMin()) {
return false
} else if (timeRange != null && !timeRange!!.contains((ctx.world.dayTime() % 24000).toInt())) {
} else if (timeRange != null && !timeRange!!.contains((spawnablePosition.world.dayTime() % 24000).toInt())) {
return false
} else if (canSeeSky != null && canSeeSky != ctx.canSeeSky) {
} else if (canSeeSky != null && canSeeSky != spawnablePosition.canSeeSky) {
return false
} else if (isRaining != null && ctx.world.isRaining != isRaining!!) {
} else if (isRaining != null && spawnablePosition.world.isRaining != isRaining!!) {
return false
} else if (isThundering != null && ctx.world.isThundering != isThundering!!) {
} else if (isThundering != null && spawnablePosition.world.isThundering != isThundering!!) {
return false
} else if (dimensions != null && dimensions!!.isNotEmpty() && ctx.world.dimensionType().effectsLocation !in dimensions!!) {
} else if (dimensions != null && dimensions!!.isNotEmpty() && spawnablePosition.world.dimension().location() !in dimensions!!) {
return false
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(ctx.biomeHolder) }) {
} else if (markers != null && markers!!.isNotEmpty() && markers!!.none { marker -> marker in spawnablePosition.markers }) {
return false
} else if (appendages.any { !it.fits(ctx) }) {
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(spawnablePosition.biomeHolder) }) {
return false
} else if (appendages.any { !it.fits(spawnablePosition) }) {
return false
} else if (structures != null && structures!!.isNotEmpty() &&
structures!!.let { structures ->
val structureAccess = ctx.world.structureManager()
val cache = ctx.getStructureCache(ctx.position)
val structureAccess = spawnablePosition.world.structureManager()
val cache = spawnablePosition.getStructureCache(spawnablePosition.position)
return@let structures.none {
it.map({ cache.check(structureAccess, ctx.position, it) }, { cache.check(structureAccess, ctx.position, it) })
it.map({ cache.check(structureAccess, spawnablePosition.position, it) }, { cache.check(structureAccess, spawnablePosition.position, it) })
}
}
) {
return false
} else if (isSlimeChunk != null && isSlimeChunk != false) {
val isSlimeChunk = WorldgenRandom.seedSlimeChunk(ctx.position.x shr 4, ctx.position.z shr 4, ctx.world.seed, 987234911L).nextInt(10) == 0
val isSlimeChunk = WorldgenRandom.seedSlimeChunk(spawnablePosition.position.x shr 4, spawnablePosition.position.z shr 4, spawnablePosition.world.seed, 987234911L).nextInt(10) == 0
if (!isSlimeChunk) {
return false
}
/*val chunkX = ctx.position.x shr 4
val chunkZ = ctx.position.z shr 4
/*val chunkX = spawnablePosition.position.x shr 4
val chunkZ = spawnablePosition.position.z shr 4
val seed = (ctx.world.seed +
val seed = (spawnablePosition.world.seed +
(chunkX * chunkX * 4987142L) + (chunkX * 5947611L) +
(chunkZ * chunkZ * 4392871L) + (chunkZ * 389711L)) xor 987234911L
@@ -9,18 +9,18 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.resources.ResourceLocation
/**
* A type of precalculation that can occur on a list of [SpawnDetail] to accelerate
* checks on whether a spawn can occur at a particular [SpawningContext]. This process
* checks on whether a spawn can occur at a particular [SpawnablePosition]. This process
* works by creating a [HashMap] keying from some property of a [SpawnDetail] and
* [SpawningContext] and mapping it to a list of spawns that were calculated ahead of
* [SpawnablePosition] and mapping it to a list of spawns that were calculated ahead of
* time as sharing that value. The requirement of implementations is only to get that
* key from both a [SpawnDetail] and a [SpawningContext].
* key from both a [SpawnDetail] and a [SpawnablePosition].
*
* When a precalculation has been registered with a [Spawner] by adding to [Spawner.precalculators],
* the precalculation result will need to be updated by running [Spawner.precalculate]. That
@@ -33,8 +33,8 @@ import net.minecraft.resources.ResourceLocation
interface SpawningPrecalculation<T : Any> {
/** Retrieves the precalculation keys for the given spawn detail. */
fun select(detail: SpawnDetail): Collection<T>
/** Retrieves the precalculation key for the given context, if that key exists for this context. */
fun select(ctx: SpawningContext): T?
/** Retrieves the precalculation key for the given spawnable position, if that key exists for this position. */
fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): T?
/**
* Generates a precalculation result for the list of [SpawnDetail]s and the subsequent precalculations.
@@ -74,23 +74,23 @@ interface SpawningPrecalculation<T : Any> {
object RootPrecalculation : SpawningPrecalculation<Any> {
val list = listOf(Unit)
override fun select(detail: SpawnDetail): List<Any> = list
override fun select(ctx: SpawningContext): Any = Unit
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): Any = Unit
}
/**
* A precalculation based on what context a spawn can be. This is a pretty sweet precalc.
* A precalculation based on what spawnable position type a spawn can be. This is a pretty sweet precalc.
*
* @author Hiroku
* @since February 14th, 2022
*/
object ContextPrecalculation : SpawningPrecalculation<Class<out SpawningContext>> {
override fun select(detail: SpawnDetail) = listOf(detail.context.clazz)
override fun select(ctx: SpawningContext) = ctx::class.java
object SpawnablePositionTypePrecalculation : SpawningPrecalculation<Class<out SpawnablePosition>> {
override fun select(detail: SpawnDetail) = listOf(detail.spawnablePositionType.clazz)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition::class.java
}
object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
override fun select(detail: SpawnDetail) = listOf(detail.bucket)
override fun select(ctx: SpawningContext) = ctx.cause.bucket
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = bucket
}
/**
@@ -99,7 +99,7 @@ object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
*/
object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
override fun select(detail: SpawnDetail) = detail.validBiomes
override fun select(ctx: SpawningContext) = ctx.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
}
/**
@@ -113,8 +113,8 @@ object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
sealed class PrecalculationResult<T : Any>(
val calculation: SpawningPrecalculation<*>
) {
/** Gets the list of spawn details that fit this context. */
abstract fun retrieve(ctx: SpawningContext): List<SpawnDetail>
/** Gets the list of spawn details that fit this spawnable position. */
abstract fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>
}
/**
@@ -127,7 +127,7 @@ class NestedPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<T>,
val mapping: Map<T, PrecalculationResult<*>> = mutableMapOf()
) : PrecalculationResult<T>(calculation) {
override fun retrieve(ctx: SpawningContext) = mapping[calculation.select(ctx)]?.retrieve(ctx) ?: emptyList()
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)]?.retrieve(bucket, spawnablePosition) ?: emptyList()
}
/**
@@ -141,5 +141,5 @@ class FinalPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<*>,
val mapping: Map<T, List<SpawnDetail>>
) : PrecalculationResult<T>(calculation) {
override fun retrieve(ctx: SpawningContext) = mapping[calculation.select(ctx)] ?: emptyList()
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)] ?: emptyList()
}
@@ -9,37 +9,37 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.material.Fluid
/**
* Base type for a spawning condition that applies to some kind of [SubmergedSpawningContext]. This
* can be extended for subclasses of [SubmergedSpawningContext].
* Base type for a spawning condition that applies to some kind of [SubmergedSpawnablePosition]. This
* can be extended for subclasses of [SubmergedSpawnablePosition].
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : AreaTypeSpawningCondition<T>() {
abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawnablePosition> : AreaTypeSpawningCondition<T>() {
var minDepth: Int? = null
var maxDepth: Int? = null
var fluidIsSource: Boolean? = null
var fluid: RegistryLikeCondition<Fluid>? = null
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
false
} else if (minHeight != null && ctx.height < minHeight!!) {
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
return false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
return false
} else if (minDepth != null && ctx.depth < minDepth!!) {
} else if (minDepth != null && spawnablePosition.depth < minDepth!!) {
false
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
} else if (maxDepth != null && spawnablePosition.depth > maxDepth!!) {
false
} else if (fluidIsSource != null && ctx.fluid.isSource != fluidIsSource!!) {
} else if (fluidIsSource != null && spawnablePosition.fluid.isSource != fluidIsSource!!) {
false
} else !(ctx.fluid.isEmpty || (fluid != null && !fluid!!.fits(ctx.fluid.type, ctx.fluidRegistry)))
} else !(spawnablePosition.fluid.isEmpty || (fluid != null && !fluid!!.fits(spawnablePosition.fluid.type, spawnablePosition.fluidRegistry)))
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -54,13 +54,13 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
}
/**
* A spawning condition for an [SubmergedSpawningContext].
* A spawning condition for an [SubmergedSpawnablePosition].
*
* @author Hiroku
* @since February 7th, 2022
*/
open class SubmergedSpawningCondition : SubmergedTypeSpawningCondition<SubmergedSpawningContext>() {
override fun contextClass() = SubmergedSpawningContext::class.java
open class SubmergedSpawningCondition : SubmergedTypeSpawningCondition<SubmergedSpawnablePosition>() {
override fun spawnablePositionClass() = SubmergedSpawnablePosition::class.java
companion object {
const val NAME = "submerged"
}
@@ -9,30 +9,30 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.position.SurfaceSpawnablePosition
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.material.Fluid
/**
* Base type for a spawning condition that applies to some kind of [SurfaceSpawningContext]. This
* can be extended for subclasses of [SurfaceSpawningContext].
* Base type for a spawning condition that applies to some kind of [SurfaceSpawnablePosition]. This
* can be extended for subclasses of [SurfaceSpawnablePosition].
*
* @author Hiroku
* @since December 15th, 2022
*/
abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawningContext> : AreaTypeSpawningCondition<T>() {
abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawnablePosition> : AreaTypeSpawningCondition<T>() {
var minDepth: Int? = null
var maxDepth: Int? = null
var fluid: RegistryLikeCondition<Fluid>? = null
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
false
} else if (minDepth != null && ctx.depth < minDepth!!) {
} else if (minDepth != null && spawnablePosition.depth < minDepth!!) {
false
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
} else if (maxDepth != null && spawnablePosition.depth > maxDepth!!) {
false
} else !(ctx.baseBlock.fluidState.isEmpty || (fluid != null && !fluid!!.fits(ctx.baseBlock.fluidState.type, ctx.fluidRegistry)))
} else !(spawnablePosition.baseBlock.fluidState.isEmpty || (fluid != null && !fluid!!.fits(spawnablePosition.baseBlock.fluidState.type, spawnablePosition.fluidRegistry)))
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -46,13 +46,13 @@ abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawningContext> : AreaTy
}
/**
* A spawning condition for a [SurfaceSpawningContext].
* A spawning condition for a [SurfaceSpawnablePosition].
*
* @author Hiroku
* @since December 15th, 2022
*/
open class SurfaceSpawningCondition : SurfaceTypeSpawningCondition<SurfaceSpawningContext>() {
override fun contextClass() = SurfaceSpawningContext::class.java
open class SurfaceSpawningCondition : SurfaceTypeSpawningCondition<SurfaceSpawnablePosition>() {
override fun spawnablePositionClass() = SurfaceSpawnablePosition::class.java
companion object {
const val NAME = "surface"
}
@@ -1,133 +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.context
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.calculators.AreaSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.AreaSpawningInput
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.core.BlockPos
/**
* Interface responsible for drawing a list of spawn contexts from a slice of the world,
* given a list of all the context calculators that should be considered in order. As soon
* as one of the context calculators returns true for [AreaSpawningContextCalculator.fits],
* no other context calculator will be considered.
*
* The default method body of this interface checks every single block in the slice
* and composes a single context per position, at most. This is almost certainly fine,
* but this interface exists, so you can override it if you want.
*
* @author Hiroku
* @since January 31st, 2022
*/
interface AreaContextResolver {
fun resolve(
spawner: Spawner,
contextCalculators: List<AreaSpawningContextCalculator<*>>,
slice: WorldSlice
): List<AreaSpawningContext> {
var pos = BlockPos.MutableBlockPos(1, 2, 3)
val input = AreaSpawningInput(spawner, pos, slice)
val contexts = mutableListOf<AreaSpawningContext>()
var x = slice.baseX
var y = slice.baseY
var z = slice.baseZ
while (x < slice.baseX + slice.length) {
while (y < slice.baseY + slice.height) {
while (z < slice.baseZ + slice.width) {
pos.set(x, y, z)
val vec = pos.toVec3d()
if (slice.nearbyEntityPositions.none { it.closerThan(vec, config.minimumDistanceBetweenEntities) && it != slice.cause.entity }) {
val fittedContextCalculator = contextCalculators
.firstOrNull { calc -> calc.fits(input) && input.spawner.influences.none { !it.isAllowedPosition(input.world, input.position, calc) } }
if (fittedContextCalculator != null) {
val context = fittedContextCalculator.calculate(input)
if (context != null) {
slice.influences.forEach {
if (it.appliesTo(context)) {
context.influences.add(it.influence)
}
}
contexts.add(context)
// The position BlockPos has been used in a context, editing the same one
// will cause entities to spawn at the wrong location (buried in walls, usually).
// I made it so that built-in context calculators explicitly take a copy of the
// BlockPos but it'd still be exposed in custom contexts so fixing it here too so
// custom context calculators don't have to remember to do it. - Hiroku
pos = BlockPos.MutableBlockPos(1, 2, 3)
input.position = pos
}
}
}
z++
}
y++
z = slice.baseZ
}
x++
y = slice.baseY
z = slice.baseZ
}
return contexts
}
// fun resolveFishing(
// spawner: Spawner,
// contextCalculators: List<AreaSpawningContextCalculator<*>>,
// slice: WorldSlice
// ): List<AreaSpawningContext> {
// var pos = BlockPos.Mutable(1, 2, 3)
// val input = AreaSpawningInput(spawner, pos, slice)
// val contexts = mutableListOf<AreaSpawningContext>()
//
// var x = slice.baseX
// var y = slice.baseY
// var z = slice.baseZ
//
// while (x < slice.baseX + slice.length) {
// while (y < slice.baseY + slice.height) {
// while (z < slice.baseZ + slice.width) {
// pos.set(x, y, z)
// val vec = pos.toVec3d()
// if (slice.nearbyEntityPositions.none { it.isInRange(vec, 50.0) && it != slice.cause.entity }) {
// val fittedContextCalculator = contextCalculators
// .firstOrNull { calc -> calc.fits(input) && input.spawner.influences.none { !it.isAllowedPosition(input.world, input.position, calc) } }
// if (fittedContextCalculator != null) {
// val context = fittedContextCalculator.calculate(input)
// if (context != null) {
// contexts.add(context)
// // The position BlockPos has been used in a context, editing the same one
// // will cause entities to spawn at the wrong location (buried in walls, usually)
// // I made it so that our context calculators specifically take a copy of the
// // BlockPos but it'd still be exposed in custom contexts so fixing it here too.
// pos = BlockPos.Mutable(1, 2, 3)
// input.position = pos
// }
// }
// }
// z++
// }
// y++
// z = slice.baseZ
// }
// x++
// y = slice.baseY
// z = slice.baseZ
// }
//
// return contexts
// }
}
@@ -1,8 +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/.
*/
@@ -1,60 +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.context.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.AreaSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
/**
* A spawning context calculator that deals with [AreaSpawningContext]s. These work off
* [AreaSpawningInput] instances, and must output some kind of [AreaSpawningContext].
*
* This is the interface to use for all of the contexts that will get used by world spawning
* as that is the primary case that area spawning is done. These get registered when
* creating the context type, in [SpawningContext.register]
*
* @author Hiroku
* @since January 31st, 2022
*/
interface AreaSpawningContextCalculator<O : AreaSpawningContext> : SpawningContextCalculator<AreaSpawningInput, O> {
/**
* Whether or not this context calculator is likely to provide a value for this location.
*
* This should be relatively final. See how the [AreaContextResolver] works to understand why
* you should be pretty sure before returning true to this function.
*/
fun fits(input: AreaSpawningInput): Boolean
fun getDepth(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int): Int
= input.slice.depthSpace(input.position.x, input.position.y, input.position.z, condition, maximum)
fun getHeight(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int, offsetX: Int = 0, offsetY: Int = 0, offsetZ: Int = 0): Int
= input.slice.heightSpace(input.position.x + offsetX, input.position.y + offsetY, input.position.z + offsetZ, condition, maximum)
fun getHorizontalSpace(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int, offsetX: Int = 0, offsetY: Int = 0, offsetZ: Int = 0): Int
= input.slice.horizontalSpace(input.position.x + offsetX, input.position.y + offsetY, input.position.z + offsetZ, condition, maximum)
fun getLight(input: AreaSpawningInput, elseLight: Int = 0): Int
= input.slice.getLight(input.position.x, input.position.y + 1, input.position.z, elseLight)
fun getSkyLight(input: AreaSpawningInput, elseLight: Int = 0): Int
= input.slice.getSkyLight(input.position.x, input.position.y + 1, input.position.z, elseLight)
fun getCanSeeSky(input: AreaSpawningInput): Boolean = input.slice.canSeeSky(input.position.x, input.position.y + 1, input.position.z)
fun getSkySpaceAbove(input: AreaSpawningInput): Int = input.slice.skySpaceAbove(input.position.x, input.position.y, input.position.z)
fun getNearbyBlocks(
input: AreaSpawningInput,
horizontalRadius: Int = config.maxNearbyBlocksHorizontalRange,
verticalRadius: Int = config.maxNearbyBlocksVerticalRange
) = input.slice.nearbyBlocks(input.position, horizontalRadius, verticalRadius)
}
open class AreaSpawningInput(val spawner: Spawner, var position: BlockPos, val slice: WorldSlice) : SpawningContextInput(slice.cause, slice.world)
@@ -9,7 +9,8 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.resolveInt
@@ -19,13 +20,13 @@ import com.cobblemon.mod.common.util.resolveInt
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(ctx, detail) {
class NPCSpawnAction(spawnablePosition: SpawnablePosition, bucket: SpawnBucket, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(spawnablePosition, bucket, detail) {
override fun createEntity(): NPCEntity {
val npc = NPCEntity(ctx.world)
val npc = NPCEntity(spawnablePosition.world)
npc.npc = detail.npcClass
npc.appliedAspects.addAll(detail.aspects)
val minLevel = ctx.runtime.resolveInt(detail.minLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val maxLevel = ctx.runtime.resolveInt(detail.maxLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val minLevel = spawnablePosition.runtime.resolveInt(detail.minLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val maxLevel = spawnablePosition.runtime.resolveInt(detail.maxLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val seedLevel = (minLevel..maxLevel).random()
npc.initialize(seedLevel)
return npc
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
@@ -35,5 +37,9 @@ class NPCSpawnDetail : SpawnDetail() {
val minLevel: Expression = "1".asExpression()
val maxLevel: Expression = "100".asExpression()
override fun doSpawn(ctx: SpawningContext) = NPCSpawnAction(ctx, this)
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
) = NPCSpawnAction(spawnablePosition, bucket, this)
}
@@ -0,0 +1,145 @@
/*
* 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.detail
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.util.weightedSelection
import com.google.gson.annotations.SerializedName
/**
* A spawn detail that spawns a herd of Pokémon. This relies on the same [PokemonSpawnAction]s as
* regular [PokemonSpawnDetail]s, but takes advantage of selection context to 'take over' the spawn
* pass and spawn many for the price of one.
*
* Selection will treat this like a single spawn until it chooses this detail once, at which point
* all the other spawns are removed, a level is calculated for the herd based on [levelRange], and
* the selector will be left to fit in as many herd members as possible with the remaining spawnable
* positions.
*
* @author Hiroku
* @since April 22nd, 2025
*/
class PokemonHerdSpawnDetail : SpawnDetail() {
companion object {
val TYPE = "pokemon-herd"
const val HERD_LEADER = "leader"
}
override val type: String = TYPE
var herdablePokemon = mutableListOf<Herdable>()
@SerializedName("level", alternate = ["levelRange"])
var levelRange: IntRange = 1..100
var maxHerdSize = 10
var minDistanceBetweenSpawns = 1F
val herdLevelKey: String
get() = "${id}__LEVEL"
class Herdable {
var pokemon: PokemonProperties = PokemonProperties()
val levelRange: IntRange? = null
var levelRangeOffset: IntRange? = null
var dropTable: DropTable? = null
var isLeader: Boolean? = null
var weight: Float = 1F
var maxTimes = 10
}
override fun isValid() = super.isValid() && herdablePokemon.all { it.pokemon.hasSpecies() && it.weight > 0F && it.maxTimes > 0 }
override fun onSelection(
spawnablePosition: SpawnablePosition,
spawnAction: SpawnAction<*>,
selectionData: SpawnSelectionData
) {
selectionData.removeSpawnDetails { it != this }
selectionData.removeSpawnablePositions { _, pos -> pos.distanceTo(spawnablePosition) < minDistanceBetweenSpawns }
val herdSpawnCount = selectionData.spawnActions.count { it.detail == this } + 1
if (herdSpawnCount >= maxHerdSize || getValidHerdMembers(selectionData).isEmpty()) {
// We can't spawn any more in the herd so remove this spawn from the selection pool.
selectionData.removeSpawnDetails { it == this }
}
}
fun lacksPossibleLeader(selectionData: SpawnSelectionData): Boolean {
val level = getOrSetHerdLevel(selectionData)
val herdable = getValidHerdMembers(selectionData)
val herdCounts = getHerdMemberCounts(selectionData)
val leaderIsPossible = herdable.any { it.levelRange?.contains(level) != false && it.isLeader == true }
val leaderIsSelected = herdCounts.any { it.key?.isLeader == true && it.value > 0 }
return leaderIsPossible && !leaderIsSelected
}
private fun getOrSetHerdLevel(selectionData: SpawnSelectionData): Int {
return (selectionData.context[herdLevelKey] as? Int) ?: run {
val level = levelRange.random()
selectionData.context[herdLevelKey] = level
return level
}
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): PokemonSpawnAction {
val validHerdMembers = getValidHerdMembers(selectionData)
val herdable = if (lacksPossibleLeader(selectionData)) {
validHerdMembers.filter { it.isLeader == true }
} else {
validHerdMembers
}.weightedSelection { it.weight } ?: herdablePokemon[0]
val level = selectionData.context[herdLevelKey] as? Int ?: 1
val levelRange = herdable.levelRangeOffset?.let { offset ->
val min = level + offset.first
val max = level + offset.last
min..max
} ?: level..level
return PokemonSpawnAction(
spawnablePosition = spawnablePosition,
bucket = bucket,
detail = this,
props = herdable.pokemon,
drops = herdable.dropTable,
heldItem = null,
levelRange = levelRange
).also {
if (herdable.isLeader == true) {
it.labels += HERD_LEADER
}
}
}
fun getHerdMemberCounts(selectionData: SpawnSelectionData): Map<Herdable?, Int> {
val relevantActions = selectionData.spawnActions.filterIsInstance<PokemonSpawnAction>().filter { it.detail == this }
return relevantActions.groupBy { action -> herdablePokemon.find { it.pokemon == action.props } }.mapValues { it.value.size }
}
private fun getValidHerdMembers(selectionData: SpawnSelectionData): List<Herdable> {
val level = getOrSetHerdLevel(selectionData)
val relevantActions = selectionData.spawnActions.filterIsInstance<PokemonSpawnAction>().filter { it.detail == this }
val counts = relevantActions.groupBy { action -> herdablePokemon.find { it.pokemon == action.props } }.mapValues { it.value.size }
return herdablePokemon.filter { herdable ->
val count = counts[herdable] ?: 0
return@filter when {
count >= herdable.maxTimes -> false
herdable.levelRange != null && level !in herdable.levelRange -> false
else -> true
}
}
}
}
@@ -9,13 +9,14 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.util.weightedSelection
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
/**
* A [SpawnAction] that will spawn a single [PokemonEntity].
@@ -24,38 +25,49 @@ import net.minecraft.server.level.ServerPlayer
* @since February 13th, 2022
*/
class PokemonSpawnAction(
ctx: SpawningContext,
override val detail: PokemonSpawnDetail,
/** The [PokemonProperties] that are about to be used. */
var props: PokemonProperties = detail.pokemon.copy()
) : SingleEntitySpawnAction<PokemonEntity>(ctx, detail) {
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
override val detail: SpawnDetail,
/**
* The [PokemonProperties] that are about to be used. When using it, a copy will be taken.
* Don't modify the contents of this, what you want to do is [updatePokemon] with a copy.
*/
var props: PokemonProperties,
var heldItem: ItemStack?,
var drops: DropTable? = null,
var levelRange: IntRange
) : SingleEntitySpawnAction<PokemonEntity>(spawnablePosition, bucket, detail) {
override fun createEntity(): PokemonEntity {
if (props.species == null) LOGGER.error("PokemonSpawnAction run with null species - Spawn detail: ${detail.id}")
if (props.level == null) {
props.level = detail.getDerivedLevelRange().random()
}
val heldItems = detail.heldItems?.takeIf { it.isNotEmpty() }?.toMutableList() ?: mutableListOf()
val heldItem = if (heldItems.isNotEmpty()) {
val until100 = 1 - heldItems.sumOf { it.percentage / 100 }
if (until100 > 0 && ctx.world.random.nextDouble() < until100) {
null
} else {
heldItems.weightedSelection { it.percentage }
}
} else {
null
}?.createStack(ctx)
val sourcePlayer = ctx.cause.entity as? ServerPlayer
val entity = props.createEntity(ctx.world, sourcePlayer)
entity.spawnCause = ctx.cause
SeasonFeatureHandler.updateSeason(entity.pokemon, Cobblemon.seasonResolver(ctx.world, ctx.position))
if (heldItem != null) {
val props = props.copy()
props.level = levelRange.random()
val sourcePlayer = spawnablePosition.cause.entity as? ServerPlayer
val entity = props.createEntity(spawnablePosition.world, sourcePlayer)
entity.spawnCause = spawnablePosition.cause
SeasonFeatureHandler.updateSeason(entity.pokemon, Cobblemon.seasonResolver(spawnablePosition.world, spawnablePosition.position))
val heldItem = heldItem
if (heldItem != ItemStack.EMPTY && heldItem != null) {
entity.pokemon.swapHeldItem(heldItem)
}
entity.drops = detail.drops
entity.drops = drops
// Useful debug code in situations where you want to find spawns
// val fireworkRocketEntity = FireworkRocketEntity(ctx.world, ctx.position.x.toDouble(), ctx.position.y.toDouble() + 2, ctx.position.z.toDouble(), ItemStack(Items.FIREWORK_ROCKET))
// ctx.world.spawnEntity(fireworkRocketEntity)
// val fireworkRocketEntity = FireworkRocketEntity(spawnablePosition.world, spawnablePosition.position.x.toDouble(), spawnablePosition.position.y.toDouble() + 2, spawnablePosition.position.z.toDouble(), ItemStack(Items.FIREWORK_ROCKET))
// spawnablePosition.world.spawnEntity(fireworkRocketEntity)
return entity
}
fun updatePokemon(props: PokemonProperties) {
this.props = props
}
fun updateHeldItem(heldItem: ItemStack) {
this.heldItem = heldItem
}
fun updateDrops(drops: DropTable) {
this.drops = drops
}
fun updateLevelRange(levelRange: IntRange) {
this.levelRange = levelRange
}
}
@@ -10,17 +10,19 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.weightedSelection
import com.google.gson.annotations.SerializedName
import kotlin.math.ceil
import net.minecraft.network.chat.MutableComponent
@@ -72,14 +74,14 @@ class PokemonSpawnDetail : SpawnDetail() {
override fun autoLabel() {
struct.addFunction("pokemon") { pokemon.asMoLangValue() }
struct.addFunction("min_level") { DoubleValue(getDerivedLevelRange().start.toDouble()) }
struct.addFunction("max_level") { DoubleValue(getDerivedLevelRange().endInclusive.toDouble()) }
struct.addFunction("min_level") { DoubleValue(pokemon.deriveLevelRange(levelRange).start.toDouble()) }
struct.addFunction("max_level") { DoubleValue(pokemon.deriveLevelRange(levelRange).endInclusive.toDouble()) }
if (pokemon.species != null) {
val species = PokemonSpecies.getByIdentifier(pokemon.species!!.asIdentifierDefaultingNamespace())
if (species != null) {
labels.addAll(
species.secondaryType?.let { listOf(species.primaryType.name.lowercase(), it.name.lowercase()) }
?: listOf(species.primaryType.name.lowercase())
?: listOf(species.primaryType.name.lowercase())
)
if (height == -1) {
@@ -95,23 +97,37 @@ class PokemonSpawnDetail : SpawnDetail() {
super.autoLabel()
}
fun getDerivedLevelRange() = levelRange.let { levelRange ->
if (levelRange == null && pokemon.level == null) {
IntRange(1, config.maxPokemonLevel)
} else if (levelRange == null) {
IntRange(pokemon.level!!, pokemon.level!!)
} else {
levelRange
}
}
override fun isValid(): Boolean {
val isValidSpecies = pokemon.species != null
if (!isValidSpecies) LOGGER.error("Invalid species for spawn detail: $id")
return super.isValid() && isValidSpecies
}
override fun doSpawn(ctx: SpawningContext): SingleEntitySpawnAction<PokemonEntity> {
return PokemonSpawnAction(ctx, this)
override fun createSpawnAction(
spawnablePosition: SpawnablePosition, bucket: SpawnBucket,
selectionData: SpawnSelectionData
): SingleEntitySpawnAction<PokemonEntity> {
val levelRange = pokemon.deriveLevelRange(levelRange)
val heldItems = heldItems?.takeIf { it.isNotEmpty() }?.toMutableList() ?: mutableListOf()
val heldItem = if (heldItems.isNotEmpty()) {
val until100 = 1 - heldItems.sumOf { it.percentage / 100 }
if (until100 > 0 && spawnablePosition.world.random.nextDouble() < until100) {
null
} else {
heldItems.weightedSelection { it.percentage }
}
} else {
null
}?.createStack(spawnablePosition)
return PokemonSpawnAction(
spawnablePosition = spawnablePosition,
bucket = bucket,
detail = this,
props = pokemon,
heldItem = heldItem,
drops = drops,
levelRange = levelRange
)
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import net.minecraft.core.component.DataComponentMap
import net.minecraft.core.component.DataComponentPatch
import net.minecraft.core.registries.Registries
@@ -23,15 +23,15 @@ class PossibleHeldItem(
val componentMap: DataComponentMap? = null,
val percentage: Double = 100.0
) {
fun createStack(ctx: SpawningContext): ItemStack? {
val itemRegistry = ctx.world.registryAccess().registryOrThrow(Registries.ITEM)
fun createStack(spawnablePosition: SpawnablePosition): ItemStack? {
val itemRegistry = spawnablePosition.world.registryAccess().registryOrThrow(Registries.ITEM)
val item = if (item.startsWith("#")) {
val tag = TagKey.create(Registries.ITEM, ResourceLocation.parse(item.substring(1)))
val opt = itemRegistry.getTag(tag)
if (opt.isPresent && opt.get().size() > 0) {
val entryList = opt.get()
entryList.getRandomElement(ctx.world.random).get().value()
entryList.getRandomElement(spawnablePosition.world.random).get().value()
} else {
LOGGER.error("Unable to find matching spawn held items for tag: $item")
null
@@ -20,5 +20,5 @@ import com.google.gson.JsonElement
class RegisteredSpawnDetail<T : SpawnDetail>(
val detailClass: Class<T>
) {
fun deserializeDetail(element: JsonElement, ctx: JsonDeserializationContext): T = ctx.deserialize(element, detailClass)
fun deserializeDetail(element: JsonElement, spawnablePosition: JsonDeserializationContext): T = spawnablePosition.deserialize(element, detailClass)
}
@@ -11,7 +11,8 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.world.entity.Entity
@@ -22,18 +23,19 @@ import net.minecraft.world.entity.Entity
* @since January 13th, 2024
*/
abstract class SingleEntitySpawnAction<T : Entity>(
ctx: SpawningContext,
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
detail: SpawnDetail
) : SpawnAction<EntitySpawnResult>(ctx, detail) {
) : SpawnAction<EntitySpawnResult>(spawnablePosition, bucket, detail) {
abstract fun createEntity(): T?
override fun run(): EntitySpawnResult? {
val e = createEntity() ?: return null
e.setPos(ctx.position.toVec3d().add(0.5, 1.0, 0.5))
e.setPos(spawnablePosition.position.toVec3d().add(0.5, 1.0, 0.5))
var shouldSpawn = false
CobblemonEvents.ENTITY_SPAWN.postThen(SpawnEvent(e, ctx), ifSucceeded = {
CobblemonEvents.ENTITY_SPAWN.postThen(SpawnEvent(e, spawnablePosition), ifSucceeded = {
this.entity.emit(e)
ctx.world.addFreshEntity(e)
spawnablePosition.world.addFreshEntity(e)
shouldSpawn = true
})
@@ -51,6 +53,6 @@ abstract class SingleEntitySpawnAction<T : Entity>(
* where the entity spawn has been canceled, so that your action will not occur.
*/
val entity: SimpleObservable<T> = SimpleObservable<T>().apply {
subscribe { entity -> ctx.afterSpawn(entity) }
subscribe { entity -> spawnablePosition.afterSpawn(action = this@SingleEntitySpawnAction, entity = entity) }
}
}
@@ -8,24 +8,27 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import java.util.concurrent.CompletableFuture
/**
* A scheduled spawning action.
* A planned, actionable spawn.
*
* @author Hiroku
* @since February 4th, 2022
*/
abstract class SpawnAction<R>(
val ctx: SpawningContext,
val spawnablePosition: SpawnablePosition,
val bucket: SpawnBucket,
open val detail: SpawnDetail
) {
val future = CompletableFuture<R>().also { it.thenApply { result -> ctx.spawner.afterSpawn(this, result) } }
val labels = mutableListOf<String>()
val future = CompletableFuture<R>().also { it.thenApply { result -> spawnablePosition.spawner.afterSpawn(this, result) } }
/**
* Does whatever action is required to spawn this detail into the context.
* Does whatever action is required to spawn this detail into the spawnable position.
*
* @return The result of spawning
*/
@@ -39,7 +42,7 @@ abstract class SpawnAction<R>(
return null
}
ctx.applyInfluences { it.affectAction(this) }
spawnablePosition.applyInfluences { it.affectAction(this) }
val result = run()
if (result != null) {
future.complete(result)
@@ -11,18 +11,21 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.ModDependant
import com.cobblemon.mod.common.api.molang.MoLangFunctions.queryStructOf
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.condition.CompositeSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asTranslated
import com.google.gson.annotations.SerializedName
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -45,7 +48,8 @@ abstract class SpawnDetail : ModDependant {
abstract val type: String
var id = ""
var displayName: String? = null
lateinit var context: RegisteredSpawningContext<*>
@SerializedName("spawnablePositionType", alternate = ["context"])
lateinit var spawnablePositionType: SpawnablePositionType<*>
var bucket = SpawnBucket("", 0F)
var conditions = mutableListOf<SpawningCondition<*>>()
var anticonditions = mutableListOf<SpawningCondition<*>>()
@@ -74,7 +78,7 @@ abstract class SpawnDetail : ModDependant {
"bucket" to { StringValue(bucket.name) },
"width" to { DoubleValue(width.toDouble()) },
"height" to { DoubleValue(height.toDouble()) },
"context" to { StringValue(context.name) },
"spawnable_position_type" to { StringValue(spawnablePositionType.name) },
"labels" to { labels.asArrayValue { StringValue(it) } }
)
@@ -102,16 +106,16 @@ abstract class SpawnDetail : ModDependant {
}
}
open fun isSatisfiedBy(ctx: SpawningContext): Boolean {
if (!ctx.preFilter(this)) {
open fun isSatisfiedBy(spawnablePosition: SpawnablePosition): Boolean {
if (!spawnablePosition.preFilter(this)) {
return false
} else if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(ctx) }) {
} else if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(spawnablePosition) }) {
return false
} else if (anticonditions.isNotEmpty() && anticonditions.any { it.isSatisfiedBy(ctx) }) {
} else if (anticonditions.isNotEmpty() && anticonditions.any { it.isSatisfiedBy(spawnablePosition) }) {
return false
} else if (compositeCondition?.satisfiedBy(ctx) == false) {
} else if (compositeCondition?.satisfiedBy(spawnablePosition) == false) {
return false
} else if (!ctx.postFilter(this)) {
} else if (!spawnablePosition.postFilter(this)) {
return false
}
@@ -131,5 +135,46 @@ abstract class SpawnDetail : ModDependant {
return super.isModDependencySatisfied() && !containsNullValues
}
abstract fun doSpawn(ctx: SpawningContext): SpawnAction<*>
/**
* Runs when selected by a selection algorithm. This handles removing any nearby spawns in the selection
* data and whatever else.
*
* [selectionData] the algorithm data that is being used to choose spawns.
* [spawnablePosition] where the spawn is taking place.
* [spawnAction] the spawn action that was chosen based off this [SpawnDetail] to spawn at this position.
*/
protected open fun onSelection(
spawnablePosition: SpawnablePosition,
spawnAction: SpawnAction<*>,
selectionData: SpawnSelectionData
) {
selectionData.removeSpawnablePositions { _, pos ->
pos.distanceTo(spawnablePosition) < Cobblemon.config.minimumDistanceBetweenEntities
}
}
/**
* Chooses this spawn detail from selection data, returning a functional
* spawn action.
*/
fun choose(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): SpawnAction<*> {
val action = createSpawnAction(spawnablePosition, bucket, selectionData)
onSelection(
spawnablePosition = spawnablePosition,
spawnAction = action,
selectionData = selectionData
)
return action
}
/** Instantiates an action at the given [SpawnablePosition] for the given [SpawnBucket]. */
protected abstract fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): SpawnAction<*>
}
@@ -11,12 +11,13 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.SpawnSet
import com.cobblemon.mod.common.api.spawning.condition.PrecalculationResult
import com.cobblemon.mod.common.api.spawning.condition.RootPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.SpawningPrecalculation
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.server
@@ -47,7 +48,7 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
override fun sync(player: ServerPlayer) {}
override fun reload(data: Map<ResourceLocation, SpawnSet>) {
details.clear()
for (set in data.values) {
for (set in data.values.filter { it.isEnabled() }) {
details.addAll(set.filter { it.isValid() })
}
val server = server()
@@ -73,8 +74,8 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
/**
* Precalculates spawns into hash mappings using the [precalculators] included
* in this pool as well as the range of contexts mentioned in the pool. This
* will speed up retrieval later, and thins the herd of spawns that need to be
* in this pool as well as the range of spawnable positions mentioned in the pool.
* This will speed up retrieval later, and thins the herd of spawns that need to be
* thoroughly examined when a spawn is occurring. This function will probably
* be slow, especially if there are many precalculators and spawns.
*/
@@ -84,21 +85,18 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
} else {
precalculation = precalculators.first().generate(details, precalculators.subList(1, precalculators.size))
}
// contexts.clear()
// details.forEach { contexts.add(it.context) }
}
/**
* Retrieves the spawns that are precalculated as being potentially spawns at
* this context. This, at most, prunes some spawns that were definitely not
* possible here. The returned list can and almost certainly will include more
* spawns that are not possible for this context - this function is simple
* this spawnable position. This, at most, prunes some spawns that were definitely
* not possible here. The returned list can and almost certainly will include more
* spawns that are not possible for this spawnable position - this function is simple
* to leverage the precalculation to get a smaller list of spawns as quickly
* as possible.
*/
fun retrieve(ctx: SpawningContext): List<SpawnDetail> {
return precalculation.retrieve(ctx)
fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
return precalculation.retrieve(bucket, spawnablePosition)
}
/**
@@ -18,7 +18,8 @@ import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
@@ -40,10 +41,10 @@ import net.minecraft.world.item.ItemStack
*/
class FishingSpawnCause(
spawner: Spawner,
bucket: SpawnBucket,
entity: Entity?,
val rodStack: ItemStack
) : SpawnCause(spawner, bucket, entity) {
val rodStack: ItemStack,
val lureLevel: Int
) : SpawnCause(spawner, entity) {
companion object {
const val FISHED_ASPECT = "fished"
fun shinyReroll(pokemonEntity: PokemonEntity, effect: SpawnBait.Effect) {
@@ -134,22 +135,28 @@ class FishingSpawnCause(
// Determine the combined bait effects if any
val baitEffects = SpawnBaitInfluence(SpawnBaitEffects.getEffectsFromRodItemStack(rodStack))
override fun affectSpawn(entity: Entity) {
super.affectSpawn(entity)
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
super.affectSpawn(action, entity)
if (entity is PokemonEntity) {
entity.pokemon.forcedAspects += FISHED_ASPECT
baitEffects.affectSpawn(entity)
baitEffects.affectSpawn(action, entity)
// Some of the bait actions might have changed the aspects and we need it to be
// in the entityData IMMEDIATELY otherwise it will flash as what it would be
// with the old aspects.
// New aspects copy into the entity data only on the next tick.
entity.entityData.set(PokemonEntity.ASPECTS, entity.pokemon.aspects)
CobblemonEvents.BOBBER_SPAWN_POKEMON_MODIFY.post(BobberSpawnPokemonEvent.Modify(bucket, rodStack, entity))
CobblemonEvents.BOBBER_SPAWN_POKEMON_MODIFY.post(
BobberSpawnPokemonEvent.Modify(
spawnAction = action,
rod = rodStack,
pokemon = entity
)
)
}
}
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
val weight = baitEffects.affectWeight(detail, ctx, weight)
return super.affectWeight(detail, ctx, weight)
override fun affectWeight(detail: SpawnDetail, spawnablePosition: SpawnablePosition, weight: Float): Float {
val weight = baitEffects.affectWeight(detail, spawnablePosition, weight)
return super.affectWeight(detail, spawnablePosition, weight)
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.spawning.fishing
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.spawner.TriggerSpawner
import net.minecraft.server.level.ServerLevel
@@ -25,14 +25,14 @@ open class FishingSpawner(
override val name: String = "fishing",
pool: SpawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
) : TriggerSpawner<FishingSpawnCause>(name, pool) {
override fun parseContext(cause: FishingSpawnCause, world: ServerLevel, pos: BlockPos): FishingSpawningContext? {
override fun parseSpawnablePosition(cause: FishingSpawnCause, world: ServerLevel, pos: BlockPos): FishingSpawnablePosition? {
// Maybe confirm that it's water we're fishing in?
val ctx = FishingSpawningContext(
val spawnablePosition = FishingSpawnablePosition(
cause = cause,
world = world,
pos = pos,
influences = influences
)
return ctx
return spawnablePosition
}
}
@@ -0,0 +1,54 @@
/*
* 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.SpawnBucket
import kotlin.math.pow
/**
* A [SpawningInfluence] that normalizes the weights of buckets based on a tier system. The idea is that
* normally the rarest buckets are MUCH rarer than the most common buckets, but in some cases we want
* those distances to be closer to each other. This is usually for when we're doing fewer spawn actions
* overall, meaning that the rarest buckets become very harsh, or we want to apply lure bonuses.
*
* The [tier] is an integer stepping up through different degrees of normalization. This is used with [gradient]
* and [firstTier] to control the rate at which the tiers move the buckets. The equation for the new
* weight of a bucket is `weight ^ (1 / normalizationFactor)`. This means that a normalization factor of 1
* will not change the weights at all, and a normalization factor of 2 will make each weight turn into its
* square root, and so on. As this number grows, the closer the bucket weights will be to each other.
*
* The normalizationFactor is calculated as `initial + (gradient * tier)`.
*
* @author Hiroku
* @since April 20th, 2025
*/
open class BucketNormalizingInfluence(
val tier: Int = 0,
val gradient: Float = 0.2F,
val firstTier: Float = 1.29F
) : SpawningInfluence {
override fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
if (tier == 0) {
return
}
val normalizationFactor = firstTier + (gradient * (tier - 1)).toDouble()
bucketWeights.keys.toList().forEach {
val weight = bucketWeights[it] ?: return@forEach
bucketWeights[it] = weight.toDouble().pow(1 / normalizationFactor).toFloat()
}
val sum = bucketWeights.values.sum()
val to100 = 100F / sum
bucketWeights.keys.toList().forEach {
val weight = bucketWeights[it] ?: return@forEach
bucketWeights[it] = weight * to100
}
}
}
@@ -8,23 +8,18 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
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.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity
class IncenseSweetInfluence(val pos: BlockPos? = null) : SpawningInfluence {
override fun affectSpawn(entity: Entity) {
super.affectSpawn(entity)
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
}
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
return super.affectWeight(detail, ctx, weight)
override fun affectWeight(detail: SpawnDetail, spawnablePosition: SpawnablePosition, weight: Float): Float {
return super.affectWeight(detail, spawnablePosition, weight)
}
}
@@ -63,7 +63,7 @@ open class PlayerLevelRangeInfluence(
override fun affectAction(action: SpawnAction<*>) {
if (action is PokemonSpawnAction && action.props.level == null) {
val playerLevelRange = getPlayerLevelRange()
val derivedLevelRange = action.detail.getDerivedLevelRange()
val derivedLevelRange = action.levelRange
var spawnLevelRange = playerLevelRange.intersection(derivedLevelRange)
val pokemonRangeWidth = derivedLevelRange.last - derivedLevelRange.first
if (spawnLevelRange.isEmpty()){
@@ -8,38 +8,49 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.block.RotatedPillarBlock
class SaccharineHoneyLogInfluence(val pos: BlockPos? = null) : SpawningInfluence {
companion object {
const val SACCHARINE_HONEY_MARKER = "saccharine_honey_log"
}
var used = false
val chanceForHA = 0.05
private fun markUsed() {
used = true // todo even though I call this it is not saved.... hmmmm
override fun affectSpawnablePosition(spawnablePosition: SpawnablePosition) {
spawnablePosition.markers.add(SACCHARINE_HONEY_MARKER)
}
override fun affectSpawn(entity: Entity) {
super.affectSpawn(entity)
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
if (entity is PokemonEntity) {
if (Math.random() <= chanceForHA) {
FishingSpawnCause.alterHAAttempt(entity)
}
this.markUsed()
if (!used) {
val logPos = pos
val level = action.spawnablePosition.world.level
if (logPos != null) {
val blockState = level.getBlockState(logPos)
if (blockState.block == CobblemonBlocks.SACCHARINE_HONEY_LOG) {
val axis = blockState.getValue(RotatedPillarBlock.AXIS)
val newState = CobblemonBlocks.SACCHARINE_LOG.defaultBlockState().setValue(RotatedPillarBlock.AXIS, axis)
level.setBlock(logPos, newState, 3)
}
}
used = true
}
}
}
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
return super.affectWeight(detail, ctx, weight)
}
fun wasUsed(): Boolean = used
}
@@ -8,23 +8,23 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
/**
* A type of [WorldSlicedSpawningInfluence] that is only applied to contexts within a particular radius
* A type of [SpawningZoneInfluence] that is only applied to spawnable positions within a particular radius
* of a position.
*
* @author Hiroku
* @since March 9th, 2025
*/
class WorldSlicedSpatialSpawningInfluence(
class SpatialSpawningZoneInfluence(
val pos: BlockPos,
val radius: Float,
influence: SpawningInfluence
) : WorldSlicedSpawningInfluence(influence) {
override fun appliesTo(context: SpawningContext): Boolean {
return sqrt(context.position.distSqr(pos)) <= radius
override val influence: SpawningInfluence
) : ConditionalSpawningZoneInfluence {
override fun appliesTo(spawnablePosition: SpawnablePosition): Boolean {
return sqrt(spawnablePosition.position.distSqr(pos)) <= radius
}
}
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.fishing.SpawnBaitUtils
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.stats.Stats
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.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity
/**
@@ -29,16 +29,16 @@ import net.minecraft.world.entity.Entity
* @author Hiroku, Plastered_Crab
* @since March 18th, 2025
*/
class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val baitPos: BlockPos? = null) : SpawningInfluence {
class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int) -> Unit = {}) : SpawningInfluence {
var used: Boolean = false
var usedTimes = 0
private fun markUsed() {
used = true
usedTimes++
onUsed(usedTimes)
}
override fun affectSpawn(entity: Entity) {
super.affectSpawn(entity)
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
if (entity is PokemonEntity) {
val merged = SpawnBaitUtils.mergeEffects(effects)
merged.forEach { effect ->
@@ -51,7 +51,7 @@ class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val baitPos: Block
}
// EV related bait effects
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
override fun affectWeight(detail: SpawnDetail, spawnablePosition: SpawnablePosition, weight: Float): Float {
val merged = SpawnBaitUtils.mergeEffects(effects)
// if bait exists and any effects are related to EV yields
@@ -65,11 +65,11 @@ class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val baitPos: Block
return when {
evYieldValue > 0 -> {
markUsed()
super.affectWeight(detail, ctx, weight)
super.affectWeight(detail, spawnablePosition, weight)
}
else -> {
markUsed()
super.affectWeight(detail, ctx, 0f)
super.affectWeight(detail, spawnablePosition, 0f)
}
}
}
@@ -87,9 +87,9 @@ class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val baitPos: Block
return when {
isMatchingType -> {
markUsed()
super.affectWeight(detail, ctx, weight * baitEffect.value.toFloat())
super.affectWeight(detail, spawnablePosition, weight * baitEffect.value.toFloat())
}
else -> super.affectWeight(detail, ctx, weight)
else -> super.affectWeight(detail, spawnablePosition, weight)
}
}
}
@@ -117,11 +117,11 @@ class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val baitPos: Block
if (matchingEffect != null) {
markUsed()
val multiplier = matchingEffect.value
return super.affectWeight(detail, ctx, (weight * multiplier).toFloat())
return super.affectWeight(detail, spawnablePosition, (weight * multiplier).toFloat())
}
}
}
}
return super.affectWeight(detail, ctx, weight)
return super.affectWeight(detail, spawnablePosition, weight)
}
}
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
@@ -28,16 +28,24 @@ import net.minecraft.world.entity.Entity
interface SpawningInfluence {
/** Whether this influence has passed and should be removed. */
fun isExpired(): Boolean = false
/** Runs for each influence when added to the spawnable position. */
fun affectSpawnablePosition(spawnablePosition: SpawnablePosition) {}
/** Returns true if the given spawn detail is able to spawn under this influence. */
fun affectSpawnable(detail: SpawnDetail, ctx: SpawningContext): Boolean = true
fun affectSpawnable(detail: SpawnDetail, spawnablePosition: SpawnablePosition): Boolean = true
/** Returns the effective weight of spawning under this influence. This is after typical weight multipliers. */
fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float = weight
fun affectWeight(detail: SpawnDetail, spawnablePosition: SpawnablePosition, weight: Float): Float = weight
/** Affects the spawn action prior to it generating the entity. */
fun affectAction(action: SpawnAction<*>) {}
/** Applies some influence over the entity that's been spawned. */
fun affectSpawn(entity: Entity) {}
fun affectSpawn(action: SpawnAction<*>, entity: Entity) {}
/** Applies some influence over the weight of spawn buckets. */
fun affectBucketWeight(bucket: SpawnBucket, weight: Float): Float = weight
/** Filters positions that could be usable for a spawning context calculator */
fun isAllowedPosition(world: ServerLevel, pos: BlockPos, contextCalculator: SpawningContextCalculator<*, *>): Boolean = true
fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {}
/** Filters positions that could be usable for a spawnable position calculator */
fun isAllowedPosition(world: ServerLevel, pos: BlockPos, spawnablePositionCalculator: SpawnablePositionCalculator<*, *>): Boolean = true
/**
* Injects additional spawn possibilities for the given bucket and spawnable position.
*
* You should make sure the spawn position satisfies any spawn details that you inject.
*/
fun injectSpawns(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>? = null
}
@@ -0,0 +1,36 @@
/*
* 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.SpawningZone
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
/**
* A wrapping around a [SpawningInfluence] that was produced for a [SpawningZone]. This is wrapped
* to explicitly state that some are [UnconditionalSpawningZoneInfluence] and therefore apply to all the
* spawnable positions and any overarching operations (like bucket selection) whereas others are
* [ConditionalSpawningZoneInfluence] and may only apply to particular [SpawnablePosition]s.
*
* The only reason this cannot be simplified to all being [ConditionalSpawningZoneInfluence] is because
* some influence functions can be run after the spawning zone has been created but outside the context
* of any particular spawnable positions. I don't want to run it with a fake spawnable position to test
* whether it's effectively unconditional.
*
* @author Hiroku
* @since March 9th, 2025
*/
sealed interface SpawningZoneInfluence {
val influence: SpawningInfluence
}
open class UnconditionalSpawningZoneInfluence(override val influence: SpawningInfluence) : SpawningZoneInfluence
interface ConditionalSpawningZoneInfluence: SpawningZoneInfluence {
/** Whether this influence will apply to */
fun appliesTo(spawnablePosition: SpawnablePosition): Boolean = true
}
@@ -1,24 +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.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
/**
* A wrapping around a [SpawningInfluence] that was produced for a [WorldSlice]. This is wrapped
* to provide a filtration mechanism on which of these can be applied to a [SpawningContext].
*
* @author Hiroku
* @since March 9th, 2025
*/
open class WorldSlicedSpawningInfluence(val influence: SpawningInfluence) {
/** Whether this influence will apply to */
open fun appliesTo(context: SpawningContext): Boolean = true
}
@@ -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/.
*/
///*
// * 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.detector
//
//import com.cobblemon.mod.common.CobblemonPoiTypes
//import com.cobblemon.mod.common.api.spawning.influence.GrottoImpactedSpawningInfluence
//import com.cobblemon.mod.common.api.spawning.influence.UnconditionalZoneSpawningInfluence
//import com.cobblemon.mod.common.api.spawning.influence.ZoneSpawningInfluence
//import com.cobblemon.mod.common.api.spawning.spawner.Spawner
//import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
//import com.cobblemon.mod.common.util.toBlockPos
//import net.minecraft.core.BlockPos
//import net.minecraft.core.Holder
//import net.minecraft.server.level.ServerLevel
//import net.minecraft.world.entity.ai.village.poi.PoiManager
//import net.minecraft.world.entity.ai.village.poi.PoiType
//import net.minecraft.world.level.block.state.BlockState
//
//object GrottoBlockDetector : SpawningInfluenceDetector {
// @JvmField
// val RANGE: Int = 48
//
// override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): List<ZoneSpawningInfluence> {
// val world = input.world
// val searchRange = maxOf(RANGE, input.length * 2, input.height * 2)
// val grottoBlockPositions = world.poiManager.findAll(
// { holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.GROTTO_BLOCK_KEY) },
// { true },
// input.getCenter().toBlockPos(),
// searchRange,
// PoiManager.Occupancy.ANY
// ).toList()
//
// return if (grottoBlockPositions.isEmpty()) {
// emptyList()
// } else {
// listOf(UnconditionalZoneSpawningInfluence(influence = GrottoImpactedSpawningInfluence()))
// }
// }
//
// override fun detectFromBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState) = emptyList<ZoneSpawningInfluence>()
//}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.influence.detector
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.state.BlockState
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.prospecting.IncenseSweetDetector
import com.cobblemon.mod.common.api.spawning.prospecting.LureCakeDetector
import com.cobblemon.mod.common.api.spawning.prospecting.SaccharineHoneyLogDetector
/**
* Prospects for [SpawningZoneInfluence] in a world at a given position. Occurs as part of a
* [SpawningZoneGenerator] scanning a part of the world while formulating a [SpawningZone].
*
* @author Hiroku
* @since March 9th, 2025
*/
interface SpawningInfluenceDetector {
companion object {
@JvmStatic
val detectors = mutableSetOf<SpawningInfluenceDetector>(
LureCakeDetector,
SaccharineHoneyLogDetector,
IncenseSweetDetector
)
}
fun detectFromInput(spawner: Spawner, input: SpawningZoneInput) : List<SpawningZoneInfluence>
fun detectFromBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): List<SpawningZoneInfluence>
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.spawning.multiplier
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
@@ -30,9 +30,9 @@ class WeightMultiplier : SpawningInfluence {
var anticonditions = mutableListOf<SpawningCondition<*>>()
var multiplier = 1F
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
val meetsConditions = (conditions.isEmpty() || conditions.any { it.isSatisfiedBy(ctx) })
&& (anticonditions.isEmpty() || anticonditions.none { it.isSatisfiedBy(ctx) })
override fun affectWeight(detail: SpawnDetail, spawnablePosition: SpawnablePosition, weight: Float): Float {
val meetsConditions = (conditions.isEmpty() || conditions.any { it.isSatisfiedBy(spawnablePosition) })
&& (anticonditions.isEmpty() || anticonditions.none { it.isSatisfiedBy(spawnablePosition) })
return if (meetsConditions) multiplier * weight else weight
}
}
@@ -6,27 +6,27 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import kotlin.math.ceil
import kotlin.math.floor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
/**
* A [SpawningContext] that is for a particular area, and therefore has spatial properties.
* A [SpawnablePosition] that is for a particular area, and therefore has spatial properties.
*
* @author Hiroku
* @since January 31st, 2022
*/
open class AreaSpawningContext(
open class AreaSpawnablePosition(
override val cause: SpawnCause,
override val world: ServerLevel,
override val position: BlockPos,
@@ -37,17 +37,17 @@ open class AreaSpawningContext(
/** Space above. */
val height: Int,
val nearbyBlocks: List<BlockState>,
val slice: WorldSlice
) : SpawningContext() {
val zone: SpawningZone
) : SpawnablePosition() {
val nearbyBlockTypes: List<Block> by lazy { nearbyBlocks.mapNotNull { it.block }.distinct() }
val nearbyBlockHolders: List<Holder<Block>> by lazy { nearbyBlockTypes.mapNotNull(blockRegistry::wrapAsHolder) }
override fun getStructureCache(pos: BlockPos): StructureChunkCache {
return slice.getStructureCache(pos)
return zone.getStructureCache(pos)
}
/**
* Returns true if the given block can be occupied by a spawn in this context.
* Returns true if the given block can be occupied by a spawn in this spawnable position.
* This is not considering the provided state as what the entity would be on top of,
* but rather the space its hitbox would fill.
*/
@@ -74,7 +74,7 @@ open class AreaSpawningContext(
for (x in minX until maxX) {
for (y in (position.y + 1)..maxY) {
for (z in minZ until maxZ) {
val state = world.getBlockState(mutable.set(x, y, z))
val state = zone.getBlockState(mutable.set(x, y, z))
if (!isSafeSpace(world, mutable, state)) {
return false
}
@@ -0,0 +1,88 @@
/*
* 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.position
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.position.calculators.AreaSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.AreaSpawningInput
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.core.BlockPos
/**
* Interface responsible for drawing a list of spawnable positions from a slice of the world,
* given a list of all the spawnable position calculators that should be considered in order. As soon
* as one of the spawnable position calculators returns true for [AreaSpawnablePositionCalculator.fits],
* no other spawnable position calculator will be considered.
*
* The default method body of this interface checks every single block in the slice
* and composes a single spawnable position per BlockPos, at most. This is almost certainly fine,
* but this interface exists, so you can override it if you want.
*
* @author Hiroku
* @since January 31st, 2022
*/
interface AreaSpawnablePositionResolver {
fun resolve(
spawner: Spawner,
spawnablePositionCalculators: List<AreaSpawnablePositionCalculator<*>>,
zone: SpawningZone
): List<AreaSpawnablePosition> {
var pos = BlockPos.MutableBlockPos(1, 2, 3)
val input = AreaSpawningInput(spawner, pos, zone)
val spawnablePositions = mutableListOf<AreaSpawnablePosition>()
var x = zone.baseX
var y = zone.baseY
var z = zone.baseZ
while (x < zone.baseX + zone.length) {
while (y < zone.baseY + zone.height) {
while (z < zone.baseZ + zone.width) {
pos.set(x, y, z)
val vec = pos.toVec3d()
if (zone.nearbyEntityPositions.none { it.closerThan(vec, config.minimumDistanceBetweenEntities) && it != zone.cause.entity }) {
val fittedSpawnablePositionCalculator = spawnablePositionCalculators
.firstOrNull { calc -> calc.fits(input) && input.spawner.influences.none { !it.isAllowedPosition(input.world, input.position, calc) } }
if (fittedSpawnablePositionCalculator != null) {
val spawnablePosition = fittedSpawnablePositionCalculator.calculate(input)
if (spawnablePosition != null) {
val influences = zone.getInfluences(spawnablePosition)
for (influence in influences) {
spawnablePosition.influences.add(influence)
influence.affectSpawnablePosition(spawnablePosition)
}
for (influence in spawner.influences) {
influence.affectSpawnablePosition(spawnablePosition)
}
spawnablePositions.add(spawnablePosition)
// The position BlockPos has been used in a spawnable position, editing the same one
// will cause entities to spawn at the wrong location (buried in walls, usually).
// I made it so that built-in spawnable position calculators explicitly take a copy of the
// BlockPos but it'd still be exposed in custom spawnable positions so fixing it here too so
// custom spawnable position calculators don't have to remember to do it. - Hiroku
pos = BlockPos.MutableBlockPos(1, 2, 3)
input.position = pos
}
}
}
z++
}
y++
z = zone.baseZ
}
x++
y = zone.baseY
z = zone.baseZ
}
return spawnablePositions
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
@@ -18,17 +18,17 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.phys.AABB
/**
* A spawning context that was generated for a fishing action.
* A spawnable position that was generated for a fishing action.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawningContext(
class FishingSpawnablePosition(
cause: FishingSpawnCause,
world: ServerLevel,
pos: BlockPos,
influences: MutableList<SpawningInfluence>
) : TriggerSpawningContext(
) : TriggerSpawnablePosition(
cause = cause,
world = world,
position = pos,
@@ -6,10 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.util.blockRegistry
import net.minecraft.core.BlockPos
@@ -20,12 +20,12 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
/**
* A type of area based spawning context with a floor.
* A type of area based spawnable position with a floor.
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class FlooredSpawningContext(
abstract class FlooredSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -35,20 +35,20 @@ abstract class FlooredSpawningContext(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : AreaSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
zone: SpawningZone
) : AreaSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
/** The block that the spawning is occurring on. */
val baseBlock = slice.getBlockState(position.x, position.y, position.z)
val baseBlock = zone.getBlockState(position.x, position.y, position.z)
val baseBlockHolder: Holder<Block> by lazy { world.blockRegistry.wrapAsHolder(baseBlock.block) }
}
/**
* A land spawning context.
* A land spawnable position.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class GroundedSpawningContext(
open class GroundedSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -58,16 +58,16 @@ open class GroundedSpawningContext(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice)
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone)
/**
* A spawning context that occurs at the bottom of a body of water.
* A spawnable position that occurs at the bottom of a body of water.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class SeafloorSpawningContext(
open class SeafloorSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -77,18 +77,18 @@ open class SeafloorSpawningContext(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
override fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState) = state.fluidState.`is`(FluidTags.WATER)
}
/**
* A spawning context that occurs at the bottom of bodies of lava.
* A spawnable position that occurs at the bottom of bodies of lava.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class LavafloorSpawningContext(
open class LavafloorSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -98,12 +98,12 @@ open class LavafloorSpawningContext(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
override fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState) = state.fluidState.`is`(FluidTags.LAVA)
}
open class SurfaceSpawningContext(
open class SurfaceSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -114,5 +114,5 @@ open class SurfaceSpawningContext(
height: Int,
val depth: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice)
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone)
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
@@ -14,9 +14,11 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
@@ -34,20 +36,20 @@ import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
/**
* A context upon which spawning is being attempted. This supplies all the information that can be used to asynchronously
* A position upon which spawning is being attempted. This supplies all the information that can be used to rapidly
* check for suitability of spawning.
*
* @author Hiroku
* @since January 24th, 2022
*/
abstract class SpawningContext {
abstract class SpawnablePosition {
companion object {
val contexts = mutableListOf<RegisteredSpawningContext<*>>()
fun getByName(name: String) = contexts.find { it.name == name }
fun getByClass(ctx: SpawningContext) = contexts.find { it.clazz == ctx::class.java }
fun <T : SpawningContext> register(name: String, clazz: Class<T>, defaultCondition: String = BasicSpawningCondition.NAME) {
contexts.add(
RegisteredSpawningContext(
val spawnablePositionTypes = mutableListOf<SpawnablePositionType<*>>()
fun getByName(name: String) = spawnablePositionTypes.find { it.name == name }
fun getByClass(spawnablePosition: SpawnablePosition) = spawnablePositionTypes.find { it.clazz == spawnablePosition::class.java }
fun <T : SpawnablePosition> register(name: String, clazz: Class<T>, defaultCondition: String = BasicSpawningCondition.NAME) {
spawnablePositionTypes.add(
SpawnablePositionType(
name = name,
clazz = clazz,
defaultCondition = defaultCondition
@@ -58,13 +60,13 @@ abstract class SpawningContext {
@Transient
val runtime = MoLangRuntime().setup()
.also { runtime -> runtime.environment.query.addFunction("context") { getOrSetupStruct() } }
.also { runtime -> runtime.environment.query.addFunction("spawnable_position") { getOrSetupStruct() } }
/** What caused the spawn context, as a [SpawnCause]. */
/** What caused the spawn spawnable position, as a [SpawnCause]. */
abstract val cause: SpawnCause
val spawner: Spawner
get() = cause.spawner
/** The [ServerLevel] the spawning context exists in. */
/** The [ServerLevel] the spawnable position exists in. */
abstract val world: ServerLevel
/** The location of the spawning attempt. */
abstract val position: BlockPos
@@ -74,7 +76,7 @@ abstract class SpawningContext {
abstract val skyLight: Int
/** Whether or not the sky is visible at this location. */
abstract val canSeeSky: Boolean
/** A list of [SpawningInfluence]s that apply due to this specific context. This generally shouldn't be consumed; use [getAllInfluences].*/
/** A list of [SpawningInfluence]s that apply due to this specific position. This generally shouldn't be consumed; use [getAllInfluences].*/
abstract val influences: MutableList<SpawningInfluence>
/** Gets a cache of structures by block coordinates, grouped by chunk. */
abstract fun getStructureCache(pos: BlockPos): StructureChunkCache
@@ -83,9 +85,12 @@ abstract class SpawningContext {
val moonPhase: Int by lazy { world.moonPhase }
/** The biome of this location. */
val biome: Biome by lazy { biomeHolder.value() }
/** The registry holder for the biome this context is in. */
/** The registry holder for the biome this position is in. */
val biomeHolder: Holder<Biome> by lazy { world.getBiome(position) }
/** A list of markers that can be used to identify this spawnable position for arbitrary conditions. */
val markers = mutableSetOf<String>()
val biomeRegistry: Registry<Biome> by lazy { world.registryAccess().registryOrThrow(Registries.BIOME) }
val blockRegistry: Registry<Block> by lazy { world.registryAccess().registryOrThrow(Registries.BLOCK) }
val fluidRegistry: Registry<Fluid> by lazy { world.registryAccess().registryOrThrow(Registries.FLUID) }
@@ -151,8 +156,8 @@ abstract class SpawningContext {
}
/**
* Filters a spawning detail by some extra condition defined by the context itself. This is for API purposes.
* @return true if the [SpawnDetail] is acceptable by the context's own logic.
* Filters a spawning detail by some extra condition defined by the spawnable position itself. This is for API purposes.
* @return true if the [SpawnDetail] is acceptable by the spawnable position's own logic.
*/
open fun preFilter(detail: SpawnDetail): Boolean {
/** Returns true if none of the influences.affectSpawnable return false */
@@ -160,13 +165,13 @@ abstract class SpawningContext {
}
/**
* Filters a spawning detail by some extra, more expensive condition defined by the context itself.
* @return true if the [SpawnDetail] is acceptable by the context's own logic.
* Filters a spawning detail by some extra, more expensive condition defined by the spawnable position itself.
* @return true if the [SpawnDetail] is acceptable by the spawnable position's own logic.
*/
open fun postFilter(detail: SpawnDetail): Boolean = true
open fun afterSpawn(entity: Entity) {
applyInfluences { it.affectSpawn(entity) }
open fun afterSpawn(action: SpawnAction<*>, entity: Entity) {
applyInfluences { it.affectSpawn(action, entity) }
}
open fun getWeight(detail: SpawnDetail): Float {
@@ -185,7 +190,7 @@ abstract class SpawningContext {
}
/**
* Gets all influences that apply to this context, including the cause, and does something with them.
* Gets all influences that apply to this spawnable position, including the cause, and does something with them.
*
* Technically we could simple make a list of influences + cause and run that, but influences MUST be able
* to run extremely frequently in performance-critical code, so we don't want to have to allocate a list.
@@ -223,4 +228,8 @@ abstract class SpawningContext {
}
return false
}
fun distanceTo(other: SpawnablePosition): Float {
return sqrt(position.distSqr(other.position)).toFloat()
}
}
@@ -6,29 +6,29 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
/**
* A [SpawningContext] type that has been registered. Don't instantiate this
* yourself because you're probably doing something wrong. Look at [SpawningContext.register].
* A [SpawnablePosition] type that has been registered. Don't instantiate this
* yourself because you're probably doing something wrong. Look at [SpawnablePosition.register].
*
* A default condition type name is provided for when a [SpawnDetail] is being interpreted
* with a specific context - conditions in that detail can specify a specific condition
* with a specific spawnable position - conditions in that detail can specify a condition
* class, or if unspecified it will use the one here for convenience.
*
* Calculators for a custom context should be registered using [SpawningContextCalculator.register].
* Calculators for a custom spawnable position should be registered using [SpawnablePositionCalculator.register].
*
* @author Hiroku
* @since January 31st, 2022
*/
data class RegisteredSpawningContext<T : SpawningContext>(
data class SpawnablePositionType<T : SpawnablePosition>(
val name: String,
val clazz: Class<T>,
val defaultCondition: String
) {
fun getWeight() = Cobblemon.bestSpawner.config.contextWeights[name] ?: 1F
fun getWeight() = Cobblemon.bestSpawner.config.spawnablePositionTypeWeights[name] ?: 1F
}
@@ -6,22 +6,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
/**
* A type of area based spawning context with a fluid base block.
* A type of area based spawnable position with a fluid base block.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class SubmergedSpawningContext(
open class SubmergedSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -32,9 +32,9 @@ open class SubmergedSpawningContext(
height: Int,
val depth: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : AreaSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
val fluid = slice.getBlockState(position.x, position.y, position.z).fluidState
zone: SpawningZone
) : AreaSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
val fluid = zone.getBlockState(position.x, position.y, position.z).fluidState
override fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState) = state.fluidState.type == fluid.type
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
@@ -14,13 +14,13 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
/**
* A spawning context relating to triggered spawning. Uses a single-point structure cache which is
* A spawnable position relating to triggered spawning. Uses a single-point structure cache which is
* the only contribution of this implementation.
*
* @author Hiroku
* @since February 3rd, 2024
*/
open class TriggerSpawningContext(
open class TriggerSpawnablePosition(
override val cause: SpawnCause,
override val world: ServerLevel,
override val position: BlockPos,
@@ -28,7 +28,7 @@ open class TriggerSpawningContext(
override val skyLight: Int,
override val canSeeSky: Boolean,
override val influences: MutableList<SpawningInfluence>
) : SpawningContext() {
) : SpawnablePosition() {
val triggerStructureCache = StructureChunkCache()
override fun getStructureCache(pos: BlockPos) = triggerStructureCache
}
@@ -0,0 +1,60 @@
/*
* 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.position.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
/**
* A spawnable position calculator that deals with [AreaSpawnablePosition]s. These work off [AreaSpawningInput]
* instances, and must output some kind of [AreaSpawnablePosition].
*
* This is the interface to use for all the spawnable positions that will get used by world spawning as that
* is the primary case that area spawning is done. These get registered when creating the spawnable position type, in
* [SpawnablePosition.register].
*
* @author Hiroku
* @since January 31st, 2022
*/
interface AreaSpawnablePositionCalculator<O : AreaSpawnablePosition> : SpawnablePositionCalculator<AreaSpawningInput, O> {
/**
* Whether this spawnable position calculator is likely to provide a value for this location.
*
* This should be relatively final. See how the [AreaSpawnablePositionResolver] works to understand why
* you should be pretty sure before returning true to this function.
*/
fun fits(input: AreaSpawningInput): Boolean
fun getDepth(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int): Int
= input.zone.depthSpace(input.position.x, input.position.y, input.position.z, condition, maximum)
fun getHeight(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int, offsetX: Int = 0, offsetY: Int = 0, offsetZ: Int = 0): Int
= input.zone.heightSpace(input.position.x + offsetX, input.position.y + offsetY, input.position.z + offsetZ, condition, maximum)
fun getHorizontalSpace(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int, offsetX: Int = 0, offsetY: Int = 0, offsetZ: Int = 0): Int
= input.zone.horizontalSpace(input.position.x + offsetX, input.position.y + offsetY, input.position.z + offsetZ, condition, maximum)
fun getLight(input: AreaSpawningInput, elseLight: Int = 0): Int
= input.zone.getLight(input.position.x, input.position.y + 1, input.position.z, elseLight)
fun getSkyLight(input: AreaSpawningInput, elseLight: Int = 0): Int
= input.zone.getSkyLight(input.position.x, input.position.y + 1, input.position.z, elseLight)
fun getCanSeeSky(input: AreaSpawningInput): Boolean = input.zone.canSeeSky(input.position.x, input.position.y + 1, input.position.z)
fun getSkySpaceAbove(input: AreaSpawningInput): Int = input.zone.skySpaceAbove(input.position.x, input.position.y, input.position.z)
fun getNearbyBlocks(
input: AreaSpawningInput,
horizontalRadius: Int = config.maxNearbyBlocksHorizontalRange,
verticalRadius: Int = config.maxNearbyBlocksVerticalRange
) = input.zone.nearbyBlocks(input.position, horizontalRadius, verticalRadius)
}
open class AreaSpawningInput(val spawner: Spawner, var position: BlockPos, val zone: SpawningZone) : SpawnablePositionInput(zone.cause, zone.world)
@@ -6,52 +6,51 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context.calculators
package com.cobblemon.mod.common.api.spawning.position.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.context.*
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isAirCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isSolidCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isWaterCondition
import com.cobblemon.mod.common.api.spawning.position.*
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isAirCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isSolidCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isWaterCondition
import net.minecraft.world.level.block.state.BlockState
/**
* A spawning context calculator that creates some kind of [FlooredSpawningContext]. The shared
* idea of these contexts is that there is a base block condition for the floor, and then some
* A spawnable position calculator that creates some kind of [FlooredSpawnablePosition]. The shared
* idea of these spawnable positions is that there is a base block condition for the floor, and then some
* other condition for its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
interface FlooredSpawningContextCalculator<T : FlooredSpawningContext> : AreaSpawningContextCalculator<T> {
interface FlooredSpawnablePositionCalculator<T : FlooredSpawnablePosition> : AreaSpawnablePositionCalculator<T> {
/** The condition that must be met by the base block. */
val baseCondition: (BlockState) -> Boolean
/** The condition that must be met by the surrounding blocks. */
val surroundingCondition: (BlockState) -> Boolean
override fun fits(input: AreaSpawningInput): Boolean {
val floorState = input.slice.getBlockState(input.position)
val aboveState = input.slice.getBlockState(input.position.above())
val floorState = input.zone.getBlockState(input.position)
val aboveState = input.zone.getBlockState(input.position.above())
return baseCondition(floorState) && surroundingCondition(aboveState)
}
}
/**
* The context calculator used for [GroundedSpawningContext]s. Requires a solid block below it and
* The spawnable position calculator used for [GroundedSpawnablePosition]s. Requires a solid block below it and
* air blocks in its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
object GroundedSpawningContextCalculator : FlooredSpawningContextCalculator<GroundedSpawningContext> {
// TODO expand base condition
object GroundedSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<GroundedSpawnablePosition> {
override val name = "grounded"
override val baseCondition: (BlockState) -> Boolean = isSolidCondition
override val surroundingCondition: (BlockState) -> Boolean = isAirCondition
override fun calculate(input: AreaSpawningInput): GroundedSpawningContext {
return GroundedSpawningContext(
override fun calculate(input: AreaSpawningInput): GroundedSpawnablePosition {
return GroundedSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -60,26 +59,26 @@ object GroundedSpawningContextCalculator : FlooredSpawningContextCalculator<Grou
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
slice = input.slice,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input)
)
}
}
/**
* The context calculator used for [SeafloorSpawningContext]s. Requires a solid block below it and
* The spawnable position calculator used for [SeafloorSpawnablePosition]s. Requires a solid block below it and
* water blocks in its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
object SeafloorSpawningContextCalculator : FlooredSpawningContextCalculator<SeafloorSpawningContext> {
object SeafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<SeafloorSpawnablePosition> {
override val name = "seafloor"
override val baseCondition: (BlockState) -> Boolean = isSolidCondition
override val surroundingCondition: (BlockState) -> Boolean = isWaterCondition
override fun calculate(input: AreaSpawningInput): SeafloorSpawningContext {
return SeafloorSpawningContext(
override fun calculate(input: AreaSpawningInput): SeafloorSpawnablePosition {
return SeafloorSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -88,26 +87,26 @@ object SeafloorSpawningContextCalculator : FlooredSpawningContextCalculator<Seaf
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
slice = input.slice,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input)
)
}
}
/**
* The context calculator used for [LavafloorSpawningContext]s. Requires a solid block below it and
* The spawnable position calculator used for [LavafloorSpawnablePosition]s. Requires a solid block below it and
* lava blocks in its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
object LavafloorSpawningContextCalculator : FlooredSpawningContextCalculator<LavafloorSpawningContext> {
object LavafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<LavafloorSpawnablePosition> {
override val name = "lavafloor"
override val baseCondition: (BlockState) -> Boolean = isSolidCondition
override val surroundingCondition: (BlockState) -> Boolean = isLavaCondition
override fun calculate(input: AreaSpawningInput): LavafloorSpawningContext {
return LavafloorSpawningContext(
override fun calculate(input: AreaSpawningInput): LavafloorSpawnablePosition {
return LavafloorSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -116,7 +115,7 @@ object LavafloorSpawningContextCalculator : FlooredSpawningContextCalculator<Lav
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
slice = input.slice,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input)
)
}
@@ -124,19 +123,19 @@ object LavafloorSpawningContextCalculator : FlooredSpawningContextCalculator<Lav
/**
* The context calculator used for [SurfaceSpawningContext]s. Requires a fluid block below it and
* The spawnable position calculator used for [SurfaceSpawnablePosition]s. Requires a fluid block below it and
* air blocks in its surroundings.
*
* @author Hiroku
* @since December 15th, 2022
*/
object SurfaceSpawningContextCalculator : FlooredSpawningContextCalculator<SurfaceSpawningContext> {
object SurfaceSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<SurfaceSpawnablePosition> {
override val name = "surface"
override val baseCondition: (BlockState) -> Boolean = { !it.fluidState.isEmpty }
override val surroundingCondition: (BlockState) -> Boolean = isAirCondition
override fun calculate(input: AreaSpawningInput): SurfaceSpawningContext {
return SurfaceSpawningContext(
override fun calculate(input: AreaSpawningInput): SurfaceSpawnablePosition {
return SurfaceSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -146,7 +145,7 @@ object SurfaceSpawningContextCalculator : FlooredSpawningContextCalculator<Surfa
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace / 2, offsetY = 1),
depth = getDepth(input, baseCondition, config.maxVerticalSpace / 2),
slice = input.slice,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input)
)
}
@@ -6,63 +6,63 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context.calculators
package com.cobblemon.mod.common.api.spawning.position.calculators
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.tags.FluidTags
import net.minecraft.world.level.block.state.BlockState
/**
* Calculates some kind of [SpawningContext] from a particular type of input data. This
* is necessary when you create a new type of [SpawningContext] with [SpawningContext.register]
* to know how to actually create these contexts.
* Calculates some kind of [SpawnablePosition] from a particular type of input data. This
* is necessary when you create a new type of [SpawnablePosition] with [SpawnablePosition.register]
* since it informs the spawner how to actually create spawnable positions of that type.
*
* If you are adding to the world spawner, then you probably actually want to create an
* [AreaSpawningContextCalculator].
* [AreaSpawnablePositionCalculator].
*
* @author Hiroku
* @since January 31st, 2022
*/
interface SpawningContextCalculator<I : SpawningContextInput, O : SpawningContext> {
interface SpawnablePositionCalculator<I : SpawnablePositionInput, O : SpawnablePosition> {
companion object {
val isAirCondition: (BlockState) -> Boolean = { it.isAir || (!it.isSolid && !it.fluidState.`is`(FluidTags.WATER) && !it.`is`(BlockTags.RAILS)) }
val isSolidCondition: (BlockState) -> Boolean = { it.isSolid }
val isWaterCondition: (BlockState) -> Boolean = { it.fluidState.`is`(FluidTags.WATER) && it.fluidState.isSource }
val isLavaCondition: (BlockState) -> Boolean = { it.fluidState.`is`(FluidTags.LAVA) && it.fluidState.isSource }
private val calculators = PrioritizedList<SpawningContextCalculator<*, *>>()
val prioritizedAreaCalculators: List<AreaSpawningContextCalculator<*>>
get() = calculators.filterIsInstance<AreaSpawningContextCalculator<*>>()
private val calculators = PrioritizedList<SpawnablePositionCalculator<*, *>>()
val prioritizedAreaCalculators: List<AreaSpawnablePositionCalculator<*>>
get() = calculators.filterIsInstance<AreaSpawnablePositionCalculator<*>>()
fun register(calculator: SpawningContextCalculator<*, *>, priority: Priority = Priority.NORMAL) {
fun register(calculator: SpawnablePositionCalculator<*, *>, priority: Priority = Priority.NORMAL) {
calculators.add(priority, calculator)
}
fun unregister(calculator: SpawningContextCalculator<*, *>) {
fun unregister(calculator: SpawnablePositionCalculator<*, *>) {
calculators.remove(calculator)
}
}
val name: String
/** Tries creating a [SpawningContext] from the given input. Returning null should be a last resort. */
/** Tries creating a [SpawnablePosition] from the given input. Returning null should be a last resort. */
fun calculate(input: I): O?
}
/**
* Base class for input to a [SpawningContextCalculator].
* Base class for input to a [SpawnablePositionCalculator].
*
* @author Hiroku
* @since January 31st, 2022
*/
open class SpawningContextInput(
/** What caused the spawn context, as a [SpawnCause]. */
open class SpawnablePositionInput(
/** What caused the spawn spawnable position, as a [SpawnCause]. */
val cause: SpawnCause,
/** The [Level] the spawning context exists in. */
/** The [ServerLevel] the spawnable position exists in. */
val world: ServerLevel
)
@@ -6,23 +6,23 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context.calculators
package com.cobblemon.mod.common.api.spawning.position.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isWaterCondition
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isWaterCondition
import net.minecraft.util.Mth.ceil
import net.minecraft.world.level.block.state.BlockState
/**
* The context calculator used for [SubmergedSpawningContext]s. Requires a fluid block as a base and the same fluid
* The spawnable position calculator used for [SubmergedSpawnablePosition]s. Requires a fluid block as a base and the same fluid
* block in surrounding space.
*
* @author Hiroku
* @since February 7th, 2022
*/
object SubmergedSpawningContextCalculator : AreaSpawningContextCalculator<SubmergedSpawningContext> {
object SubmergedSpawnablePositionCalculator : AreaSpawnablePositionCalculator<SubmergedSpawnablePosition> {
override val name = "submerged"
val fluidConditions = mutableListOf(
isWaterCondition,
@@ -32,16 +32,16 @@ object SubmergedSpawningContextCalculator : AreaSpawningContextCalculator<Submer
override fun fits(input: AreaSpawningInput): Boolean {
val condition = getFluidCondition(input)
// For it to fit, there must be a known fluid above and below the base block. That's what defines submerged.
return condition != null && condition(input.slice.getBlockState(input.position.below())) && condition(input.slice.getBlockState(input.position.above()))
return condition != null && condition(input.zone.getBlockState(input.position.below())) && condition(input.zone.getBlockState(input.position.above()))
}
fun getFluidCondition(input: AreaSpawningInput): ((BlockState) -> Boolean)? {
return fluidConditions.firstOrNull { it(input.slice.getBlockState(input.position)) }
return fluidConditions.firstOrNull { it(input.zone.getBlockState(input.position)) }
}
override fun calculate(input: AreaSpawningInput): SubmergedSpawningContext {
override fun calculate(input: AreaSpawningInput): SubmergedSpawnablePosition {
val fluidCondition = getFluidCondition(input)!!
return SubmergedSpawningContext(
return SubmergedSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -51,7 +51,7 @@ object SubmergedSpawningContextCalculator : AreaSpawningContextCalculator<Submer
influences = input.spawner.copyInfluences(),
height = getHeight(input, fluidCondition, ceil(config.maxVerticalSpace / 2F)),
depth = getDepth(input, fluidCondition, ceil(config.maxVerticalSpace / 2F)),
slice = input.slice,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input)
)
}
@@ -12,9 +12,10 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.util.adapters.RegisteredSpawningContextAdapter
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.util.adapters.RegisteredSpawnablePositionAdapter
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import java.io.File
import java.io.FileOutputStream
import java.io.FileReader
@@ -29,25 +30,26 @@ import java.io.InputStreamReader
*/
class BestSpawnerConfig {
val version = 0
/** Whether or not an external config will be replaced by an internal one once [version] is higher on the internal. */
/** Whether an external config will be replaced by an internal one once [version] is higher on the internal. */
val replaceWithNewVersion = true
val contextWeights = mutableMapOf(
@SerializedName("spawnablePositionTypeWeights", alternate = ["contextWeights"])
val spawnablePositionTypeWeights = mutableMapOf(
"grounded" to 1F,
"submerged" to 0.99F,
"surface" to 0.01F,
"seafloor" to 1F
)
val buckets = mutableListOf(
SpawnBucket("common", 91.6F),
SpawnBucket("uncommon", 7F),
SpawnBucket("rare", 1.0F),
SpawnBucket("ultra-rare", 0.4F)
SpawnBucket("common", 94.3F),
SpawnBucket("uncommon", 5F),
SpawnBucket("rare", 0.5F),
SpawnBucket("ultra-rare", 0.2F)
)
companion object {
val GSON = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.setLenient()
.disableHtmlEscaping()
.create()
@@ -12,12 +12,13 @@ import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.util.MergeMode
import com.cobblemon.mod.common.util.adapters.SpawnDetailAdapter
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
/**
* Base class for spawn detail presets. Presets are a spawn loading mechanism that allows various properties to be
@@ -42,7 +43,8 @@ import com.google.gson.JsonObject
abstract class SpawnDetailPreset {
var bucket: SpawnBucket? = null
var spawnDetailType: String? = null
var context: RegisteredSpawningContext<*>? = null
@SerializedName("spawnablePositionType", alternate = ["context"])
var spawnablePositionType: SpawnablePositionType<*>? = null
var condition: JsonObject? = null
var anticondition: JsonObject? = null
var weightMultipliers: MutableList<WeightMultiplier>? = null
@@ -52,7 +54,7 @@ abstract class SpawnDetailPreset {
open fun apply(spawnDetail: SpawnDetail) {
bucket?.let { spawnDetail.bucket = it }
context?.let { spawnDetail.context = it }
spawnablePositionType?.let { spawnDetail.spawnablePositionType = it }
weight?.let { spawnDetail.weight = it }
percentage?.let { spawnDetail.percentage = it }
mergeMode.merge(spawnDetail.weightMultipliers, weightMultipliers)
@@ -70,7 +72,7 @@ abstract class SpawnDetailPreset {
}
fun resolveCondition(spawnDetail: SpawnDetail, conditionJson: JsonObject): SpawningCondition<*> {
SpawnLoader.deserializingConditionClass = SpawningCondition.getByName(spawnDetail.context.defaultCondition)
SpawnLoader.deserializingConditionClass = SpawningCondition.getByName(spawnDetail.spawnablePositionType.defaultCondition)
return SpawnLoader.gson.fromJson(conditionJson, SpawningCondition::class.java)
}
}
@@ -10,33 +10,32 @@ package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.spawning.influence.IncenseSweetInfluence
import com.cobblemon.mod.common.api.spawning.influence.SaccharineHoneyLogInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpatialSpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import kotlin.math.ceil
import kotlin.math.sqrt
object IncenseSweetProspector : SpawningInfluenceProspector {
object IncenseSweetDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 32
override fun prospect(spawner: Spawner, area: SpawningArea): MutableList<WorldSlicedSpawningInfluence> {
val world = area.world
val listOfInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((area.length pow 2) + (area.width pow 2)).toDouble())).toInt()
val centerPos = area.getCenter().toBlockPos()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val centerPos = input.getCenter().toBlockPos()
val sweetIncensePositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.INCENSE_SWEET_KEY) },
@@ -47,12 +46,16 @@ object IncenseSweetProspector : SpawningInfluenceProspector {
).toList()
for (pos in sweetIncensePositions) {
val influence = WorldSlicedSpatialSpawningInfluence(pos, RANGE.toFloat(), IncenseSweetInfluence(pos))
val influence = SpatialSpawningZoneInfluence(pos, radius = RANGE.toFloat(), IncenseSweetInfluence(pos))
listOfInfluences.add(influence)
}
return listOfInfluences
}
override fun prospectBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): WorldSlicedSpatialSpawningInfluence? { return null }
override fun detectFromBlock(
world: ServerLevel,
pos: BlockPos,
blockState: BlockState
): List<SpawningZoneInfluence> = emptyList()
}
@@ -0,0 +1,104 @@
/*
* 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.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.block.LureCakeBlock
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.state.BlockState
object LureCakeDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 48
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val lureCakePositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.LURE_CAKE_KEY) },
{ true },
input.getCenter().toBlockPos(),
searchRange,
PoiManager.Occupancy.ANY
).toList()
var highestLureTier = 0
for (lureCakePos in lureCakePositions) {
val blockState = world.getBlockState(lureCakePos)
if (blockState.block !is LureCakeBlock) continue
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: continue
val baitEffects = blockEntity.getBaitEffectsFromLureCake()
highestLureTier = maxOf(highestLureTier, baitEffects.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }.maxOfOrNull { it.value }?.toInt() ?: 0)
listOfInfluences.add(
SpatialSpawningZoneInfluence(
pos = lureCakePos,
radius = RANGE.toFloat(),
influence = SpawnBaitInfluence(
effects = baitEffects,
onUsed = { time ->
if (time == 1) {
tryBiteCake(world, lureCakePos)
}
}
)
)
)
}
if (highestLureTier > 0) {
listOfInfluences.add(UnconditionalSpawningZoneInfluence(influence = BucketNormalizingInfluence(tier = highestLureTier)))
}
return listOfInfluences
}
private fun tryBiteCake(world: ServerLevel, lureCakePos: BlockPos) {
if (world.isLoaded(lureCakePos)) {
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: return
blockEntity.bites++
if (blockEntity.bites >= blockEntity.maxBites) {
world.removeBlock(lureCakePos, false)
} else {
blockEntity.setChanged()
world.sendBlockUpdated(lureCakePos, blockEntity.blockState, blockEntity.blockState, 3)
}
}
}
override fun detectFromBlock(
world: ServerLevel,
pos: BlockPos,
blockState: BlockState
) = emptyList<SpawningZoneInfluence>()
}
@@ -1,55 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpatialSpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
import com.cobblemon.mod.common.block.LureCakeBlock
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.state.BlockState
import kotlin.math.ceil
import kotlin.math.sqrt
object LureCakeProspector : SpawningInfluenceProspector {
@JvmField
val RANGE: Int = 48
override fun prospect(spawner: Spawner, area: SpawningArea): MutableList<WorldSlicedSpawningInfluence> {
val world = area.world
val listOfInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
val searchRange = RANGE + ceil(sqrt(((area.length pow 2) + (area.width pow 2)).toDouble())).toInt()
val lureCakePositions = world.poiManager.findAll({ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.LURE_CAKE_KEY) },{ true },area.getCenter().toBlockPos(),searchRange, PoiManager.Occupancy.ANY).toList()
for (lureCakePos in lureCakePositions) {
val blockState = world.getBlockState(lureCakePos)
if (blockState.block !is LureCakeBlock) continue
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: continue
val baitEffects = blockEntity.getBaitEffectsFromLureCake()
listOfInfluences.add(WorldSlicedSpatialSpawningInfluence(lureCakePos, RANGE.toFloat(), influence = SpawnBaitInfluence(baitEffects, lureCakePos)))
}
return listOfInfluences
}
override fun prospectBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): WorldSlicedSpatialSpawningInfluence? { return null }
}
@@ -10,32 +10,32 @@ package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.spawning.influence.SaccharineHoneyLogInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpatialSpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import kotlin.math.ceil
import kotlin.math.sqrt
object SaccharineHoneyLogProspector : SpawningInfluenceProspector {
object SaccharineHoneyLogDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 32
override fun prospect(spawner: Spawner, area: SpawningArea): MutableList<WorldSlicedSpawningInfluence> {
val world = area.world
val listOfInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((area.length pow 2) + (area.width pow 2)).toDouble())).toInt()
val centerPos = area.getCenter().toBlockPos()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val centerPos = input.getCenter().toBlockPos()
val honeyLogPositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.SACCHARINE_HONEY_LOG_KEY) },
@@ -46,12 +46,15 @@ object SaccharineHoneyLogProspector : SpawningInfluenceProspector {
).toList()
for (pos in honeyLogPositions) {
val influence = WorldSlicedSpatialSpawningInfluence(pos, RANGE.toFloat(), SaccharineHoneyLogInfluence(pos))
listOfInfluences.add(influence)
listOfInfluences.add(SpatialSpawningZoneInfluence(pos, radius = RANGE.toFloat(), SaccharineHoneyLogInfluence(pos)))
}
return listOfInfluences
}
override fun prospectBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): WorldSlicedSpatialSpawningInfluence? { return null }
override fun detectFromBlock(
world: ServerLevel,
pos: BlockPos,
blockState: BlockState
): List<SpawningZoneInfluence> = emptyList()
}
@@ -1,39 +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.prospecting
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.state.BlockState
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
/**
* Prospects for [WorldSlicedSpawningInfluence] in a world at a given position. Occurs as part of a
* [SpawningProspector] scanning a part of the world while formulating a [WorldSlice].
*
* @author Hiroku
* @since March 9th, 2025
*/
interface SpawningInfluenceProspector {
companion object {
@JvmStatic
val prospectors = mutableSetOf<SpawningInfluenceProspector>(
LureCakeProspector,
SaccharineHoneyLogProspector,
IncenseSweetProspector
)
}
fun prospect(spawner: Spawner, area: SpawningArea) : MutableList<WorldSlicedSpawningInfluence>
fun prospectBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): WorldSlicedSpawningInfluence?
}
@@ -1,26 +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.prospecting
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.AreaSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
/**
* Interface responsible for slicing out an async-save [WorldSlice] that can be used for generating
* [SpawningContext]s, specifically [AreaSpawningContext]s.
*
* @author Hiroku
* @since January 29th, 2022
*/
interface SpawningProspector {
fun prospect(spawner: Spawner, area: SpawningArea): WorldSlice
}
@@ -12,16 +12,17 @@ import com.bedrockk.molang.Expression
import com.bedrockk.molang.ast.BooleanExpression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.rules.selector.AllSpawnDetailSelector
import com.cobblemon.mod.common.api.spawning.rules.selector.AllSpawningContextSelector
import com.cobblemon.mod.common.api.spawning.rules.selector.AllSpawnablePositionSelector
import com.cobblemon.mod.common.api.spawning.rules.selector.SpawnDetailSelector
import com.cobblemon.mod.common.api.spawning.rules.selector.SpawningContextSelector
import com.cobblemon.mod.common.api.spawning.rules.selector.SpawnablePositionSelector
import com.cobblemon.mod.common.util.resolveBoolean
import com.google.gson.annotations.SerializedName
/**
* A rule component that targets specific spawns and contexts and judges whether it
* A rule component that targets specific spawns and spawnable positions and judges whether it
* is allowed to spawn. This runs prior to the regular SpawnDetail conditions.
*
* @author Hiroku
@@ -31,14 +32,17 @@ class FilterRuleComponent : SpawnRuleComponent {
@Transient
val runtime = MoLangRuntime().setup()
val spawnSelector: SpawnDetailSelector = AllSpawnDetailSelector
val contextSelector: SpawningContextSelector = AllSpawningContextSelector
@SerializedName("spawnDetailSelector", alternate = ["spawnSelector"])
val spawnDetailSelector: SpawnDetailSelector = AllSpawnDetailSelector
@SerializedName("spawnablePositionSelector", alternate = ["contextSelector"])
val spawnablePositionSelector: SpawnablePositionSelector = AllSpawnablePositionSelector
val allow: Expression = BooleanExpression(true)
override fun affectSpawnable(detail: SpawnDetail, ctx: SpawningContext): Boolean {
return if (spawnSelector.selects(detail) && contextSelector.selects(ctx)) {
override fun affectSpawnable(detail: SpawnDetail, spawnablePosition: SpawnablePosition): Boolean {
return if (spawnDetailSelector.selects(detail) && spawnablePositionSelector.selects(spawnablePosition)) {
runtime.environment.setSimpleVariable("spawn", detail.struct)
runtime.environment.setSimpleVariable("context", ctx.getOrSetupStruct())
runtime.environment.setSimpleVariable("spawn_detail", detail.struct)
runtime.environment.setSimpleVariable("spawnable_position", spawnablePosition.getOrSetupStruct())
runtime.resolveBoolean(allow)
} else {
true

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