mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-27 20:46:27 -04:00
Compare commits
69
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a7145ae4d | ||
|
|
709e6f4a63 | ||
|
|
73b72ff19d | ||
|
|
15aac5e20a | ||
|
|
6916cd51f7 | ||
|
|
b2ebfcc3b0 | ||
|
|
3b73493801 | ||
|
|
bfb48e8583 | ||
|
|
01c44537d0 | ||
|
|
e414dd94c3 | ||
|
|
2d4800a733 | ||
|
|
90e193e5a0 | ||
|
|
c7e86cf767 | ||
|
|
d8906992a3 | ||
|
|
972c259f6d | ||
|
|
8228424191 | ||
|
|
9bb67087b3 | ||
|
|
0f08446732 | ||
|
|
af09a6a0c7 | ||
|
|
b4d2f39c48 | ||
|
|
1a12bbd493 | ||
|
|
8a24f635cf | ||
|
|
d6e066ddcd | ||
|
|
50335135c9 | ||
|
|
1ee6f83a5b | ||
|
|
e8b747a432 | ||
|
|
8f8804f3f4 | ||
|
|
ef826158ce | ||
|
|
d32314742d | ||
|
|
e8e0ca47dd | ||
|
|
ebcac6e782 | ||
|
|
63bfa634ae | ||
|
|
7ea85dbd1e | ||
|
|
6cdb14d2f8 | ||
|
|
f041049576 | ||
|
|
e1aa6445fa | ||
|
|
f1b7ee5b9c | ||
|
|
9ea0f7e4f8 | ||
|
|
d0edd6ecbc | ||
|
|
1372505486 | ||
|
|
a4ce4bf99b | ||
|
|
d72dad355c | ||
|
|
40b5726c5b | ||
|
|
d32aa46832 | ||
|
|
5068e66fa7 | ||
|
|
482b682595 | ||
|
|
ffecc58979 | ||
|
|
eaaf2ca05c | ||
|
|
e91651553b | ||
|
|
a2094677cd | ||
|
|
eae4eb5121 | ||
|
|
ed3a008515 | ||
|
|
462074ce43 | ||
|
|
d54a85aa68 | ||
|
|
bf8433f6e9 | ||
|
|
51b0f768a0 | ||
|
|
106e487035 | ||
|
|
2ecbe28c87 | ||
|
|
80112e0b70 | ||
|
|
f776542365 | ||
|
|
045ca3a07f | ||
|
|
2085f12fef | ||
|
|
863b43f902 | ||
|
|
474918e414 | ||
|
|
0e93383c90 | ||
|
|
abb44cf696 | ||
|
|
e6bd7cad72 | ||
|
|
e2db59c837 | ||
|
|
7d06f1e2cd |
@@ -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
|
||||
@@ -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))
|
||||
|
||||
|
||||
+1
-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> {
|
||||
|
||||
+45
@@ -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>()
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.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()
|
||||
+7
-7
@@ -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) {
|
||||
|
||||
+10
-5
@@ -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>()
|
||||
|
||||
+35
-36
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
-1
@@ -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)
|
||||
|
||||
+19
-11
@@ -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
|
||||
}
|
||||
+3
-4
@@ -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
|
||||
}
|
||||
+13
-13
@@ -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"
|
||||
}
|
||||
|
||||
+4
-4
@@ -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"
|
||||
}
|
||||
|
||||
+4
-5
@@ -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) })
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -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
|
||||
}
|
||||
|
||||
+12
-12
@@ -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"
|
||||
}
|
||||
|
||||
+13
-13
@@ -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"
|
||||
}
|
||||
|
||||
+34
-34
@@ -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
|
||||
|
||||
|
||||
+17
-17
@@ -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()
|
||||
}
|
||||
+15
-15
@@ -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"
|
||||
}
|
||||
|
||||
+12
-12
@@ -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"
|
||||
}
|
||||
|
||||
-133
@@ -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
|
||||
// }
|
||||
}
|
||||
-8
@@ -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/.
|
||||
*/
|
||||
|
||||
-60
@@ -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)
|
||||
+6
-5
@@ -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
|
||||
|
||||
+8
-2
@@ -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)
|
||||
}
|
||||
+145
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
-31
@@ -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
|
||||
}
|
||||
}
|
||||
+33
-17
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
+9
-7
@@ -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)
|
||||
|
||||
+56
-11
@@ -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
-11
@@ -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)
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
}
|
||||
}
|
||||
+54
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()){
|
||||
|
||||
+26
-15
@@ -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
|
||||
}
|
||||
|
||||
+7
-7
@@ -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
|
||||
}
|
||||
}
|
||||
+14
-14
@@ -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)
|
||||
}
|
||||
}
|
||||
+16
-8
@@ -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
|
||||
}
|
||||
+36
@@ -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
|
||||
}
|
||||
-24
@@ -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
|
||||
}
|
||||
+56
@@ -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>()
|
||||
//}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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>
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -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
|
||||
}
|
||||
+88
@@ -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
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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,
|
||||
+22
-22
@@ -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)
|
||||
+30
-21
@@ -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()
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -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
|
||||
}
|
||||
+7
-7
@@ -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
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
}
|
||||
+60
@@ -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)
|
||||
+31
-32
@@ -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)
|
||||
)
|
||||
}
|
||||
+17
-17
@@ -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
|
||||
)
|
||||
+11
-11
@@ -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)
|
||||
)
|
||||
}
|
||||
+11
-9
@@ -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()
|
||||
|
||||
+6
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
+18
-15
@@ -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()
|
||||
}
|
||||
+104
@@ -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>()
|
||||
}
|
||||
-55
@@ -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 }
|
||||
}
|
||||
+18
-15
@@ -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()
|
||||
}
|
||||
-39
@@ -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?
|
||||
}
|
||||
-26
@@ -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
|
||||
}
|
||||
+13
-9
@@ -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
Reference in New Issue
Block a user