├── .gitignore ├── MANIFEST.in ├── README.md ├── apc.py ├── apc ├── __init__.py ├── __main__.py ├── adf.py ├── adf_profile.py ├── babel.cfg ├── config.py ├── config │ ├── animal_details.json │ ├── animal_names.json │ ├── fur_names.json │ ├── reserve_details.json │ └── reserve_names.json ├── hacks.py ├── locale │ ├── apc.pot │ ├── de_DE │ │ └── LC_MESSAGES │ │ │ ├── apc.mo │ │ │ └── apc.po │ ├── en_US │ │ └── LC_MESSAGES │ │ │ ├── apc.mo │ │ │ └── apc.po │ ├── es_ES │ │ └── LC_MESSAGES │ │ │ ├── apc.mo │ │ │ └── apc.po │ ├── ru_RU │ │ └── LC_MESSAGES │ │ │ ├── apc.mo │ │ │ └── apc.po │ └── zh_CN │ │ └── LC_MESSAGES │ │ ├── apc.mo │ │ └── apc.po ├── populations.py └── utils.py ├── apcgui.py ├── apcgui ├── __init__.py ├── __main__.py ├── gui.py └── logo.py ├── deca ├── errors.py ├── fast_file.py ├── ff_adf.py ├── file.py └── hashes.py ├── pyproject.toml ├── requirements.txt ├── scans ├── furs.json ├── global_furs.json └── scan.csv ├── scripts ├── apc.bat ├── apc_pack.bat ├── apcgui.bat ├── apcgui_pack.bat ├── compile.bat ├── extract.bat ├── init.bat ├── update.bat └── upgrade_pysimplegui.bat ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .working 2 | apc/__pycache__ 3 | config/save_path.txt 4 | apc.egg-info 5 | mods 6 | backups 7 | venv 8 | */__pycache__ 9 | .DS_Store 10 | build 11 | dist 12 | *.spec 13 | ..spec 14 | apc/config/save_path.txt 15 | imgui.ini -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include apc/config/*.json 2 | include apc/locale/*/LC_MESSAGES/apc.mo 3 | include apcgui/locale/*/LC_MESSAGES/apcgui.mo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # animal-population-changer 2 | 3 | > [!WARNING] 4 | > This application is no longer maintained. 5 | 6 | A tool that allows anyone to change the animal population on all the reserves in theHunter: Call of the Wild (COTW). 7 | 8 | ![GUI](https://user-images.githubusercontent.com/2107385/248629158-47bec7aa-a978-4cc7-8fd0-dfeaea8e6777.gif) 9 | 10 | This tool can make all species a diamond, the appropriate species a Great one, or have a rare fur. 11 | 12 | The following mods are possible with this tool: 13 | 1. Make an animal a Great One. 14 | 1. Make an animal a Diamond. 15 | 1. Make an animal have a rare fur. 16 | 1. Make a female animal a male. 17 | 1. Make a male a female. 18 | 19 | The modded population files can be found in a `mods` folder in the same directory you are running the tool. 20 | 21 | To download the latest releases of this tool, go to [NexusMods](https://www.nexusmods.com/thehuntercallofthewild/mods/225) where you can also post bugs and have a conversation with the COTW modding community. 22 | 23 | Testing with game version: 2649775 24 | 25 | ## Limitations: 26 | * This tool was tested on Windows 11 with the game installed via Steam. It is smart enough to also look where Epic Games saves its files too. If your game files are saved somewhere else besides where Steam or Epic saves them, use the `Configure Game Path` button. 27 | * The species that use the newer TruRACS trophy system may not become a diamond. This is an area where I am still doing research to figure out how exactly to manipulate. 28 | * If you use the executables (EXE) files, your system may complain there is a virus. This is not true, but the `pyinstaller` package that builds the executable is often used by hackers, and so it is being flagged. To avoid this, simply install the tool from the `wheel` file or build it from source. 29 | 30 | ## How To Build 31 | 32 | > Note: This code was built and tested with Python 3.10.10. 33 | 34 | To install dependencies: 35 | ```sh 36 | pip install -r requirements.txt 37 | python -m PySimpleGUI.PySimpleGUI upgrade 38 | ``` 39 | 40 | You can run the packages directly by using: 41 | ```sh 42 | pyton -m apcgui 43 | ``` 44 | 45 | You can install a developer version by using: 46 | ```sh 47 | pip install . 48 | ``` 49 | 50 | If you want to build from a wheel: 51 | ```sh 52 | pip install -U build 53 | python -m build 54 | pip install dist/apc-0.1.0-py3-none-any.whl 55 | ``` 56 | 57 | If you want to build directly from GitHub: 58 | ```sh 59 | pip install -e git+https://github.com/rollerb/apc.git#egg=apc 60 | ``` 61 | 62 | If you want to build an executable (i.e., from Windows): 63 | ```sh 64 | pip install -U pyinstaller 65 | pyinstaller --noconsole --add-data "apc/config;config" --add-data "apc/locale;locale" --add-data "apcgui/locale;locale" apcgui.py 66 | ./dist/apcgui/apcgui.exe 67 | ``` 68 | -------------------------------------------------------------------------------- /apc.py: -------------------------------------------------------------------------------- 1 | from apc.__main__ import main 2 | 3 | if __name__ == "__main__": 4 | main() -------------------------------------------------------------------------------- /apc/__init__.py: -------------------------------------------------------------------------------- 1 | __app_name__ = "apc" 2 | __version__ = "1.1.0" -------------------------------------------------------------------------------- /apc/__main__.py: -------------------------------------------------------------------------------- 1 | from apc import hacks 2 | 3 | def main(): 4 | hacks.seed_animals("emerald") 5 | 6 | if __name__ == "__main__": 7 | main() -------------------------------------------------------------------------------- /apc/adf.py: -------------------------------------------------------------------------------- 1 | import zlib, contextlib, random, math, struct 2 | from deca.file import ArchiveFile 3 | from deca.ff_adf import Adf 4 | from pathlib import Path 5 | from apc import config 6 | from apc.adf_profile import * 7 | 8 | class FileNotFound(Exception): 9 | pass 10 | 11 | class DecompressedAdfFile(): 12 | def __init__(self, basename: str, filename: Path, file_header: bytearray, header: bytearray, data: bytearray) -> None: 13 | self.basename = basename 14 | self.filename = filename 15 | self.file_header = file_header 16 | self.header = header 17 | self.data = data 18 | self.org_size = len(header + data) 19 | 20 | def save(self, destination: Path, verbose = False) -> None: 21 | decompressed_data_bytes = self.header + self.data 22 | new_size = len(decompressed_data_bytes) 23 | if self.org_size != new_size: 24 | print("Org:", self.org_size, "New:", new_size) 25 | decompressed_size = struct.pack("I", new_size) 26 | self.file_header[8:12] = decompressed_size 27 | self.file_header[24:28] = decompressed_size 28 | commpressed_data_bytes = self.file_header + _compress_bytes(decompressed_data_bytes) 29 | 30 | adf_file = destination / self.basename 31 | if verbose: 32 | print(f"Saving modded file to {adf_file}") 33 | _save_file(adf_file, commpressed_data_bytes, verbose=verbose) 34 | 35 | class ParsedAdfFile(): 36 | def __init__(self, decompressed: DecompressedAdfFile, adf: Adf) -> None: 37 | self.decompressed = decompressed 38 | self.adf = adf 39 | 40 | def _get_file_name(reserve: str, mod: bool) -> Path: 41 | save_path = config.MOD_DIR_PATH if mod else config.get_save_path() 42 | if save_path is None: 43 | raise FileNotFound("Please configure your game save path") 44 | filename = save_path / config.get_population_file_name(reserve) 45 | if not filename.exists(): 46 | raise FileNotFound(f'{filename} does not exist') 47 | return filename 48 | 49 | def _read_file(filename: Path, verbose = False): 50 | if verbose: 51 | print(f"Reading {filename}") 52 | return filename.read_bytes() 53 | 54 | def _decompress_bytes(data_bytes: bytearray) -> bytearray: 55 | decompress = zlib.decompressobj() 56 | decompressed = decompress.decompress(data_bytes) 57 | decompressed = decompressed + decompress.flush() 58 | return decompressed 59 | 60 | def _compress_bytes(data_bytes: bytearray) -> bytearray: 61 | compress = zlib.compressobj() 62 | compressed = compress.compress(data_bytes) 63 | compressed = compressed + compress.flush() 64 | return compressed 65 | 66 | def _save_file(filename: Path, data_bytes: bytearray, verbose = False): 67 | Path(filename.parent).mkdir(exist_ok=True) 68 | filename.write_bytes(data_bytes) 69 | if verbose: 70 | print(f"Saved {filename}") 71 | 72 | def _parse_adf_file(filename: Path, suffix: str = None, verbose = False) -> Adf: 73 | obj = Adf() 74 | with ArchiveFile(open(filename, 'rb')) as f: 75 | with contextlib.redirect_stdout(None): 76 | obj.deserialize(f) 77 | content = obj.dump_to_string() 78 | suffix = f"_{suffix}.txt" if suffix else ".txt" 79 | txt_filename = config.APP_DIR_PATH / f".working/{filename.name}{suffix}" 80 | _save_file(txt_filename, bytearray(content, 'utf-8'), verbose) 81 | return obj 82 | 83 | def _decompress_adf_file(filename: Path, verbose = False) -> DecompressedAdfFile: 84 | # read entire adf file 85 | data_bytes = _read_file(filename, verbose) 86 | data_bytes = bytearray(data_bytes) 87 | 88 | # split out header 89 | header = data_bytes[0:32] 90 | data_bytes = data_bytes[32:] 91 | 92 | # decompress data 93 | decompressed_data_bytes = _decompress_bytes(data_bytes) 94 | decompressed_data_bytes = bytearray(decompressed_data_bytes) 95 | 96 | # split out compression header 97 | decompressed_header = decompressed_data_bytes[0:5] 98 | decompressed_data_bytes = decompressed_data_bytes[5:] 99 | 100 | # save uncompressed adf data to file 101 | parsed_basename = filename.name 102 | adf_file = config.APP_DIR_PATH / f".working/{parsed_basename}_sliced" 103 | _save_file(adf_file, decompressed_data_bytes, verbose) 104 | 105 | return DecompressedAdfFile( 106 | parsed_basename, 107 | adf_file, 108 | header, 109 | decompressed_header, 110 | decompressed_data_bytes 111 | ) 112 | 113 | def _update_non_instance_offsets(data: bytearray, profile: dict, added_size: int) -> None: 114 | offsets_to_update = [ 115 | (profile["header_instance_offset"], profile["instance_header_start"]), 116 | (profile["header_typedef_offset"], profile["typedef_start"]), 117 | (profile["header_nametable_offset"], profile["nametable_start"]), 118 | (profile["header_total_size_offset"], profile["total_size"]), 119 | (profile["instance_header_start"]+12, profile["details"]["instance_offsets"]["instances"][0]["size"]) 120 | ] 121 | for offset in offsets_to_update: 122 | write_value(data, create_u32(offset[1] + added_size), offset[0]) 123 | 124 | def _insert_animal(data: bytearray, animal: Animal, array: AdfArray) -> None: 125 | write_value(data, create_u32(read_u32(data[array.header_length_offset:array.header_length_offset+4])+1), array.header_length_offset) 126 | animal_bytes = animal.to_bytes() 127 | data[array.array_org_end_offset:array.array_org_end_offset] = animal_bytes 128 | 129 | def _remove_animal(data: bytearray, array: AdfArray, index: int) -> None: 130 | write_value(data, create_u32(read_u32(data[array.header_length_offset:array.header_length_offset+4])-1), array.header_length_offset) 131 | index_offset = array.array_org_start_offset + index * 32 132 | del data[index_offset:index_offset+32] 133 | 134 | def _update_instance_arrays(data: bytearray, animal_arrays: List[AdfArray], target_array: AdfArray, size: int): 135 | arrays_modded = [] 136 | for animal_array in animal_arrays: 137 | if animal_array.array_start_offset >= target_array.array_end_offset and animal_array.array_start_offset != 0: 138 | arrays_modded.append(animal_array) 139 | animal_array.array_start_offset = animal_array.array_start_offset + size 140 | animal_array.array_end_offset = animal_array.array_end_offset + size 141 | animal_array.rel_array_start_offset = animal_array.rel_array_start_offset + size 142 | write_value(data, create_u32(animal_array.rel_array_start_offset), animal_array.header_array_offset) 143 | # if animal_array.header_start_offset >= target_array.array_end_offset: 144 | # print(animal_array) 145 | 146 | def parse_adf(filename: Path, suffix: str = None, verbose = False) -> Adf: 147 | if verbose: 148 | print(f"Parsing {filename}") 149 | return _parse_adf_file(filename, suffix, verbose=verbose) 150 | 151 | def load_adf(filename: Path, verbose = False) -> ParsedAdfFile: 152 | data = _decompress_adf_file(filename, verbose=verbose) 153 | adf = parse_adf(data.filename, verbose=verbose) 154 | return ParsedAdfFile(data, adf) 155 | 156 | def load_reserve(reserve_name: str, mod: bool = False, verbose = False) -> ParsedAdfFile: 157 | filename = _get_file_name(reserve_name, mod) 158 | return load_adf(filename, verbose=verbose) 159 | 160 | def add_animals_to_reserve(reserve_name: str, species_key: str, animals: List[Animal], verbose: bool, mod: bool) -> None: 161 | print("add animals") 162 | if len(animals) == 0: 163 | return 164 | org_filename = _get_file_name(reserve_name, mod) 165 | decompressed_adf = _decompress_adf_file(org_filename, verbose=True) 166 | reserve_data = decompressed_adf.data 167 | profile = create_profile(decompressed_adf.filename) 168 | population_index = config.RESERVES[reserve_name]["species"].index(species_key) 169 | animal_arrays, other_arrays = find_arrays(profile, reserve_data) 170 | all_arrays = animal_arrays+other_arrays 171 | eligible_animal_arrays = [x for x in animal_arrays if x.population == population_index] 172 | eligible_animal_arrays = sorted(eligible_animal_arrays, key=lambda x: x.array_start_offset, reverse=True) 173 | 174 | total_size = animals[0].size * len(animals) 175 | _update_non_instance_offsets(reserve_data, profile, total_size) 176 | n = 1 if len(animals) < len(eligible_animal_arrays) else math.ceil(len(animals) / len(eligible_animal_arrays)) 177 | animal_chunks = [animals[i:i + n] for i in range(0, len(animals), n)] 178 | for i, animal_chunk in enumerate(animal_chunks): 179 | chosen_array = eligible_animal_arrays[i] 180 | for animal in animal_chunk: 181 | _update_instance_arrays(reserve_data, all_arrays, chosen_array, animal.size) 182 | for i, animal_chunk in enumerate(animal_chunks): 183 | chosen_array = eligible_animal_arrays[i] 184 | for animal in animal_chunk: 185 | _insert_animal(reserve_data, animal, chosen_array) 186 | decompressed_adf.save(config.MOD_DIR_PATH, verbose=True) 187 | 188 | def remove_animals_from_reserve(reserve_name: str, species_key: str, animal_cnt: int, gender: str, verbose: bool, mod: bool) -> None: 189 | print("remove animals") 190 | if animal_cnt == 0: 191 | return 192 | org_filename = _get_file_name(reserve_name, mod) 193 | decompressed_adf = _decompress_adf_file(org_filename, verbose=verbose) 194 | profile = create_profile(decompressed_adf.filename) 195 | reserve_data = decompressed_adf.data 196 | animal_arrays, other_arrays = find_arrays(profile, reserve_data) 197 | all_arrays = animal_arrays+other_arrays 198 | population_index = config.RESERVES[reserve_name]["species"].index(species_key) 199 | eligible_animal_arrays = [x for x in animal_arrays if x.population == population_index and ((x.male_cnt > 0 and gender == "male") or (x.female_cnt > 0 and gender == "female"))] 200 | eligible_animal_arrays = sorted(eligible_animal_arrays, key=lambda x: x.array_start_offset, reverse=True) 201 | animal_size = 32 202 | 203 | animals_left_to_remove = animal_cnt 204 | arrays_to_remove_from = [] 205 | for animal_array in eligible_animal_arrays: 206 | if animals_left_to_remove == 0: 207 | break 208 | array_length = animal_array.length 209 | if array_length > 1: 210 | if gender == "male": 211 | remove_cnt = animal_array.male_cnt - 1 212 | else: 213 | remove_cnt = animal_array.female_cnt - 1 214 | if remove_cnt > animals_left_to_remove: 215 | remove_cnt = animals_left_to_remove 216 | if remove_cnt > 0: 217 | arrays_to_remove_from.append((remove_cnt, animal_array)) 218 | animals_left_to_remove = animals_left_to_remove - remove_cnt 219 | if animals_left_to_remove > 0: 220 | raise Exception("Not enough animals to remove") 221 | 222 | total_size = animal_size * animal_cnt 223 | _update_non_instance_offsets(reserve_data, profile, -total_size) 224 | for remove_cnt, animal_array in arrays_to_remove_from: 225 | _update_instance_arrays(reserve_data, all_arrays, animal_array, -(animal_size*remove_cnt)) 226 | for remove_cnt, animal_array in arrays_to_remove_from: 227 | remove_indices = animal_array.male_indices if gender == "male" else animal_array.female_indices 228 | removed_cnt = 0 229 | while removed_cnt < remove_cnt: 230 | _remove_animal(reserve_data, animal_array, remove_indices[remove_cnt-removed_cnt]) 231 | removed_cnt += 1 232 | 233 | decompressed_adf.save(config.MOD_DIR_PATH, verbose=verbose) -------------------------------------------------------------------------------- /apc/adf_profile.py: -------------------------------------------------------------------------------- 1 | import struct, json, re 2 | from pathlib import Path 3 | from typing import Tuple, List 4 | 5 | typedef_s8 = 1477249634 6 | typedef_u8 = 211976733 7 | typedef_s16 = 3510620051 8 | typedef_u16 = 2261865149 9 | typedef_s32 = 422569523 10 | typedef_u32 = 123620943 11 | typedef_s64 = 2940286287 12 | typedef_u64 = 2704924703 13 | typedef_f32 = 1964352007 14 | typedef_f64 = 3322541667 15 | PRIMITIVES = [typedef_s8, typedef_u8, typedef_s16, typedef_u16, typedef_s32, typedef_u32, typedef_s64, typedef_u64, typedef_f32, typedef_f64] 16 | PRIMITIVE_1 = [typedef_s8, typedef_u8] 17 | PRIMITIVE_2 = [typedef_s16, typedef_u16] 18 | PRIMITIVE_8 = [typedef_s64, typedef_u64, typedef_f64] 19 | STRUCTURE = 1 20 | ARRAY = 3 21 | 22 | class AdfArray: 23 | def __init__(self, name: str, population: int, group: int, length: int, header_start_offset: int, header_length_offset: int, header_array_offset: int, array_start_offset: int, array_end_offset: int, rel_array_start_offset: int, rel_array_end_offset: int, male_indices: List[int], female_indices: List[int]) -> None: 24 | self.name = name 25 | self.population = population 26 | self.group = group 27 | self.length = length 28 | self.header_start_offset = header_start_offset 29 | self.header_length_offset = header_length_offset 30 | self.header_array_offset = header_array_offset 31 | self.array_org_start_offset = array_start_offset 32 | self.array_org_end_offset = array_end_offset 33 | self.array_start_offset = array_start_offset 34 | self.array_end_offset = array_end_offset 35 | self.rel_array_start_offset = rel_array_start_offset 36 | self.rel_array_end_offset = rel_array_end_offset 37 | self.male_indices = male_indices 38 | self.female_indices = female_indices 39 | self.male_cnt = len(male_indices) if male_indices else 0 40 | self.female_cnt = len(female_indices) if female_indices else 0 41 | 42 | def __repr__(self) -> str: 43 | return f"{self.name} ; Header Offset: {self.header_start_offset},{hex(self.header_start_offset)}; Data Offset: {self.array_start_offset},{hex(self.array_start_offset)}" 44 | 45 | class Animal: 46 | def __init__(self, gender: str, weight: float, score: float, is_great_one: bool, visual_variation_seed: int) -> None: 47 | self.gender = 1 if gender == "male" else 2 48 | self.weight = weight 49 | self.score = score 50 | self.is_great_one = 1 if is_great_one else 0 51 | self.visual_variation_seed = visual_variation_seed 52 | self.id = 0 53 | self.map_position_x = 0.0 54 | self.map_position_y = 0.0 55 | self.size = len(self.to_bytes()) 56 | 57 | def __repr__(self) -> str: 58 | return f""" 59 | gender: {self.gender},{create_u8(self.gender)}, 60 | weight: {self.weight},{create_f32(self.weight)}, 61 | score: {self.score},{create_f32(self.score)}, 62 | is_great_one: {self.is_great_one},{create_u8(self.is_great_one)}, 63 | seed: {self.visual_variation_seed},{create_u32(self.visual_variation_seed)}, 64 | id: {self.id},{create_u32(self.id)}, 65 | x: {self.map_position_x},{create_f32(self.map_position_x)}, 66 | y: {self.map_position_y},{create_f32(self.map_position_y)} 67 | """ 68 | 69 | def to_bytes(self) -> bytearray: 70 | gender = create_u8(self.gender) 71 | weight = create_f32(self.weight) 72 | score = create_f32(self.score) 73 | is_great_one = create_u8(self.is_great_one) 74 | visual_variation_seed = create_u32(self.visual_variation_seed) 75 | id = create_u32(self.id) 76 | map_position_x = create_f32(self.map_position_x) 77 | map_position_y = create_f32(self.map_position_y) 78 | return gender+weight+score+is_great_one+visual_variation_seed+id+map_position_x+map_position_y 79 | 80 | 81 | def read_u32(data: bytearray) -> int: 82 | return struct.unpack("I", data)[0] 83 | 84 | def create_u32(value: int) -> bytearray: 85 | return bytearray(struct.pack("I", value)) 86 | 87 | def read_u8(data: bytearray) -> int: 88 | return struct.unpack("B", data)[0] 89 | 90 | def create_u8(value: int) -> bytearray: 91 | return bytearray(struct.pack("B0I", value)) 92 | 93 | def create_f32(value: float) -> bytearray: 94 | return bytearray(struct.pack("f", value)) 95 | 96 | def read_u64(data: bytearray) -> int: 97 | return struct.unpack("Q", data)[0] 98 | 99 | def read_str(data: bytearray) -> str: 100 | value = data[0:-1] 101 | return value.decode("utf-8") 102 | 103 | def write_value(data: bytearray, new_data: bytearray, offset: int) -> None: 104 | data[offset:offset+len(new_data)] = new_data 105 | 106 | def find_length_of_string(data: bytearray) -> bytearray: 107 | for i in range(len(data)): 108 | if data[i:i+1] == b'\00': 109 | return i 110 | return 0 111 | 112 | def find_nametable_size(data: bytearray, count: int) -> int: 113 | size = 0 114 | eos = 1 115 | for i in range(count): 116 | i_length = read_u8(data[i:i+1]) 117 | size += 1 + i_length + eos 118 | return size 119 | 120 | def read_nametables(data: bytearray, count: int) -> List[str]: 121 | nametable_sizes = [] 122 | nametables = [] 123 | for i in range(count): 124 | i_length = read_u8(data[i:i+1]) 125 | nametable_sizes.append(i_length) 126 | 127 | table_offset = count 128 | pointer = table_offset 129 | 130 | for i in range(count): 131 | i_length = nametable_sizes[i] 132 | nametables.append(read_str(data[pointer:pointer+i_length+1])) 133 | pointer += i_length + 1 134 | 135 | return nametables 136 | 137 | def read_typemember(data: bytearray, nametables: List[str]) -> dict: 138 | name_index = read_u64(data[0:8]) 139 | name = nametables[name_index] 140 | type_hash = read_u32(data[8:12]) 141 | size = read_u32(data[12:16]) 142 | offset = read_u32(data[16:20]) 143 | return { 144 | "name": name, 145 | "type_hash": type_hash, 146 | "size": size, 147 | "offset": offset 148 | } 149 | 150 | def read_typedef(header: bytearray, offset: int, nametables: List[str]) -> Tuple[int, dict]: 151 | header_size = 36 152 | member_size = 32 153 | metatype = read_u32(header[0:4]) 154 | size = read_u32(header[4:8]) 155 | type_hash = read_u32(header[12:16]) 156 | name_index = read_u64(header[16:24]) 157 | element_type_hash = read_u32(header[28:32]) 158 | name = nametables[name_index] 159 | 160 | if metatype == 1: 161 | member_count = read_u32(header[header_size:header_size+4]) 162 | structure_size = header_size + 4 + (member_size * member_count) 163 | members = [] 164 | for i in range(member_count): 165 | pointer = i * member_size 166 | members.append(read_typemember(header[header_size+4+pointer:], nametables)) 167 | return (structure_size, { 168 | "name": name, 169 | "metatype": metatype, 170 | "type_hash": type_hash, 171 | "start": offset, 172 | "end": offset + structure_size, 173 | "size": size, 174 | "members": members 175 | }) 176 | elif metatype == 0: 177 | return (header_size, { 178 | "name": name, 179 | "metatype": metatype, 180 | "type_hash": type_hash, 181 | "start": offset, 182 | "end": offset + header_size 183 | }) 184 | else: 185 | return (header_size+4, { 186 | "name": name, 187 | "metatype": metatype, 188 | "type_hash": type_hash, 189 | "element_type_hash": element_type_hash, 190 | "start": offset, 191 | "end": offset+header_size+4 192 | }) 193 | 194 | def find_typedef_offset(data: bytearray, typedef_offset: int, count: int, nametables: List[str]) -> dict: 195 | pointer = typedef_offset 196 | offsets = [] 197 | for i in range(count): 198 | read_size, info = read_typedef(data[pointer:], pointer, nametables) 199 | pointer += read_size 200 | offsets.append(info) 201 | 202 | type_map = {} 203 | for offset in offsets: 204 | type_map[offset["type_hash"]] = { 205 | "name": offset["name"], 206 | "metatype": offset["metatype"], 207 | "size": offset["size"] if "size" in offset else None, 208 | "element_type_hash": offset["element_type_hash"] if "element_type_hash" in offset else None, 209 | "members": offset["members"] if "members" in offset else [] 210 | } 211 | 212 | return { 213 | "start": typedef_offset, 214 | "end": pointer, 215 | "offsets": offsets, 216 | "type_map": type_map 217 | } 218 | 219 | def get_primitive_size(type_id: int) -> int: 220 | if type_id in PRIMITIVE_1: 221 | return 1 222 | elif type_id in PRIMITIVE_2: 223 | return 2 224 | elif type_id in PRIMITIVE_8: 225 | return 8 226 | else: 227 | return 4 228 | 229 | def read_instance(data: bytearray, offset: int, pointer: int, type_id: int, type_map: dict) -> dict: 230 | value = None 231 | pos = offset+pointer 232 | 233 | if type_id in PRIMITIVES: 234 | primitive_size = get_primitive_size(type_id) 235 | value = f"Primitive ({primitive_size}, {pos})" 236 | pointer += primitive_size 237 | else: 238 | type_def = type_map[type_id] 239 | if type_def["metatype"] == STRUCTURE: 240 | value = {} 241 | value["structure_offset"] = (pos, pos+type_def["size"]) 242 | org_pointer = pointer 243 | for m in type_def["members"]: 244 | m_offset = m["offset"] 245 | pointer = org_pointer + m_offset 246 | v = read_instance(data, offset, pointer, int(m["type_hash"]), type_map) 247 | value[m["name"]] = { 248 | "value": v[0] 249 | } 250 | pointer = org_pointer + type_def["size"] 251 | elif type_def["metatype"] == ARRAY: 252 | array_offset = read_u32(data[pos:pos+4]) 253 | flags = read_u32(data[pos+4:pos+8]) 254 | length = read_u32(data[pos+8:pos+12]) 255 | array_header_size = 12 256 | org_pos = pos 257 | pointer += array_header_size 258 | org_pointer = pointer 259 | pos = offset+pointer 260 | value = { "Array": { 261 | "name": type_def["name"], 262 | "header_offset": (org_pos, org_pos+array_header_size), 263 | "flags": flags, 264 | "length": length 265 | }} 266 | pointer = array_offset 267 | 268 | if length > 0: 269 | element_type = type_def["element_type_hash"] 270 | new_pointer = pointer 271 | values = [] 272 | for i in range(length): 273 | v, new_pointer = read_instance(data, offset, new_pointer, int(element_type), type_map) 274 | values.append(v) 275 | value["Array"]["type"] = "Primitives" if element_type in PRIMITIVES else "Structures" 276 | value["Array"]["array_offset"] = (offset+pointer, offset+new_pointer) 277 | value["Array"]["values"] = values 278 | 279 | pointer = org_pointer 280 | else: 281 | print(f"Unknown metatype: {type_def['metatype']}") 282 | 283 | return (value, pointer) 284 | 285 | def find_instance_offset(data: bytearray, offset: int, count: int, nametables: List[str], type_map: dict) -> dict: 286 | instance_header_size = 24 287 | instances = [] 288 | for i in range(count): 289 | pointer = offset + i * count 290 | instance_type = read_u32(data[pointer+4:pointer+8]) 291 | instance_offset = read_u32(data[pointer+8:pointer+12]) 292 | instance_size = read_u32(data[pointer+12:pointer+16]) 293 | instance_name = nametables[read_u64(data[pointer+16:pointer+24])] 294 | instances.append({ 295 | "offset": (instance_offset, instance_offset + instance_size), 296 | "size": instance_size, 297 | f"{instance_name}": read_instance(data, instance_offset, 0, instance_type, type_map)[0] 298 | }) 299 | 300 | return { 301 | "offset": (offset, offset + count*instance_header_size), 302 | "instances": instances 303 | } 304 | 305 | def find_population_array_offsets(offsets: dict, result: List[dict] = [], org_path: str = "", prev_key: str = "", index: int = 0) -> List[dict]: 306 | for key, v in offsets.items(): 307 | if org_path == "": 308 | path = "" 309 | else: 310 | path = org_path 311 | if isinstance(v, dict): 312 | if prev_key != "": 313 | path += f"{prev_key}[{index}];" 314 | value = v["value"] 315 | if isinstance(v, dict) and "Array" in value: 316 | array_details = value["Array"] 317 | result.append({ 318 | "path": path, 319 | "key": key, 320 | "name": array_details["name"], 321 | "index": index, 322 | "length": array_details["length"], 323 | "header": array_details["header_offset"], 324 | "values": array_details["array_offset"] if "array_offset" in array_details else None 325 | }) 326 | 327 | if "values" in array_details: 328 | array_values = array_details["values"] 329 | if len(array_values) > 0 and isinstance(array_values[0], dict): 330 | for i, value in enumerate(array_details["values"]): 331 | find_population_array_offsets(value, result, path, key, i) 332 | return result 333 | 334 | def parse_gender_cnt(data: bytearray, length: int, data_offset: int) -> dict: 335 | male_indices = [] 336 | female_indices = [] 337 | for i in range(length): 338 | animal_offset = data_offset+i*32 339 | gender = read_u32(data[animal_offset:animal_offset+4]) 340 | if gender == 1: 341 | male_indices.append(i) 342 | else: 343 | female_indices.append(i) 344 | return (male_indices, female_indices) 345 | 346 | def create_array(offset: dict, instance_offset: int, data: bytearray = None, population: int = 0, group: int = 0) -> AdfArray: 347 | header_start_offset = offset["header"][0] 348 | value_start_offset, value_end_offset = offset["values"] if offset["values"] else (0,0) 349 | if data: 350 | male_indices, female_indices = parse_gender_cnt(data, offset["length"], value_start_offset) 351 | else: 352 | male_indices = None 353 | female_indices = None 354 | return AdfArray( 355 | f"{offset['path']}{offset['key']};{offset['name']}", 356 | int(population), 357 | int(group), 358 | offset["length"], 359 | header_start_offset, 360 | header_start_offset+8, 361 | header_start_offset, 362 | value_start_offset, 363 | value_end_offset, 364 | value_start_offset-instance_offset, 365 | value_end_offset-instance_offset, 366 | male_indices, 367 | female_indices 368 | ) 369 | 370 | def create_animal_array(offset: dict, instance_offset: int, data: bytearray) -> AdfArray: 371 | population, group = re.findall(r'\d+', offset["path"]) 372 | return create_array(offset, instance_offset, data, population, group) 373 | 374 | def profile_header(data: bytearray) -> dict: 375 | header = data[:64] 376 | instance_count = read_u32(header[8:12]) 377 | instance_offset = read_u32(header[12:16]) 378 | typedef_count = read_u32(header[16:20]) 379 | typedef_offset = read_u32(header[20:24]) 380 | stringhash_offset = read_u32(header[28:32]) 381 | nametable_count = read_u32(header[32:36]) 382 | nametable_offset = read_u32(header[36:40]) 383 | total_size = read_u32(header[40:44]) 384 | 385 | return { 386 | "total_size": total_size, 387 | "typedef_count": typedef_count, 388 | "typedef_offset": typedef_offset, 389 | "nametable_count": nametable_count, 390 | "nametable_offset": nametable_offset, 391 | "instance_count": instance_count, 392 | "instance_offset": instance_offset, 393 | "stringhash_offset": stringhash_offset, 394 | "header_start": 0, 395 | "header_instance_offset": 12, 396 | "header_typedef_offset": 20, 397 | "header_stringhash_offset": 28, 398 | "header_nametable_offset": 36, 399 | "header_total_size_offset": 40, 400 | "header_end": 64 401 | } 402 | 403 | def create_profile(filename: Path) -> None: 404 | data = bytearray(filename.read_bytes()) 405 | header_profile = profile_header(data) 406 | instance_count = header_profile["instance_count"] 407 | instance_offset = header_profile["instance_offset"] 408 | typedef_count = header_profile["typedef_count"] 409 | typedef_offset = header_profile["typedef_offset"] 410 | nametable_count = header_profile["nametable_count"] 411 | nametable_offset = header_profile["nametable_offset"] 412 | total_size = header_profile["total_size"] 413 | comment_size = find_length_of_string(data[64:]) 414 | nametable_size = find_nametable_size(data[nametable_offset:], nametable_count) 415 | nametables = read_nametables(data[nametable_offset:], nametable_count) 416 | typedef_offsets = find_typedef_offset(data, typedef_offset, typedef_count, nametables) 417 | type_map = typedef_offsets["type_map"] 418 | instance_offsets = find_instance_offset(data, instance_offset, instance_count, nametables, type_map) 419 | 420 | return { 421 | "total_size": total_size, 422 | "header_start": 0, 423 | "header_instance_offset": 12, 424 | "header_typedef_offset": 20, 425 | "header_stringhash_offset": 28, 426 | "header_nametable_offset": 36, 427 | "header_total_size_offset": 40, 428 | "header_end": 64, 429 | "comment_start": 64, 430 | "comment_end": 64 + comment_size, 431 | "instance_start": instance_offsets["instances"][0]["offset"][0], 432 | "instance_end": instance_offsets["instances"][0]["offset"][0] + instance_offsets["instances"][0]["size"], 433 | "instance_header_start": instance_offsets["offset"][0], 434 | "instance_header_end": instance_offsets["offset"][1], 435 | "typedef_start": typedef_offset, 436 | "typedef_end": typedef_offsets["end"], 437 | "nametable_start": nametable_offset, 438 | "nametable_end": nametable_offset+nametable_size, 439 | "details": { 440 | "instance_offsets": instance_offsets 441 | } 442 | } 443 | 444 | def find_arrays(profile: dict, data: bytearray) -> Tuple[List[AdfArray], List[AdfArray]]: 445 | instance_offsets = profile["details"]["instance_offsets"] 446 | instance_offset = instance_offsets["instances"][0]["offset"][0] 447 | array_offsets = find_population_array_offsets(instance_offsets["instances"][0]["0"], []) 448 | animal_arrays = [create_animal_array(x, instance_offset, data) for x in array_offsets if x["key"] == 'Animals'] 449 | other_arrays = [create_array(x, instance_offset) for x in array_offsets if x["key"] != 'Animals'] 450 | return (animal_arrays, other_arrays) 451 | -------------------------------------------------------------------------------- /apc/babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | 3 | [json_md: config/**.json] 4 | name_list = animal_name,reserve_name,fur_name -------------------------------------------------------------------------------- /apc/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | import sys 5 | import locale 6 | import gettext 7 | from pathlib import Path 8 | from enum import Enum 9 | from apc import __app_name__ 10 | from typing import List, Tuple 11 | 12 | SUPPORTED_LANGUAGES = ["en_US", "de_DE", "zh_CN", "ru_RU", "es_ES"] 13 | default_locale = None 14 | 15 | def get_languages() -> list: 16 | global default_locale 17 | default_locale, _ = locale.getdefaultlocale() 18 | env_language = os.environ.get("LANGUAGE") 19 | global use_languages 20 | if env_language: 21 | use_languages = env_language.split(':') 22 | else: 23 | use_languages = [default_locale] 24 | 25 | use_languages = list(filter(lambda x: x in SUPPORTED_LANGUAGES, use_languages)) 26 | if len(use_languages) == 0: 27 | use_languages = ["en_US"] 28 | 29 | return (default_locale, use_languages) 30 | 31 | LOCALE_PATH = Path(getattr(sys, '_MEIPASS', Path(__file__).resolve().parent)) / "locale/" 32 | _default, use_languages = get_languages() 33 | t = gettext.translation("apc", localedir=LOCALE_PATH, languages=use_languages) 34 | translate = t.gettext 35 | 36 | def setup_translations() -> None: 37 | global APC 38 | APC = translate("Animal Population Changer") 39 | global SPECIES 40 | SPECIES = translate("Species") 41 | global ANIMALS_TITLE 42 | ANIMALS_TITLE = translate("Animals") 43 | global MALE 44 | MALE = translate("Male") 45 | global MALES 46 | MALES = translate("Males") 47 | global FEMALE 48 | FEMALE = translate("Female") 49 | global FEMALES 50 | FEMALES = translate("Females") 51 | global HIGH_WEIGHT 52 | HIGH_WEIGHT = translate("High Weight") 53 | global HIGH_SCORE 54 | HIGH_SCORE = translate("High Score") 55 | global LEVEL 56 | LEVEL = translate("Level") 57 | global GENDER 58 | GENDER = translate("Gender") 59 | global WEIGHT 60 | WEIGHT = translate("Weight") 61 | global SCORE 62 | SCORE = translate("Score") 63 | global VISUALSEED 64 | VISUALSEED = translate("Visual Seed") 65 | global FUR 66 | FUR = translate("Fur") 67 | global DIAMOND 68 | DIAMOND = translate("Diamond") 69 | global GREATONE 70 | GREATONE = translate("Great One") 71 | global SUMMARY 72 | SUMMARY = translate("Summary") 73 | global RESERVE 74 | RESERVE = translate("Reserve") 75 | global RESERVES_TITLE 76 | RESERVES_TITLE = translate("Reserves") 77 | global RESERVE_NAME_KEY 78 | RESERVE_NAME_KEY = translate("Reserve Name (key)") 79 | global YES 80 | YES = translate("Yes") 81 | global MODDED 82 | MODDED = translate("Modded") 83 | global SPECIES_NAME_KEY 84 | SPECIES_NAME_KEY = translate("Species (key)") 85 | global VIEWING_MODDED 86 | VIEWING_MODDED = translate("viewing modded") 87 | global NEW_BUG 88 | NEW_BUG = translate("Please copy and paste as a new bug on Nexusmods here") 89 | global ERROR 90 | ERROR = translate("Error") 91 | global UNEXPECTED_ERROR 92 | UNEXPECTED_ERROR = translate("Unexpected Error") 93 | global WARNING 94 | WARNING = translate("Warning") 95 | global SAVED 96 | SAVED = translate("Saved") 97 | global DOES_NOT_EXIST 98 | DOES_NOT_EXIST = translate("does not exist") 99 | global FAILED_TO_BACKUP 100 | FAILED_TO_BACKUP = translate("failed to backup game") 101 | global FAILED_TO_LOAD_BACKUP 102 | FAILED_TO_LOAD_BACKUP = translate("failed to load backup file") 103 | global FAILED_TO_LOAD_MOD 104 | FAILED_TO_LOAD_MOD = translate("failed to load mod") 105 | global FAILED_TO_UNLOAD_MOD 106 | FAILED_TO_UNLOAD_MOD = translate("failed to unload mod") 107 | global MOD_LOADED 108 | MOD_LOADED = translate("Mod has been loaded") 109 | global MOD_UNLOADED 110 | MOD_UNLOADED = translate("Mod has been unloaded") 111 | global VERSION 112 | VERSION = translate("Version") 113 | global HUNTING_RESERVE 114 | HUNTING_RESERVE = translate("Hunting Reserve") 115 | global UPDATE_BY_PERCENTAGE 116 | UPDATE_BY_PERCENTAGE = translate("update by percentage") 117 | global MORE_MALES 118 | MORE_MALES = translate("More Males") 119 | global MORE_FEMALES 120 | MORE_FEMALES = translate("More Females") 121 | global GREATONES 122 | GREATONES = translate("More Great Ones") 123 | global DIAMONDS 124 | DIAMONDS = translate("More Diamonds") 125 | global INCLUDE_RARE_FURS 126 | INCLUDE_RARE_FURS = translate("include diamond rare furs") 127 | global ALL_FURS 128 | ALL_FURS = translate("All Furs") 129 | global RESET 130 | RESET = translate("Reset") 131 | global UPDATE_ANIMALS 132 | UPDATE_ANIMALS = translate("Update Animals") 133 | global JUST_FURS 134 | JUST_FURS = translate("Just the Furs") 135 | global ONE_OF_EACH_FUR 136 | ONE_OF_EACH_FUR = translate("one of each fur") 137 | global OTHERS 138 | OTHERS = translate("Others") 139 | global PARTY 140 | PARTY = translate("Party") 141 | global GREATONE_PARTY 142 | GREATONE_PARTY = translate("Great One Party") 143 | global DIAMOND_PARTY 144 | DIAMOND_PARTY = translate("Diamond Party") 145 | global WE_ALL_PARTY 146 | WE_ALL_PARTY = translate("We All Party") 147 | global FUR_PARTY 148 | FUR_PARTY = translate("Fur Party") 149 | global EXPLORE 150 | EXPLORE = translate("Explore") 151 | global DIAMONDS_AND_GREATONES 152 | DIAMONDS_AND_GREATONES = translate("diamonds and Great Ones") 153 | global LOOK_MODDED_ANIMALS 154 | LOOK_MODDED_ANIMALS = translate("look at modded animals") 155 | global LOOK_ALL_RESERVES 156 | LOOK_ALL_RESERVES = translate("look at all reserves") 157 | global ONLY_TOP_SCORES 158 | ONLY_TOP_SCORES = translate("only top 10 scores") 159 | global SHOW_ANIMALS 160 | SHOW_ANIMALS = translate("Show Animals") 161 | global FILES 162 | FILES = translate("Files") 163 | global CONFIGURE_GAME_PATH 164 | CONFIGURE_GAME_PATH = translate("Configure Game Path") 165 | global LIST_MODS 166 | LIST_MODS = translate("List Mods") 167 | global LOAD_MOD 168 | LOAD_MOD = translate("Load Mod") 169 | global UNLOAD_MOD 170 | UNLOAD_MOD = translate("Unload Mod") 171 | global SELECT_FOLDER 172 | SELECT_FOLDER = translate("Select the folder where the game saves your files") 173 | global SAVES_PATH_TITLE 174 | SAVES_PATH_TITLE = translate("Saves Path") 175 | global PATH_SAVED 176 | PATH_SAVED = translate("Game path saved") 177 | global CONFIRM_LOAD_MOD 178 | CONFIRM_LOAD_MOD = translate("Are you sure you want to overwrite your game file with the modded one?") 179 | global BACKUP_WILL_BE_MADE 180 | BACKUP_WILL_BE_MADE = translate("Don't worry, a backup copy will be made.") 181 | global CONFIRMATION 182 | CONFIRMATION = translate("Confirmation") 183 | global MOD 184 | MOD = translate("Mod") 185 | global VIEW_MODDED_VERSION 186 | VIEW_MODDED_VERSION = translate("view modded version") 187 | global LOADED 188 | LOADED = translate("Loaded") 189 | global MODDED_FILE 190 | MODDED_FILE = translate("Modded File") 191 | global BACK_TO_RESERVE 192 | BACK_TO_RESERVE = translate("Back to Reserve") 193 | global UPDATE_TRANSLATIONS 194 | UPDATE_TRANSLATIONS = translate("update translations") 195 | global SWITCH_LANGUAGE 196 | SWITCH_LANGUAGE = translate("switch language") 197 | global PLEASE_RESTART 198 | PLEASE_RESTART = translate("Please restart to see changes") 199 | global DEFAULT 200 | DEFAULT = translate("default") 201 | global USING 202 | USING = translate("using") 203 | global OK 204 | OK = translate("OK") 205 | global CANCEL 206 | CANCEL = translate("Cancel") 207 | global MODIFY_ANIMALS 208 | MODIFY_ANIMALS = translate("Modify Animals") 209 | global FURS 210 | FURS = translate("Furs") 211 | global MODIFY_ANIMAL_FURS 212 | MODIFY_ANIMAL_FURS = translate("Modify Animal Furs") 213 | global MALE_FURS 214 | MALE_FURS = translate("Male Furs") 215 | global FEMALE_FURS 216 | FEMALE_FURS = translate("Female Furs") 217 | global CHANGE_ALL_SPECIES 218 | CHANGE_ALL_SPECIES = translate("Change All Species") 219 | global EXPLORE_ANIMALS 220 | EXPLORE_ANIMALS = translate("Explore Animals") 221 | global MANAGE_MODDED_RESERVES 222 | MANAGE_MODDED_RESERVES = translate("Manage Modded Reserves") 223 | global VIEWING_LOADED_MOD 224 | VIEWING_LOADED_MOD = translate("viewing loaded") 225 | global USE_ALL_FURS 226 | USE_ALL_FURS = translate("use all furs") 227 | global NO 228 | NO = translate("No") 229 | global ANIMAL_DETAILS 230 | ANIMAL_DETAILS = translate("Animal Details") 231 | global RANDOM_FUR 232 | RANDOM_FUR = translate("default is random fur") 233 | global UPDATE_ANIMAL 234 | UPDATE_ANIMAL = translate("Update Animal") 235 | global TOP_10 236 | TOP_10 = translate("Top 10") 237 | global LOADED_MOD 238 | LOADED_MOD = translate("Loaded") 239 | global EXPORT_MOD 240 | EXPORT_MOD = translate("Export Mod") 241 | global IMPORT_MOD 242 | IMPORT_MOD = translate("Import Mod") 243 | global EXPORT 244 | EXPORT = translate("Export") 245 | global IMPORT 246 | IMPORT = translate("Import") 247 | global EXPORT_MSG 248 | EXPORT_MSG = translate("Select the location and filename where you want to export") 249 | global IMPORT_MSG 250 | IMPORT_MSG = translate("Select the reserve mod to import") 251 | global EXPORT_AS 252 | EXPORT_AS = translate("Export As") 253 | global SELECT_FILE 254 | SELECT_FILE = translate("Select File") 255 | global MOD_EXPORTED 256 | MOD_EXPORTED = translate("Mod Exported") 257 | global MOD_IMPORTED 258 | MOD_IMPORTED = translate("Mod Imported") 259 | 260 | setup_translations() 261 | 262 | def update_language(locale: str) -> None: 263 | global use_languages 264 | use_languages = [locale] 265 | t = gettext.translation("apc", localedir=LOCALE_PATH, languages=use_languages) 266 | global translate 267 | translate = t.gettext 268 | setup_translations() 269 | 270 | def _find_saves_path() -> str: 271 | steam_saves = Path().home() / "Documents/Avalanche Studios/COTW/Saves" 272 | steam_onedrive = Path().home() / "OneDrive/Documents/Avalanche Studios/COTW/Saves" 273 | epic_saves = Path().home() / "Documents/Avalanche Studios/Epic Games Store/COTW/Saves" 274 | epic_onedrive = Path().home() / "OneDrive/Documents/Avalanche Studios/Epic Games Store/COTW/Saves" 275 | 276 | base_saves = None 277 | if steam_saves.exists(): 278 | base_saves = steam_saves 279 | elif epic_saves.exists(): 280 | base_saves = epic_saves 281 | elif steam_onedrive.exists(): 282 | base_saves = steam_onedrive 283 | elif epic_onedrive.exists(): 284 | base_saves = epic_onedrive 285 | 286 | save_folder = None 287 | if base_saves: 288 | folders = os.listdir(base_saves) 289 | all_numbers = re.compile(r"\d+") 290 | for folder in folders: 291 | if all_numbers.match(folder): 292 | save_folder = folder 293 | break 294 | if save_folder: 295 | return base_saves / save_folder 296 | else: 297 | return None 298 | 299 | APP_DIR_PATH = Path(getattr(sys, '_MEIPASS', Path(__file__).resolve().parent)) 300 | EXPORTS_PATH = APP_DIR_PATH / "exports" 301 | EXPORTS_PATH.mkdir(exist_ok=True, parents=True) 302 | DEFAULT_SAVE_PATH = _find_saves_path() 303 | CONFIG_PATH = APP_DIR_PATH / "config" 304 | SAVE_PATH = CONFIG_PATH / "save_path.txt" 305 | SAVE_PATH.parent.mkdir(exist_ok=True, parents=True) 306 | MOD_DIR_PATH = Path().cwd() / "mods" 307 | MOD_DIR_PATH.mkdir(exist_ok=True, parents=True) 308 | BACKUP_DIR_PATH = Path().cwd() / "backups" 309 | BACKUP_DIR_PATH.mkdir(exist_ok=True, parents=True) 310 | HIGH_NUMBER = 100000 311 | 312 | ANIMAL_NAMES = json.load((CONFIG_PATH / "animal_names.json").open())["animal_names"] 313 | RESERVE_NAMES = json.load((CONFIG_PATH / "reserve_names.json").open())["reserve_names"] 314 | FUR_NAMES = json.load((CONFIG_PATH / "fur_names.json").open())["fur_names"] 315 | RESERVES = json.load((CONFIG_PATH / "reserve_details.json").open()) 316 | ANIMALS = json.load((CONFIG_PATH / "animal_details.json").open()) 317 | # TODO: diamonds that can be both genders need different weight / score values 318 | # TODO: kangaroos with multiple white furs 319 | # TODO: crocodiles with multiple spots 320 | 321 | class Reserve(str, Enum): 322 | hirsch = "hirsch" 323 | layton = "layton" 324 | medved = "medved" 325 | vurhonga = "vurhonga" 326 | parque = "parque" 327 | yukon = "yukon" 328 | cuatro = "cuatro" 329 | silver = "silver" 330 | teawaroa = "teawaroa" 331 | rancho = "rancho" 332 | mississippi = "mississippi" 333 | revontuli = "revontuli" 334 | newengland = "newengland" 335 | 336 | class Strategy(str, Enum): 337 | go_all = "go-all" 338 | go_furs = "go-furs" 339 | go_some = "go-some" 340 | diamond_all = "diamond-all" 341 | diamond_furs = "diamond-furs" 342 | diamond_some = "diamond-some" 343 | males = "males" 344 | furs_some = "furs-some" 345 | females = "females" 346 | add = "add" 347 | remove = "remove" 348 | 349 | class GreatOnes(str, Enum): 350 | moose = "moose" 351 | black_bear = "black_bear" 352 | whitetail_deer = "whitetail_deer" 353 | red_deer = "red_deer" 354 | fallow_deer = "fallow_deer" 355 | 356 | class Levels(int, Enum): 357 | TRIVIAL = 1 358 | MINOR = 2 359 | VERY_EASY = 3 360 | EASY = 4 361 | MEDIUM = 5 362 | HARD = 6 363 | VERY_HARD = 7 364 | MYTHICAL = 8 365 | LEGENDARY = 9 366 | GREAT_ONE = 10 367 | 368 | def get_level_name(level: Levels): 369 | if level == Levels.TRIVIAL: 370 | return translate("Trivial") 371 | if level == Levels.MINOR: 372 | return translate("Minor") 373 | if level == Levels.VERY_EASY: 374 | return translate("Very Easy") 375 | if level == Levels.EASY: 376 | return translate("Easy") 377 | if level == Levels.MEDIUM: 378 | return translate("Medium") 379 | if level == Levels.HARD: 380 | return translate("Hard") 381 | if level == Levels.VERY_HARD: 382 | return translate("Very Hard") 383 | if level == Levels.MYTHICAL: 384 | return translate("Mythical") 385 | if level == Levels.LEGENDARY: 386 | return translate("Legendary") 387 | if level == Levels.GREAT_ONE: 388 | return translate("Great One") 389 | return None 390 | 391 | def format_key(key: str) -> str: 392 | key = [s.capitalize() for s in re.split("_|-", key)] 393 | return " ".join(key) 394 | 395 | def load_config(config_path: Path) -> int: 396 | config_path.read_text() 397 | 398 | def get_save_path() -> Path: 399 | if SAVE_PATH.exists(): 400 | return Path(SAVE_PATH.read_text()) 401 | return DEFAULT_SAVE_PATH 402 | 403 | def save_path(save_path_location: str) -> None: 404 | SAVE_PATH.write_text(save_path_location) 405 | 406 | def get_reserve_species_renames(reserve_key: str) -> dict: 407 | reserve = get_reserve(reserve_key) 408 | return reserve["renames"] if "renames" in reserve else {} 409 | 410 | def get_species_name(key: str) -> str: 411 | is_unique = species_unique_to_reserve(key) 412 | return F"{translate(ANIMAL_NAMES[key]['animal_name'])}{' ⋆' if is_unique else ''}" 413 | 414 | def get_fur_name(key: str) -> str: 415 | return translate(FUR_NAMES[key]["fur_name"]) 416 | 417 | def get_fur_seed(species_key: str, fur_key: str, gender: str, go: bool = False) -> int: 418 | if go: 419 | return get_species(species_key)["go"]["furs"][fur_key] 420 | else: 421 | return get_species(species_key)["diamonds"]["furs"][gender][fur_key] 422 | 423 | def species(reserve_key: str, include_keys = False) -> list: 424 | species_keys = RESERVES[reserve_key]["species"] 425 | return [f"{get_species_name(s)}{' (' + s + ')' if include_keys else ''}" for s in species_keys] 426 | 427 | def get_species_key(species_name: str) -> str: 428 | for animal_name_key, names in ANIMAL_NAMES.items(): 429 | if names["animal_name"] == species_name: 430 | return animal_name_key 431 | return None 432 | 433 | def get_species_furs(species_key: str, gender: str, go: bool = False) -> List[str]: 434 | species = get_species(species_key) 435 | if go: 436 | return list(species["go"]["furs"].values()) 437 | elif gender == "both": 438 | males = list(species["diamonds"]["furs"]["male"].values()) 439 | females = list(species["diamonds"]["furs"]["female"].values()) 440 | return males + females 441 | else: 442 | return list(species["diamonds"]["furs"][gender].values()) 443 | 444 | def get_species_fur_names(species_key: str, gender: str, go: bool = False) -> Tuple[List[str],List[int]]: 445 | species = get_species(species_key) 446 | species_config = species["go"]["furs"] if go else species["diamonds"]["furs"][gender] 447 | return ([get_fur_name(x) for x in list(species_config.keys())], [x for x in list(species_config.keys())]) 448 | 449 | def get_reserve_species_name(species_key: str, reserve_key: str) -> str: 450 | renames = get_reserve_species_renames(reserve_key) 451 | species_key = renames[species_key] if species_key in renames else species_key 452 | return get_species_name(species_key) 453 | 454 | def get_reserve_name(key: str) -> str: 455 | return translate(RESERVE_NAMES[key]["reserve_name"]) 456 | 457 | def reserve_keys() -> list: 458 | return list(dict.keys(RESERVES)) 459 | 460 | def reserves(include_keys = False) -> list: 461 | keys = list(dict.keys(RESERVES)) 462 | return [f"{get_reserve_name(r)}{' (' + r + ')' if include_keys else ''}" for r in keys] 463 | 464 | def get_reserve(reserve_key: str) -> dict: 465 | return RESERVES[reserve_key] 466 | 467 | def get_reserve_species(reserve_key: str) -> dict: 468 | return get_reserve(reserve_key)["species"] 469 | 470 | def get_species(species_key: str) -> dict: 471 | return ANIMALS[species_key] 472 | 473 | def get_diamond_gender(species_key: str) -> str: 474 | species_config = get_species(species_key)["diamonds"] 475 | return species_config["gender"] if "gender" in species_config else "male" 476 | 477 | def _get_fur(furs: dict, seed: int) -> str: 478 | try: 479 | return next(key for key, value in furs.items() if value == seed) 480 | except: 481 | return None 482 | 483 | def get_animal_fur_by_seed(species: str, gender: str, seed: int, is_go: bool = False) -> str: 484 | if species not in ANIMALS: 485 | return "-" 486 | 487 | animal = ANIMALS[species] 488 | go_furs = animal["go"]["furs"] if "go" in animal and "furs" in animal["go"] else [] 489 | diamond_furs = animal["diamonds"]["furs"] if "furs" in animal["diamonds"] else [] 490 | diamond_furs = diamond_furs[gender] if gender in diamond_furs else [] 491 | go_key = _get_fur(go_furs, seed) 492 | diamond_key = _get_fur(diamond_furs, seed) 493 | if go_key and is_go: 494 | return get_fur_name(go_key) 495 | elif diamond_key: 496 | return get_fur_name(diamond_key) 497 | else: 498 | return "-" 499 | 500 | def valid_species_for_reserve(species: str, reserve: str) -> bool: 501 | return reserve in RESERVES and species in RESERVES[reserve]["species"] 502 | 503 | def valid_species(species: str) -> bool: 504 | return species in list(ANIMALS.keys()) 505 | 506 | def valid_go_species(species: str) -> bool: 507 | return species in GreatOnes.__members__ 508 | 509 | def valid_fur_species(species_key: str) -> bool: 510 | return True 511 | 512 | def get_population_file_name(reserve: str): 513 | index = RESERVES[reserve]["index"] 514 | return f"animal_population_{index}" 515 | 516 | def get_population_reserve_key(filename: str): 517 | for _reserve, details in RESERVES.items(): 518 | reserve_filename = f"animal_population_{details['index']}" 519 | if reserve_filename == filename: 520 | return _reserve 521 | return None 522 | 523 | def get_population_name(filename: str): 524 | for _reserve, details in RESERVES.items(): 525 | reserve_filename = f"animal_population_{details['index']}" 526 | if reserve_filename == filename: 527 | return translate(details["name"]) 528 | return None 529 | 530 | def species_unique_to_reserve(species_key: str) -> bool: 531 | cnt = 0 532 | for _reserve_key, reserve_details in RESERVES.items(): 533 | if species_key in reserve_details["species"]: 534 | cnt += 1 535 | return (cnt == 1) -------------------------------------------------------------------------------- /apc/config/animal_names.json: -------------------------------------------------------------------------------- 1 | { 2 | "animal_names": { 3 | "american_alligator": { 4 | "animal_name": "American Alligator" 5 | }, 6 | "antelope_jackrabbit": { 7 | "animal_name": "Antelope Jackrabbit" 8 | }, 9 | "axis_deer": { 10 | "animal_name": "Axis Deer" 11 | }, 12 | "beceite_ibex": { 13 | "animal_name": "Beceite Ibex" 14 | }, 15 | "bighorn_sheep": { 16 | "animal_name": "Bighorn Sheep" 17 | }, 18 | "black_bear": { 19 | "animal_name": "Black Bear" 20 | }, 21 | "black_grouse": { 22 | "animal_name": "Black Grouse" 23 | }, 24 | "blackbuck": { 25 | "animal_name": "Blackbuck" 26 | }, 27 | "blacktail_deer": { 28 | "animal_name": "Blacktail Deer" 29 | }, 30 | "blue_wildebeest": { 31 | "animal_name": "Blue Wildebeest" 32 | }, 33 | "bobcat": { 34 | "animal_name": "Bobcat" 35 | }, 36 | "canada_goose": { 37 | "animal_name": "Canada Goose" 38 | }, 39 | "cape_buffalo": { 40 | "animal_name": "Cape Buffalo" 41 | }, 42 | "caribou": { 43 | "animal_name": "Caribou" 44 | }, 45 | "chamois": { 46 | "animal_name": "Chamois" 47 | }, 48 | "cinnamon_teal": { 49 | "animal_name": "Cinnamon Teal" 50 | }, 51 | "collared_peccary": { 52 | "animal_name": "Collared Peccary" 53 | }, 54 | "coyote": { 55 | "animal_name": "Coyote" 56 | }, 57 | "eastern_cottontail_rabbit": { 58 | "animal_name": "Eastern Cottontail Rabbit" 59 | }, 60 | "eastern_wild_turkey": { 61 | "animal_name": "Eastern Wild Turkey" 62 | }, 63 | "eu_bison": { 64 | "animal_name": "Eu Bison" 65 | }, 66 | "eu_hare": { 67 | "animal_name": "Eu Hare" 68 | }, 69 | "eu_rabbit": { 70 | "animal_name": "Eu Rabbit" 71 | }, 72 | "eurasian_brown_bear": { 73 | "animal_name": "Eurasian Brown Bear" 74 | }, 75 | "eurasian_lynx": { 76 | "animal_name": "Eurasian Lynx" 77 | }, 78 | "eurasian_teal": { 79 | "animal_name": "Eurasian Teal" 80 | }, 81 | "eurasian_wigeon": { 82 | "animal_name": "Eurasian Wigeon" 83 | }, 84 | "fallow_deer": { 85 | "animal_name": "Fallow Deer" 86 | }, 87 | "feral_goat": { 88 | "animal_name": "Feral Goat" 89 | }, 90 | "feral_pig": { 91 | "animal_name": "Feral Pig" 92 | }, 93 | "gemsbok": { 94 | "animal_name": "Gemsbok" 95 | }, 96 | "goldeneye": { 97 | "animal_name": "Goldeneye" 98 | }, 99 | "gray_fox": { 100 | "animal_name": "Gray Fox" 101 | }, 102 | "gray_wolf": { 103 | "animal_name": "Gray Wolf" 104 | }, 105 | "gredos_ibex": { 106 | "animal_name": "Gredos Ibex" 107 | }, 108 | "green_wing_teal": { 109 | "animal_name": "Green Wing Teal" 110 | }, 111 | "greylag_goose": { 112 | "animal_name": "Greylag Goose" 113 | }, 114 | "grizzly_bear": { 115 | "animal_name": "Grizzly Bear" 116 | }, 117 | "harlequin_duck": { 118 | "animal_name": "Harlequin Duck" 119 | }, 120 | "hazel_grouse": { 121 | "animal_name": "Hazel Grouse" 122 | }, 123 | "iberian_mouflon": { 124 | "animal_name": "Iberian Mouflon" 125 | }, 126 | "iberian_wolf": { 127 | "animal_name": "Iberian Wolf" 128 | }, 129 | "jackrabbit": { 130 | "animal_name": "Jackrabbit" 131 | }, 132 | "lesser_kudu": { 133 | "animal_name": "Lesser Kudu" 134 | }, 135 | "lion": { 136 | "animal_name": "Lion" 137 | }, 138 | "mallard": { 139 | "animal_name": "Mallard" 140 | }, 141 | "mexican_bobcat": { 142 | "animal_name": "Mexican Bobcat" 143 | }, 144 | "moose": { 145 | "animal_name": "Moose" 146 | }, 147 | "mountain_goat": { 148 | "animal_name": "Mountain Goat" 149 | }, 150 | "mountain_hare": { 151 | "animal_name": "Mountain Hare" 152 | }, 153 | "mountain_lion": { 154 | "animal_name": "Mountain Lion" 155 | }, 156 | "mule_deer": { 157 | "animal_name": "Mule Deer" 158 | }, 159 | "northern_bobwhite_quail": { 160 | "animal_name": "Northern Bobwhite Quail" 161 | }, 162 | "pheasant": { 163 | "animal_name": "Pheasant" 164 | }, 165 | "plains_bison": { 166 | "animal_name": "Plains Bison" 167 | }, 168 | "prong_horn": { 169 | "animal_name": "Prong Horn" 170 | }, 171 | "puma": { 172 | "animal_name": "Puma" 173 | }, 174 | "raccoon": { 175 | "animal_name": "Raccoon" 176 | }, 177 | "raccoon_dog": { 178 | "animal_name": "Raccoon Dog" 179 | }, 180 | "red_deer": { 181 | "animal_name": "Red Deer" 182 | }, 183 | "red_fox": { 184 | "animal_name": "Red Fox" 185 | }, 186 | "reindeer": { 187 | "animal_name": "Reindeer" 188 | }, 189 | "rio_grande_turkey": { 190 | "animal_name": "Rio Grande Turkey" 191 | }, 192 | "rock_ptarmigan": { 193 | "animal_name": "Rock Ptarmigan" 194 | }, 195 | "rockymountain_elk": { 196 | "animal_name": "Rockymountain Elk" 197 | }, 198 | "roe_deer": { 199 | "animal_name": "Roe Deer" 200 | }, 201 | "ronda_ibex": { 202 | "animal_name": "Ronda Ibex" 203 | }, 204 | "roosevelt_elk": { 205 | "animal_name": "Roosevelt Elk" 206 | }, 207 | "scrub_hare": { 208 | "animal_name": "Scrub Hare" 209 | }, 210 | "siberian_musk_deer": { 211 | "animal_name": "Siberian Musk Deer" 212 | }, 213 | "sidestriped_jackal": { 214 | "animal_name": "Sidestriped Jackal" 215 | }, 216 | "sika_deer": { 217 | "animal_name": "Sika Deer" 218 | }, 219 | "southeastern_ibex": { 220 | "animal_name": "Southeastern Ibex" 221 | }, 222 | "springbok": { 223 | "animal_name": "Springbok" 224 | }, 225 | "tufted_duck": { 226 | "animal_name": "Tufted Duck" 227 | }, 228 | "tundra_bean_goose": { 229 | "animal_name": "Tundra Bean Goose" 230 | }, 231 | "warthog": { 232 | "animal_name": "Warthog" 233 | }, 234 | "water_buffalo": { 235 | "animal_name": "Water Buffalo" 236 | }, 237 | "western_capercaillie": { 238 | "animal_name": "Western Capercaillie" 239 | }, 240 | "whitetail_deer": { 241 | "animal_name": "Whitetail Deer" 242 | }, 243 | "wild_boar": { 244 | "animal_name": "Wild Boar" 245 | }, 246 | "wild_hog": { 247 | "animal_name": "Wild Hog" 248 | }, 249 | "wild_turkey": { 250 | "animal_name": "Wild Turkey" 251 | }, 252 | "willow_ptarmigan": { 253 | "animal_name": "Willow Ptarmigan" 254 | }, 255 | "hog_deer": { 256 | "animal_name": "Hog Deer" 257 | }, 258 | "magpie_goose": { 259 | "animal_name": "Magpie Goose" 260 | }, 261 | "eg_kangaroo": { 262 | "animal_name": "Eastern Kangaroo" 263 | }, 264 | "sambar": { 265 | "animal_name": "Sambar Deer" 266 | }, 267 | "banteng": { 268 | "animal_name": "Banteng" 269 | }, 270 | "sw_crocodile": { 271 | "animal_name": "Saltwater Crocodile" 272 | }, 273 | "stubble_quail": { 274 | "animal_name": "Stubble Quail" 275 | }, 276 | "javan_rusa": { 277 | "animal_name": "Javan Rusa" 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /apc/config/fur_names.json: -------------------------------------------------------------------------------- 1 | { 2 | "fur_names": { 3 | "piebald": { 4 | "fur_name": "Piebald" 5 | }, 6 | "olive": { 7 | "fur_name": "Olive" 8 | }, 9 | "dark_brown": { 10 | "fur_name": "Dark Brown" 11 | }, 12 | "albino": { 13 | "fur_name": "Albino" 14 | }, 15 | "melanistic": { 16 | "fur_name": "Melanistic" 17 | }, 18 | "gray": { 19 | "fur_name": "Gray" 20 | }, 21 | "brown": { 22 | "fur_name": "Brown" 23 | }, 24 | "mottled": { 25 | "fur_name": "Mottled" 26 | }, 27 | "spotted": { 28 | "fur_name": "Spotted" 29 | }, 30 | "dark": { 31 | "fur_name": "Dark" 32 | }, 33 | "orange": { 34 | "fur_name": "Orange" 35 | }, 36 | "brown_hybrid": { 37 | "fur_name": "Brown Hybrid" 38 | }, 39 | "gray_brown": { 40 | "fur_name": "Gray Brown" 41 | }, 42 | "light_brown": { 43 | "fur_name": "Light Brown" 44 | }, 45 | "buff": { 46 | "fur_name": "Buff" 47 | }, 48 | "black": { 49 | "fur_name": "Black" 50 | }, 51 | "bronze": { 52 | "fur_name": "Bronze" 53 | }, 54 | "dusky": { 55 | "fur_name": "Dusky" 56 | }, 57 | "cinnamon": { 58 | "fur_name": "Cinnamon" 59 | }, 60 | "blonde": { 61 | "fur_name": "Blonde" 62 | }, 63 | "fabled_glacier": { 64 | "fur_name": "Fabled Glacier" 65 | }, 66 | "fabled_glacier2": { 67 | "fur_name": "Fabled Glacier 2" 68 | }, 69 | "fabled_chestnut": { 70 | "fur_name": "Fabled Chestnut" 71 | }, 72 | "fabled_cream": { 73 | "fur_name": "Fabled Cream" 74 | }, 75 | "fabled_spotted": { 76 | "fur_name": "Fabled Spotted" 77 | }, 78 | "fabled_spirit": { 79 | "fur_name": "Fabled Spirit" 80 | }, 81 | "leucistic": { 82 | "fur_name": "Leucistic" 83 | }, 84 | "gold": { 85 | "fur_name": "Gold" 86 | }, 87 | "dark_grey": { 88 | "fur_name": "Dark Grey" 89 | }, 90 | "tan": { 91 | "fur_name": "Tan" 92 | }, 93 | "crowned": { 94 | "fur_name": "Crowned" 95 | }, 96 | "blue": { 97 | "fur_name": "Blue" 98 | }, 99 | "red": { 100 | "fur_name": "Red" 101 | }, 102 | "lg leucist.": { 103 | "fur_name": "Lg leucist." 104 | }, 105 | "bald leuc.": { 106 | "fur_name": "Bald leuc." 107 | }, 108 | "honeytones": { 109 | "fur_name": "Honeytones" 110 | }, 111 | "beige": { 112 | "fur_name": "Beige" 113 | }, 114 | "ochre": { 115 | "fur_name": "Ochre" 116 | }, 117 | "light_gray": { 118 | "fur_name": "Light Gray" 119 | }, 120 | "light_bronze": { 121 | "fur_name": "Light Bronze" 122 | }, 123 | "spirit": { 124 | "fur_name": "Spirit" 125 | }, 126 | "red_brown": { 127 | "fur_name": "Red Brown" 128 | }, 129 | "dark_green": { 130 | "fur_name": "Dark Green" 131 | }, 132 | "light_green": { 133 | "fur_name": "Light Green" 134 | }, 135 | "hybrid_blue": { 136 | "fur_name": "Hybrid Blue" 137 | }, 138 | "hybrid_green": { 139 | "fur_name": "Hybrid Green" 140 | }, 141 | "eclipse": { 142 | "fur_name": "Eclipse" 143 | }, 144 | "hybrid": { 145 | "fur_name": "Hybrid" 146 | }, 147 | "spotted_dark": { 148 | "fur_name": "Spotted Dark" 149 | }, 150 | "spotted_red": { 151 | "fur_name": "Spotted Red" 152 | }, 153 | "white_brown": { 154 | "fur_name": "White Brown" 155 | }, 156 | "black_brown": { 157 | "fur_name": "Black Brown" 158 | }, 159 | "black_white": { 160 | "fur_name": "Black White" 161 | }, 162 | "white": { 163 | "fur_name": "White" 164 | }, 165 | "mixed": { 166 | "fur_name": "Mixed" 167 | }, 168 | "black_spots": { 169 | "fur_name": "Black Spots" 170 | }, 171 | "blackgold": { 172 | "fur_name": "Blackgold" 173 | }, 174 | "pink": { 175 | "fur_name": "Pink" 176 | }, 177 | "two_tones": { 178 | "fur_name": "Two Tones" 179 | }, 180 | "eggwhite": { 181 | "fur_name": "Eggwhite" 182 | }, 183 | "pale": { 184 | "fur_name": "Pale" 185 | }, 186 | "pristine": { 187 | "fur_name": "Pristine" 188 | }, 189 | "winter": { 190 | "fur_name": "Winter" 191 | }, 192 | "fabled_two_tone": { 193 | "fur_name": "Fabled Two Tone" 194 | }, 195 | "fabled_birch": { 196 | "fur_name": "Fabled Birch" 197 | }, 198 | "fabled_oak": { 199 | "fur_name": "Fabled Oak" 200 | }, 201 | "fabled_speckled": { 202 | "fur_name": "Fabled Speckled" 203 | }, 204 | "fabled_spruce": { 205 | "fur_name": "Fabled Spruce" 206 | }, 207 | "fabled_ashen": { 208 | "fur_name": "Fabled Ashen" 209 | }, 210 | "molting": { 211 | "fur_name": "Molting" 212 | }, 213 | "dilute": { 214 | "fur_name": "Dilute" 215 | }, 216 | "dark_red": { 217 | "fur_name": "Dark Red" 218 | }, 219 | "piebald-grey": { 220 | "fur_name": "Piebald Grey" 221 | }, 222 | "piebald-blnd": { 223 | "fur_name": "Piebald Blnd" 224 | }, 225 | "lightbuff": { 226 | "fur_name": "Lightbuff" 227 | }, 228 | "bicolor": { 229 | "fur_name": "Bicolor" 230 | }, 231 | "chestnut": { 232 | "fur_name": "Chestnut" 233 | }, 234 | "cream": { 235 | "fur_name": "Cream" 236 | }, 237 | "bright": { 238 | "fur_name": "Bright" 239 | }, 240 | "fabled_hooded": { 241 | "fur_name": "Fabled Hooded" 242 | }, 243 | "fabled_mocha": { 244 | "fur_name": "Fabled Mocha" 245 | }, 246 | "two_tone": { 247 | "fur_name": "Two Tone" 248 | }, 249 | "grey_brown": { 250 | "fur_name": "Grey Brown" 251 | }, 252 | "mocha": { 253 | "fur_name": "Mocha" 254 | }, 255 | "dusky_gradient": { 256 | "fur_name": "Dusky Gradient" 257 | }, 258 | "yellow": { 259 | "fur_name": "Yellow" 260 | }, 261 | "maroon": { 262 | "fur_name": "Maroon" 263 | }, 264 | "dark_spotted": { 265 | "fur_name": "Dark Spotted" 266 | }, 267 | "chocolate": { 268 | "fur_name": "Chocolate" 269 | }, 270 | "fabled_silver": { 271 | "fur_name": "Fabled Silver" 272 | }, 273 | "fabled_golden": { 274 | "fur_name": "Fabled Golden" 275 | }, 276 | "fabled_painted": { 277 | "fur_name": "Fabled Painted" 278 | }, 279 | "redbrown": { 280 | "fur_name": "Redbrown" 281 | }, 282 | "lg_leucist.": { 283 | "fur_name": "Lg Leucist" 284 | }, 285 | "bald_leuc.": { 286 | "fur_name": "Bald Leucist" 287 | } 288 | } 289 | } -------------------------------------------------------------------------------- /apc/config/reserve_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "hirsch": { 3 | "name": "Hirschfelden", 4 | "index": 0, 5 | "species": [ 6 | "wild_boar", 7 | "eu_rabbit", 8 | "fallow_deer", 9 | "eu_bison", 10 | "roe_deer", 11 | "red_fox", 12 | "pheasant", 13 | "canada_goose", 14 | "red_deer" 15 | ] 16 | }, 17 | "layton": { 18 | "name": "Layton Lake", 19 | "index": 1, 20 | "species": [ 21 | "moose", 22 | "jackrabbit", 23 | "mallard", 24 | "wild_turkey", 25 | "black_bear", 26 | "roosevelt_elk", 27 | "coyote", 28 | "blacktail_deer", 29 | "whitetail_deer" 30 | ] 31 | }, 32 | "medved": { 33 | "name": "Medved-Taiga National Park", 34 | "index": 2, 35 | "species": [ 36 | "siberian_musk_deer", 37 | "moose", 38 | "wild_boar", 39 | "reindeer", 40 | "eurasian_lynx", 41 | "eurasian_brown_bear", 42 | "western_capercaillie", 43 | "gray_wolf" 44 | ] 45 | }, 46 | "vurhonga": { 47 | "name": "Vurhonga Savanna", 48 | "index": 3, 49 | "species": [ 50 | "eurasian_wigeon", 51 | "blue_wildebeest", 52 | "sidestriped_jackal", 53 | "gemsbok", 54 | "lesser_kudu", 55 | "scrub_hare", 56 | "lion", 57 | "warthog", 58 | "cape_buffalo", 59 | "springbok" 60 | ] 61 | }, 62 | "parque": { 63 | "name": "Parque Fernando", 64 | "index": 4, 65 | "species": [ 66 | "red_deer", 67 | "water_buffalo", 68 | "puma", 69 | "blackbuck", 70 | "cinnamon_teal", 71 | "collared_peccary", 72 | "mule_deer", 73 | "axis_deer" 74 | ] 75 | }, 76 | "yukon": { 77 | "name": "Yukon Valley", 78 | "index": 6, 79 | "species": [ 80 | "harlequin_duck", 81 | "moose", 82 | "red_fox", 83 | "caribou", 84 | "canada_goose", 85 | "grizzly_bear", 86 | "gray_wolf", 87 | "plains_bison" 88 | ] 89 | }, 90 | "cuatro": { 91 | "name": "Cuatro Colinas Game Reserve", 92 | "index": 8, 93 | "species": [ 94 | "southeastern_ibex", 95 | "iberian_wolf", 96 | "red_deer", 97 | "iberian_mouflon", 98 | "wild_boar", 99 | "beceite_ibex", 100 | "eu_hare", 101 | "roe_deer", 102 | "ronda_ibex", 103 | "pheasant", 104 | "gredos_ibex" 105 | ] 106 | }, 107 | "silver": { 108 | "name": "Silver Ridge Peaks", 109 | "index": 9, 110 | "renames": { 111 | "puma": "mountain_lion" 112 | }, 113 | "species": [ 114 | "prong_horn", 115 | "puma", 116 | "mountain_goat", 117 | "bighorn_sheep", 118 | "wild_turkey", 119 | "black_bear", 120 | "mule_deer", 121 | "rockymountain_elk", 122 | "plains_bison" 123 | ] 124 | }, 125 | "teawaroa": { 126 | "name": "Te Awaroa National Park", 127 | "index": 10, 128 | "species": [ 129 | "red_deer", 130 | "eu_rabbit", 131 | "feral_pig", 132 | "fallow_deer", 133 | "chamois", 134 | "mallard", 135 | "wild_turkey", 136 | "sika_deer", 137 | "feral_goat" 138 | ] 139 | }, 140 | "rancho": { 141 | "name": "Rancho del Arroyo", 142 | "index": 11, 143 | "species": [ 144 | "mexican_bobcat", 145 | "rio_grande_turkey", 146 | "prong_horn", 147 | "bighorn_sheep", 148 | "collared_peccary", 149 | "antelope_jackrabbit", 150 | "mule_deer", 151 | "coyote", 152 | "pheasant", 153 | "whitetail_deer" 154 | ] 155 | }, 156 | "mississippi": { 157 | "name": "Mississippi Acres Preserve", 158 | "index": 12, 159 | "renames": { 160 | "feral_pig": "wild_hog" 161 | }, 162 | "species": [ 163 | "feral_pig", 164 | "raccoon", 165 | "eastern_cottontail_rabbit", 166 | "northern_bobwhite_quail", 167 | "eastern_wild_turkey", 168 | "gray_fox", 169 | "black_bear", 170 | "american_alligator", 171 | "green_wing_teal", 172 | "whitetail_deer" 173 | ] 174 | }, 175 | "revontuli": { 176 | "name": "Revontuli Coast", 177 | "index": 13, 178 | "species": [ 179 | "mallard", 180 | "rock_ptarmigan", 181 | "eurasian_wigeon", 182 | "moose", 183 | "goldeneye", 184 | "mountain_hare", 185 | "tufted_duck", 186 | "black_grouse", 187 | "tundra_bean_goose", 188 | "willow_ptarmigan", 189 | "eurasian_lynx", 190 | "hazel_grouse", 191 | "eurasian_brown_bear", 192 | "eurasian_teal", 193 | "western_capercaillie", 194 | "canada_goose", 195 | "greylag_goose", 196 | "whitetail_deer", 197 | "raccoon_dog" 198 | ] 199 | }, 200 | "newengland": { 201 | "name": "New England Mountains", 202 | "index": 14, 203 | "species": [ 204 | "mallard", 205 | "moose", 206 | "goldeneye", 207 | "raccoon", 208 | "eastern_cottontail_rabbit", 209 | "northern_bobwhite_quail", 210 | "eastern_wild_turkey", 211 | "gray_fox", 212 | "red_fox", 213 | "black_bear", 214 | "bobcat", 215 | "coyote", 216 | "pheasant", 217 | "green_wing_teal", 218 | "whitetail_deer" 219 | ] 220 | }, 221 | "emerald": { 222 | "name": "Emerald Coast", 223 | "index": 16, 224 | "species": [ 225 | "hog_deer", 226 | "fallow_deer", 227 | "red_deer", 228 | "feral_pig", 229 | "magpie_goose", 230 | "eg_kangaroo", 231 | "red_fox", 232 | "sambar", 233 | "banteng", 234 | "sw_crocodile", 235 | "feral_goat", 236 | "stubble_quail", 237 | "axis_deer", 238 | "javan_rusa" 239 | ] 240 | } 241 | } -------------------------------------------------------------------------------- /apc/config/reserve_names.json: -------------------------------------------------------------------------------- 1 | { 2 | "reserve_names": { 3 | "hirsch": { 4 | "reserve_name": "Hirschfelden" 5 | }, 6 | "layton": { 7 | "reserve_name": "Layton Lake" 8 | }, 9 | "medved": { 10 | "reserve_name": "Medved-Taiga National Park" 11 | }, 12 | "vurhonga": { 13 | "reserve_name": "Vurhonga Savanna" 14 | }, 15 | "parque": { 16 | "reserve_name": "Parque Fernando" 17 | }, 18 | "yukon": { 19 | "reserve_name": "Yukon Valley" 20 | }, 21 | "cuatro": { 22 | "reserve_name": "Cuatro Colinas Game Reserve" 23 | }, 24 | "silver": { 25 | "reserve_name": "Silver Ridge Peaks" 26 | }, 27 | "teawaroa": { 28 | "reserve_name": "Te Awaroa National Park" 29 | }, 30 | "rancho": { 31 | "reserve_name": "Rancho del Arroyo" 32 | }, 33 | "mississippi": { 34 | "reserve_name": "Mississippi Acres Preserve" 35 | }, 36 | "revontuli": { 37 | "reserve_name": "Revontuli Coast" 38 | }, 39 | "newengland": { 40 | "reserve_name": "New England Mountains" 41 | }, 42 | "emerald": { 43 | "reserve_name": "Emerald Coast" 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /apc/hacks.py: -------------------------------------------------------------------------------- 1 | import json, subprocess, pyautogui, time, re 2 | from pathlib import Path 3 | from apc import populations, adf, config, utils 4 | from typing import List 5 | 6 | def extract_animal_names(path: Path) -> dict: 7 | data = json.load(path.open()) 8 | names = {} 9 | for animal in data.keys(): 10 | names[animal] = { "animal_name": config.format_key(animal) } 11 | return { 12 | "animal_names": names 13 | } 14 | 15 | def extract_reserve_names(path: Path) -> dict: 16 | data = json.load(path.open()) 17 | names = {} 18 | for reserve in data.keys(): 19 | names[reserve] = { "reserve_name": data[reserve]["name"] } 20 | return { 21 | "reserve_names": names 22 | } 23 | 24 | def extract_furs_names(path: Path) -> dict: 25 | data = json.load(path.open()) 26 | fur_names = {} 27 | for animal in list(data.keys()): 28 | for _gender, furs in data[animal]["diamonds"]["furs"].items(): 29 | for fur in furs.keys(): 30 | if fur not in fur_names: 31 | fur_name = config.format_key(fur) 32 | fur_names[fur] = { "fur_name": fur_name } 33 | if "go" in data[animal]: 34 | for fur in data[animal]["go"]["furs"].keys(): 35 | if fur not in fur_names: 36 | fur_name = config.format_key(fur) 37 | fur_names[fur] = { "fur_name": fur_name } 38 | return { 39 | "fur_names": fur_names 40 | } 41 | 42 | def bad_scores(path: Path) -> None: 43 | data = json.load(path.open()) 44 | for animal_key in data.keys(): 45 | animal = data[animal_key] 46 | if animal["diamonds"]["score_low"] > animal["diamonds"]["score_high"]: 47 | print(animal_key, animal) 48 | 49 | def merge_furs() -> None: 50 | scan = json.load(Path("scans/furs.json").open()) 51 | details = json.load(Path("apc/config/animal_details.json").open()) 52 | for animal_name, furs in scan.items(): 53 | existing_animal = details[animal_name] 54 | if existing_animal: 55 | existing_animal_furs = existing_animal["diamonds"]["furs"] 56 | if list(existing_animal_furs["male"].values())[0] == 0: 57 | details[animal_name]["diamonds"]["furs"] = furs 58 | Path("apc/config/animal_details2.json").write_text(json.dumps(details, indent=2)) 59 | 60 | def analyze_reserve(path: Path) -> None: 61 | pops = populations._get_populations(adf.load_adf(path, True).adf) 62 | group_weight = {} 63 | for p_i, p in enumerate(pops): 64 | groups = p.value["Groups"].value 65 | high_weight = 0 66 | for g in groups: 67 | animals = g.value["Animals"].value 68 | for a in animals: 69 | a = populations.AdfAnimal(a, "unknown") 70 | if a.weight > high_weight: 71 | high_weight = a.weight 72 | group_weight[p_i] = high_weight 73 | print(json.dumps(group_weight, indent=2)) 74 | 75 | def compare_fur_cnt() -> None: 76 | details = json.load(Path("apc/config/animal_details.json").open()) 77 | global_furs = json.load(Path("scans/global_furs.json").open()) 78 | for animal_name, detail in details.items(): 79 | if animal_name == "eg_kangaroo": 80 | animal_name = "eastern_grey_kangaroo" 81 | elif animal_name == "sw_crocodile": 82 | animal_name = "saltwater_crocodile" 83 | if animal_name in global_furs: 84 | global_male_cnt = global_furs[animal_name]["male_cnt"] 85 | global_female_cnt = global_furs[animal_name]["female_cnt"] 86 | male_cnt = len(detail["diamonds"]["furs"]["male"]) 87 | female_cnt = len(detail["diamonds"]["furs"]["female"]) 88 | missing_male = global_male_cnt != male_cnt 89 | missing_female = global_female_cnt != female_cnt 90 | if missing_male or missing_female: 91 | print(f"{animal_name} (male: {global_male_cnt - male_cnt}) (female: {global_female_cnt - female_cnt})") 92 | else: 93 | print("** MISSING:", animal_name) 94 | 95 | FURS_PATH = Path("scans/furs.json") 96 | 97 | class ApsAnimal: 98 | def __init__(self, animal_line: str) -> None: 99 | animal_parts = animal_line.split(",") 100 | self.species = utils.unformat_key(animal_parts[0]) 101 | self.gender = "male" if animal_parts[2].lower() == "male" else "female" 102 | self.weight = float(animal_parts[3].split(" ")[0]) 103 | self.score = float(animal_parts[4].split(" ")[0]) 104 | self.score = float(animal_parts[4].split(" ")[0]) 105 | self.fur = animal_parts[5].lower().rstrip() 106 | 107 | def __repr__(self) -> str: 108 | return f"{self.species}, {self.gender}, {self.weight}, {self.score}, {self.fur}" 109 | 110 | def reset_ini() -> None: 111 | filename = Path("imgui.ini") 112 | content = filename.read_text() 113 | new_content = re.sub("Pos=\d+,\d+", "Pos=0,0", content, flags=re.RegexFlag.MULTILINE) 114 | filename.write_text(new_content) 115 | 116 | def launch_aps() -> None: 117 | reset_ini() 118 | subprocess.Popen(f"AnimalPopulationScanner.exe -p > scans\scan.csv", shell=True) 119 | 120 | def map_aps(reserve_name: str, species_key: str) -> str: 121 | if species_key == "eu_rabbit": 122 | return "euro_rabbit" 123 | if species_key == "eu_bison": 124 | return "euro_bison" 125 | if species_key == "siberian_musk_deer": 126 | return "musk_deer" 127 | if species_key == "eurasian_brown_bear": 128 | return "brown_bear" 129 | if species_key == "western_capercaillie": 130 | return "W.Capercaillie" 131 | if species_key == "gray_wolf": 132 | return "grey_wolf" 133 | if species_key == "eurasian_wigeon": 134 | return "Eu.Wigeon" 135 | if species_key == "sidestriped_jackal": 136 | return "S-Striped_jackal" 137 | if species_key == "cinnamon_teal": 138 | return "cin_teal" 139 | if species_key == "collared_peccary": 140 | return "coll._peccary" 141 | if species_key == "harlequin_duck": 142 | return "h_duck" 143 | if species_key == "gray_wolf": 144 | return "grey_wolf" 145 | if species_key == "eu_hare": 146 | return "european_hare" 147 | if species_key == "southeastern_ibex": 148 | return "ses_ibex" 149 | if species_key == "wild_turkey": 150 | return "turkey" 151 | if species_key == "prong_horn": 152 | return "pronghorn" 153 | # if reserve_name == "silver" and species_key == "puma": 154 | # return "mountain_lion" 155 | if species_key == "rockymountain_elk": 156 | return "rm_elk" 157 | if species_key == "rio_grande_turkey": 158 | return "rg_turkey" 159 | if species_key == "antelope_jackrabbit": 160 | return "ant._jackrabbit" 161 | if species_key == "northern_bobwhite_quail": 162 | return "Bobwhite_Quail" 163 | if species_key == "green_wing_teal": 164 | return "green-winged_teal" 165 | if species_key == "eastern_cottontail_rabbit": 166 | return "ect_rabbit" 167 | if species_key == "eastern_wild_turkey": 168 | return "ew_turkey" 169 | if reserve_name == "mississippi" and species_key == "feral_pig": 170 | return "wild_hog" 171 | if species_key == "american_alligator": 172 | return "Am._Alligator" 173 | if species_key == "tundra_bean_goose": 174 | return "t.bean_goose" 175 | if species_key == "eurasian_teal": 176 | return "eu.teal" 177 | 178 | return species_key 179 | 180 | """ 181 | Captures first seed that gives fur 182 | """ 183 | def process_aps(species_key: str, filename: Path) -> None: 184 | scanned_furs = {} 185 | with filename.open() as csvfile: 186 | animals = csvfile.readlines() 187 | for animal in animals: 188 | try: 189 | animal = ApsAnimal(animal) 190 | if animal.species.lower() == species_key.lower(): 191 | if animal.gender not in scanned_furs: 192 | scanned_furs[animal.gender] = {} 193 | if animal.fur not in scanned_furs[animal.gender]: 194 | scanned_furs[animal.gender][animal.fur] = animal.weight 195 | except: 196 | pass 197 | return scanned_furs 198 | 199 | def combine_furs(existing: dict, latest: dict) -> None: 200 | existing_female_furs = existing["female"] if "female" in existing else {} 201 | latest_female_furs = latest["female"] if "female" in latest else {} 202 | existing_male_furs = existing["male"] if "male" in existing else {} 203 | latest_male_furs = latest["male"] if "male" in latest else {} 204 | 205 | for fur, seed in latest_female_furs.items(): 206 | fur = fur.replace(" ", "_") 207 | if fur not in existing_female_furs and seed != 0.0: 208 | existing_female_furs[fur] = int(seed) 209 | for fur, seed in latest_male_furs.items(): 210 | fur = fur.replace(" ", "_") 211 | if fur not in existing_male_furs and seed != 0.0: 212 | existing_male_furs[fur] = int(seed) 213 | 214 | new_existing = { 215 | "male": existing_male_furs, 216 | "female": existing_female_furs 217 | } 218 | return new_existing 219 | 220 | def combine_furs2(existing: dict, latest: dict, gender: str, last_seed: float, new_seed: float) -> None: 221 | existing_furs = existing[gender] if gender in existing else {} 222 | latest_furs = latest[gender] if gender in latest else {} 223 | print(existing_furs) 224 | print(latest_furs) 225 | 226 | for fur, seed in latest_furs.items(): 227 | seed = int(seed) 228 | if fur not in existing_furs: 229 | existing_furs[fur] = range(last_seed, new_seed) 230 | else: 231 | existing_fur = existing_furs[fur] 232 | if existing_fur.stop == last_seed: 233 | existing_fur = range(existing_fur.start, seed) 234 | else: 235 | existing_furs[f'{fur}2'] = range(last_seed, new_seed) 236 | 237 | new_existing = { 238 | "male": existing_furs if gender == "male" else existing["male"], 239 | "female": existing_furs if gender == "female" else existing["female"] 240 | } 241 | return new_existing 242 | 243 | def show_mouse() -> None: 244 | try: 245 | while True: 246 | x, y = pyautogui.position() 247 | positionStr = 'X: ' + str(x).rjust(4) + ' Y: ' + str(y).rjust(4) 248 | print(positionStr, end='') 249 | print('\b' * len(positionStr), end='', flush=True) 250 | except KeyboardInterrupt: 251 | print('\n') 252 | 253 | def click() -> None: 254 | pyautogui.mouseDown() 255 | time.sleep(0.2) 256 | pyautogui.mouseUp() 257 | 258 | def doubleClick() -> None: 259 | pyautogui.mouseDown() 260 | pyautogui.mouseUp() 261 | pyautogui.mouseDown() 262 | pyautogui.mouseUp() 263 | 264 | def click_reserve(reserve_name: str) -> None: 265 | pyautogui.moveTo(500, 174, duration=0.5) 266 | click() 267 | if reserve_name == "hirsch": 268 | pyautogui.moveTo(500, 195, duration=0.2) 269 | click() 270 | if reserve_name == "layton": 271 | pyautogui.moveTo(500, 215, duration=0.2) 272 | click() 273 | if reserve_name == "medved": 274 | pyautogui.moveTo(500, 235, duration=0.2) 275 | click() 276 | if reserve_name == "vurhonga": 277 | pyautogui.moveTo(500, 255, duration=0.2) 278 | click() 279 | if reserve_name == "parque": 280 | pyautogui.moveTo(500, 265, duration=0.2) 281 | click() 282 | if reserve_name == "yukon": 283 | pyautogui.moveTo(500, 285, duration=0.2) 284 | click() 285 | if reserve_name == "cuatro": 286 | pyautogui.moveTo(500, 295, duration=0.2) 287 | click() 288 | if reserve_name == "silver": 289 | pyautogui.moveTo(500, 310, duration=0.2) 290 | click() 291 | if reserve_name == "teawaroa": 292 | pyautogui.moveTo(512, 228, duration=0.2) 293 | pyautogui.dragTo(512, 282, duration=0.5) 294 | pyautogui.moveTo(403, 247) 295 | click() 296 | if reserve_name == "rancho": 297 | pyautogui.moveTo(512, 228, duration=0.2) 298 | pyautogui.dragTo(512, 282, duration=0.5) 299 | pyautogui.moveTo(403, 270) 300 | click() 301 | if reserve_name == "mississippi": 302 | pyautogui.moveTo(512, 228, duration=0.2) 303 | pyautogui.dragTo(512, 282, duration=0.5) 304 | pyautogui.moveTo(403, 286) 305 | click() 306 | if reserve_name == "revontuli": 307 | pyautogui.moveTo(512, 228, duration=0.2) 308 | pyautogui.dragTo(512, 282, duration=0.5) 309 | pyautogui.moveTo(403, 305) 310 | click() 311 | if reserve_name == "newengland": 312 | pyautogui.moveTo(512, 228, duration=0.2) 313 | pyautogui.dragTo(512, 282, duration=0.5) 314 | pyautogui.moveTo(403, 318) 315 | click() 316 | if reserve_name == "emerald": 317 | pyautogui.moveTo(512, 228, duration=0.2) 318 | pyautogui.dragTo(512, 292, duration=0.5) 319 | pyautogui.moveTo(403, 325) 320 | click() 321 | 322 | pyautogui.moveTo(500, 275, duration=0.2) 323 | doubleClick() 324 | pyautogui.moveTo(1469, 116, duration=0.5) 325 | click() 326 | 327 | def load_furs() -> dict: 328 | return json.load(FURS_PATH.open()) 329 | 330 | def save_furs(furs: dict) -> None: 331 | FURS_PATH.write_text(json.dumps(furs, indent=2)) 332 | 333 | def seed_animals(reserve_key: str) -> None: 334 | print() 335 | print(config.get_reserve_name(reserve_key)) 336 | print() 337 | reserve_species = config.get_reserve(reserve_key)["species"] 338 | all_furs = load_furs() 339 | for species in reserve_species: 340 | aps_species = map_aps(reserve_key, species) 341 | print(species.upper()) 342 | if species in all_furs: 343 | print(f"{aps_species} already processed") 344 | continue 345 | reserve = adf.load_reserve(reserve_key, False, False) 346 | species_details = populations._species(reserve_key, reserve.adf, species) 347 | groups = species_details.value["Groups"].value 348 | species_furs = {} 349 | for gender in [1, 2]: 350 | seed = 0 351 | while seed < 12000: 352 | initial_seed = seed 353 | seed = populations.diamond_test_seed(species, groups, reserve.decompressed.data, seed, gender) 354 | print(f"[{initial_seed}-{seed}]") 355 | reserve.decompressed.save(config.MOD_DIR_PATH, False) 356 | launch_aps() 357 | click_reserve(reserve_key) 358 | new_furs = process_aps(aps_species, Path(f"scans/scan.csv")) 359 | if not bool(new_furs): 360 | print(f"we didn't find any furs; probably have name wrong: {species.lower()}:{utils.unformat_key(aps_species).lower()}") 361 | seed = initial_seed 362 | continue 363 | species_furs = combine_furs(species_furs, new_furs) 364 | print(species_furs) 365 | 366 | if bool(species_furs): 367 | print(species_furs) 368 | all_furs[species] = species_furs 369 | save_furs(all_furs) 370 | 371 | """ 372 | { 373 | "fallow_deer": { 374 | "male": { 375 | "piebald": range(0, 20), 0-19 inclusive 376 | "brown": range(20, 40) 377 | } 378 | } 379 | } 380 | """ 381 | 382 | def seed_animals2(reserve_key: str, species: str) -> None: 383 | print() 384 | print(config.get_reserve_name(reserve_key)) 385 | print() 386 | aps_species = map_aps(reserve_key, species) 387 | print(species.upper()) 388 | reserve = adf.load_reserve(reserve_key, False, False) 389 | species_details = populations._species(reserve_key, reserve.adf, species) 390 | groups = species_details.value["Groups"].value 391 | species_furs = { "male": {}, "female": {} } 392 | for gender in [1, 2]: 393 | seed = 0 394 | while seed < 12000: 395 | initial_seed = seed 396 | seed = populations.diamond_test_seed(species, groups, reserve.decompressed.data, seed, gender) 397 | print(f"[{initial_seed}-{seed}]") 398 | reserve.decompressed.save(config.MOD_DIR_PATH, False) 399 | launch_aps() 400 | click_reserve(reserve_key) 401 | new_furs = process_aps(aps_species, Path(f"scans/scan.csv")) 402 | species_furs = combine_furs2(species_furs, new_furs, "male" if gender == 1 else "female", initial_seed, seed) 403 | print(species_furs) 404 | print() 405 | print(json.dumps(species_furs, indent=2)) 406 | 407 | def get_reserve_keys() -> list: 408 | return list(dict.keys(config.RESERVES)) 409 | 410 | def test_reserve(reserve_key) -> None: 411 | print(reserve_key) 412 | launch_aps() 413 | click_reserve(reserve_key) 414 | 415 | def seed_reserves() -> None: 416 | reserves_keys = get_reserve_keys() 417 | for reserve in reserves_keys: 418 | seed_animals(reserve) 419 | 420 | def verify_seeds(seeds: List[int]) -> None: 421 | reserve_name = "cuatro" 422 | species = "beceite_ibex" 423 | reserve = adf.load_reserve(reserve_name, False, False) 424 | species_details = populations._species(reserve_name, reserve.adf, species) 425 | groups = species_details.value["Groups"].value 426 | populations.diamond_test_seeds(species, groups, reserve.decompressed.data, seeds) 427 | reserve.decompressed.save(config.MOD_DIR_PATH, False) 428 | print("done") 429 | 430 | def calc_seed(furs: List[int]) -> None: 431 | total = sum(furs) 432 | per = [fur / total for fur in furs] 433 | for i in range(0, 100000): 434 | block = 100 + i 435 | fur_size = [round(block * fur) for fur in per] 436 | current = 0 437 | blocks = [] 438 | for i, size in enumerate(fur_size): 439 | if i == 0: 440 | blocks.append((current, current+size)) 441 | current += size + 1 442 | else: 443 | blocks.append((current, size + blocks[i-1][1])) 444 | current += size 445 | if blocks[1][0] == 2497 and blocks[1][1] == 2506: 446 | print(block) 447 | print(blocks) 448 | 449 | def convert_fur_float_to_int(furs: dict) -> dict: 450 | for fur in furs["male"]: 451 | furs["male"][fur] = int(furs["male"][fur]) 452 | for fur in furs["female"]: 453 | furs["female"][fur] = int(furs["female"][fur]) 454 | return furs 455 | 456 | def merge_furs_into_animals() -> None: 457 | animals = json.load(Path("apc/config/animal_details.json").open()) 458 | furs = load_furs() 459 | 460 | for animal_name, animal in animals.items(): 461 | animal_furs = convert_fur_float_to_int(furs[animal_name]) 462 | animal["diamonds"]["furs"] = animal_furs 463 | Path("apc/config/animal_details.json").write_text(json.dumps(animals, indent=2)) 464 | 465 | def fix_furs() -> None: 466 | animals = json.load(Path("apc/config/animal_details.json").open()) 467 | for _, animal in animals.items(): 468 | if isinstance(list(animal["diamonds"]["furs"]["male"].values())[0], float): 469 | animal["diamonds"]["furs"] = convert_fur_float_to_int(animal["diamonds"]["furs"]) 470 | Path("apc/config/animal_details.json").write_text(json.dumps(animals, indent=2)) 471 | 472 | if __name__ == "__main__": 473 | # analyze_reserve(config.get_save_path() / "animal_population_16") 474 | # fix_furs() 475 | # seed_animals("hirsch") 476 | # launch_aps() 477 | # click_reserve("emerald") 478 | compare_fur_cnt() -------------------------------------------------------------------------------- /apc/locale/apc.pot: -------------------------------------------------------------------------------- 1 | # Translations template for PROJECT. 2 | # Copyright (C) 2023 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2023. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PROJECT VERSION\n" 10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 11 | "POT-Creation-Date: 2023-06-27 20:12-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.12.1\n" 19 | 20 | #: apc/config.py:38 21 | msgid "Animal Population Changer" 22 | msgstr "" 23 | 24 | #: apc/config.py:40 25 | msgid "Species" 26 | msgstr "" 27 | 28 | #: apc/config.py:42 29 | msgid "Animals" 30 | msgstr "" 31 | 32 | #: apc/config.py:44 33 | msgid "Male" 34 | msgstr "" 35 | 36 | #: apc/config.py:46 37 | msgid "Males" 38 | msgstr "" 39 | 40 | #: apc/config.py:48 41 | msgid "Female" 42 | msgstr "" 43 | 44 | #: apc/config.py:50 45 | msgid "Females" 46 | msgstr "" 47 | 48 | #: apc/config.py:52 49 | msgid "High Weight" 50 | msgstr "" 51 | 52 | #: apc/config.py:54 53 | msgid "High Score" 54 | msgstr "" 55 | 56 | #: apc/config.py:56 57 | msgid "Level" 58 | msgstr "" 59 | 60 | #: apc/config.py:58 61 | msgid "Gender" 62 | msgstr "" 63 | 64 | #: apc/config.py:60 65 | msgid "Weight" 66 | msgstr "" 67 | 68 | #: apc/config.py:62 69 | msgid "Score" 70 | msgstr "" 71 | 72 | #: apc/config.py:64 73 | msgid "Visual Seed" 74 | msgstr "" 75 | 76 | #: apc/config.py:66 77 | msgid "Fur" 78 | msgstr "" 79 | 80 | #: apc/config.py:68 81 | msgid "Diamond" 82 | msgstr "" 83 | 84 | #: apc/config.py:70 apc/config.py:385 85 | msgid "Great One" 86 | msgstr "" 87 | 88 | #: apc/config.py:72 89 | msgid "Summary" 90 | msgstr "" 91 | 92 | #: apc/config.py:74 93 | msgid "Reserve" 94 | msgstr "" 95 | 96 | #: apc/config.py:76 97 | msgid "Reserves" 98 | msgstr "" 99 | 100 | #: apc/config.py:78 101 | msgid "Reserve Name (key)" 102 | msgstr "" 103 | 104 | #: apc/config.py:80 105 | msgid "Yes" 106 | msgstr "" 107 | 108 | #: apc/config.py:82 109 | msgid "Modded" 110 | msgstr "" 111 | 112 | #: apc/config.py:84 113 | msgid "Species (key)" 114 | msgstr "" 115 | 116 | #: apc/config.py:86 117 | msgid "viewing modded" 118 | msgstr "" 119 | 120 | #: apc/config.py:88 121 | msgid "Please copy and paste as a new bug on Nexusmods here" 122 | msgstr "" 123 | 124 | #: apc/config.py:90 125 | msgid "Error" 126 | msgstr "" 127 | 128 | #: apc/config.py:92 129 | msgid "Unexpected Error" 130 | msgstr "" 131 | 132 | #: apc/config.py:94 133 | msgid "Warning" 134 | msgstr "" 135 | 136 | #: apc/config.py:96 137 | msgid "Saved" 138 | msgstr "" 139 | 140 | #: apc/config.py:98 141 | msgid "does not exist" 142 | msgstr "" 143 | 144 | #: apc/config.py:100 145 | msgid "failed to backup game" 146 | msgstr "" 147 | 148 | #: apc/config.py:102 149 | msgid "failed to load backup file" 150 | msgstr "" 151 | 152 | #: apc/config.py:104 153 | msgid "failed to load mod" 154 | msgstr "" 155 | 156 | #: apc/config.py:106 157 | msgid "failed to unload mod" 158 | msgstr "" 159 | 160 | #: apc/config.py:108 161 | msgid "Mod has been loaded" 162 | msgstr "" 163 | 164 | #: apc/config.py:110 165 | msgid "Mod has been unloaded" 166 | msgstr "" 167 | 168 | #: apc/config.py:112 169 | msgid "Version" 170 | msgstr "" 171 | 172 | #: apc/config.py:114 173 | msgid "Hunting Reserve" 174 | msgstr "" 175 | 176 | #: apc/config.py:116 177 | msgid "update by percentage" 178 | msgstr "" 179 | 180 | #: apc/config.py:118 181 | msgid "More Males" 182 | msgstr "" 183 | 184 | #: apc/config.py:120 185 | msgid "More Females" 186 | msgstr "" 187 | 188 | #: apc/config.py:122 189 | msgid "More Great Ones" 190 | msgstr "" 191 | 192 | #: apc/config.py:124 193 | msgid "More Diamonds" 194 | msgstr "" 195 | 196 | #: apc/config.py:126 197 | msgid "include diamond rare furs" 198 | msgstr "" 199 | 200 | #: apc/config.py:128 201 | msgid "All Furs" 202 | msgstr "" 203 | 204 | #: apc/config.py:130 205 | msgid "Reset" 206 | msgstr "" 207 | 208 | #: apc/config.py:132 209 | msgid "Update Animals" 210 | msgstr "" 211 | 212 | #: apc/config.py:134 213 | msgid "Just the Furs" 214 | msgstr "" 215 | 216 | #: apc/config.py:136 217 | msgid "one of each fur" 218 | msgstr "" 219 | 220 | #: apc/config.py:138 221 | msgid "Others" 222 | msgstr "" 223 | 224 | #: apc/config.py:140 225 | msgid "Party" 226 | msgstr "" 227 | 228 | #: apc/config.py:142 229 | msgid "Great One Party" 230 | msgstr "" 231 | 232 | #: apc/config.py:144 233 | msgid "Diamond Party" 234 | msgstr "" 235 | 236 | #: apc/config.py:146 237 | msgid "We All Party" 238 | msgstr "" 239 | 240 | #: apc/config.py:148 241 | msgid "Fur Party" 242 | msgstr "" 243 | 244 | #: apc/config.py:150 245 | msgid "Explore" 246 | msgstr "" 247 | 248 | #: apc/config.py:152 249 | msgid "diamonds and Great Ones" 250 | msgstr "" 251 | 252 | #: apc/config.py:154 253 | msgid "look at modded animals" 254 | msgstr "" 255 | 256 | #: apc/config.py:156 257 | msgid "look at all reserves" 258 | msgstr "" 259 | 260 | #: apc/config.py:158 261 | msgid "only top 10 scores" 262 | msgstr "" 263 | 264 | #: apc/config.py:160 265 | msgid "Show Animals" 266 | msgstr "" 267 | 268 | #: apc/config.py:162 269 | msgid "Files" 270 | msgstr "" 271 | 272 | #: apc/config.py:164 273 | msgid "Configure Game Path" 274 | msgstr "" 275 | 276 | #: apc/config.py:166 277 | msgid "List Mods" 278 | msgstr "" 279 | 280 | #: apc/config.py:168 281 | msgid "Load Mod" 282 | msgstr "" 283 | 284 | #: apc/config.py:170 285 | msgid "Unload Mod" 286 | msgstr "" 287 | 288 | #: apc/config.py:172 289 | msgid "Select the folder where the game saves your files" 290 | msgstr "" 291 | 292 | #: apc/config.py:174 293 | msgid "Saves Path" 294 | msgstr "" 295 | 296 | #: apc/config.py:176 297 | msgid "Game path saved" 298 | msgstr "" 299 | 300 | #: apc/config.py:178 301 | msgid "Are you sure you want to overwrite your game file with the modded one?" 302 | msgstr "" 303 | 304 | #: apc/config.py:180 305 | msgid "Don't worry, a backup copy will be made." 306 | msgstr "" 307 | 308 | #: apc/config.py:182 309 | msgid "Confirmation" 310 | msgstr "" 311 | 312 | #: apc/config.py:184 313 | msgid "Mod" 314 | msgstr "" 315 | 316 | #: apc/config.py:186 317 | msgid "view modded version" 318 | msgstr "" 319 | 320 | #: apc/config.py:188 321 | msgid "Loaded" 322 | msgstr "" 323 | 324 | #: apc/config.py:190 325 | msgid "Modded File" 326 | msgstr "" 327 | 328 | #: apc/config.py:192 329 | msgid "Back to Reserve" 330 | msgstr "" 331 | 332 | #: apc/config.py:194 333 | msgid "update translations" 334 | msgstr "" 335 | 336 | #: apc/config.py:196 337 | msgid "switch language" 338 | msgstr "" 339 | 340 | #: apc/config.py:198 341 | msgid "Please restart to see changes" 342 | msgstr "" 343 | 344 | #: apc/config.py:200 345 | msgid "default" 346 | msgstr "" 347 | 348 | #: apc/config.py:202 349 | msgid "using" 350 | msgstr "" 351 | 352 | #: apc/config.py:204 353 | msgid "OK" 354 | msgstr "" 355 | 356 | #: apc/config.py:206 357 | msgid "Cancel" 358 | msgstr "" 359 | 360 | #: apc/config.py:208 361 | msgid "Modify Animals" 362 | msgstr "" 363 | 364 | #: apc/config.py:210 365 | msgid "Furs" 366 | msgstr "" 367 | 368 | #: apc/config.py:212 369 | msgid "Modify Animal Furs" 370 | msgstr "" 371 | 372 | #: apc/config.py:214 373 | msgid "Male Furs" 374 | msgstr "" 375 | 376 | #: apc/config.py:216 377 | msgid "Female Furs" 378 | msgstr "" 379 | 380 | #: apc/config.py:218 381 | msgid "Change All Species" 382 | msgstr "" 383 | 384 | #: apc/config.py:220 385 | msgid "Explore Animals" 386 | msgstr "" 387 | 388 | #: apc/config.py:222 389 | msgid "Manage Modded Reserves" 390 | msgstr "" 391 | 392 | #: apc/config.py:224 393 | msgid "viewing loaded mod" 394 | msgstr "" 395 | 396 | #: apc/config.py:226 397 | msgid "use all furs" 398 | msgstr "" 399 | 400 | #: apc/config.py:228 401 | msgid "No" 402 | msgstr "" 403 | 404 | #: apc/config.py:230 405 | msgid "Animal Details" 406 | msgstr "" 407 | 408 | #: apc/config.py:232 409 | msgid "default is random fur" 410 | msgstr "" 411 | 412 | #: apc/config.py:234 413 | msgid "Update Animal" 414 | msgstr "" 415 | 416 | #: apc/config.py:236 417 | msgid "Top 10" 418 | msgstr "" 419 | 420 | #: apc/config.py:238 421 | msgid "Loaded Mod" 422 | msgstr "" 423 | 424 | #: apc/config.py:240 425 | msgid "Export Mod" 426 | msgstr "" 427 | 428 | #: apc/config.py:242 429 | msgid "Import Mod" 430 | msgstr "" 431 | 432 | #: apc/config.py:244 433 | msgid "Export" 434 | msgstr "" 435 | 436 | #: apc/config.py:246 437 | msgid "Import" 438 | msgstr "" 439 | 440 | #: apc/config.py:248 441 | msgid "Select the location and filename where you want to export" 442 | msgstr "" 443 | 444 | #: apc/config.py:250 445 | msgid "Select the reserve mod to import" 446 | msgstr "" 447 | 448 | #: apc/config.py:252 449 | msgid "Export As" 450 | msgstr "" 451 | 452 | #: apc/config.py:254 453 | msgid "Select File" 454 | msgstr "" 455 | 456 | #: apc/config.py:256 457 | msgid "Mod Exported" 458 | msgstr "" 459 | 460 | #: apc/config.py:258 461 | msgid "Mod Imported" 462 | msgstr "" 463 | 464 | #: apc/config.py:367 465 | msgid "Trivial" 466 | msgstr "" 467 | 468 | #: apc/config.py:369 469 | msgid "Minor" 470 | msgstr "" 471 | 472 | #: apc/config.py:371 473 | msgid "Very Easy" 474 | msgstr "" 475 | 476 | #: apc/config.py:373 477 | msgid "Easy" 478 | msgstr "" 479 | 480 | #: apc/config.py:375 481 | msgid "Medium" 482 | msgstr "" 483 | 484 | #: apc/config.py:377 485 | msgid "Hard" 486 | msgstr "" 487 | 488 | #: apc/config.py:379 489 | msgid "Very Hard" 490 | msgstr "" 491 | 492 | #: apc/config.py:381 493 | msgid "Mythical" 494 | msgstr "" 495 | 496 | #: apc/config.py:383 497 | msgid "Legendary" 498 | msgstr "" 499 | 500 | #: apc/config.py:412 501 | msgid "fur_name" 502 | msgstr "" 503 | 504 | #: apc/config.py:452 505 | msgid "reserve_name" 506 | msgstr "" 507 | 508 | #: apc/config.py:524 509 | msgid "name" 510 | msgstr "" 511 | 512 | #: apc/config/animal_names.json:4 513 | msgid "American Alligator" 514 | msgstr "" 515 | 516 | #: apc/config/animal_names.json:7 517 | msgid "Antelope Jackrabbit" 518 | msgstr "" 519 | 520 | #: apc/config/animal_names.json:10 521 | msgid "Axis Deer" 522 | msgstr "" 523 | 524 | #: apc/config/animal_names.json:13 525 | msgid "Beceite Ibex" 526 | msgstr "" 527 | 528 | #: apc/config/animal_names.json:16 529 | msgid "Bighorn Sheep" 530 | msgstr "" 531 | 532 | #: apc/config/animal_names.json:19 533 | msgid "Black Bear" 534 | msgstr "" 535 | 536 | #: apc/config/animal_names.json:22 537 | msgid "Black Grouse" 538 | msgstr "" 539 | 540 | #: apc/config/animal_names.json:25 541 | msgid "Blackbuck" 542 | msgstr "" 543 | 544 | #: apc/config/animal_names.json:28 545 | msgid "Blacktail Deer" 546 | msgstr "" 547 | 548 | #: apc/config/animal_names.json:31 549 | msgid "Blue Wildebeest" 550 | msgstr "" 551 | 552 | #: apc/config/animal_names.json:34 553 | msgid "Bobcat" 554 | msgstr "" 555 | 556 | #: apc/config/animal_names.json:37 557 | msgid "Canada Goose" 558 | msgstr "" 559 | 560 | #: apc/config/animal_names.json:40 561 | msgid "Cape Buffalo" 562 | msgstr "" 563 | 564 | #: apc/config/animal_names.json:43 565 | msgid "Caribou" 566 | msgstr "" 567 | 568 | #: apc/config/animal_names.json:46 569 | msgid "Chamois" 570 | msgstr "" 571 | 572 | #: apc/config/animal_names.json:49 573 | msgid "Cinnamon Teal" 574 | msgstr "" 575 | 576 | #: apc/config/animal_names.json:52 577 | msgid "Collared Peccary" 578 | msgstr "" 579 | 580 | #: apc/config/animal_names.json:55 581 | msgid "Coyote" 582 | msgstr "" 583 | 584 | #: apc/config/animal_names.json:58 585 | msgid "Eastern Cottontail Rabbit" 586 | msgstr "" 587 | 588 | #: apc/config/animal_names.json:61 589 | msgid "Eastern Wild Turkey" 590 | msgstr "" 591 | 592 | #: apc/config/animal_names.json:64 593 | msgid "Eu Bison" 594 | msgstr "" 595 | 596 | #: apc/config/animal_names.json:67 597 | msgid "Eu Hare" 598 | msgstr "" 599 | 600 | #: apc/config/animal_names.json:70 601 | msgid "Eu Rabbit" 602 | msgstr "" 603 | 604 | #: apc/config/animal_names.json:73 605 | msgid "Eurasian Brown Bear" 606 | msgstr "" 607 | 608 | #: apc/config/animal_names.json:76 609 | msgid "Eurasian Lynx" 610 | msgstr "" 611 | 612 | #: apc/config/animal_names.json:79 613 | msgid "Eurasian Teal" 614 | msgstr "" 615 | 616 | #: apc/config/animal_names.json:82 617 | msgid "Eurasian Wigeon" 618 | msgstr "" 619 | 620 | #: apc/config/animal_names.json:85 621 | msgid "Fallow Deer" 622 | msgstr "" 623 | 624 | #: apc/config/animal_names.json:88 625 | msgid "Feral Goat" 626 | msgstr "" 627 | 628 | #: apc/config/animal_names.json:91 629 | msgid "Feral Pig" 630 | msgstr "" 631 | 632 | #: apc/config/animal_names.json:94 633 | msgid "Gemsbok" 634 | msgstr "" 635 | 636 | #: apc/config/animal_names.json:97 637 | msgid "Goldeneye" 638 | msgstr "" 639 | 640 | #: apc/config/animal_names.json:100 641 | msgid "Gray Fox" 642 | msgstr "" 643 | 644 | #: apc/config/animal_names.json:103 645 | msgid "Gray Wolf" 646 | msgstr "" 647 | 648 | #: apc/config/animal_names.json:106 649 | msgid "Gredos Ibex" 650 | msgstr "" 651 | 652 | #: apc/config/animal_names.json:109 653 | msgid "Green Wing Teal" 654 | msgstr "" 655 | 656 | #: apc/config/animal_names.json:112 657 | msgid "Greylag Goose" 658 | msgstr "" 659 | 660 | #: apc/config/animal_names.json:115 661 | msgid "Grizzly Bear" 662 | msgstr "" 663 | 664 | #: apc/config/animal_names.json:118 665 | msgid "Harlequin Duck" 666 | msgstr "" 667 | 668 | #: apc/config/animal_names.json:121 669 | msgid "Hazel Grouse" 670 | msgstr "" 671 | 672 | #: apc/config/animal_names.json:124 673 | msgid "Iberian Mouflon" 674 | msgstr "" 675 | 676 | #: apc/config/animal_names.json:127 677 | msgid "Iberian Wolf" 678 | msgstr "" 679 | 680 | #: apc/config/animal_names.json:130 681 | msgid "Jackrabbit" 682 | msgstr "" 683 | 684 | #: apc/config/animal_names.json:133 685 | msgid "Lesser Kudu" 686 | msgstr "" 687 | 688 | #: apc/config/animal_names.json:136 689 | msgid "Lion" 690 | msgstr "" 691 | 692 | #: apc/config/animal_names.json:139 693 | msgid "Mallard" 694 | msgstr "" 695 | 696 | #: apc/config/animal_names.json:142 697 | msgid "Mexican Bobcat" 698 | msgstr "" 699 | 700 | #: apc/config/animal_names.json:145 701 | msgid "Moose" 702 | msgstr "" 703 | 704 | #: apc/config/animal_names.json:148 705 | msgid "Mountain Goat" 706 | msgstr "" 707 | 708 | #: apc/config/animal_names.json:151 709 | msgid "Mountain Hare" 710 | msgstr "" 711 | 712 | #: apc/config/animal_names.json:154 713 | msgid "Mountain Lion" 714 | msgstr "" 715 | 716 | #: apc/config/animal_names.json:157 717 | msgid "Mule Deer" 718 | msgstr "" 719 | 720 | #: apc/config/animal_names.json:160 721 | msgid "Northern Bobwhite Quail" 722 | msgstr "" 723 | 724 | #: apc/config/animal_names.json:163 725 | msgid "Pheasant" 726 | msgstr "" 727 | 728 | #: apc/config/animal_names.json:166 729 | msgid "Plains Bison" 730 | msgstr "" 731 | 732 | #: apc/config/animal_names.json:169 733 | msgid "Prong Horn" 734 | msgstr "" 735 | 736 | #: apc/config/animal_names.json:172 737 | msgid "Puma" 738 | msgstr "" 739 | 740 | #: apc/config/animal_names.json:175 741 | msgid "Raccoon" 742 | msgstr "" 743 | 744 | #: apc/config/animal_names.json:178 745 | msgid "Raccoon Dog" 746 | msgstr "" 747 | 748 | #: apc/config/animal_names.json:181 749 | msgid "Red Deer" 750 | msgstr "" 751 | 752 | #: apc/config/animal_names.json:184 753 | msgid "Red Fox" 754 | msgstr "" 755 | 756 | #: apc/config/animal_names.json:187 757 | msgid "Reindeer" 758 | msgstr "" 759 | 760 | #: apc/config/animal_names.json:190 761 | msgid "Rio Grande Turkey" 762 | msgstr "" 763 | 764 | #: apc/config/animal_names.json:193 765 | msgid "Rock Ptarmigan" 766 | msgstr "" 767 | 768 | #: apc/config/animal_names.json:196 769 | msgid "Rockymountain Elk" 770 | msgstr "" 771 | 772 | #: apc/config/animal_names.json:199 773 | msgid "Roe Deer" 774 | msgstr "" 775 | 776 | #: apc/config/animal_names.json:202 777 | msgid "Ronda Ibex" 778 | msgstr "" 779 | 780 | #: apc/config/animal_names.json:205 781 | msgid "Roosevelt Elk" 782 | msgstr "" 783 | 784 | #: apc/config/animal_names.json:208 785 | msgid "Scrub Hare" 786 | msgstr "" 787 | 788 | #: apc/config/animal_names.json:211 789 | msgid "Siberian Musk Deer" 790 | msgstr "" 791 | 792 | #: apc/config/animal_names.json:214 793 | msgid "Sidestriped Jackal" 794 | msgstr "" 795 | 796 | #: apc/config/animal_names.json:217 797 | msgid "Sika Deer" 798 | msgstr "" 799 | 800 | #: apc/config/animal_names.json:220 801 | msgid "Southeastern Ibex" 802 | msgstr "" 803 | 804 | #: apc/config/animal_names.json:223 805 | msgid "Springbok" 806 | msgstr "" 807 | 808 | #: apc/config/animal_names.json:226 809 | msgid "Tufted Duck" 810 | msgstr "" 811 | 812 | #: apc/config/animal_names.json:229 813 | msgid "Tundra Bean Goose" 814 | msgstr "" 815 | 816 | #: apc/config/animal_names.json:232 817 | msgid "Warthog" 818 | msgstr "" 819 | 820 | #: apc/config/animal_names.json:235 821 | msgid "Water Buffalo" 822 | msgstr "" 823 | 824 | #: apc/config/animal_names.json:238 825 | msgid "Western Capercaillie" 826 | msgstr "" 827 | 828 | #: apc/config/animal_names.json:241 829 | msgid "Whitetail Deer" 830 | msgstr "" 831 | 832 | #: apc/config/animal_names.json:244 833 | msgid "Wild Boar" 834 | msgstr "" 835 | 836 | #: apc/config/animal_names.json:247 837 | msgid "Wild Hog" 838 | msgstr "" 839 | 840 | #: apc/config/animal_names.json:250 841 | msgid "Wild Turkey" 842 | msgstr "" 843 | 844 | #: apc/config/animal_names.json:253 845 | msgid "Willow Ptarmigan" 846 | msgstr "" 847 | 848 | #: apc/config/animal_names.json:256 849 | msgid "Hog Deer" 850 | msgstr "" 851 | 852 | #: apc/config/animal_names.json:259 853 | msgid "Magpie Goose" 854 | msgstr "" 855 | 856 | #: apc/config/animal_names.json:262 857 | msgid "Eastern Kangaroo" 858 | msgstr "" 859 | 860 | #: apc/config/animal_names.json:265 861 | msgid "Sambar Deer" 862 | msgstr "" 863 | 864 | #: apc/config/animal_names.json:268 865 | msgid "Banteng" 866 | msgstr "" 867 | 868 | #: apc/config/animal_names.json:271 869 | msgid "Saltwater Crocodile" 870 | msgstr "" 871 | 872 | #: apc/config/animal_names.json:274 873 | msgid "Stubble Quail" 874 | msgstr "" 875 | 876 | #: apc/config/animal_names.json:277 877 | msgid "Javan Rusa" 878 | msgstr "" 879 | 880 | #: apc/config/fur_names.json:4 881 | msgid "Piebald" 882 | msgstr "" 883 | 884 | #: apc/config/fur_names.json:7 885 | msgid "Olive" 886 | msgstr "" 887 | 888 | #: apc/config/fur_names.json:10 889 | msgid "Dark Brown" 890 | msgstr "" 891 | 892 | #: apc/config/fur_names.json:13 893 | msgid "Albino" 894 | msgstr "" 895 | 896 | #: apc/config/fur_names.json:16 897 | msgid "Melanistic" 898 | msgstr "" 899 | 900 | #: apc/config/fur_names.json:19 901 | msgid "Gray" 902 | msgstr "" 903 | 904 | #: apc/config/fur_names.json:22 905 | msgid "Brown" 906 | msgstr "" 907 | 908 | #: apc/config/fur_names.json:25 909 | msgid "Mottled" 910 | msgstr "" 911 | 912 | #: apc/config/fur_names.json:28 913 | msgid "Spotted" 914 | msgstr "" 915 | 916 | #: apc/config/fur_names.json:31 917 | msgid "Dark" 918 | msgstr "" 919 | 920 | #: apc/config/fur_names.json:34 921 | msgid "Orange" 922 | msgstr "" 923 | 924 | #: apc/config/fur_names.json:37 925 | msgid "Brown Hybrid" 926 | msgstr "" 927 | 928 | #: apc/config/fur_names.json:40 929 | msgid "Gray Brown" 930 | msgstr "" 931 | 932 | #: apc/config/fur_names.json:43 933 | msgid "Light Brown" 934 | msgstr "" 935 | 936 | #: apc/config/fur_names.json:46 937 | msgid "Buff" 938 | msgstr "" 939 | 940 | #: apc/config/fur_names.json:49 941 | msgid "Black" 942 | msgstr "" 943 | 944 | #: apc/config/fur_names.json:52 945 | msgid "Bronze" 946 | msgstr "" 947 | 948 | #: apc/config/fur_names.json:55 949 | msgid "Dusky" 950 | msgstr "" 951 | 952 | #: apc/config/fur_names.json:58 953 | msgid "Cinnamon" 954 | msgstr "" 955 | 956 | #: apc/config/fur_names.json:61 957 | msgid "Blonde" 958 | msgstr "" 959 | 960 | #: apc/config/fur_names.json:64 961 | msgid "Fabled Glacier" 962 | msgstr "" 963 | 964 | #: apc/config/fur_names.json:67 965 | msgid "Fabled Chestnut" 966 | msgstr "" 967 | 968 | #: apc/config/fur_names.json:70 969 | msgid "Fabled Cream" 970 | msgstr "" 971 | 972 | #: apc/config/fur_names.json:73 973 | msgid "Fabled Spotted" 974 | msgstr "" 975 | 976 | #: apc/config/fur_names.json:76 977 | msgid "Fabled Spirit" 978 | msgstr "" 979 | 980 | #: apc/config/fur_names.json:79 981 | msgid "Leucistic" 982 | msgstr "" 983 | 984 | #: apc/config/fur_names.json:82 985 | msgid "Gold" 986 | msgstr "" 987 | 988 | #: apc/config/fur_names.json:85 989 | msgid "Dark Grey" 990 | msgstr "" 991 | 992 | #: apc/config/fur_names.json:88 993 | msgid "Tan" 994 | msgstr "" 995 | 996 | #: apc/config/fur_names.json:91 997 | msgid "Crowned" 998 | msgstr "" 999 | 1000 | #: apc/config/fur_names.json:94 1001 | msgid "Blue" 1002 | msgstr "" 1003 | 1004 | #: apc/config/fur_names.json:97 1005 | msgid "Red" 1006 | msgstr "" 1007 | 1008 | #: apc/config/fur_names.json:100 1009 | msgid "Lg leucist." 1010 | msgstr "" 1011 | 1012 | #: apc/config/fur_names.json:103 1013 | msgid "Bald leuc." 1014 | msgstr "" 1015 | 1016 | #: apc/config/fur_names.json:106 1017 | msgid "Honeytones" 1018 | msgstr "" 1019 | 1020 | #: apc/config/fur_names.json:109 1021 | msgid "Beige" 1022 | msgstr "" 1023 | 1024 | #: apc/config/fur_names.json:112 1025 | msgid "Ochre" 1026 | msgstr "" 1027 | 1028 | #: apc/config/fur_names.json:115 1029 | msgid "Light Gray" 1030 | msgstr "" 1031 | 1032 | #: apc/config/fur_names.json:118 1033 | msgid "Light Bronze" 1034 | msgstr "" 1035 | 1036 | #: apc/config/fur_names.json:121 1037 | msgid "Spirit" 1038 | msgstr "" 1039 | 1040 | #: apc/config/fur_names.json:124 1041 | msgid "Red Brown" 1042 | msgstr "" 1043 | 1044 | #: apc/config/fur_names.json:127 1045 | msgid "Dark Green" 1046 | msgstr "" 1047 | 1048 | #: apc/config/fur_names.json:130 1049 | msgid "Light Green" 1050 | msgstr "" 1051 | 1052 | #: apc/config/fur_names.json:133 1053 | msgid "Hybrid Blue" 1054 | msgstr "" 1055 | 1056 | #: apc/config/fur_names.json:136 1057 | msgid "Hybrid Green" 1058 | msgstr "" 1059 | 1060 | #: apc/config/fur_names.json:139 1061 | msgid "Eclipse" 1062 | msgstr "" 1063 | 1064 | #: apc/config/fur_names.json:142 1065 | msgid "Hybrid" 1066 | msgstr "" 1067 | 1068 | #: apc/config/fur_names.json:145 1069 | msgid "Spotted Dark" 1070 | msgstr "" 1071 | 1072 | #: apc/config/fur_names.json:148 1073 | msgid "Spotted Red" 1074 | msgstr "" 1075 | 1076 | #: apc/config/fur_names.json:151 1077 | msgid "White Brown" 1078 | msgstr "" 1079 | 1080 | #: apc/config/fur_names.json:154 1081 | msgid "Black Brown" 1082 | msgstr "" 1083 | 1084 | #: apc/config/fur_names.json:157 1085 | msgid "Black White" 1086 | msgstr "" 1087 | 1088 | #: apc/config/fur_names.json:160 1089 | msgid "White" 1090 | msgstr "" 1091 | 1092 | #: apc/config/fur_names.json:163 1093 | msgid "Mixed" 1094 | msgstr "" 1095 | 1096 | #: apc/config/fur_names.json:166 1097 | msgid "Black Spots" 1098 | msgstr "" 1099 | 1100 | #: apc/config/fur_names.json:169 1101 | msgid "Blackgold" 1102 | msgstr "" 1103 | 1104 | #: apc/config/fur_names.json:172 1105 | msgid "Pink" 1106 | msgstr "" 1107 | 1108 | #: apc/config/fur_names.json:175 1109 | msgid "Two Tones" 1110 | msgstr "" 1111 | 1112 | #: apc/config/fur_names.json:178 1113 | msgid "Eggwhite" 1114 | msgstr "" 1115 | 1116 | #: apc/config/fur_names.json:181 1117 | msgid "Pale" 1118 | msgstr "" 1119 | 1120 | #: apc/config/fur_names.json:184 1121 | msgid "Pristine" 1122 | msgstr "" 1123 | 1124 | #: apc/config/fur_names.json:187 1125 | msgid "Winter" 1126 | msgstr "" 1127 | 1128 | #: apc/config/fur_names.json:190 1129 | msgid "Fabled Two Tone" 1130 | msgstr "" 1131 | 1132 | #: apc/config/fur_names.json:193 1133 | msgid "Fabled Birch" 1134 | msgstr "" 1135 | 1136 | #: apc/config/fur_names.json:196 1137 | msgid "Fabled Oak" 1138 | msgstr "" 1139 | 1140 | #: apc/config/fur_names.json:199 1141 | msgid "Fabled Speckled" 1142 | msgstr "" 1143 | 1144 | #: apc/config/fur_names.json:202 1145 | msgid "Fabled Spruce" 1146 | msgstr "" 1147 | 1148 | #: apc/config/fur_names.json:205 1149 | msgid "Fabled Ashen" 1150 | msgstr "" 1151 | 1152 | #: apc/config/fur_names.json:208 1153 | msgid "Molting" 1154 | msgstr "" 1155 | 1156 | #: apc/config/fur_names.json:211 1157 | msgid "Dilute" 1158 | msgstr "" 1159 | 1160 | #: apc/config/fur_names.json:214 1161 | msgid "Dark Red" 1162 | msgstr "" 1163 | 1164 | #: apc/config/fur_names.json:217 1165 | msgid "Piebald Grey" 1166 | msgstr "" 1167 | 1168 | #: apc/config/fur_names.json:220 1169 | msgid "Piebald Blnd" 1170 | msgstr "" 1171 | 1172 | #: apc/config/fur_names.json:223 1173 | msgid "Lightbuff" 1174 | msgstr "" 1175 | 1176 | #: apc/config/fur_names.json:226 1177 | msgid "Bicolor" 1178 | msgstr "" 1179 | 1180 | #: apc/config/fur_names.json:229 1181 | msgid "Chestnut" 1182 | msgstr "" 1183 | 1184 | #: apc/config/fur_names.json:232 1185 | msgid "Cream" 1186 | msgstr "" 1187 | 1188 | #: apc/config/fur_names.json:235 1189 | msgid "Bright" 1190 | msgstr "" 1191 | 1192 | #: apc/config/fur_names.json:238 1193 | msgid "Fabled Hooded" 1194 | msgstr "" 1195 | 1196 | #: apc/config/fur_names.json:241 1197 | msgid "Fabled Mocha" 1198 | msgstr "" 1199 | 1200 | #: apc/config/fur_names.json:244 1201 | msgid "Two Tone" 1202 | msgstr "" 1203 | 1204 | #: apc/config/fur_names.json:247 1205 | msgid "Grey Brown" 1206 | msgstr "" 1207 | 1208 | #: apc/config/fur_names.json:250 1209 | msgid "Mocha" 1210 | msgstr "" 1211 | 1212 | #: apc/config/fur_names.json:253 1213 | msgid "Dusky Gradient" 1214 | msgstr "" 1215 | 1216 | #: apc/config/fur_names.json:256 1217 | msgid "Yellow" 1218 | msgstr "" 1219 | 1220 | #: apc/config/fur_names.json:259 1221 | msgid "Maroon" 1222 | msgstr "" 1223 | 1224 | #: apc/config/fur_names.json:262 1225 | msgid "Dark Spotted" 1226 | msgstr "" 1227 | 1228 | #: apc/config/fur_names.json:265 1229 | msgid "Chocolate" 1230 | msgstr "" 1231 | 1232 | #: apc/config/fur_names.json:268 1233 | msgid "Fabled Silver" 1234 | msgstr "" 1235 | 1236 | #: apc/config/fur_names.json:271 1237 | msgid "Fabled Golden" 1238 | msgstr "" 1239 | 1240 | #: apc/config/fur_names.json:274 1241 | msgid "Fabled Painted" 1242 | msgstr "" 1243 | 1244 | #: apc/config/reserve_names.json:4 1245 | msgid "Hirschfelden" 1246 | msgstr "" 1247 | 1248 | #: apc/config/reserve_names.json:7 1249 | msgid "Layton Lake" 1250 | msgstr "" 1251 | 1252 | #: apc/config/reserve_names.json:10 1253 | msgid "Medved-Taiga National Park" 1254 | msgstr "" 1255 | 1256 | #: apc/config/reserve_names.json:13 1257 | msgid "Vurhonga Savanna" 1258 | msgstr "" 1259 | 1260 | #: apc/config/reserve_names.json:16 1261 | msgid "Parque Fernando" 1262 | msgstr "" 1263 | 1264 | #: apc/config/reserve_names.json:19 1265 | msgid "Yukon Valley" 1266 | msgstr "" 1267 | 1268 | #: apc/config/reserve_names.json:22 1269 | msgid "Cuatro Colinas Game Reserve" 1270 | msgstr "" 1271 | 1272 | #: apc/config/reserve_names.json:25 1273 | msgid "Silver Ridge Peaks" 1274 | msgstr "" 1275 | 1276 | #: apc/config/reserve_names.json:28 1277 | msgid "Te Awaroa National Park" 1278 | msgstr "" 1279 | 1280 | #: apc/config/reserve_names.json:31 1281 | msgid "Rancho del Arroyo" 1282 | msgstr "" 1283 | 1284 | #: apc/config/reserve_names.json:34 1285 | msgid "Mississippi Acres Preserve" 1286 | msgstr "" 1287 | 1288 | #: apc/config/reserve_names.json:37 1289 | msgid "Revontuli Coast" 1290 | msgstr "" 1291 | 1292 | #: apc/config/reserve_names.json:40 1293 | msgid "New England Mountains" 1294 | msgstr "" 1295 | 1296 | #: apc/config/reserve_names.json:43 1297 | msgid "Emerald Coast" 1298 | msgstr "" 1299 | 1300 | -------------------------------------------------------------------------------- /apc/locale/de_DE/LC_MESSAGES/apc.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpypasta/apc/86a5dc2eb473feb58a7a1372f488930400e9e0cb/apc/locale/de_DE/LC_MESSAGES/apc.mo -------------------------------------------------------------------------------- /apc/locale/en_US/LC_MESSAGES/apc.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpypasta/apc/86a5dc2eb473feb58a7a1372f488930400e9e0cb/apc/locale/en_US/LC_MESSAGES/apc.mo -------------------------------------------------------------------------------- /apc/locale/en_US/LC_MESSAGES/apc.po: -------------------------------------------------------------------------------- 1 | # English (United States) translations for PROJECT. 2 | # Copyright (C) 2023 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2023-06-27 20:12-0500\n" 11 | "PO-Revision-Date: 2023-04-02 23:55-0500\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language: en_US\n" 14 | "Language-Team: en_US \n" 15 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.12.1\n" 20 | 21 | #: apc/config.py:38 22 | msgid "Animal Population Changer" 23 | msgstr "" 24 | 25 | #: apc/config.py:40 26 | msgid "Species" 27 | msgstr "" 28 | 29 | #: apc/config.py:42 30 | msgid "Animals" 31 | msgstr "" 32 | 33 | #: apc/config.py:44 34 | msgid "Male" 35 | msgstr "" 36 | 37 | #: apc/config.py:46 38 | msgid "Males" 39 | msgstr "" 40 | 41 | #: apc/config.py:48 42 | msgid "Female" 43 | msgstr "" 44 | 45 | #: apc/config.py:50 46 | msgid "Females" 47 | msgstr "" 48 | 49 | #: apc/config.py:52 50 | msgid "High Weight" 51 | msgstr "" 52 | 53 | #: apc/config.py:54 54 | msgid "High Score" 55 | msgstr "" 56 | 57 | #: apc/config.py:56 58 | msgid "Level" 59 | msgstr "" 60 | 61 | #: apc/config.py:58 62 | msgid "Gender" 63 | msgstr "" 64 | 65 | #: apc/config.py:60 66 | msgid "Weight" 67 | msgstr "" 68 | 69 | #: apc/config.py:62 70 | msgid "Score" 71 | msgstr "" 72 | 73 | #: apc/config.py:64 74 | msgid "Visual Seed" 75 | msgstr "" 76 | 77 | #: apc/config.py:66 78 | msgid "Fur" 79 | msgstr "" 80 | 81 | #: apc/config.py:68 82 | msgid "Diamond" 83 | msgstr "" 84 | 85 | #: apc/config.py:70 apc/config.py:385 86 | msgid "Great One" 87 | msgstr "" 88 | 89 | #: apc/config.py:72 90 | msgid "Summary" 91 | msgstr "" 92 | 93 | #: apc/config.py:74 94 | msgid "Reserve" 95 | msgstr "" 96 | 97 | #: apc/config.py:76 98 | msgid "Reserves" 99 | msgstr "" 100 | 101 | #: apc/config.py:78 102 | msgid "Reserve Name (key)" 103 | msgstr "" 104 | 105 | #: apc/config.py:80 106 | msgid "Yes" 107 | msgstr "" 108 | 109 | #: apc/config.py:82 110 | msgid "Modded" 111 | msgstr "" 112 | 113 | #: apc/config.py:84 114 | msgid "Species (key)" 115 | msgstr "" 116 | 117 | #: apc/config.py:86 118 | msgid "viewing modded" 119 | msgstr "" 120 | 121 | #: apc/config.py:88 122 | msgid "Please copy and paste as a new bug on Nexusmods here" 123 | msgstr "" 124 | 125 | #: apc/config.py:90 126 | msgid "Error" 127 | msgstr "" 128 | 129 | #: apc/config.py:92 130 | msgid "Unexpected Error" 131 | msgstr "" 132 | 133 | #: apc/config.py:94 134 | msgid "Warning" 135 | msgstr "" 136 | 137 | #: apc/config.py:96 138 | msgid "Saved" 139 | msgstr "" 140 | 141 | #: apc/config.py:98 142 | msgid "does not exist" 143 | msgstr "" 144 | 145 | #: apc/config.py:100 146 | msgid "failed to backup game" 147 | msgstr "" 148 | 149 | #: apc/config.py:102 150 | msgid "failed to load backup file" 151 | msgstr "" 152 | 153 | #: apc/config.py:104 154 | msgid "failed to load mod" 155 | msgstr "" 156 | 157 | #: apc/config.py:106 158 | msgid "failed to unload mod" 159 | msgstr "" 160 | 161 | #: apc/config.py:108 162 | msgid "Mod has been loaded" 163 | msgstr "" 164 | 165 | #: apc/config.py:110 166 | msgid "Mod has been unloaded" 167 | msgstr "" 168 | 169 | #: apc/config.py:112 170 | msgid "Version" 171 | msgstr "" 172 | 173 | #: apc/config.py:114 174 | msgid "Hunting Reserve" 175 | msgstr "" 176 | 177 | #: apc/config.py:116 178 | msgid "update by percentage" 179 | msgstr "" 180 | 181 | #: apc/config.py:118 182 | msgid "More Males" 183 | msgstr "" 184 | 185 | #: apc/config.py:120 186 | msgid "More Females" 187 | msgstr "" 188 | 189 | #: apc/config.py:122 190 | msgid "More Great Ones" 191 | msgstr "" 192 | 193 | #: apc/config.py:124 194 | msgid "More Diamonds" 195 | msgstr "" 196 | 197 | #: apc/config.py:126 198 | msgid "include diamond rare furs" 199 | msgstr "" 200 | 201 | #: apc/config.py:128 202 | msgid "All Furs" 203 | msgstr "" 204 | 205 | #: apc/config.py:130 206 | msgid "Reset" 207 | msgstr "" 208 | 209 | #: apc/config.py:132 210 | msgid "Update Animals" 211 | msgstr "" 212 | 213 | #: apc/config.py:134 214 | msgid "Just the Furs" 215 | msgstr "" 216 | 217 | #: apc/config.py:136 218 | msgid "one of each fur" 219 | msgstr "" 220 | 221 | #: apc/config.py:138 222 | msgid "Others" 223 | msgstr "" 224 | 225 | #: apc/config.py:140 226 | msgid "Party" 227 | msgstr "" 228 | 229 | #: apc/config.py:142 230 | msgid "Great One Party" 231 | msgstr "" 232 | 233 | #: apc/config.py:144 234 | msgid "Diamond Party" 235 | msgstr "" 236 | 237 | #: apc/config.py:146 238 | msgid "We All Party" 239 | msgstr "" 240 | 241 | #: apc/config.py:148 242 | msgid "Fur Party" 243 | msgstr "" 244 | 245 | #: apc/config.py:150 246 | msgid "Explore" 247 | msgstr "" 248 | 249 | #: apc/config.py:152 250 | msgid "diamonds and Great Ones" 251 | msgstr "" 252 | 253 | #: apc/config.py:154 254 | msgid "look at modded animals" 255 | msgstr "" 256 | 257 | #: apc/config.py:156 258 | msgid "look at all reserves" 259 | msgstr "" 260 | 261 | #: apc/config.py:158 262 | msgid "only top 10 scores" 263 | msgstr "" 264 | 265 | #: apc/config.py:160 266 | msgid "Show Animals" 267 | msgstr "" 268 | 269 | #: apc/config.py:162 270 | msgid "Files" 271 | msgstr "" 272 | 273 | #: apc/config.py:164 274 | msgid "Configure Game Path" 275 | msgstr "" 276 | 277 | #: apc/config.py:166 278 | msgid "List Mods" 279 | msgstr "" 280 | 281 | #: apc/config.py:168 282 | msgid "Load Mod" 283 | msgstr "" 284 | 285 | #: apc/config.py:170 286 | msgid "Unload Mod" 287 | msgstr "" 288 | 289 | #: apc/config.py:172 290 | msgid "Select the folder where the game saves your files" 291 | msgstr "" 292 | 293 | #: apc/config.py:174 294 | msgid "Saves Path" 295 | msgstr "" 296 | 297 | #: apc/config.py:176 298 | msgid "Game path saved" 299 | msgstr "" 300 | 301 | #: apc/config.py:178 302 | msgid "Are you sure you want to overwrite your game file with the modded one?" 303 | msgstr "" 304 | 305 | #: apc/config.py:180 306 | msgid "Don't worry, a backup copy will be made." 307 | msgstr "" 308 | 309 | #: apc/config.py:182 310 | msgid "Confirmation" 311 | msgstr "" 312 | 313 | #: apc/config.py:184 314 | msgid "Mod" 315 | msgstr "" 316 | 317 | #: apc/config.py:186 318 | msgid "view modded version" 319 | msgstr "" 320 | 321 | #: apc/config.py:188 322 | msgid "Loaded" 323 | msgstr "" 324 | 325 | #: apc/config.py:190 326 | msgid "Modded File" 327 | msgstr "" 328 | 329 | #: apc/config.py:192 330 | msgid "Back to Reserve" 331 | msgstr "" 332 | 333 | #: apc/config.py:194 334 | msgid "update translations" 335 | msgstr "" 336 | 337 | #: apc/config.py:196 338 | msgid "switch language" 339 | msgstr "" 340 | 341 | #: apc/config.py:198 342 | msgid "Please restart to see changes" 343 | msgstr "" 344 | 345 | #: apc/config.py:200 346 | msgid "default" 347 | msgstr "" 348 | 349 | #: apc/config.py:202 350 | msgid "using" 351 | msgstr "" 352 | 353 | #: apc/config.py:204 354 | msgid "OK" 355 | msgstr "" 356 | 357 | #: apc/config.py:206 358 | msgid "Cancel" 359 | msgstr "" 360 | 361 | #: apc/config.py:208 362 | msgid "Modify Animals" 363 | msgstr "" 364 | 365 | #: apc/config.py:210 366 | msgid "Furs" 367 | msgstr "" 368 | 369 | #: apc/config.py:212 370 | msgid "Modify Animal Furs" 371 | msgstr "" 372 | 373 | #: apc/config.py:214 374 | msgid "Male Furs" 375 | msgstr "" 376 | 377 | #: apc/config.py:216 378 | msgid "Female Furs" 379 | msgstr "" 380 | 381 | #: apc/config.py:218 382 | msgid "Change All Species" 383 | msgstr "" 384 | 385 | #: apc/config.py:220 386 | msgid "Explore Animals" 387 | msgstr "" 388 | 389 | #: apc/config.py:222 390 | msgid "Manage Modded Reserves" 391 | msgstr "" 392 | 393 | #: apc/config.py:224 394 | msgid "viewing loaded mod" 395 | msgstr "" 396 | 397 | #: apc/config.py:226 398 | msgid "use all furs" 399 | msgstr "" 400 | 401 | #: apc/config.py:228 402 | msgid "No" 403 | msgstr "" 404 | 405 | #: apc/config.py:230 406 | msgid "Animal Details" 407 | msgstr "" 408 | 409 | #: apc/config.py:232 410 | msgid "default is random fur" 411 | msgstr "" 412 | 413 | #: apc/config.py:234 414 | msgid "Update Animal" 415 | msgstr "" 416 | 417 | #: apc/config.py:236 418 | msgid "Top 10" 419 | msgstr "" 420 | 421 | #: apc/config.py:238 422 | msgid "Loaded Mod" 423 | msgstr "" 424 | 425 | #: apc/config.py:240 426 | msgid "Export Mod" 427 | msgstr "" 428 | 429 | #: apc/config.py:242 430 | msgid "Import Mod" 431 | msgstr "" 432 | 433 | #: apc/config.py:244 434 | msgid "Export" 435 | msgstr "" 436 | 437 | #: apc/config.py:246 438 | msgid "Import" 439 | msgstr "" 440 | 441 | #: apc/config.py:248 442 | msgid "Select the location and filename where you want to export" 443 | msgstr "" 444 | 445 | #: apc/config.py:250 446 | msgid "Select the reserve mod to import" 447 | msgstr "" 448 | 449 | #: apc/config.py:252 450 | msgid "Export As" 451 | msgstr "" 452 | 453 | #: apc/config.py:254 454 | msgid "Select File" 455 | msgstr "" 456 | 457 | #: apc/config.py:256 458 | msgid "Mod Exported" 459 | msgstr "" 460 | 461 | #: apc/config.py:258 462 | msgid "Mod Imported" 463 | msgstr "" 464 | 465 | #: apc/config.py:367 466 | msgid "Trivial" 467 | msgstr "" 468 | 469 | #: apc/config.py:369 470 | msgid "Minor" 471 | msgstr "" 472 | 473 | #: apc/config.py:371 474 | msgid "Very Easy" 475 | msgstr "" 476 | 477 | #: apc/config.py:373 478 | msgid "Easy" 479 | msgstr "" 480 | 481 | #: apc/config.py:375 482 | msgid "Medium" 483 | msgstr "" 484 | 485 | #: apc/config.py:377 486 | msgid "Hard" 487 | msgstr "" 488 | 489 | #: apc/config.py:379 490 | msgid "Very Hard" 491 | msgstr "" 492 | 493 | #: apc/config.py:381 494 | msgid "Mythical" 495 | msgstr "" 496 | 497 | #: apc/config.py:383 498 | msgid "Legendary" 499 | msgstr "" 500 | 501 | #: apc/config.py:412 502 | msgid "fur_name" 503 | msgstr "" 504 | 505 | #: apc/config.py:452 506 | msgid "reserve_name" 507 | msgstr "" 508 | 509 | #: apc/config.py:524 510 | msgid "name" 511 | msgstr "" 512 | 513 | #: apc/config/animal_names.json:4 514 | msgid "American Alligator" 515 | msgstr "" 516 | 517 | #: apc/config/animal_names.json:7 518 | msgid "Antelope Jackrabbit" 519 | msgstr "" 520 | 521 | #: apc/config/animal_names.json:10 522 | msgid "Axis Deer" 523 | msgstr "" 524 | 525 | #: apc/config/animal_names.json:13 526 | msgid "Beceite Ibex" 527 | msgstr "" 528 | 529 | #: apc/config/animal_names.json:16 530 | msgid "Bighorn Sheep" 531 | msgstr "" 532 | 533 | #: apc/config/animal_names.json:19 534 | msgid "Black Bear" 535 | msgstr "" 536 | 537 | #: apc/config/animal_names.json:22 538 | msgid "Black Grouse" 539 | msgstr "" 540 | 541 | #: apc/config/animal_names.json:25 542 | msgid "Blackbuck" 543 | msgstr "" 544 | 545 | #: apc/config/animal_names.json:28 546 | msgid "Blacktail Deer" 547 | msgstr "" 548 | 549 | #: apc/config/animal_names.json:31 550 | msgid "Blue Wildebeest" 551 | msgstr "" 552 | 553 | #: apc/config/animal_names.json:34 554 | msgid "Bobcat" 555 | msgstr "" 556 | 557 | #: apc/config/animal_names.json:37 558 | msgid "Canada Goose" 559 | msgstr "" 560 | 561 | #: apc/config/animal_names.json:40 562 | msgid "Cape Buffalo" 563 | msgstr "" 564 | 565 | #: apc/config/animal_names.json:43 566 | msgid "Caribou" 567 | msgstr "" 568 | 569 | #: apc/config/animal_names.json:46 570 | msgid "Chamois" 571 | msgstr "" 572 | 573 | #: apc/config/animal_names.json:49 574 | msgid "Cinnamon Teal" 575 | msgstr "" 576 | 577 | #: apc/config/animal_names.json:52 578 | msgid "Collared Peccary" 579 | msgstr "" 580 | 581 | #: apc/config/animal_names.json:55 582 | msgid "Coyote" 583 | msgstr "" 584 | 585 | #: apc/config/animal_names.json:58 586 | msgid "Eastern Cottontail Rabbit" 587 | msgstr "" 588 | 589 | #: apc/config/animal_names.json:61 590 | msgid "Eastern Wild Turkey" 591 | msgstr "" 592 | 593 | #: apc/config/animal_names.json:64 594 | msgid "Eu Bison" 595 | msgstr "" 596 | 597 | #: apc/config/animal_names.json:67 598 | msgid "Eu Hare" 599 | msgstr "" 600 | 601 | #: apc/config/animal_names.json:70 602 | msgid "Eu Rabbit" 603 | msgstr "" 604 | 605 | #: apc/config/animal_names.json:73 606 | msgid "Eurasian Brown Bear" 607 | msgstr "" 608 | 609 | #: apc/config/animal_names.json:76 610 | msgid "Eurasian Lynx" 611 | msgstr "" 612 | 613 | #: apc/config/animal_names.json:79 614 | msgid "Eurasian Teal" 615 | msgstr "" 616 | 617 | #: apc/config/animal_names.json:82 618 | msgid "Eurasian Wigeon" 619 | msgstr "" 620 | 621 | #: apc/config/animal_names.json:85 622 | msgid "Fallow Deer" 623 | msgstr "" 624 | 625 | #: apc/config/animal_names.json:88 626 | msgid "Feral Goat" 627 | msgstr "" 628 | 629 | #: apc/config/animal_names.json:91 630 | msgid "Feral Pig" 631 | msgstr "" 632 | 633 | #: apc/config/animal_names.json:94 634 | msgid "Gemsbok" 635 | msgstr "" 636 | 637 | #: apc/config/animal_names.json:97 638 | msgid "Goldeneye" 639 | msgstr "" 640 | 641 | #: apc/config/animal_names.json:100 642 | msgid "Gray Fox" 643 | msgstr "" 644 | 645 | #: apc/config/animal_names.json:103 646 | msgid "Gray Wolf" 647 | msgstr "" 648 | 649 | #: apc/config/animal_names.json:106 650 | msgid "Gredos Ibex" 651 | msgstr "" 652 | 653 | #: apc/config/animal_names.json:109 654 | msgid "Green Wing Teal" 655 | msgstr "" 656 | 657 | #: apc/config/animal_names.json:112 658 | msgid "Greylag Goose" 659 | msgstr "" 660 | 661 | #: apc/config/animal_names.json:115 662 | msgid "Grizzly Bear" 663 | msgstr "" 664 | 665 | #: apc/config/animal_names.json:118 666 | msgid "Harlequin Duck" 667 | msgstr "" 668 | 669 | #: apc/config/animal_names.json:121 670 | msgid "Hazel Grouse" 671 | msgstr "" 672 | 673 | #: apc/config/animal_names.json:124 674 | msgid "Iberian Mouflon" 675 | msgstr "" 676 | 677 | #: apc/config/animal_names.json:127 678 | msgid "Iberian Wolf" 679 | msgstr "" 680 | 681 | #: apc/config/animal_names.json:130 682 | msgid "Jackrabbit" 683 | msgstr "" 684 | 685 | #: apc/config/animal_names.json:133 686 | msgid "Lesser Kudu" 687 | msgstr "" 688 | 689 | #: apc/config/animal_names.json:136 690 | msgid "Lion" 691 | msgstr "" 692 | 693 | #: apc/config/animal_names.json:139 694 | msgid "Mallard" 695 | msgstr "" 696 | 697 | #: apc/config/animal_names.json:142 698 | msgid "Mexican Bobcat" 699 | msgstr "" 700 | 701 | #: apc/config/animal_names.json:145 702 | msgid "Moose" 703 | msgstr "" 704 | 705 | #: apc/config/animal_names.json:148 706 | msgid "Mountain Goat" 707 | msgstr "" 708 | 709 | #: apc/config/animal_names.json:151 710 | msgid "Mountain Hare" 711 | msgstr "" 712 | 713 | #: apc/config/animal_names.json:154 714 | msgid "Mountain Lion" 715 | msgstr "" 716 | 717 | #: apc/config/animal_names.json:157 718 | msgid "Mule Deer" 719 | msgstr "" 720 | 721 | #: apc/config/animal_names.json:160 722 | msgid "Northern Bobwhite Quail" 723 | msgstr "" 724 | 725 | #: apc/config/animal_names.json:163 726 | msgid "Pheasant" 727 | msgstr "" 728 | 729 | #: apc/config/animal_names.json:166 730 | msgid "Plains Bison" 731 | msgstr "" 732 | 733 | #: apc/config/animal_names.json:169 734 | msgid "Prong Horn" 735 | msgstr "" 736 | 737 | #: apc/config/animal_names.json:172 738 | msgid "Puma" 739 | msgstr "" 740 | 741 | #: apc/config/animal_names.json:175 742 | msgid "Raccoon" 743 | msgstr "" 744 | 745 | #: apc/config/animal_names.json:178 746 | msgid "Raccoon Dog" 747 | msgstr "" 748 | 749 | #: apc/config/animal_names.json:181 750 | msgid "Red Deer" 751 | msgstr "" 752 | 753 | #: apc/config/animal_names.json:184 754 | msgid "Red Fox" 755 | msgstr "" 756 | 757 | #: apc/config/animal_names.json:187 758 | msgid "Reindeer" 759 | msgstr "" 760 | 761 | #: apc/config/animal_names.json:190 762 | msgid "Rio Grande Turkey" 763 | msgstr "" 764 | 765 | #: apc/config/animal_names.json:193 766 | msgid "Rock Ptarmigan" 767 | msgstr "" 768 | 769 | #: apc/config/animal_names.json:196 770 | msgid "Rockymountain Elk" 771 | msgstr "" 772 | 773 | #: apc/config/animal_names.json:199 774 | msgid "Roe Deer" 775 | msgstr "" 776 | 777 | #: apc/config/animal_names.json:202 778 | msgid "Ronda Ibex" 779 | msgstr "" 780 | 781 | #: apc/config/animal_names.json:205 782 | msgid "Roosevelt Elk" 783 | msgstr "" 784 | 785 | #: apc/config/animal_names.json:208 786 | msgid "Scrub Hare" 787 | msgstr "" 788 | 789 | #: apc/config/animal_names.json:211 790 | msgid "Siberian Musk Deer" 791 | msgstr "" 792 | 793 | #: apc/config/animal_names.json:214 794 | msgid "Sidestriped Jackal" 795 | msgstr "" 796 | 797 | #: apc/config/animal_names.json:217 798 | msgid "Sika Deer" 799 | msgstr "" 800 | 801 | #: apc/config/animal_names.json:220 802 | msgid "Southeastern Ibex" 803 | msgstr "" 804 | 805 | #: apc/config/animal_names.json:223 806 | msgid "Springbok" 807 | msgstr "" 808 | 809 | #: apc/config/animal_names.json:226 810 | msgid "Tufted Duck" 811 | msgstr "" 812 | 813 | #: apc/config/animal_names.json:229 814 | msgid "Tundra Bean Goose" 815 | msgstr "" 816 | 817 | #: apc/config/animal_names.json:232 818 | msgid "Warthog" 819 | msgstr "" 820 | 821 | #: apc/config/animal_names.json:235 822 | msgid "Water Buffalo" 823 | msgstr "" 824 | 825 | #: apc/config/animal_names.json:238 826 | msgid "Western Capercaillie" 827 | msgstr "" 828 | 829 | #: apc/config/animal_names.json:241 830 | msgid "Whitetail Deer" 831 | msgstr "" 832 | 833 | #: apc/config/animal_names.json:244 834 | msgid "Wild Boar" 835 | msgstr "" 836 | 837 | #: apc/config/animal_names.json:247 838 | msgid "Wild Hog" 839 | msgstr "" 840 | 841 | #: apc/config/animal_names.json:250 842 | msgid "Wild Turkey" 843 | msgstr "" 844 | 845 | #: apc/config/animal_names.json:253 846 | msgid "Willow Ptarmigan" 847 | msgstr "" 848 | 849 | #: apc/config/animal_names.json:256 850 | msgid "Hog Deer" 851 | msgstr "" 852 | 853 | #: apc/config/animal_names.json:259 854 | msgid "Magpie Goose" 855 | msgstr "" 856 | 857 | #: apc/config/animal_names.json:262 858 | msgid "Eastern Kangaroo" 859 | msgstr "" 860 | 861 | #: apc/config/animal_names.json:265 862 | msgid "Sambar Deer" 863 | msgstr "" 864 | 865 | #: apc/config/animal_names.json:268 866 | msgid "Banteng" 867 | msgstr "" 868 | 869 | #: apc/config/animal_names.json:271 870 | msgid "Saltwater Crocodile" 871 | msgstr "" 872 | 873 | #: apc/config/animal_names.json:274 874 | msgid "Stubble Quail" 875 | msgstr "" 876 | 877 | #: apc/config/animal_names.json:277 878 | msgid "Javan Rusa" 879 | msgstr "" 880 | 881 | #: apc/config/fur_names.json:4 882 | msgid "Piebald" 883 | msgstr "" 884 | 885 | #: apc/config/fur_names.json:7 886 | msgid "Olive" 887 | msgstr "" 888 | 889 | #: apc/config/fur_names.json:10 890 | msgid "Dark Brown" 891 | msgstr "" 892 | 893 | #: apc/config/fur_names.json:13 894 | msgid "Albino" 895 | msgstr "" 896 | 897 | #: apc/config/fur_names.json:16 898 | msgid "Melanistic" 899 | msgstr "" 900 | 901 | #: apc/config/fur_names.json:19 902 | msgid "Gray" 903 | msgstr "" 904 | 905 | #: apc/config/fur_names.json:22 906 | msgid "Brown" 907 | msgstr "" 908 | 909 | #: apc/config/fur_names.json:25 910 | msgid "Mottled" 911 | msgstr "" 912 | 913 | #: apc/config/fur_names.json:28 914 | msgid "Spotted" 915 | msgstr "" 916 | 917 | #: apc/config/fur_names.json:31 918 | msgid "Dark" 919 | msgstr "" 920 | 921 | #: apc/config/fur_names.json:34 922 | msgid "Orange" 923 | msgstr "" 924 | 925 | #: apc/config/fur_names.json:37 926 | msgid "Brown Hybrid" 927 | msgstr "" 928 | 929 | #: apc/config/fur_names.json:40 930 | msgid "Gray Brown" 931 | msgstr "" 932 | 933 | #: apc/config/fur_names.json:43 934 | msgid "Light Brown" 935 | msgstr "" 936 | 937 | #: apc/config/fur_names.json:46 938 | msgid "Buff" 939 | msgstr "" 940 | 941 | #: apc/config/fur_names.json:49 942 | msgid "Black" 943 | msgstr "" 944 | 945 | #: apc/config/fur_names.json:52 946 | msgid "Bronze" 947 | msgstr "" 948 | 949 | #: apc/config/fur_names.json:55 950 | msgid "Dusky" 951 | msgstr "" 952 | 953 | #: apc/config/fur_names.json:58 954 | msgid "Cinnamon" 955 | msgstr "" 956 | 957 | #: apc/config/fur_names.json:61 958 | msgid "Blonde" 959 | msgstr "" 960 | 961 | #: apc/config/fur_names.json:64 962 | msgid "Fabled Glacier" 963 | msgstr "" 964 | 965 | #: apc/config/fur_names.json:67 966 | msgid "Fabled Chestnut" 967 | msgstr "" 968 | 969 | #: apc/config/fur_names.json:70 970 | msgid "Fabled Cream" 971 | msgstr "" 972 | 973 | #: apc/config/fur_names.json:73 974 | msgid "Fabled Spotted" 975 | msgstr "" 976 | 977 | #: apc/config/fur_names.json:76 978 | msgid "Fabled Spirit" 979 | msgstr "" 980 | 981 | #: apc/config/fur_names.json:79 982 | msgid "Leucistic" 983 | msgstr "" 984 | 985 | #: apc/config/fur_names.json:82 986 | msgid "Gold" 987 | msgstr "" 988 | 989 | #: apc/config/fur_names.json:85 990 | msgid "Dark Grey" 991 | msgstr "" 992 | 993 | #: apc/config/fur_names.json:88 994 | msgid "Tan" 995 | msgstr "" 996 | 997 | #: apc/config/fur_names.json:91 998 | msgid "Crowned" 999 | msgstr "" 1000 | 1001 | #: apc/config/fur_names.json:94 1002 | msgid "Blue" 1003 | msgstr "" 1004 | 1005 | #: apc/config/fur_names.json:97 1006 | msgid "Red" 1007 | msgstr "" 1008 | 1009 | #: apc/config/fur_names.json:100 1010 | msgid "Lg leucist." 1011 | msgstr "" 1012 | 1013 | #: apc/config/fur_names.json:103 1014 | msgid "Bald leuc." 1015 | msgstr "" 1016 | 1017 | #: apc/config/fur_names.json:106 1018 | msgid "Honeytones" 1019 | msgstr "" 1020 | 1021 | #: apc/config/fur_names.json:109 1022 | msgid "Beige" 1023 | msgstr "" 1024 | 1025 | #: apc/config/fur_names.json:112 1026 | msgid "Ochre" 1027 | msgstr "" 1028 | 1029 | #: apc/config/fur_names.json:115 1030 | msgid "Light Gray" 1031 | msgstr "" 1032 | 1033 | #: apc/config/fur_names.json:118 1034 | msgid "Light Bronze" 1035 | msgstr "" 1036 | 1037 | #: apc/config/fur_names.json:121 1038 | msgid "Spirit" 1039 | msgstr "" 1040 | 1041 | #: apc/config/fur_names.json:124 1042 | msgid "Red Brown" 1043 | msgstr "" 1044 | 1045 | #: apc/config/fur_names.json:127 1046 | msgid "Dark Green" 1047 | msgstr "" 1048 | 1049 | #: apc/config/fur_names.json:130 1050 | msgid "Light Green" 1051 | msgstr "" 1052 | 1053 | #: apc/config/fur_names.json:133 1054 | msgid "Hybrid Blue" 1055 | msgstr "" 1056 | 1057 | #: apc/config/fur_names.json:136 1058 | msgid "Hybrid Green" 1059 | msgstr "" 1060 | 1061 | #: apc/config/fur_names.json:139 1062 | msgid "Eclipse" 1063 | msgstr "" 1064 | 1065 | #: apc/config/fur_names.json:142 1066 | msgid "Hybrid" 1067 | msgstr "" 1068 | 1069 | #: apc/config/fur_names.json:145 1070 | msgid "Spotted Dark" 1071 | msgstr "" 1072 | 1073 | #: apc/config/fur_names.json:148 1074 | msgid "Spotted Red" 1075 | msgstr "" 1076 | 1077 | #: apc/config/fur_names.json:151 1078 | msgid "White Brown" 1079 | msgstr "" 1080 | 1081 | #: apc/config/fur_names.json:154 1082 | msgid "Black Brown" 1083 | msgstr "" 1084 | 1085 | #: apc/config/fur_names.json:157 1086 | msgid "Black White" 1087 | msgstr "" 1088 | 1089 | #: apc/config/fur_names.json:160 1090 | msgid "White" 1091 | msgstr "" 1092 | 1093 | #: apc/config/fur_names.json:163 1094 | msgid "Mixed" 1095 | msgstr "" 1096 | 1097 | #: apc/config/fur_names.json:166 1098 | msgid "Black Spots" 1099 | msgstr "" 1100 | 1101 | #: apc/config/fur_names.json:169 1102 | msgid "Blackgold" 1103 | msgstr "" 1104 | 1105 | #: apc/config/fur_names.json:172 1106 | msgid "Pink" 1107 | msgstr "" 1108 | 1109 | #: apc/config/fur_names.json:175 1110 | msgid "Two Tones" 1111 | msgstr "" 1112 | 1113 | #: apc/config/fur_names.json:178 1114 | msgid "Eggwhite" 1115 | msgstr "" 1116 | 1117 | #: apc/config/fur_names.json:181 1118 | msgid "Pale" 1119 | msgstr "" 1120 | 1121 | #: apc/config/fur_names.json:184 1122 | msgid "Pristine" 1123 | msgstr "" 1124 | 1125 | #: apc/config/fur_names.json:187 1126 | msgid "Winter" 1127 | msgstr "" 1128 | 1129 | #: apc/config/fur_names.json:190 1130 | msgid "Fabled Two Tone" 1131 | msgstr "" 1132 | 1133 | #: apc/config/fur_names.json:193 1134 | msgid "Fabled Birch" 1135 | msgstr "" 1136 | 1137 | #: apc/config/fur_names.json:196 1138 | msgid "Fabled Oak" 1139 | msgstr "" 1140 | 1141 | #: apc/config/fur_names.json:199 1142 | msgid "Fabled Speckled" 1143 | msgstr "" 1144 | 1145 | #: apc/config/fur_names.json:202 1146 | msgid "Fabled Spruce" 1147 | msgstr "" 1148 | 1149 | #: apc/config/fur_names.json:205 1150 | msgid "Fabled Ashen" 1151 | msgstr "" 1152 | 1153 | #: apc/config/fur_names.json:208 1154 | msgid "Molting" 1155 | msgstr "" 1156 | 1157 | #: apc/config/fur_names.json:211 1158 | msgid "Dilute" 1159 | msgstr "" 1160 | 1161 | #: apc/config/fur_names.json:214 1162 | msgid "Dark Red" 1163 | msgstr "" 1164 | 1165 | #: apc/config/fur_names.json:217 1166 | msgid "Piebald Grey" 1167 | msgstr "" 1168 | 1169 | #: apc/config/fur_names.json:220 1170 | msgid "Piebald Blnd" 1171 | msgstr "" 1172 | 1173 | #: apc/config/fur_names.json:223 1174 | msgid "Lightbuff" 1175 | msgstr "" 1176 | 1177 | #: apc/config/fur_names.json:226 1178 | msgid "Bicolor" 1179 | msgstr "" 1180 | 1181 | #: apc/config/fur_names.json:229 1182 | msgid "Chestnut" 1183 | msgstr "" 1184 | 1185 | #: apc/config/fur_names.json:232 1186 | msgid "Cream" 1187 | msgstr "" 1188 | 1189 | #: apc/config/fur_names.json:235 1190 | msgid "Bright" 1191 | msgstr "" 1192 | 1193 | #: apc/config/fur_names.json:238 1194 | msgid "Fabled Hooded" 1195 | msgstr "" 1196 | 1197 | #: apc/config/fur_names.json:241 1198 | msgid "Fabled Mocha" 1199 | msgstr "" 1200 | 1201 | #: apc/config/fur_names.json:244 1202 | msgid "Two Tone" 1203 | msgstr "" 1204 | 1205 | #: apc/config/fur_names.json:247 1206 | msgid "Grey Brown" 1207 | msgstr "" 1208 | 1209 | #: apc/config/fur_names.json:250 1210 | msgid "Mocha" 1211 | msgstr "" 1212 | 1213 | #: apc/config/fur_names.json:253 1214 | msgid "Dusky Gradient" 1215 | msgstr "" 1216 | 1217 | #: apc/config/fur_names.json:256 1218 | msgid "Yellow" 1219 | msgstr "" 1220 | 1221 | #: apc/config/fur_names.json:259 1222 | msgid "Maroon" 1223 | msgstr "" 1224 | 1225 | #: apc/config/fur_names.json:262 1226 | msgid "Dark Spotted" 1227 | msgstr "" 1228 | 1229 | #: apc/config/fur_names.json:265 1230 | msgid "Chocolate" 1231 | msgstr "" 1232 | 1233 | #: apc/config/fur_names.json:268 1234 | msgid "Fabled Silver" 1235 | msgstr "" 1236 | 1237 | #: apc/config/fur_names.json:271 1238 | msgid "Fabled Golden" 1239 | msgstr "" 1240 | 1241 | #: apc/config/fur_names.json:274 1242 | msgid "Fabled Painted" 1243 | msgstr "" 1244 | 1245 | #: apc/config/reserve_names.json:4 1246 | msgid "Hirschfelden" 1247 | msgstr "" 1248 | 1249 | #: apc/config/reserve_names.json:7 1250 | msgid "Layton Lake" 1251 | msgstr "" 1252 | 1253 | #: apc/config/reserve_names.json:10 1254 | msgid "Medved-Taiga National Park" 1255 | msgstr "" 1256 | 1257 | #: apc/config/reserve_names.json:13 1258 | msgid "Vurhonga Savanna" 1259 | msgstr "" 1260 | 1261 | #: apc/config/reserve_names.json:16 1262 | msgid "Parque Fernando" 1263 | msgstr "" 1264 | 1265 | #: apc/config/reserve_names.json:19 1266 | msgid "Yukon Valley" 1267 | msgstr "" 1268 | 1269 | #: apc/config/reserve_names.json:22 1270 | msgid "Cuatro Colinas Game Reserve" 1271 | msgstr "" 1272 | 1273 | #: apc/config/reserve_names.json:25 1274 | msgid "Silver Ridge Peaks" 1275 | msgstr "" 1276 | 1277 | #: apc/config/reserve_names.json:28 1278 | msgid "Te Awaroa National Park" 1279 | msgstr "" 1280 | 1281 | #: apc/config/reserve_names.json:31 1282 | msgid "Rancho del Arroyo" 1283 | msgstr "" 1284 | 1285 | #: apc/config/reserve_names.json:34 1286 | msgid "Mississippi Acres Preserve" 1287 | msgstr "" 1288 | 1289 | #: apc/config/reserve_names.json:37 1290 | msgid "Revontuli Coast" 1291 | msgstr "" 1292 | 1293 | #: apc/config/reserve_names.json:40 1294 | msgid "New England Mountains" 1295 | msgstr "" 1296 | 1297 | #: apc/config/reserve_names.json:43 1298 | msgid "Emerald Coast" 1299 | msgstr "" 1300 | 1301 | #~ msgid "include rare furs" 1302 | #~ msgstr "" 1303 | 1304 | #~ msgid "viewing modded mod" 1305 | #~ msgstr "" 1306 | 1307 | #~ msgid "Great Ones" 1308 | #~ msgstr "" 1309 | 1310 | #~ msgid "Diamonds" 1311 | #~ msgstr "" 1312 | 1313 | -------------------------------------------------------------------------------- /apc/locale/es_ES/LC_MESSAGES/apc.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpypasta/apc/86a5dc2eb473feb58a7a1372f488930400e9e0cb/apc/locale/es_ES/LC_MESSAGES/apc.mo -------------------------------------------------------------------------------- /apc/locale/ru_RU/LC_MESSAGES/apc.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpypasta/apc/86a5dc2eb473feb58a7a1372f488930400e9e0cb/apc/locale/ru_RU/LC_MESSAGES/apc.mo -------------------------------------------------------------------------------- /apc/locale/zh_CN/LC_MESSAGES/apc.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpypasta/apc/86a5dc2eb473feb58a7a1372f488930400e9e0cb/apc/locale/zh_CN/LC_MESSAGES/apc.mo -------------------------------------------------------------------------------- /apc/populations.py: -------------------------------------------------------------------------------- 1 | import random 2 | from apc.utils import update_float, update_uint 3 | from deca.ff_adf import Adf, AdfValue 4 | from apc import config, adf, adf_profile 5 | from rich import print 6 | from apc.adf import ParsedAdfFile, load_reserve 7 | from apc.config import get_animal_fur_by_seed, get_species_name, get_reserve_name, get_level_name, get_reserve, valid_species_for_reserve, format_key 8 | from typing import List 9 | 10 | class AdfAnimal: 11 | def __init__(self, details: AdfValue, species: str) -> None: 12 | gender = "male" if details.value["Gender"].value == 1 else "female" 13 | self.gender = gender 14 | self.weight = float(details.value["Weight"].value) 15 | self.score = float(details.value["Score"].value) 16 | self.go = details.value["IsGreatOne"].value == 1 17 | self.visual_seed = details.value["VisualVariationSeed"].value 18 | self.weight_offset = details.value["Weight"].data_offset 19 | self.score_offset = details.value["Score"].data_offset 20 | self.go_offset = details.value["IsGreatOne"].data_offset 21 | self.visual_seed_offset = details.value["VisualVariationSeed"].data_offset 22 | self.gender_offset = details.value["Gender"].data_offset 23 | self.species = species 24 | 25 | def __repr__(self) -> str: 26 | return str({ 27 | "species": self.species, 28 | "gender": self.gender, 29 | "weight": self.weight, 30 | "score": self.score, 31 | "go": self.go, 32 | "seed": self.visual_seed, 33 | "score_offset": self.score_offset 34 | }) 35 | 36 | class NoAnimalsException(Exception): 37 | pass 38 | 39 | def _species(reserve_name: str, reserve_details: Adf, species: str) -> list: 40 | reserve = config.RESERVES[reserve_name] 41 | species_index = reserve["species"].index(species) 42 | populations = _get_populations(reserve_details) 43 | return populations[species_index] 44 | 45 | def _random_float(low: int, high: int, precision: int = 3) -> float: 46 | return round(random.uniform(low, high), precision) 47 | 48 | def _dict_values(dict): 49 | return list(dict.values()) 50 | 51 | def _random_choice(choices): 52 | return random.choice(_dict_values(choices)) 53 | 54 | def reserve_keys() -> list: 55 | return config.reserve_keys() 56 | 57 | def reserves(include_keys = False) -> list: 58 | return config.reserves(include_keys) 59 | 60 | def species(reserve_key: str, include_keys = False) -> list: 61 | return config.species(reserve_key, include_keys) 62 | 63 | def _get_populations(reserve_details: Adf) -> list: 64 | populations = reserve_details.table_instance_full_values[0].value["Populations"].value 65 | return [p for p in populations if len(p.value["Groups"].value) > 0] 66 | 67 | def _find_animal_level(weight: float, levels: list) -> int: 68 | level = 1 69 | weight = round(weight) if weight > 1 else round(weight, 3) 70 | for value_i, value in enumerate(levels): 71 | low_bound, high_bound = value 72 | high_bound = round(high_bound) if high_bound > 1 else round(high_bound, 3) 73 | if (weight <= high_bound and weight > low_bound) or weight > high_bound: 74 | level = value_i + 1 75 | return level 76 | 77 | def _is_go(animal: AdfAnimal) -> bool: 78 | return animal.gender == "male" and animal.go 79 | 80 | def _is_diamond(animal: AdfAnimal) -> bool: 81 | known_species = config.valid_species(animal.species) 82 | diamond_config = config.ANIMALS[animal.species]["diamonds"] 83 | diamond_score = diamond_config["score_low"] if known_species else config.HIGH_NUMBER 84 | diamond_gender = config.get_diamond_gender(animal.species) 85 | return animal.score >= diamond_score and (animal.gender == diamond_gender or diamond_gender == "both") 86 | 87 | def find_animals(species: str, modded = False, good = False, verbose = False, top: bool = False) -> list: 88 | reserves = reserve_keys() 89 | animals = [] 90 | for reserve in reserves: 91 | if valid_species_for_reserve(species, reserve): 92 | try: 93 | reserve_details = adf.load_reserve(reserve, modded) 94 | reserve_animals = describe_animals(reserve, species, reserve_details.adf, good, verbose) 95 | animals.extend(reserve_animals) 96 | except: 97 | continue 98 | animals = sorted(animals, key = lambda x : x[4], reverse=True) 99 | return animals[:10] if top else animals 100 | 101 | def describe_animals(reserve_name: str, species: str, reserve_details: Adf, good = False, verbose = False, top: bool = False) -> list: 102 | populations = _get_populations(reserve_details) 103 | population = populations[get_reserve(reserve_name)["species"].index(species)] 104 | groups = population.value["Groups"].value 105 | 106 | if verbose: 107 | print(f"processing {format_key(species)} animals...") 108 | 109 | rows = [] 110 | 111 | known_species = config.valid_species(species) 112 | diamond_config = config.ANIMALS[species]["diamonds"] 113 | diamond_weight = diamond_config["weight_low"] if known_species else config.HIGH_NUMBER 114 | diamond_score = diamond_config["score_low"] if known_species else config.HIGH_NUMBER 115 | animal_levels = diamond_config["levels"] if known_species else [] 116 | 117 | if verbose and diamond_score != config.HIGH_NUMBER: 118 | print(f"Species: {species}, Diamond Weight: {diamond_weight}, Diamond Score: {diamond_score}") 119 | 120 | for group in groups: 121 | animals = group.value["Animals"].value 122 | 123 | for animal in animals: 124 | animal = AdfAnimal(animal, species) 125 | is_diamond = _is_diamond(animal) 126 | is_go = _is_go(animal) 127 | 128 | if ((good and (is_diamond or is_go)) or not good): 129 | level = _find_animal_level(animal.weight, animal_levels) if not is_go else 10 130 | level_name = get_level_name(config.Levels(level)) 131 | rows.append([ 132 | get_reserve_name(reserve_name), 133 | f"{level_name}, {level}", 134 | config.MALE if animal.gender == "male" else config.FEMALE, 135 | round(animal.weight,2), 136 | round(animal.score, 2), 137 | get_animal_fur_by_seed(species, animal.gender, animal.visual_seed, is_go), 138 | config.YES if is_diamond and not is_go else "-", 139 | config.YES if is_go else "-", 140 | animal 141 | ]) 142 | 143 | rows.sort(key=lambda x: x[4], reverse=True) 144 | return rows[:10] if top else rows 145 | 146 | def describe_reserve(reserve_key: str, reserve_details: Adf, include_species = True, verbose = False) -> tuple: 147 | populations = _get_populations(reserve_details) 148 | reserve_species = config.get_reserve(reserve_key)["species"] 149 | if verbose: 150 | print(f"processing {len(populations)} species...") 151 | 152 | rows = [] 153 | total_cnt = 0 154 | population_cnt = 0 155 | species_groups = {} 156 | 157 | for population_i, population in enumerate(populations): 158 | groups = population.value["Groups"].value 159 | animal_cnt = 0 160 | population_high_weight = 0 161 | population_high_score = 0 162 | female_cnt = 0 163 | male_cnt = 0 164 | go_cnt = 0 165 | diamond_cnt = 0 166 | female_groups = [] 167 | male_groups = [] 168 | 169 | species_key = config.RESERVES[reserve_key]["species"][population_cnt] if include_species else str(population_cnt) 170 | known_species = species_key in config.ANIMALS.keys() 171 | diamond_weight = config.ANIMALS[species_key]["diamonds"]["weight_low"] if known_species else config.HIGH_NUMBER 172 | diamond_score = config.ANIMALS[species_key]["diamonds"]["score_low"] if known_species else config.HIGH_NUMBER 173 | species_level = len(config.ANIMALS[species_key]["diamonds"]["levels"]) 174 | species_name = f"{species_level}. {config.get_reserve_species_name(species_key, reserve_key)}" 175 | population_cnt += 1 176 | 177 | if verbose and diamond_score != config.HIGH_NUMBER: 178 | print(f"Species: {species_name}, Diamond Weight: {diamond_weight}, Diamond Score: {diamond_score}") 179 | 180 | for group_i, group in enumerate(groups): 181 | animals = group.value["Animals"].value 182 | group_animal_cnt = len(animals) 183 | animal_cnt += group_animal_cnt 184 | total_cnt += group_animal_cnt 185 | group_male_cnt = 0 186 | group_female_cnt = 0 187 | 188 | for animal in animals: 189 | animal = AdfAnimal(animal, species_key) 190 | 191 | if animal.gender == "male": 192 | male_cnt += 1 193 | group_male_cnt += 1 194 | if animal.score > population_high_score: 195 | population_high_score = animal.score 196 | else: 197 | female_cnt += 1 198 | group_female_cnt += 1 199 | 200 | if animal.weight > population_high_weight: 201 | population_high_weight = animal.weight 202 | 203 | if animal.go: 204 | go_cnt += 1 205 | elif _is_diamond(animal): 206 | diamond_cnt += 1 207 | if group_male_cnt > 0: 208 | male_groups.append(group_i) 209 | if group_female_cnt > 0: 210 | female_groups.append(group_i) 211 | 212 | species_groups[reserve_species[population_i]] = { "male": male_groups, "female": female_groups } 213 | 214 | rows.append([ 215 | species_key, 216 | species_level, 217 | species_name, 218 | animal_cnt, 219 | male_cnt, 220 | female_cnt, 221 | round(population_high_weight, 2), 222 | round(population_high_score, 2), 223 | diamond_cnt, 224 | go_cnt 225 | ]) 226 | 227 | return (sorted(rows, key = lambda x: x[1]), species_groups) 228 | 229 | def _create_new_animal(animals: List[AdfAnimal]) -> adf_profile.Animal: 230 | chosen_animal = random.choice(animals) 231 | seed = random.uniform(0.000001, 0.099999) 232 | return adf_profile.Animal(chosen_animal.gender, chosen_animal.weight+seed, chosen_animal.score, False, chosen_animal.visual_seed) 233 | 234 | def _get_eligible_animals(groups: list, species: str, gender: str = "male", include_diamonds: bool = False) -> list: 235 | eligible_animals = [] 236 | for group in groups: 237 | animals = group.value["Animals"].value 238 | for animal in animals: 239 | animal = AdfAnimal(animal, species) 240 | if (not _is_go(animal)): 241 | if not include_diamonds and _is_diamond(animal): 242 | continue 243 | else: 244 | if animal.gender == gender or gender == "both": 245 | eligible_animals.append(animal) 246 | return eligible_animals 247 | 248 | def _update_animal(data: bytearray, animal: AdfAnimal, go: bool, gender: str, weight: float, score: float, visual_seed: int) -> None: 249 | update_uint(data, animal.gender_offset, 1 if gender == "male" else 2) 250 | update_float(data, animal.weight_offset, weight) 251 | update_float(data, animal.score_offset, score) 252 | update_uint(data, animal.go_offset, 1 if go else 0) 253 | update_uint(data, animal.visual_seed_offset, visual_seed) 254 | 255 | def _create_go(animal: AdfAnimal, go_config: dict, data: bytearray, fur: int = None) -> None: 256 | new_weight = _random_float(go_config["weight_low"], go_config["weight_high"]) 257 | new_score = _random_float(go_config["score_low"], go_config["score_high"]) 258 | visual_seed = fur if fur else _random_choice(go_config["furs"]) 259 | update_float(data, animal.weight_offset, new_weight) 260 | update_float(data, animal.score_offset, new_score) 261 | update_uint(data, animal.go_offset, 1) 262 | update_uint(data, animal.visual_seed_offset, visual_seed) 263 | 264 | def _create_diamond(animal: AdfAnimal, species_config: dict, data: bytearray, fur: int = None, rares: bool = False) -> None: 265 | new_weight = _random_float(species_config["weight_low"], species_config["weight_high"]) 266 | new_score = _random_float(species_config["score_low"], species_config["score_high"]) 267 | visual_seed = None 268 | if fur != None: 269 | visual_seed = fur 270 | elif rares: 271 | visual_seed = _random_choice(species_config["furs"][animal.gender]) 272 | update_float(data, animal.weight_offset, new_weight) 273 | update_float(data, animal.score_offset, new_score) 274 | if visual_seed != None: 275 | update_uint(data, animal.visual_seed_offset, visual_seed) 276 | 277 | def _create_fur(animal: AdfAnimal, species_config: dict, data: bytearray, fur: int = None) -> None: 278 | visual_seed = None 279 | if fur != None: 280 | visual_seed = fur 281 | elif "furs" in species_config and animal.gender in species_config["furs"]: 282 | visual_seed = _random_choice(species_config["furs"][animal.gender]) 283 | update_uint(data, animal.visual_seed_offset, visual_seed) 284 | 285 | def _create_male(animal: AdfAnimal, _config: dict, data: bytearray) -> None: 286 | update_uint(data, animal.gender_offset, 1) 287 | 288 | def _create_female(animal: AdfAnimal, _config: dict, data: bytearray) -> None: 289 | update_uint(data, animal.gender_offset, 2) 290 | 291 | def _process_all(species: str, species_config: dict, groups: list, reserve_data: bytearray, cb: callable, kwargs = {}, gender: str = "male") -> None: 292 | animals = _get_eligible_animals(groups, species, gender=gender) 293 | if len(animals) == 0: 294 | raise NoAnimalsException(f"There are not enough {get_species_name(species)} to process") 295 | for animal in animals: 296 | cb(animal, species_config, reserve_data, **kwargs) 297 | 298 | def _go_all(species: str, groups: list, reserve_data: bytearray) -> None: 299 | go_config = config.ANIMALS[species]["go"] 300 | _process_all(species, go_config, groups, reserve_data, _create_go) 301 | 302 | def _diamond_all(species: str, groups: list, reserve_data: bytearray, rares: bool = False) -> None: 303 | species_config = config.ANIMALS[species]["diamonds"] 304 | diamond_gender = config.get_diamond_gender(species) 305 | _process_all(species, species_config, groups, reserve_data, _create_diamond, { "rares": rares }, gender=diamond_gender) 306 | 307 | def diamond_test_seed(species: str, groups: list, data: bytearray, seed: int, gender: int = 1) -> None: 308 | eligible_animals = [] 309 | for group in groups: 310 | animals = group.value["Animals"].value 311 | for animal in animals: 312 | animal = AdfAnimal(animal, species) 313 | eligible_animals.append(animal) 314 | 315 | for animal in eligible_animals: 316 | update_float(data, animal.weight_offset, seed) 317 | update_float(data, animal.score_offset, seed / 10000) 318 | update_uint(data, animal.visual_seed_offset, seed) 319 | update_uint(data, animal.gender_offset, gender) 320 | seed += 1 321 | return seed 322 | 323 | def diamond_test_seeds(species: str, groups: list, data: bytearray, seeds: List[int]) -> None: 324 | eligible_animals = [] 325 | for group in groups: 326 | animals = group.value["Animals"].value 327 | for animal in animals[:len(seeds)]: 328 | animal = AdfAnimal(animal, species) 329 | eligible_animals.append(animal) 330 | 331 | for animal_i, animal in enumerate(eligible_animals[:len(seeds)]): 332 | seed = seeds[animal_i] 333 | update_float(data, animal.weight_offset, seed) 334 | update_float(data, animal.score_offset, seed / 10000) 335 | update_uint(data, animal.visual_seed_offset, seed) 336 | update_uint(data, animal.gender_offset, 1) 337 | seed += 1 338 | return seed 339 | 340 | def _process_furs(species, species_config: dict, furs: list, groups: list, reserve_data: bytearray, cb: callable, gender: str = "male") -> None: 341 | eligible_animals = _get_eligible_animals(groups, species, gender=gender) 342 | if len(eligible_animals) == 0: 343 | raise NoAnimalsException(f"There are not enough {get_species_name(species)} to process") 344 | chosen_animals = random.sample(eligible_animals, k = len(furs)) 345 | for animal_i, animal in enumerate(chosen_animals): 346 | cb(animal, species_config, reserve_data, fur = furs[animal_i]) 347 | 348 | def _go_furs(species: str, groups: list, reserve_data: bytearray) -> None: 349 | go_config = config.ANIMALS[species]["go"] 350 | go_furs = _dict_values(go_config["furs"]) 351 | _process_furs(species, go_config, go_furs, groups, reserve_data, _create_go) 352 | 353 | def _diamond_furs(species: str, groups: list, reserve_data: bytearray) -> None: 354 | species_config = config.ANIMALS[species]["diamonds"] 355 | diamond_gender = config.get_diamond_gender(species) 356 | diamond_furs = config.get_species_furs(species, diamond_gender) 357 | 358 | _process_furs(species, species_config, diamond_furs, groups, reserve_data, _create_diamond, gender=diamond_gender) 359 | 360 | def _update_with_furs(species_key: str, groups: list, reserve_data: bytearray, male_fur_keys: List[str], female_fur_keys: List[str], male_fur_cnt: int, female_fur_cnt: int) -> None: 361 | species_config = config.ANIMALS[species_key]["diamonds"] 362 | male_animals = _get_eligible_animals(groups, species_key, "male", include_diamonds=True) 363 | male_animals = random.sample(male_animals, k = male_fur_cnt) 364 | male_fur_seeds = [config.get_fur_seed(species_key, x, "male") for x in male_fur_keys] 365 | female_animals = _get_eligible_animals(groups, species_key, "female", include_diamonds=True) 366 | female_animals = random.sample(female_animals, k = female_fur_cnt) 367 | female_fur_seeds = [config.get_fur_seed(species_key, x, "female") for x in female_fur_keys] 368 | 369 | for animal in male_animals: 370 | _create_fur(animal, species_config, reserve_data, random.choice(male_fur_seeds)) 371 | for animal in female_animals: 372 | _create_fur(animal, species_config, reserve_data, random.choice(female_fur_seeds)) 373 | 374 | def _process_some(species, species_config: dict, groups: list, reserve_data: bytearray, modifier: int, percentage: bool, cb: callable, kwargs: dict = {}, gender: str = "male", include_diamonds: bool = False) -> None: 375 | eligible_animals = _get_eligible_animals(groups, species, gender=gender, include_diamonds=include_diamonds) 376 | if len(eligible_animals) == 0: 377 | raise NoAnimalsException(f"There are not enough {get_species_name(species)} to process") 378 | if percentage: 379 | animal_cnt = round((modifier / 100) * len(eligible_animals)) 380 | else: 381 | animal_cnt = modifier 382 | chosen_animals = random.sample(eligible_animals, k = animal_cnt) 383 | for animal in chosen_animals: 384 | cb(animal, species_config, reserve_data, **kwargs) 385 | 386 | def _go_some(species: str, groups: list, reserve_data: bytearray, modifier: int = None, percentage: bool = False) -> None: 387 | go_config = config.ANIMALS[species]["go"] 388 | _process_some(species, go_config, groups, reserve_data, modifier, percentage, _create_go, include_diamonds=True) 389 | 390 | def _diamond_some(species: str, groups: list, reserve_data: bytearray, modifier: int = None, percentage: bool = False, rares: bool = False) -> None: 391 | species_config = config.ANIMALS[species]["diamonds"] 392 | diamond_gender = config.get_diamond_gender(species) 393 | _process_some(species, species_config, groups, reserve_data, modifier, percentage, _create_diamond, { "rares": rares }, gender=diamond_gender) 394 | 395 | def _furs_some(species: str, groups: list, reserve_data: bytearray, modifier: int = None, percentage: bool = False) -> None: 396 | species_config = config.ANIMALS[species]["diamonds"] 397 | _process_some(species, species_config, groups, reserve_data, modifier, percentage, _create_fur, gender="both") 398 | 399 | def _male_some(species: str, groups: list, reserve_data: bytearray, modifier: int = None, percentage: bool = False) -> None: 400 | _process_some(species, {}, groups, reserve_data, modifier, percentage, _create_male, gender = "female") 401 | 402 | def _female_some(species: str, groups: list, reserve_data: bytearray, modifier: int = None, percentage: bool = False) -> None: 403 | _process_some(species, {}, groups, reserve_data, modifier, percentage, _create_female, gender = "male") 404 | 405 | def _add_animals(groups: list, reserve_name: str, species_key: str, animal_cnt: int, gender: str, verbose: bool, mod: bool) -> None: 406 | eligible_animals = _get_eligible_animals(groups, species_key, gender) 407 | animals = [] 408 | if animal_cnt: 409 | for i in range(animal_cnt): 410 | animals.append(_create_new_animal(eligible_animals)) 411 | else: 412 | animals.append(_create_new_animal(eligible_animals)) 413 | adf.add_animals_to_reserve(reserve_name, species_key, animals, verbose, mod) 414 | 415 | def _remove_animals(reserve_name: str, species_key: str, modifier: int, gender: str, verbose: bool, mod: bool) -> None: 416 | adf.remove_animals_from_reserve(reserve_name, species_key, modifier, gender, verbose, mod) 417 | 418 | def mod_furs(reserve_name: str, reserve_details: ParsedAdfFile, species_key: str, male_fur_keys: List[str], female_fur_keys: List[str], male_fur_cnt: int, female_fur_cnt: int, verbose: bool = False) -> None: 419 | species_details = _species(reserve_name, reserve_details.adf, species_key) 420 | groups = species_details.value["Groups"].value 421 | species_name = config.get_species_name(species_key) 422 | reserve_data = reserve_details.decompressed.data 423 | _update_with_furs(species_key, groups, reserve_data, male_fur_keys, female_fur_keys, male_fur_cnt, female_fur_cnt) 424 | print(f"[green]All {species_name} furs have been updated![/green]") 425 | reserve_details.decompressed.save(config.MOD_DIR_PATH, verbose=verbose) 426 | 427 | def mod_diamonds(reserve_name: str, reserve_details: ParsedAdfFile, species_key: str, diamond_cnt: int, male_fur_keys: List[str], female_fur_keys: List[str]) -> list: 428 | species_details = _species(reserve_name, reserve_details.adf, species_key) 429 | groups = species_details.value["Groups"].value 430 | species_name = config.get_species_name(species_key) 431 | reserve_data = reserve_details.decompressed.data 432 | diamond_gender = config.get_diamond_gender(species_key) 433 | animals = _get_eligible_animals(groups, species_key, diamond_gender) 434 | animals = random.sample(animals, k=diamond_cnt) 435 | 436 | species_config = config.ANIMALS[species_key]["diamonds"] 437 | male_fur_seeds = [config.get_fur_seed(species_key, x, "male") for x in male_fur_keys] 438 | female_fur_seeds = [config.get_fur_seed(species_key, x, "female") for x in female_fur_keys] 439 | for animal in animals: 440 | if diamond_gender == "male": 441 | _create_diamond(animal, species_config, reserve_data, fur=random.choice(male_fur_seeds)) 442 | elif diamond_gender == "female": 443 | _create_diamond(animal, species_config, reserve_data, fur=random.choice(female_fur_seeds)) 444 | else: 445 | if random.choice(["male", "female"]) == "male": 446 | fur = random.choice(male_fur_seeds) if len(male_fur_seeds) > 0 else None 447 | _create_diamond(animal, species_config, reserve_data, fur=fur) 448 | else: 449 | fur = random.choice(female_fur_seeds) if len(female_fur_seeds) > 0 else None 450 | _create_diamond(animal, species_config, reserve_data, fur=fur) 451 | 452 | print(f"[green]All {diamond_cnt} {species_name} diamonds have been added![/green]") 453 | reserve_details.decompressed.save(config.MOD_DIR_PATH) 454 | return describe_reserve(reserve_name, load_reserve(reserve_name, True).adf) 455 | 456 | def mod_animal(reserve_details: ParsedAdfFile, species_key: str, animal: AdfAnimal, go: bool, gender: str, weight: float, score: float, fur_key: str) -> list: 457 | if fur_key == None: 458 | visual_seed = random.choice(config.get_species_furs(species_key, gender, go)) 459 | print("Random:", visual_seed) 460 | else: 461 | visual_seed = config.get_fur_seed(species_key, fur_key, gender, go) 462 | _update_animal(reserve_details.decompressed.data, animal, go, gender, weight, score, visual_seed) 463 | print(f"[green]Animal has been updated![/green]") 464 | reserve_details.decompressed.save(config.MOD_DIR_PATH) 465 | 466 | def mod_animal_cnt(reserve_key:str, reserve_details: ParsedAdfFile, species_key: str, animal_cnt: int, cnt_type: str, gender: str, modded: bool = False) -> list: 467 | species_details = _species(reserve_key, reserve_details.adf, species_key) 468 | groups = species_details.value["Groups"].value 469 | species_name = config.get_species_name(species_key) 470 | 471 | if cnt_type == "add": 472 | _add_animals(groups, reserve_key, species_key, animal_cnt, gender, False, modded) 473 | elif cnt_type == "remove": 474 | _remove_animals(reserve_key, species_key, animal_cnt, gender, False, modded) 475 | print(f"[green]All {animal_cnt} {gender} {species_name} animals have been {'added' if cnt_type == 'add' else 'removed'}![/green]") 476 | return describe_reserve(reserve_key, load_reserve(reserve_key, True).adf) 477 | 478 | def mod(reserve_key: str, reserve_details: ParsedAdfFile, species_key: str, strategy: str, modifier: int = None, percentage: bool = False, rares: bool = False, verbose = False, mod: bool = False): 479 | species_details = _species(reserve_key, reserve_details.adf, species_key) 480 | groups = species_details.value["Groups"].value 481 | species_name = config.get_species_name(species_key) 482 | reserve_data = reserve_details.decompressed.data 483 | 484 | if (strategy == config.Strategy.go_all): 485 | _go_all(species_key, groups, reserve_data) 486 | print(f"[green]All {species_name} are now Great Ones![/green]") 487 | elif (strategy == config.Strategy.go_furs): 488 | _go_furs(species_key, groups, reserve_data) 489 | print(f"[green]All {species_name} Great One furs have been added![/green]") 490 | elif (strategy == config.Strategy.go_some): 491 | _go_some(species_key, groups, reserve_data, modifier, percentage) 492 | print(f"[green]All {modifier}{'%' if percentage else ''} {species_name} are now Great Ones![/green]") 493 | elif (strategy == config.Strategy.diamond_all): 494 | _diamond_all(species_key, groups, reserve_data, rares) 495 | print(f"[green]All {species_name} are now Diamonds![/green]") 496 | elif (strategy == config.Strategy.diamond_furs): 497 | _diamond_furs(species_key, groups, reserve_data) 498 | print(f"[green]All {species_name} are now Diamonds![/green]") 499 | elif (strategy == config.Strategy.diamond_some): 500 | _diamond_some(species_key, groups, reserve_data, modifier, percentage, rares) 501 | print(f"[green]All {modifier}{'%' if percentage else ''} {species_name} are now Diamonds![/green]") 502 | elif (strategy == config.Strategy.males): 503 | _male_some(species_key, groups, reserve_data, modifier, percentage) 504 | print(f"[green]All {modifier}{'%' if percentage else ''} {species_name} are now males![/green]") 505 | elif (strategy == config.Strategy.females): 506 | _female_some(species_key, groups, reserve_data, modifier, percentage) 507 | print(f"[green]All {modifier}{'%' if percentage else ''} {species_name} are now females![/green]") 508 | elif (strategy == config.Strategy.furs_some): 509 | _furs_some(species_key, groups, reserve_data, modifier, percentage) 510 | print(f"[green]All {modifier}{'%' if percentage else ''} {species_name} are now random furs![/green]") 511 | else: 512 | print(f"[red]Unknown strategy: {strategy}") 513 | 514 | reserve_details.decompressed.save(config.MOD_DIR_PATH, verbose=verbose) 515 | return describe_reserve(reserve_key, load_reserve(reserve_key, True, verbose=verbose).adf, verbose=verbose) -------------------------------------------------------------------------------- /apc/utils.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from rich.table import Table 3 | 4 | def list_to_table( 5 | data: list, 6 | table: Table, 7 | ) -> Table: 8 | for row in data: 9 | row = [str(x) for x in row] 10 | table.add_row(*row) 11 | 12 | return table 13 | 14 | def update_uint(data_bytes: bytearray, offset: int, new_value: int) -> None: 15 | value_bytes = new_value.to_bytes(4, byteorder='little') 16 | for i in range(0, len(value_bytes)): 17 | data_bytes[offset + i] = value_bytes[i] 18 | 19 | def update_float(data_bytes: bytearray, offset: int, new_value: float) -> None: 20 | hex_float = struct.pack("f", new_value) 21 | for i in range(0, 4): 22 | data_bytes[offset + i] = hex_float[i] 23 | 24 | def unformat_key(value: str) -> str: 25 | """do not use in production code""" 26 | parts = value.lower().split(" ") 27 | return "_".join(parts) -------------------------------------------------------------------------------- /apcgui.py: -------------------------------------------------------------------------------- 1 | from apcgui.gui import main 2 | 3 | if __name__ == "__main__": 4 | main() -------------------------------------------------------------------------------- /apcgui/__init__.py: -------------------------------------------------------------------------------- 1 | from apc import config 2 | 3 | default_locale, use_languages = config.get_languages() 4 | 5 | __app_name__ = "apcgui" 6 | __version__ = "1.3.5" -------------------------------------------------------------------------------- /apcgui/__main__.py: -------------------------------------------------------------------------------- 1 | from apcgui import gui 2 | 3 | def main(): 4 | gui.main() 5 | 6 | if __name__ == "__main__": 7 | main() -------------------------------------------------------------------------------- /apcgui/logo.py: -------------------------------------------------------------------------------- 1 | value = b'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' -------------------------------------------------------------------------------- /deca/errors.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class EDecaErrorParse(Exception): 4 | def __init__(self, *args, **kwargs): 5 | Exception.__init__(self, *args, **kwargs) 6 | 7 | 8 | class EDecaFileExists(Exception): 9 | def __init__(self, *args, **kwargs): 10 | Exception.__init__(self, *args, **kwargs) 11 | 12 | 13 | class EDecaFileMissing(Exception): 14 | def __init__(self, *args, **kwargs): 15 | Exception.__init__(self, *args, **kwargs) 16 | 17 | 18 | class EDecaBuildError(Exception): 19 | def __init__(self, *args, **kwargs): 20 | Exception.__init__(self, *args, **kwargs) 21 | 22 | 23 | class EDecaIncorrectFileFormat(Exception): 24 | def __init__(self, *args, **kwargs): 25 | Exception.__init__(self, *args, **kwargs) 26 | 27 | 28 | class EDecaOutOfData(Exception): 29 | def __init__(self, *args, **kwargs): 30 | Exception.__init__(self, *args, **kwargs) 31 | 32 | 33 | class EDecaUnknownCompressionType(Exception): 34 | def __init__(self, type_id, *args, **kwargs): 35 | Exception.__init__(self, *args) 36 | self.type_id = type_id 37 | 38 | 39 | class EDecaMissingAdfType(Exception): 40 | def __init__(self, type_id, *args, **kwargs): 41 | Exception.__init__(self, *args) 42 | self.type_id = type_id 43 | -------------------------------------------------------------------------------- /deca/fast_file.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class FFError(Exception): 4 | pass 5 | 6 | 7 | params = { 8 | 'inline': 'always', 9 | 'nogil': True, 10 | } 11 | 12 | def raise_error(): 13 | raise FFError('ff_read: not enough data') 14 | return b'' 15 | 16 | 17 | def ff_read(buffer, n_buffer, pos, n): 18 | # buffer = ff 19 | # n_buffer = len(buffer) 20 | 21 | if n_buffer >= (pos + n): 22 | ret = buffer[pos:(pos + n)] 23 | return ret, pos + n 24 | else: 25 | return raise_error() 26 | 27 | 28 | def make_read_one(data_type): 29 | dt = np.dtype(data_type) 30 | ele_size = dt.itemsize 31 | 32 | def f(buffer, n_buffer, pos): 33 | new_pos = pos + ele_size 34 | if new_pos > n_buffer: 35 | raise_error() 36 | v = np.frombuffer(buffer[pos:new_pos], dtype=dt) 37 | return v[0], new_pos 38 | 39 | return f 40 | 41 | 42 | def make_read_many(data_type): 43 | dt = np.dtype(data_type) 44 | ele_size = dt.itemsize 45 | 46 | def f(buffer, n_buffer, pos, count): 47 | new_pos = pos + ele_size * count 48 | if new_pos > n_buffer: 49 | raise_error() 50 | v = np.frombuffer(buffer[pos:new_pos], dtype=dt) 51 | return list(v), new_pos 52 | 53 | return f 54 | 55 | 56 | ff_read_u8 = make_read_one(np.uint8) 57 | ff_read_s8 = make_read_one(np.int8) 58 | ff_read_u16 = make_read_one(np.uint16) 59 | ff_read_s16 = make_read_one(np.int16) 60 | ff_read_u32 = make_read_one(np.uint32) 61 | ff_read_s32 = make_read_one(np.int32) 62 | ff_read_u64 = make_read_one(np.uint64) 63 | ff_read_s64 = make_read_one(np.int64) 64 | ff_read_f32 = make_read_one(np.float32) 65 | ff_read_f64 = make_read_one(np.float64) 66 | 67 | ff_read_u8s = make_read_many(np.uint8) 68 | ff_read_s8s = make_read_many(np.int8) 69 | ff_read_u16s = make_read_many(np.uint16) 70 | ff_read_s16s = make_read_many(np.int16) 71 | ff_read_u32s = make_read_many(np.uint32) 72 | ff_read_s32s = make_read_many(np.int32) 73 | ff_read_u64s = make_read_many(np.uint64) 74 | ff_read_s64s = make_read_many(np.int64) 75 | ff_read_f32s = make_read_many(np.float32) 76 | ff_read_f64s = make_read_many(np.float64) 77 | 78 | 79 | def ff_read_strz(buffer, n_buffer, pos): 80 | pos0 = pos 81 | while buffer[pos] != 0 and pos < n_buffer: 82 | pos += 1 83 | return buffer[pos0:pos], pos 84 | -------------------------------------------------------------------------------- /deca/file.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from deca.errors import EDecaOutOfData 3 | 4 | 5 | class SubsetFile: 6 | def __init__(self, f, size): 7 | self.f = f 8 | self.f0 = f 9 | self.bpos = f.tell() 10 | self.epos = self.bpos + size 11 | 12 | def __enter__(self): 13 | self.f = self.f0.__enter__() 14 | return self 15 | 16 | def __exit__(self, t, value, traceback): 17 | self.f0.__exit__(t, value, traceback) 18 | 19 | def seek(self, pos): 20 | npos = self.bpos + pos 21 | if npos > self.epos: 22 | raise Exception('Seek Beyond End Of File') 23 | return self.f.seek(npos) 24 | 25 | def tell(self): 26 | return self.f.tell() - self.bpos 27 | 28 | def read(self, n=None): 29 | bpos = self.f.tell() 30 | if n is None: 31 | epos = self.epos 32 | else: 33 | epos = bpos + n 34 | epos = min(epos, self.epos) 35 | return self.f.read(epos - bpos) 36 | 37 | def write(self, blk): 38 | bpos = self.f.tell() 39 | epos = bpos + len(blk) 40 | if epos > self.epos: 41 | raise Exception('Write Beyond End Of File') 42 | return self.f.write(blk) 43 | 44 | 45 | class ArchiveFile: 46 | def __init__(self, f, debug=False, endian=None): 47 | self.f0 = f 48 | self.f = f 49 | self.debug = debug 50 | 51 | def __enter__(self): 52 | self.f = self.f0.__enter__() 53 | return self 54 | 55 | def __exit__(self, t, value, traceback): 56 | self.f0.__exit__(t, value, traceback) 57 | 58 | def seek(self, pos): 59 | return self.f.seek(pos) 60 | 61 | def tell(self): 62 | return self.f.tell() 63 | 64 | def read(self, n=None): 65 | return self.f.read(n) 66 | 67 | def write(self, blk): 68 | return self.f.write(blk) 69 | 70 | def read_strz(self, delim=b'\00'): 71 | eof = False 72 | r = b'' 73 | while True: 74 | v = self.f.read(1) 75 | if len(v) == 0: 76 | eof = True 77 | break 78 | elif v == delim: 79 | break 80 | else: 81 | r = r + v 82 | 83 | if eof: 84 | return None 85 | else: 86 | return r 87 | 88 | def read_base(self, fmt, elen, n, raise_on_no_data): 89 | if n is None: 90 | buf = self.f.read(elen) 91 | if len(buf) != elen: 92 | if raise_on_no_data: 93 | raise EDecaOutOfData() 94 | return None 95 | v = struct.unpack(fmt, buf)[0] 96 | else: 97 | buf = self.f.read(elen * n) 98 | if len(buf) != elen * n: 99 | if raise_on_no_data: 100 | raise EDecaOutOfData() 101 | return None 102 | v = struct.unpack(fmt * n, buf) 103 | 104 | if self.debug: 105 | vs = ['{:02x}'.format(t) for t in buf] 106 | vs = ''.join(vs) 107 | print('{} {}'.format(vs, v)) 108 | 109 | return v 110 | 111 | def read_c8(self, n=None, raise_on_no_data=False): 112 | return self.read_base('c', 1, n, raise_on_no_data) 113 | 114 | def read_strl_u32(self, n=None, raise_on_no_data=False): 115 | if n is None: 116 | sz = self.read_u32(raise_on_no_data=raise_on_no_data) 117 | return self.read_strl(sz, raise_on_no_data=raise_on_no_data) 118 | else: 119 | sl = [] 120 | for i in range(n): 121 | sl.append(self.read_strl_u32(raise_on_no_data=raise_on_no_data)) 122 | return sl 123 | 124 | def read_strl(self, n=None, raise_on_no_data=False): 125 | v = self.read_base('c', 1, n, raise_on_no_data) 126 | return b''.join(v) 127 | 128 | def read_s8(self, n=None, raise_on_no_data=False): 129 | return self.read_base('b', 1, n, raise_on_no_data) 130 | 131 | def read_u8(self, n=None, raise_on_no_data=False): 132 | return self.read_base('B', 1, n, raise_on_no_data) 133 | 134 | def read_s16(self, n=None, raise_on_no_data=False): 135 | return self.read_base('h', 2, n, raise_on_no_data) 136 | 137 | def read_u16(self, n=None, raise_on_no_data=False): 138 | return self.read_base('H', 2, n, raise_on_no_data) 139 | 140 | def read_s32(self, n=None, raise_on_no_data=False): 141 | return self.read_base('i', 4, n, raise_on_no_data) 142 | 143 | def read_u32(self, n=None, raise_on_no_data=False): 144 | return self.read_base('I', 4, n, raise_on_no_data) 145 | 146 | def read_s64(self, n=None, raise_on_no_data=False): 147 | return self.read_base('q', 8, n, raise_on_no_data) 148 | 149 | def read_u64(self, n=None, raise_on_no_data=False): 150 | return self.read_base('Q', 8, n, raise_on_no_data) 151 | 152 | def read_f32(self, n=None, raise_on_no_data=False): 153 | return self.read_base('f', 4, n, raise_on_no_data) 154 | 155 | def read_f64(self, n=None, raise_on_no_data=False): 156 | return self.read_base('d', 8, n, raise_on_no_data) 157 | 158 | def write_base(self, fmt, elen, v): 159 | if isinstance(v, list) or isinstance(v, tuple): 160 | buf = struct.pack(fmt * len(v), *v) 161 | self.f.write(buf) 162 | else: 163 | buf = struct.pack(fmt, v) 164 | self.f.write(buf) 165 | 166 | if self.debug: 167 | vs = ['{:02x}'.format(t) for t in buf] 168 | vs = ''.join(vs) 169 | print('{} {}'.format(vs, v)) 170 | 171 | return None 172 | 173 | def write_c8(self, v): 174 | return self.write_base('c', 1, v) 175 | 176 | def write_strl(self, v, n=None): 177 | return self.write_base('c', 1, v) 178 | 179 | def write_s8(self, v): 180 | return self.write_base('b', 1, v) 181 | 182 | def write_u8(self, v): 183 | return self.write_base('B', 1, v) 184 | 185 | def write_s16(self, v): 186 | return self.write_base('h', 2, v) 187 | 188 | def write_u16(self, v): 189 | return self.write_base('H', 2, v) 190 | 191 | def write_s32(self, v): 192 | return self.write_base('i', 4, v) 193 | 194 | def write_u32(self, v): 195 | return self.write_base('I', 4, v) 196 | 197 | def write_s64(self, v): 198 | return self.write_base('q', 8, v) 199 | 200 | def write_u64(self, v): 201 | return self.write_base('Q', 8, v) 202 | 203 | def write_f32(self, v): 204 | return self.write_base('f', 4, v) 205 | 206 | def write_f64(self, v): 207 | return self.write_base('d', 8, v) 208 | 209 | 210 | -------------------------------------------------------------------------------- /deca/hashes.py: -------------------------------------------------------------------------------- 1 | def rot(x, k): 2 | return (x << k) | (x >> (32 - k)) 3 | 4 | def mix(a, b, c): 5 | a &= 0xffffffff; b &= 0xffffffff; c &= 0xffffffff 6 | a -= c; a &= 0xffffffff; a ^= rot(c,4); a &= 0xffffffff; c += b; c &= 0xffffffff 7 | b -= a; b &= 0xffffffff; b ^= rot(a,6); b &= 0xffffffff; a += c; a &= 0xffffffff 8 | c -= b; c &= 0xffffffff; c ^= rot(b,8); c &= 0xffffffff; b += a; b &= 0xffffffff 9 | a -= c; a &= 0xffffffff; a ^= rot(c,16); a &= 0xffffffff; c += b; c &= 0xffffffff 10 | b -= a; b &= 0xffffffff; b ^= rot(a,19); b &= 0xffffffff; a += c; a &= 0xffffffff 11 | c -= b; c &= 0xffffffff; c ^= rot(b,4); c &= 0xffffffff; b += a; b &= 0xffffffff 12 | return a, b, c 13 | 14 | def final(a, b, c): 15 | a &= 0xffffffff; b &= 0xffffffff; c &= 0xffffffff 16 | c ^= b; c &= 0xffffffff; c -= rot(b,14); c &= 0xffffffff 17 | a ^= c; a &= 0xffffffff; a -= rot(c,11); a &= 0xffffffff 18 | b ^= a; b &= 0xffffffff; b -= rot(a,25); b &= 0xffffffff 19 | c ^= b; c &= 0xffffffff; c -= rot(b,16); c &= 0xffffffff 20 | a ^= c; a &= 0xffffffff; a -= rot(c,4); a &= 0xffffffff 21 | b ^= a; b &= 0xffffffff; b -= rot(a,14); b &= 0xffffffff 22 | c ^= b; c &= 0xffffffff; c -= rot(b,24); c &= 0xffffffff 23 | return a, b, c 24 | 25 | def hashlittle2(data, initval=0, initval2=0): 26 | length = lenpos = len(data) 27 | 28 | a = b = c = (0xdeadbeef + length + initval) 29 | 30 | c += initval2 31 | c &= 0xffffffff 32 | 33 | p = 0 # string offset 34 | while lenpos > 12: 35 | a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); a &= 0xffffffff 36 | b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16) + ((data[p+7])<<24)); b &= 0xffffffff 37 | c += ((data[p+8]) + ((data[p+9])<<8) + ((data[p+10])<<16) + ((data[p+11])<<24)); c &= 0xffffffff 38 | a, b, c = mix(a, b, c) 39 | p += 12 40 | lenpos -= 12 41 | 42 | if lenpos == 12: c += ((data[p+8]) + ((data[p+9])<<8) + ((data[p+10])<<16) + ((data[p+11])<<24)); b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16) + ((data[p+7])<<24)); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 43 | if lenpos == 11: c += ((data[p+8]) + ((data[p+9])<<8) + ((data[p+10])<<16)); b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16) + ((data[p+7])<<24)); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 44 | if lenpos == 10: c += ((data[p+8]) + ((data[p+9])<<8)); b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16) + ((data[p+7])<<24)); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 45 | if lenpos == 9: c += ((data[p+8])); b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16) + ((data[p+7])<<24)); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 46 | if lenpos == 8: b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16) + ((data[p+7])<<24)); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 47 | if lenpos == 7: b += ((data[p+4]) + ((data[p+5])<<8) + ((data[p+6])<<16)); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 48 | if lenpos == 6: b += (((data[p+5])<<8) + (data[p+4])); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)) 49 | if lenpos == 5: b += ((data[p+4])); a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)); 50 | if lenpos == 4: a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16) + ((data[p+3])<<24)) 51 | if lenpos == 3: a += ((data[p+0]) + ((data[p+1])<<8) + ((data[p+2])<<16)) 52 | if lenpos == 2: a += ((data[p+0]) + ((data[p+1])<<8)) 53 | if lenpos == 1: a += (data[p+0]) 54 | a &= 0xffffffff; b &= 0xffffffff; c &= 0xffffffff 55 | if lenpos == 0: return c, b 56 | 57 | a, b, c = final(a, b, c) 58 | 59 | return c, b 60 | 61 | def hash32_func_bytes(data, init_val=0): 62 | c, b = hashlittle2(data, init_val, 0) 63 | return c 64 | 65 | def hash32_func(data, init_val=0): 66 | if isinstance(data, str): 67 | data = data.encode('ascii') 68 | return hash32_func_bytes(data, init_val) -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools", "wheel"] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | typer<0.5.0 2 | rich==12.6.0 3 | numpy==1.23.4 4 | PySimpleGUI==4.60.4 5 | pybabel-json-md==0.1.0 6 | pybabel==0.0.0.dev0 -------------------------------------------------------------------------------- /scans/global_furs.json: -------------------------------------------------------------------------------- 1 | { 2 | "raccoon_dog": { 3 | "male_cnt": 8, 4 | "female_cnt": 8 5 | }, 6 | "feral_goat": { 7 | "male_cnt": 11, 8 | "female_cnt": 5 9 | }, 10 | "beceite_ibex": { 11 | "male_cnt": 6, 12 | "female_cnt": 5 13 | }, 14 | "raccoon": { 15 | "male_cnt": 7, 16 | "female_cnt": 6 17 | }, 18 | "ronda_ibex": { 19 | "male_cnt": 6, 20 | "female_cnt": 5 21 | }, 22 | "bighorn_sheep": { 23 | "male_cnt": 5, 24 | "female_cnt": 5 25 | }, 26 | "coyote": { 27 | "male_cnt": 6, 28 | "female_cnt": 6 29 | }, 30 | "red_deer": { 31 | "male_cnt": 6, 32 | "female_cnt": 6 33 | }, 34 | "springbok": { 35 | "male_cnt": 3, 36 | "female_cnt": 5 37 | }, 38 | "eurasian_lynx": { 39 | "male_cnt": 5, 40 | "female_cnt": 5 41 | }, 42 | "lesser_kudu": { 43 | "male_cnt": 3, 44 | "female_cnt": 5 45 | }, 46 | "jackrabbit": { 47 | "male_cnt": 5, 48 | "female_cnt": 5 49 | }, 50 | "chamois": { 51 | "male_cnt": 7, 52 | "female_cnt": 7 53 | }, 54 | "antelope_jackrabbit": { 55 | "male_cnt": 6, 56 | "female_cnt": 6 57 | }, 58 | "banteng": { 59 | "male_cnt": 6, 60 | "female_cnt": 4 61 | }, 62 | "feral_pig": { 63 | "male_cnt": 10, 64 | "female_cnt": 3 65 | }, 66 | "sidestriped_jackal": { 67 | "male_cnt": 6, 68 | "female_cnt": 6 69 | }, 70 | "eastern_cottontail_rabbit": { 71 | "male_cnt": 8, 72 | "female_cnt": 8 73 | }, 74 | "siberian_weasel": { 75 | "male_cnt": 4, 76 | "female_cnt": 4 77 | }, 78 | "eu_bison": { 79 | "male_cnt": 6, 80 | "female_cnt": 6 81 | }, 82 | "magpie_goose": { 83 | "male_cnt": 8, 84 | "female_cnt": 8 85 | }, 86 | "gemsbok": { 87 | "male_cnt": 5, 88 | "female_cnt": 5 89 | }, 90 | "southeastern_ibex": { 91 | "male_cnt": 6, 92 | "female_cnt": 5 93 | }, 94 | "hazel_grouse": { 95 | "male_cnt": 5, 96 | "female_cnt": 5 97 | }, 98 | "red_fox": { 99 | "male_cnt": 6, 100 | "female_cnt": 6 101 | }, 102 | "black_bear": { 103 | "male_cnt": 6, 104 | "female_cnt": 6 105 | }, 106 | "saltwater_crocodile": { 107 | "male_cnt": 9, 108 | "female_cnt": 9 109 | }, 110 | "willow_ptarmigan": { 111 | "male_cnt": 4, 112 | "female_cnt": 5 113 | }, 114 | "lion": { 115 | "male_cnt": 5, 116 | "female_cnt": 5 117 | }, 118 | "cinnamon_teal": { 119 | "male_cnt": 4, 120 | "female_cnt": 3 121 | }, 122 | "prong_horn": { 123 | "male_cnt": 7, 124 | "female_cnt": 6 125 | }, 126 | "pheasant": { 127 | "male_cnt": 7, 128 | "female_cnt": 5 129 | }, 130 | "eastern_wild_turkey": { 131 | "male_cnt": 7, 132 | "female_cnt": 7 133 | }, 134 | "blackbuck": { 135 | "male_cnt": 4, 136 | "female_cnt": 4 137 | }, 138 | "eu_hare": { 139 | "male_cnt": 6, 140 | "female_cnt": 6 141 | }, 142 | "mountain_hare": { 143 | "male_cnt": 8, 144 | "female_cnt": 8 145 | }, 146 | "gray_wolf": { 147 | "male_cnt": 6, 148 | "female_cnt": 6 149 | }, 150 | "western_capercaillie": { 151 | "male_cnt": 4, 152 | "female_cnt": 4 153 | }, 154 | "moose": { 155 | "male_cnt": 7, 156 | "female_cnt": 6 157 | }, 158 | "blue_wildebeest": { 159 | "male_cnt": 4, 160 | "female_cnt": 4 161 | }, 162 | "caribou": { 163 | "male_cnt": 6, 164 | "female_cnt": 5 165 | }, 166 | "siberian_musk_deer": { 167 | "male_cnt": 5, 168 | "female_cnt": 5 169 | }, 170 | "american_alligator": { 171 | "male_cnt": 9, 172 | "female_cnt": 9 173 | }, 174 | "water_buffalo": { 175 | "male_cnt": 4, 176 | "female_cnt": 5 177 | }, 178 | "goldeneye": { 179 | "male_cnt": 6, 180 | "female_cnt": 4 181 | }, 182 | "northern_bobwhite_quail": { 183 | "male_cnt": 4, 184 | "female_cnt": 2 185 | }, 186 | "stubble_quail": { 187 | "male_cnt": 4, 188 | "female_cnt": 3 189 | }, 190 | "mule_deer": { 191 | "male_cnt": 8, 192 | "female_cnt": 8 193 | }, 194 | "eastern_grey_kangaroo": { 195 | "male_cnt": 10, 196 | "female_cnt": 10 197 | }, 198 | "rock_ptarmigan": { 199 | "male_cnt": 4, 200 | "female_cnt": 5 201 | }, 202 | "green_wing_teal": { 203 | "male_cnt": 5, 204 | "female_cnt": 3 205 | }, 206 | "iberian_wolf": { 207 | "male_cnt": 10, 208 | "female_cnt": 10 209 | }, 210 | "sambar": { 211 | "male_cnt": 9, 212 | "female_cnt": 7 213 | }, 214 | "sika_deer": { 215 | "male_cnt": 6, 216 | "female_cnt": 6 217 | }, 218 | "canada_goose": { 219 | "male_cnt": 6, 220 | "female_cnt": 6 221 | }, 222 | "axis_deer": { 223 | "male_cnt": 5, 224 | "female_cnt": 5 225 | }, 226 | "scrub_hare": { 227 | "male_cnt": 4, 228 | "female_cnt": 4 229 | }, 230 | "bobcat": { 231 | "male_cnt": 7, 232 | "female_cnt": 7 233 | }, 234 | "gray_fox": { 235 | "male_cnt": 8, 236 | "female_cnt": 8 237 | }, 238 | "greylag_goose": { 239 | "male_cnt": 8, 240 | "female_cnt": 8 241 | }, 242 | "eu_rabbit": { 243 | "male_cnt": 8, 244 | "female_cnt": 8 245 | }, 246 | "eurasian_brown_bear": { 247 | "male_cnt": 11, 248 | "female_cnt": 11 249 | }, 250 | "eurasian_wigeon": { 251 | "male_cnt": 6, 252 | "female_cnt": 5 253 | }, 254 | "unknown": { 255 | "male_cnt": 1, 256 | "female_cnt": 1 257 | }, 258 | "harlequin_duck": { 259 | "male_cnt": 4, 260 | "female_cnt": 5 261 | }, 262 | "red_squirrel": { 263 | "male_cnt": 4, 264 | "female_cnt": 0 265 | }, 266 | "black_grouse": { 267 | "male_cnt": 6, 268 | "female_cnt": 4 269 | }, 270 | "wild_boar": { 271 | "male_cnt": 6, 272 | "female_cnt": 6 273 | }, 274 | "hog_deer": { 275 | "male_cnt": 6, 276 | "female_cnt": 5 277 | }, 278 | "roe_deer": { 279 | "male_cnt": 6, 280 | "female_cnt": 6 281 | }, 282 | "mountain_goat": { 283 | "male_cnt": 6, 284 | "female_cnt": 6 285 | }, 286 | "whitetail_deer": { 287 | "male_cnt": 6, 288 | "female_cnt": 6 289 | }, 290 | "eurasian_teal": { 291 | "male_cnt": 6, 292 | "female_cnt": 2 293 | }, 294 | "reindeer": { 295 | "male_cnt": 6, 296 | "female_cnt": 6 297 | }, 298 | "tufted_duck": { 299 | "male_cnt": 5, 300 | "female_cnt": 4 301 | }, 302 | "plains_bison": { 303 | "male_cnt": 7, 304 | "female_cnt": 7 305 | }, 306 | "rio_grande_turkey": { 307 | "male_cnt": 7, 308 | "female_cnt": 7 309 | }, 310 | "warthog": { 311 | "male_cnt": 4, 312 | "female_cnt": 5 313 | }, 314 | "wild_turkey": { 315 | "male_cnt": 6, 316 | "female_cnt": 6 317 | }, 318 | "javan_rusa": { 319 | "male_cnt": 8, 320 | "female_cnt": 6 321 | }, 322 | "mexican_bobcat": { 323 | "male_cnt": 7, 324 | "female_cnt": 7 325 | }, 326 | "tundra_bean_goose": { 327 | "male_cnt": 6, 328 | "female_cnt": 6 329 | }, 330 | "grizzly_bear": { 331 | "male_cnt": 4, 332 | "female_cnt": 4 333 | }, 334 | "blacktail_deer": { 335 | "male_cnt": 6, 336 | "female_cnt": 6 337 | }, 338 | "iberian_mouflon": { 339 | "male_cnt": 6, 340 | "female_cnt": 5 341 | }, 342 | "homo_sapien": { 343 | "male_cnt": 1, 344 | "female_cnt": 1 345 | }, 346 | "fallow_deer": { 347 | "male_cnt": 11, 348 | "female_cnt": 6 349 | }, 350 | "roosevelt_elk": { 351 | "male_cnt": 6, 352 | "female_cnt": 6 353 | }, 354 | "collared_peccary": { 355 | "male_cnt": 8, 356 | "female_cnt": 8 357 | }, 358 | "cape_buffalo": { 359 | "male_cnt": 5, 360 | "female_cnt": 5 361 | }, 362 | "mallard": { 363 | "male_cnt": 5, 364 | "female_cnt": 5 365 | }, 366 | "rockymountain_elk": { 367 | "male_cnt": 6, 368 | "female_cnt": 6 369 | }, 370 | "puma": { 371 | "male_cnt": 5, 372 | "female_cnt": 5 373 | }, 374 | "gredos_ibex": { 375 | "male_cnt": 6, 376 | "female_cnt": 5 377 | } 378 | } -------------------------------------------------------------------------------- /scripts/apc.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | del "%CD%\apc\config\save_path.txt" 3 | 4 | del /q "%CD%\dist\apc-*" 5 | python -m build -------------------------------------------------------------------------------- /scripts/apc_pack.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | del /q "%CD%\dist\wheel.7z" 3 | "C:\Program Files\7-Zip\7z.exe" a "%CD%\dist\wheel.7z" "%CD%\dist\*.whl" -------------------------------------------------------------------------------- /scripts/apcgui.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | pyinstaller --noconsole --add-data "%CD%\apc\config;config" --add-data "%CD%\apc\locale;locale" apcgui.py -------------------------------------------------------------------------------- /scripts/apcgui_pack.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | del /q "%CD%\dist\apcgui.7z" 3 | "C:\Program Files\7-Zip\7z.exe" a "%CD%\dist\apcgui.7z" "%CD%\dist\apcgui" -------------------------------------------------------------------------------- /scripts/compile.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | pybabel compile --domain=apc --directory="%CD%\apc\locale" -------------------------------------------------------------------------------- /scripts/extract.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | pybabel extract -o %CD%/apc/locale/apc.pot -F %CD%\apc\babel.cfg -k translate apc -------------------------------------------------------------------------------- /scripts/init.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | pybabel init -l en_US -i "%CD%\apc\locale\apc.pot" -d "%CD%\apc\locale" -D apc 3 | pybabel init -l de_DE -i "%CD%\apc\locale\apc.pot" -d "%CD%\apc\locale" -D apc 4 | pybabel init -l zh_CN -i "%CD%\apc\locale\apc.pot" -d "%CD%\apc\locale" -D apc 5 | pybabel init -l ru_RU -i "%CD%\apc\locale\apc.pot" -d "%CD%\apc\locale" -D apc 6 | pybabel init -l es_MX -i "%CD%\apc\locale\apc.pot" -d "%CD%\apc\locale" -D apc -------------------------------------------------------------------------------- /scripts/update.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | pybabel extract -o "%CD%/apc/locale/apc.pot" -F "%CD%\apc\babel.cfg" -k translate apc 3 | pybabel update --domain=apc -d "%CD%\apc\locale" -i "%CD%\apc\locale\apc.pot" --no-fuzzy-matching 4 | -------------------------------------------------------------------------------- /scripts/upgrade_pysimplegui.bat: -------------------------------------------------------------------------------- 1 | python -m PySimpleGUI.PySimpleGUI upgrade -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = apc 3 | version = 1.3.5 4 | url = https://github.com/rollerb/apc 5 | 6 | [options] 7 | packages = 8 | apc 9 | apcgui 10 | deca 11 | install_requires = 12 | typer<0.5.0 13 | rich==12.6.0 14 | numpy==1.23.4 15 | PySimpleGUI==4.60.4 16 | pybabel==0.0.0.dev0 17 | include_package_data = True 18 | 19 | [options.entry_points] 20 | console_scripts = 21 | apc=apc.__main__:main 22 | apcgui=apcgui.__main__:main -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() --------------------------------------------------------------------------------