 5f73c245fc
			
		
	
	5f73c245fc
	
	
	
		
			
			* Init * remove submodule * Init * Update docs * Fix tests * Update to use apcivvi * Update Readme and codeowners * Minor changes * Remove .value from options (except starting hint) * Minor updates * remove unnecessary property * Cleanup Rules and Region * Fix output file generation * Implement feedback * Remove 'AP' tag and fix issue with format strings and using same quotes * Update worlds/civ_6/__init__.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Minor docs changes * minor updates * Small rework of create items * Minor updates * Remove unused variable * Move client to Launcher Components with rest of similar clients * Revert "Move client to Launcher Components with rest of similar clients" This reverts commit f9fd5df9fdf19eaf4f1de54e21e3c33a74f02364. * modify component * Fix generation issues * Fix tests * Minor change * Add improvement and test case * Minor options changes * . * Preliminary Review * Fix failing test due to slot data serialization * Format json * Remove exclude missable boosts * Update options (update goody hut text, make research multiplier a range) * Update docs punctuation and slot data init * Move priority/excluded locations into options * Implement docs PR feedback * PR Feedback for options * PR feedback misc * Update location classification and fix client type * Fix typings * Update research cost multiplier * Remove unnecessary location priority code * Remove extrenous use of items() * WIP PR Feedback * WIP PR Feedback * Add victory event * Add option set for death link effect * PR improvements * Update post fill hint to support items with multiple classifications * remove unnecessary len * Move location exclusion logic * Update test to use set instead of accidental dict * Update docs around progressive eras and boost locations * Update docs for options to be more readable * Fix issue with filler items and prehints * Update filler_data to be static * Update links in docs * Minor updates and PR feedback * Update boosts data * Update era required items * Update existing techs * Update existing techs * move boost data class * Update reward data * Update prereq data * Update new items and progressive districts * Remove unused code * Make filler item name func more efficient * Update death link text * Move Civ6 to the end of readme * Fix bug with hidden locations and location.name * Partial PR Feedback Implementation * Format changes * Minor review feedback * Modify access rules to use list created in generate_early * Modify boost rules to precalculate requirements * Remove option checks from access rules * Fix issue with pre initialized dicts * Add inno setup for civ6 client * Update inno_setup.iss --------- Co-authored-by: Scipio Wright <scipiowright@gmail.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Exempt-Medic <ExemptMedic@Gmail.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
		
			
				
	
	
		
			327 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from collections import defaultdict
 | |
| import math
 | |
| import os
 | |
| from typing import Any, Dict, List, Set
 | |
| 
 | |
| from .ProgressiveDistricts import get_flat_progressive_districts
 | |
| from worlds.generic.Rules import forbid_item
 | |
| 
 | |
| 
 | |
| from .Data import (
 | |
|     get_boosts_data,
 | |
|     get_era_required_items_data,
 | |
| )
 | |
| 
 | |
| from .Rules import create_boost_rules
 | |
| from .Container import (
 | |
|     CivVIContainer,
 | |
|     generate_goody_hut_sql,
 | |
|     generate_new_items,
 | |
|     generate_setup_file,
 | |
|     generate_update_boosts_sql,
 | |
| )
 | |
| from .Enum import CivVICheckType, CivVIHintClassification
 | |
| from .Items import (
 | |
|     BOOSTSANITY_PROGRESSION_ITEMS,
 | |
|     FILLER_DISTRIBUTION,
 | |
|     CivVIEvent,
 | |
|     CivVIItemData,
 | |
|     FillerItemRarity,
 | |
|     format_item_name,
 | |
|     generate_item_table,
 | |
|     CivVIItem,
 | |
|     get_item_by_civ_name,
 | |
|     get_random_filler_by_rarity,
 | |
| )
 | |
| from .Locations import (
 | |
|     CivVILocation,
 | |
|     CivVILocationData,
 | |
|     EraType,
 | |
|     generate_era_location_table,
 | |
|     generate_flat_location_table,
 | |
| )
 | |
| from .Options import CivVIOptions
 | |
| from .Regions import create_regions
 | |
| from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial
 | |
| from worlds.AutoWorld import World, WebWorld
 | |
| from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess  # type: ignore
 | |
| 
 | |
| 
 | |
| def run_client(*args: Any):
 | |
|     print("Running Civ6 Client")
 | |
|     from .Civ6Client import main  # lazy import
 | |
| 
 | |
|     launch_subprocess(main, name="Civ6Client")
 | |
| 
 | |
| 
 | |
| components.append(
 | |
|     Component(
 | |
|         "Civ6 Client",
 | |
|         func=run_client,
 | |
|         component_type=Type.CLIENT,
 | |
|         file_identifier=SuffixIdentifier(".apcivvi"),
 | |
|     )
 | |
| )
 | |
| 
 | |
| 
 | |
| class CivVIWeb(WebWorld):
 | |
|     tutorials = [
 | |
|         Tutorial(
 | |
|             "Multiworld Setup Guide",
 | |
|             "A guide to setting up Civilization VI for MultiWorld.",
 | |
|             "English",
 | |
|             "setup_en.md",
 | |
|             "setup/en",
 | |
|             ["hesto2"],
 | |
|         )
 | |
|     ]
 | |
|     theme = "ocean"
 | |
| 
 | |
| 
 | |
| class CivVIWorld(World):
 | |
|     """
 | |
|     Civilization VI is a turn-based strategy video game in which one or more players compete alongside computer-controlled opponents to grow their individual civilization from a small tribe to control the entire planet across several periods of development.
 | |
|     """
 | |
| 
 | |
|     game = "Civilization VI"
 | |
|     topology_present = False
 | |
|     options_dataclass = CivVIOptions
 | |
|     options: CivVIOptions  # type: ignore
 | |
| 
 | |
|     web = CivVIWeb()
 | |
| 
 | |
|     item_name_to_id = {item.name: item.code for item in generate_item_table().values()}
 | |
|     location_name_to_id = {
 | |
|         location.name: location.code
 | |
|         for location in generate_flat_location_table().values()
 | |
|     }
 | |
| 
 | |
|     item_table: Dict[str, CivVIItemData] = {}
 | |
|     location_by_era: Dict[str, Dict[str, CivVILocationData]]
 | |
|     required_client_version = (0, 4, 5)
 | |
|     location_table: Dict[str, CivVILocationData]
 | |
|     era_required_non_progressive_items: Dict[EraType, List[str]]
 | |
|     era_required_progressive_items_counts: Dict[EraType, Dict[str, int]]
 | |
|     era_required_progressive_era_counts: Dict[EraType, int]
 | |
|     item_by_civ_name: Dict[str, str]
 | |
| 
 | |
|     def __init__(self, multiworld: MultiWorld, player: int):
 | |
|         super().__init__(multiworld, player)
 | |
|         self.location_by_era = generate_era_location_table()
 | |
| 
 | |
|         self.location_table: Dict[str, CivVILocationData] = {}
 | |
|         self.item_table = generate_item_table()
 | |
| 
 | |
|         self.era_required_non_progressive_items = {}
 | |
|         self.era_required_progressive_items_counts = {}
 | |
|         self.era_required_progressive_era_counts = {}
 | |
| 
 | |
|         for locations in self.location_by_era.values():
 | |
|             for location in locations.values():
 | |
|                 self.location_table[location.name] = location
 | |
| 
 | |
|     def generate_early(self) -> None:
 | |
|         flat_progressive_items = get_flat_progressive_districts()
 | |
| 
 | |
|         self.item_by_civ_name = {
 | |
|             item.civ_name: get_item_by_civ_name(item.civ_name, self.item_table).name
 | |
|             for item in self.item_table.values()
 | |
|             if item.civ_name
 | |
|         }
 | |
| 
 | |
|         previous_era_counts = None
 | |
|         eras_list = [e.value for e in EraType]
 | |
|         for era in EraType:
 | |
|             # Initialize era_required_progressive_era_counts
 | |
|             era_index = eras_list.index(era.value)
 | |
|             self.era_required_progressive_era_counts[era] = (
 | |
|                 0
 | |
|                 if era in {EraType.ERA_FUTURE, EraType.ERA_INFORMATION}
 | |
|                 else era_index + 1
 | |
|             )
 | |
| 
 | |
|             # Initialize era_required_progressive_items_counts
 | |
|             self.era_required_progressive_items_counts[era] = defaultdict(int)
 | |
| 
 | |
|             if previous_era_counts:
 | |
|                 self.era_required_progressive_items_counts[era].update(
 | |
|                     previous_era_counts
 | |
|                 )
 | |
| 
 | |
|             # Initialize era_required_non_progressive_items and add to item counts
 | |
|             self.era_required_non_progressive_items[era] = []
 | |
| 
 | |
|             for item in get_era_required_items_data()[era.value]:
 | |
|                 if (
 | |
|                     item in flat_progressive_items
 | |
|                     and self.options.progression_style != "none"
 | |
|                 ):
 | |
|                     progressive_name = format_item_name(flat_progressive_items[item])
 | |
|                     self.era_required_progressive_items_counts[era][
 | |
|                         progressive_name
 | |
|                     ] += 1
 | |
|                 else:
 | |
|                     self.era_required_non_progressive_items[era].append(
 | |
|                         self.item_by_civ_name[item]
 | |
|                     )
 | |
| 
 | |
|             previous_era_counts = self.era_required_progressive_items_counts[era].copy()
 | |
| 
 | |
|     def get_filler_item_name(self) -> str:
 | |
|         return get_random_filler_by_rarity(self, FillerItemRarity.COMMON).name
 | |
| 
 | |
|     def create_regions(self) -> None:
 | |
|         create_regions(self)
 | |
| 
 | |
|     def set_rules(self) -> None:
 | |
|         if self.options.boostsanity:
 | |
|             create_boost_rules(self)
 | |
| 
 | |
|     def create_event(self, event: str):
 | |
|         return CivVIEvent(event, ItemClassification.progression, None, self.player)
 | |
| 
 | |
|     def create_item(self, name: str) -> Item:
 | |
|         item: CivVIItemData = self.item_table[name]
 | |
|         classification = item.classification
 | |
|         if self.options.boostsanity:
 | |
|             if item.civ_name in BOOSTSANITY_PROGRESSION_ITEMS:
 | |
|                 classification = ItemClassification.progression
 | |
| 
 | |
|         return CivVIItem(item, self.player, classification)
 | |
| 
 | |
|     def create_items(self) -> None:
 | |
|         data = get_era_required_items_data()
 | |
|         early_items = data[EraType.ERA_ANCIENT.value]
 | |
|         early_locations = [
 | |
|             location
 | |
|             for location in self.location_table.values()
 | |
|             if location.era_type == EraType.ERA_ANCIENT.value
 | |
|         ]
 | |
|         for item_name, item_data in self.item_table.items():
 | |
|             # These item types are handled individually
 | |
|             if item_data.item_type in [
 | |
|                 CivVICheckType.PROGRESSIVE_DISTRICT,
 | |
|                 CivVICheckType.ERA,
 | |
|                 CivVICheckType.GOODY,
 | |
|             ]:
 | |
|                 continue
 | |
| 
 | |
|             # If we're using progressive districts, we need to check if we need to create a different item instead
 | |
|             item_to_create = item_name
 | |
|             item: CivVIItemData = self.item_table[item_name]
 | |
|             if self.options.progression_style != "none":
 | |
|                 if item.progressive_name:
 | |
|                     item_to_create = self.item_table[item.progressive_name].name
 | |
| 
 | |
|             self.multiworld.itempool += [self.create_item(item_to_create)]
 | |
|             if item.civ_name in early_items:
 | |
|                 self.multiworld.early_items[self.player][item_to_create] = 1
 | |
|             elif self.item_table[item_name].era in [
 | |
|                 EraType.ERA_ATOMIC,
 | |
|                 EraType.ERA_INFORMATION,
 | |
|                 EraType.ERA_FUTURE,
 | |
|             ]:
 | |
|                 for location in early_locations:
 | |
|                     found_location = None
 | |
|                     try:
 | |
|                         found_location = self.get_location(location.name)
 | |
|                         forbid_item(found_location, item_to_create, self.player)
 | |
|                     except KeyError:
 | |
|                         pass
 | |
| 
 | |
|         # Era items
 | |
|         if self.options.progression_style == "eras_and_districts":
 | |
|             # Add one less than the total number of eras (start in ancient, don't need to find it)
 | |
|             for era in EraType:
 | |
|                 if era.value == "ERA_ANCIENT":
 | |
|                     continue
 | |
|                 progressive_era_item = self.item_table.get("Progressive Era")
 | |
|                 assert progressive_era_item is not None
 | |
|                 self.multiworld.itempool += [
 | |
|                     self.create_item(progressive_era_item.name)
 | |
|                 ]
 | |
| 
 | |
|             self.multiworld.early_items[self.player]["Progressive Era"] = 2
 | |
| 
 | |
|         num_filler_items = 0
 | |
|         # Goody items, create 10 by default if options are enabled
 | |
|         if self.options.shuffle_goody_hut_rewards:
 | |
|             num_filler_items += 10
 | |
| 
 | |
|         if self.options.boostsanity:
 | |
|             num_filler_items += len(get_boosts_data())
 | |
| 
 | |
|         filler_count = {
 | |
|             rarity: math.ceil(FILLER_DISTRIBUTION[rarity] * num_filler_items)
 | |
|             for rarity in FillerItemRarity.__reversed__()
 | |
|         }
 | |
|         filler_count[FillerItemRarity.COMMON] -= (
 | |
|             sum(filler_count.values()) - num_filler_items
 | |
|         )
 | |
|         self.multiworld.itempool += [
 | |
|             self.create_item(get_random_filler_by_rarity(self, rarity).name)
 | |
|             for rarity, count in filler_count.items()
 | |
|             for _ in range(count)
 | |
|         ]
 | |
| 
 | |
|     def post_fill(self) -> None:
 | |
|         if not self.options.pre_hint_items.value:
 | |
|             return
 | |
| 
 | |
|         def is_hintable_filler_item(item: Item) -> bool:
 | |
|             return (
 | |
|                 item.classification == 0
 | |
|                 and CivVIHintClassification.FILLER.value
 | |
|                 in self.options.pre_hint_items.value
 | |
|             )
 | |
| 
 | |
|         start_location_hints: Set[str] = self.options.start_location_hints.value
 | |
|         non_filler_flags = [
 | |
|             CivVIHintClassification(flag).to_item_classification()
 | |
|             for flag in self.options.pre_hint_items.value
 | |
|             if flag != CivVIHintClassification.FILLER.value
 | |
|         ]
 | |
|         for location_name, location_data in self.location_table.items():
 | |
|             if (
 | |
|                 location_data.location_type != CivVICheckType.CIVIC
 | |
|                 and location_data.location_type != CivVICheckType.TECH
 | |
|             ):
 | |
|                 continue
 | |
| 
 | |
|             location: CivVILocation = self.get_location(location_name)  # type: ignore
 | |
| 
 | |
|             if location.item and (
 | |
|                 is_hintable_filler_item(location.item)
 | |
|                 or any(
 | |
|                     flag in location.item.classification for flag in non_filler_flags
 | |
|                 )
 | |
|             ):
 | |
|                 start_location_hints.add(location_name)
 | |
| 
 | |
|     def fill_slot_data(self) -> Dict[str, Any]:
 | |
|         return self.options.as_dict(
 | |
|             "progression_style",
 | |
|             "death_link",
 | |
|             "research_cost_multiplier",
 | |
|             "death_link_effect",
 | |
|             "death_link_effect_percent",
 | |
|         )
 | |
| 
 | |
|     def generate_output(self, output_directory: str):
 | |
|         mod_name = self.multiworld.get_out_file_name_base(self.player)
 | |
|         mod_dir = os.path.join(output_directory, mod_name)
 | |
|         mod_files = {
 | |
|             f"NewItems.xml": generate_new_items(self),
 | |
|             f"InitOptions.lua": generate_setup_file(self),
 | |
|             f"GoodyHutOverride.sql": generate_goody_hut_sql(self),
 | |
|             f"UpdateExistingBoosts.sql": generate_update_boosts_sql(self),
 | |
|         }
 | |
|         mod = CivVIContainer(
 | |
|             mod_files,
 | |
|             mod_dir,
 | |
|             output_directory,
 | |
|             self.player,
 | |
|             self.multiworld.get_file_safe_player_name(self.player),
 | |
|         )
 | |
|         mod.write()
 |