Files
mcpacker/mcpacker/model/core/flora/patch.py
T
Andrew Miner 3a62225c9e Add the ability to specify companion plants
- add full list of disabled plant placed features
2025-10-21 10:12:46 -06:00

98 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from collections.abc import Iterable
from mcpacker.model.core.blockstate import BlockState
from mcpacker.model.core.flora.companion import Companion
from mcpacker.model.core.flora.density import Density
from mcpacker.model.core.flora.immersion import Immersion
from mcpacker.model.core.flora.plant import Plant
from mcpacker.model.core.resourceid import ResourceId
from mcpacker.model.core.scarcity import Scarcity
import math
import mcpacker.model.core.flora.density as DE
import mcpacker.model.core.flora.immersion as IM
# Class ############################################################################################
class Patch(Plant):
def __init__(
self,
name:str,
blocks:Iterable[BlockState|str]|BlockState|str,
density:Density=DE.THICK,
radius:int=4,
substrates:Iterable[ResourceId]|Iterable[str]|ResourceId|str|None=None,
companions:Iterable[Companion]|Companion|None=None,
immersion:Immersion=IM.DRY,
):
super().__init__(name)
if isinstance(blocks, str) or isinstance(blocks, BlockState):
blocks = [blocks]
if not companions:
companions = []
if isinstance(companions, Companion):
companions = [companions]
if not substrates:
substrates = ["#minecraft:dirt"]
if not isinstance(substrates, Iterable):
substrates = [substrates]
self.blocks = [BlockState.parse(b) for b in blocks]
self.companions = list(companions)
self.density = density
self.immersion = immersion
self.radius = radius
self.substrates = [ResourceId.parse(s) for s in substrates]
def __repr__(self) -> str:
return (
"Patch("
f"blocks={self.blocks!r}, "
f"companions={self.companions!r}, "
f"density={self.density!r}, "
f"immersion={self.immersion!r}, "
f"radius={self.radius!r}, "
f"substrates={self.substrates!r}"
")"
)
def attempts(self, successRate:float=0.25) -> int:
"""
Estimate how many placement attempts a generator should make for this patch.
The value models repeated random placement within a circular area until the expected
fraction of successful placements (`coverage`) for the patch's density is reached. It
accounts for both for the presumed rate of successful placements (controlled by
`successRate`) and duplicate hits on alreadyfilled blocks.
The relationship is derived from the expected coverage equation:
coverage = 1 - (1 - successRate / area) ** attempts
which rearranges to the continuous approximation:
attempts = -(area / successRate) * ln(1 - coverage)
where:
area = π * radius²
successRate = probability that any single attempt succeeds
coverage = target fill fraction for this patch's density
see ChatGPT conversation for full details: http://bit.ly/4neps2i
"""
coverage = {
DE.CARPET: 0.99,
DE.PACKED: 0.8,
DE.THICK: 0.6,
DE.THIN: 0.4,
DE.SPARSE: 0.2,
}[self.density]
return math.ceil(max(1/successRate,
-(math.pi * self.radius * self.radius / successRate) * math.log(1 - coverage)
))