140 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			140 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from abc import ABC, abstractmethod
							 | 
						||
| 
								 | 
							
								from dataclasses import dataclass
							 | 
						||
| 
								 | 
							
								from functools import lru_cache
							 | 
						||
| 
								 | 
							
								from typing import Optional, Tuple, ClassVar
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from ...data.villagers_data import Villager
							 | 
						||
| 
								 | 
							
								from ...strings.villager_names import NPC
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								suffix = " <3"
							 | 
						||
| 
								 | 
							
								location_prefix = "Friendsanity: "
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def to_item_name(npc_name: str) -> str:
							 | 
						||
| 
								 | 
							
								    return npc_name + suffix
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def to_location_name(npc_name: str, heart: int) -> str:
							 | 
						||
| 
								 | 
							
								    return location_prefix + npc_name + " " + str(heart) + suffix
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								pet_heart_item_name = to_item_name(NPC.pet)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def extract_npc_from_item_name(item_name: str) -> Optional[str]:
							 | 
						||
| 
								 | 
							
								    if not item_name.endswith(suffix):
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return item_name[:-len(suffix)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def extract_npc_from_location_name(location_name: str) -> Tuple[Optional[str], int]:
							 | 
						||
| 
								 | 
							
								    if not location_name.endswith(suffix):
							 | 
						||
| 
								 | 
							
								        return None, 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    trimmed = location_name[len(location_prefix):-len(suffix)]
							 | 
						||
| 
								 | 
							
								    last_space = trimmed.rindex(" ")
							 | 
						||
| 
								 | 
							
								    return trimmed[:last_space], int(trimmed[last_space + 1:])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@lru_cache(maxsize=32)  # Should not go pass 32 values if every friendsanity options are in the multi world
							 | 
						||
| 
								 | 
							
								def get_heart_steps(max_heart: int, heart_size: int) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								    return tuple(range(heart_size, max_heart + 1, heart_size)) + ((max_heart,) if max_heart % heart_size else ())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass(frozen=True)
							 | 
						||
| 
								 | 
							
								class FriendsanityFeature(ABC):
							 | 
						||
| 
								 | 
							
								    is_enabled: ClassVar[bool]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    heart_size: int
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    to_item_name = staticmethod(to_item_name)
							 | 
						||
| 
								 | 
							
								    to_location_name = staticmethod(to_location_name)
							 | 
						||
| 
								 | 
							
								    pet_heart_item_name = pet_heart_item_name
							 | 
						||
| 
								 | 
							
								    extract_npc_from_item_name = staticmethod(extract_npc_from_item_name)
							 | 
						||
| 
								 | 
							
								    extract_npc_from_location_name = staticmethod(extract_npc_from_location_name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @abstractmethod
							 | 
						||
| 
								 | 
							
								    def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_pet_randomized(self):
							 | 
						||
| 
								 | 
							
								        return bool(self.get_pet_randomized_hearts())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @abstractmethod
							 | 
						||
| 
								 | 
							
								    def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FriendsanityNone(FriendsanityFeature):
							 | 
						||
| 
								 | 
							
								    is_enabled = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self):
							 | 
						||
| 
								 | 
							
								        super().__init__(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        return ()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        return ()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass(frozen=True)
							 | 
						||
| 
								 | 
							
								class FriendsanityBachelors(FriendsanityFeature):
							 | 
						||
| 
								 | 
							
								    is_enabled = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        if not villager.bachelor:
							 | 
						||
| 
								 | 
							
								            return ()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(8, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        return ()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass(frozen=True)
							 | 
						||
| 
								 | 
							
								class FriendsanityStartingNpc(FriendsanityFeature):
							 | 
						||
| 
								 | 
							
								    is_enabled = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        if not villager.available:
							 | 
						||
| 
								 | 
							
								            return ()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if villager.bachelor:
							 | 
						||
| 
								 | 
							
								            return get_heart_steps(8, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(10, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(5, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass(frozen=True)
							 | 
						||
| 
								 | 
							
								class FriendsanityAll(FriendsanityFeature):
							 | 
						||
| 
								 | 
							
								    is_enabled = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        if villager.bachelor:
							 | 
						||
| 
								 | 
							
								            return get_heart_steps(8, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(10, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(5, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass(frozen=True)
							 | 
						||
| 
								 | 
							
								class FriendsanityAllWithMarriage(FriendsanityFeature):
							 | 
						||
| 
								 | 
							
								    is_enabled = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        if villager.bachelor:
							 | 
						||
| 
								 | 
							
								            return get_heart_steps(14, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(10, self.heart_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
							 | 
						||
| 
								 | 
							
								        return get_heart_steps(5, self.heart_size)
							 |