New Game Implementation: Civilization VI (#3736)

* 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>
This commit is contained in:
Carter Hesterman
2025-03-10 07:53:26 -06:00
committed by GitHub
parent 21ffc0fc54
commit 5f73c245fc
37 changed files with 6111 additions and 0 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
*_Spoiler.txt
*.bmbp
*.apbp
*.apcivvi
*.apl2ac
*.apm3
*.apmc

View File

@@ -80,6 +80,7 @@ Currently, the following games are supported:
* Saving Princess
* Castlevania: Circle of the Moon
* Inscryption
* Civilization VI
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@@ -45,6 +45,9 @@
# ChecksFinder
/worlds/checksfinder/ @SunCatMC
# Civilization VI
/worlds/civ6/ @hesto2
# Clique
/worlds/clique/ @ThePhar

View File

@@ -221,6 +221,11 @@ Root: HKCR; Subkey: "{#MyAppName}ygo06patch"; ValueData: "Ar
Root: HKCR; Subkey: "{#MyAppName}ygo06patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}ygo06patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: "";
Root: HKCR; Subkey: ".apcivvi"; ValueData: "{#MyAppName}apcivvipatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}apcivvipatch"; ValueData: "Archipelago Civilization 6 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}apcivvipatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLauncher.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}apcivvipatch\shell\open\command"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1"""; ValueType: string; ValueName: "";
Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: "";

342
worlds/civ_6/Civ6Client.py Normal file
View File

@@ -0,0 +1,342 @@
import asyncio
import logging
import os
import traceback
from typing import Any, Dict, List, Optional
import zipfile
from CommonClient import ClientCommandProcessor, CommonContext, get_base_parser, logger, server_loop, gui_enabled
from .Data import get_progressive_districts_data
from .DeathLink import handle_check_deathlink
from NetUtils import ClientStatus
import Utils
from .CivVIInterface import CivVIInterface, ConnectionState
from .Enum import CivVICheckType
from .Items import CivVIItemData, generate_item_table, get_item_by_civ_name
from .Locations import CivVILocationData, generate_era_location_table
from .TunerClient import TunerErrorException, TunerTimeoutException
class CivVICommandProcessor(ClientCommandProcessor):
def __init__(self, ctx: CommonContext):
super().__init__(ctx)
def _cmd_deathlink(self):
"""Toggle deathlink from client. Overrides default setting."""
if isinstance(self.ctx, CivVIContext):
self.ctx.death_link_enabled = not self.ctx.death_link_enabled
self.ctx.death_link_just_changed = True
Utils.async_start(self.ctx.update_death_link(
self.ctx.death_link_enabled), name="Update Deathlink")
self.ctx.logger.info(f"Deathlink is now {'enabled' if self.ctx.death_link_enabled else 'disabled'}")
def _cmd_resync(self):
"""Resends all items to client, and has client resend all locations to server. This can take up to a minute if the player has received a lot of items"""
if isinstance(self.ctx, CivVIContext):
logger.info("Resyncing...")
asyncio.create_task(self.ctx.resync())
def _cmd_toggle_progressive_eras(self):
"""If you get stuck for some reason and unable to continue your game, you can run this command to disable the defeat that comes from pushing past the max unlocked era """
if isinstance(self.ctx, CivVIContext):
print("Toggling progressive eras, stand by...")
self.ctx.is_pending_toggle_progressive_eras = True
class CivVIContext(CommonContext):
is_pending_death_link_reset = False
is_pending_toggle_progressive_eras = False
command_processor = CivVICommandProcessor
game = "Civilization VI"
items_handling = 0b111
tuner_sync_task: Optional[asyncio.Task[None]] = None
game_interface: CivVIInterface
location_name_to_civ_location: Dict[str, CivVILocationData] = {}
location_name_to_id: Dict[str, int] = {}
item_id_to_civ_item: Dict[int, CivVIItemData] = {}
item_table: Dict[str, CivVIItemData] = {}
processing_multiple_items = False
received_death_link = False
death_link_message = ""
death_link_enabled = False
slot_data: Dict[str, Any]
death_link_just_changed = False
# Used to prevent the deathlink from triggering when someone re enables it
logger = logger
progressive_items_by_type = get_progressive_districts_data()
item_name_to_id = {
item.name: item.code for item in generate_item_table().values()}
connection_state = ConnectionState.DISCONNECTED
def __init__(self, server_address: Optional[str], password: Optional[str], apcivvi_file: Optional[str] = None):
super().__init__(server_address, password)
self.slot_data: Dict[str, Any] = {}
self.game_interface = CivVIInterface(logger)
location_by_era = generate_era_location_table()
self.item_table = generate_item_table()
self.apcivvi_file = apcivvi_file
# Get tables formatted in a way that is easier to use here
for locations in location_by_era.values():
for location in locations.values():
self.location_name_to_id[location.name] = location.code
self.location_name_to_civ_location[location.name] = location
for item in self.item_table.values():
self.item_id_to_civ_item[item.code] = item
async def resync(self):
if self.processing_multiple_items:
logger.info(
"Waiting for items to finish processing, try again later")
return
await self.game_interface.resync()
await handle_receive_items(self, -1)
logger.info("Resynced")
def on_deathlink(self, data: Utils.Dict[str, Utils.Any]) -> None:
super().on_deathlink(data)
text = data.get("cause", "")
if text:
message = text
else:
message = f"Received from {data['source']}"
self.death_link_message = message
self.received_death_link = True
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(CivVIContext, self).server_auth(password_requested)
await self.get_username()
self.tags = set()
await self.send_connect()
def run_gui(self):
from kvui import GameManager
class CivVIManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago Civilization VI Client"
self.ui = CivVIManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def on_package(self, cmd: str, args: Dict[str, Any]):
if cmd == "Connected":
self.slot_data = args["slot_data"]
if "death_link" in args["slot_data"]:
self.death_link_enabled = bool(args["slot_data"]["death_link"])
Utils.async_start(self.update_death_link(
bool(args["slot_data"]["death_link"])))
def update_connection_status(ctx: CivVIContext, status: ConnectionState):
if ctx.connection_state == status:
return
elif status == ConnectionState.IN_GAME:
ctx.logger.info("Connected to Civ VI")
elif status == ConnectionState.IN_MENU:
ctx.logger.info("Connected to Civ VI, waiting for game to start")
elif status == ConnectionState.DISCONNECTED:
ctx.logger.info("Disconnected from Civ VI, attempting to reconnect...")
ctx.connection_state = status
async def tuner_sync_task(ctx: CivVIContext):
logger.info("Starting CivVI connector")
while not ctx.exit_event.is_set():
if not ctx.slot:
await asyncio.sleep(3)
continue
else:
try:
if ctx.processing_multiple_items:
await asyncio.sleep(3)
else:
state = await ctx.game_interface.is_in_game()
update_connection_status(ctx, state)
if state == ConnectionState.IN_GAME:
await _handle_game_ready(ctx)
else:
await asyncio.sleep(3)
except TunerTimeoutException:
logger.error(
"Timeout occurred while receiving data from Civ VI, this usually isn't a problem unless you see it repeatedly")
await asyncio.sleep(3)
except Exception as e:
if isinstance(e, TunerErrorException):
logger.debug(str(e))
else:
logger.debug(traceback.format_exc())
await asyncio.sleep(3)
continue
async def handle_toggle_progressive_eras(ctx: CivVIContext):
if ctx.is_pending_toggle_progressive_eras:
ctx.is_pending_toggle_progressive_eras = False
current = await ctx.game_interface.get_max_allowed_era()
if current > -1:
await ctx.game_interface.set_max_allowed_era(-1)
logger.info("Disabled progressive eras")
else:
count = 0
for _, network_item in enumerate(ctx.items_received):
item: CivVIItemData = ctx.item_id_to_civ_item[network_item.item]
if item.item_type == CivVICheckType.ERA:
count += 1
await ctx.game_interface.set_max_allowed_era(count)
logger.info(f"Enabled progressive eras, set to {count}")
async def handle_checked_location(ctx: CivVIContext):
checked_locations = await ctx.game_interface.get_checked_locations()
checked_location_ids = [location.code for location_name, location in ctx.location_name_to_civ_location.items(
) if location_name in checked_locations]
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": checked_location_ids}])
async def handle_receive_items(ctx: CivVIContext, last_received_index_override: Optional[int] = None):
try:
last_received_index = last_received_index_override or await ctx.game_interface.get_last_received_index()
if len(ctx.items_received) - last_received_index > 1:
ctx.processing_multiple_items = True
progressive_districts: List[CivVIItemData] = []
progressive_eras: List[CivVIItemData] = []
for index, network_item in enumerate(ctx.items_received):
# Track these separately so if we replace "PROGRESSIVE_DISTRICT" with a specific tech, we can still check if need to add it to the list of districts
item: CivVIItemData = ctx.item_id_to_civ_item[network_item.item]
item_to_send: CivVIItemData = ctx.item_id_to_civ_item[network_item.item]
if index > last_received_index:
if item.item_type == CivVICheckType.PROGRESSIVE_DISTRICT and item.civ_name:
# if the item is progressive, then check how far in that progression type we are and send the appropriate item
count = sum(
1 for count_item in progressive_districts if count_item.civ_name == item.civ_name)
if count >= len(ctx.progressive_items_by_type[item.civ_name]):
logger.error(
f"Received more progressive items than expected for {item.civ_name}")
continue
item_civ_name = ctx.progressive_items_by_type[item.civ_name][count]
actual_item_name = get_item_by_civ_name(item_civ_name, ctx.item_table).name
item_to_send = ctx.item_table[actual_item_name]
sender = ctx.player_names[network_item.player]
if item.item_type == CivVICheckType.ERA:
count = len(progressive_eras) + 1
await ctx.game_interface.give_item_to_player(item_to_send, sender, count)
elif item.item_type == CivVICheckType.GOODY and item_to_send.civ_name:
await ctx.game_interface.give_item_to_player(item_to_send, sender, game_id_override=item_to_send.civ_name)
else:
await ctx.game_interface.give_item_to_player(item_to_send, sender)
await asyncio.sleep(0.02)
if item.item_type == CivVICheckType.PROGRESSIVE_DISTRICT:
progressive_districts.append(item)
elif item.item_type == CivVICheckType.ERA:
progressive_eras.append(item)
ctx.processing_multiple_items = False
finally:
# If something errors out, then unblock item processing
ctx.processing_multiple_items = False
async def handle_check_goal_complete(ctx: CivVIContext):
if ctx.finished_game:
return
result = await ctx.game_interface.check_victory()
if result:
logger.info("Sending Victory to server!")
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
async def _handle_game_ready(ctx: CivVIContext):
if ctx.server:
if not ctx.slot:
await asyncio.sleep(3)
return
await handle_receive_items(ctx)
await handle_checked_location(ctx)
await handle_check_goal_complete(ctx)
if ctx.death_link_enabled:
await handle_check_deathlink(ctx)
# process pending commands
await handle_toggle_progressive_eras(ctx)
await asyncio.sleep(3)
else:
logger.info("Waiting for player to connect to server")
await asyncio.sleep(3)
def main(connect: Optional[str] = None, password: Optional[str] = None, name: Optional[str] = None):
Utils.init_logging("Civilization VI Client")
async def _main(connect: Optional[str], password: Optional[str], name: Optional[str]):
parser = get_base_parser()
parser.add_argument("apcivvi_file", default="", type=str, nargs="?", help="Path to apcivvi file")
args = parser.parse_args()
ctx = CivVIContext(connect, password, args.apcivvi_file)
if args.apcivvi_file:
parent_dir: str = os.path.dirname(args.apcivvi_file)
target_name: str = os.path.basename(args.apcivvi_file).replace(".apcivvi", "-MOD-FILES")
target_path: str = os.path.join(parent_dir, target_name)
if not os.path.exists(target_path):
os.makedirs(target_path, exist_ok=True)
logger.info("Extracting mod files to %s", target_path)
with zipfile.ZipFile(args.apcivvi_file, "r") as zip_ref:
for member in zip_ref.namelist():
zip_ref.extract(member, target_path)
ctx.auth = name
ctx.server_task = asyncio.create_task(
server_loop(ctx), name="ServerLoop")
if gui_enabled:
ctx.run_gui()
await asyncio.sleep(1)
ctx.tuner_sync_task = asyncio.create_task(
tuner_sync_task(ctx), name="TunerSync")
await ctx.exit_event.wait()
ctx.server_address = None
await ctx.shutdown()
if ctx.tuner_sync_task:
await asyncio.sleep(3)
await ctx.tuner_sync_task
import colorama
colorama.init()
asyncio.run(_main(connect, password, name))
colorama.deinit()
def debug_main():
parser = get_base_parser()
parser.add_argument("apcivvi_file", default="", type=str, nargs="?", help="Path to apcivvi file")
parser.add_argument("--name", default=None,
help="Slot Name to connect as.")
parser.add_argument("--debug", default=None,
help="debug mode, additional logging")
args = parser.parse_args()
if args.debug:
logger.setLevel(logging.DEBUG)
main(args.connect, args.password, args.name)

View File

@@ -0,0 +1,119 @@
from enum import Enum
from logging import Logger
from typing import List, Optional
from .Items import CivVIItemData
from .TunerClient import TunerClient, TunerConnectionException, TunerTimeoutException
class ConnectionState(Enum):
DISCONNECTED = 0
IN_GAME = 1
IN_MENU = 2
class CivVIInterface:
logger: Logger
tuner: TunerClient
last_error: Optional[str] = None
def __init__(self, logger: Logger):
self.logger = logger
self.tuner = TunerClient(logger)
async def is_in_game(self) -> ConnectionState:
command = "IsInGame()"
try:
result = await self.tuner.send_game_command(command)
if result == "false":
return ConnectionState.IN_MENU
self.last_error = None
return ConnectionState.IN_GAME
except TunerTimeoutException:
self.print_connection_error(
"Not connected to game, waiting for connection to be available")
return ConnectionState.DISCONNECTED
except TunerConnectionException as e:
if "The remote computer refused the network connection" in str(e):
self.print_connection_error(
"Unable to connect to game. Verify that the tuner is enabled. Attempting to reconnect")
else:
self.print_connection_error(
"Not connected to game, waiting for connection to be available")
return ConnectionState.DISCONNECTED
except Exception as e:
if "attempt to index a nil valuestack traceback" in str(e) \
or ".. is not supported for string .. nilstack traceback" in str(e):
return ConnectionState.IN_MENU
return ConnectionState.DISCONNECTED
def print_connection_error(self, error: str) -> None:
if error != self.last_error:
self.last_error = error
self.logger.info(error)
async def give_item_to_player(self, item: CivVIItemData, sender: str = "", amount: int = 1, game_id_override: Optional[str] = None) -> None:
if game_id_override:
item_id = f'"{game_id_override}"'
else:
item_id = item.civ_vi_id
command = f"HandleReceiveItem({item_id}, \"{item.name}\", \"{item.item_type.value}\", \"{sender}\", {amount})"
await self.tuner.send_game_command(command)
async def resync(self) -> None:
"""Has the client resend all the checked locations"""
command = "Resync()"
await self.tuner.send_game_command(command)
async def check_victory(self) -> bool:
command = "ClientGetVictory()"
result = await self.tuner.send_game_command(command)
return result == "true"
async def get_checked_locations(self) -> List[str]:
command = "GetUnsentCheckedLocations()"
result = await self.tuner.send_game_command(command, 2048 * 4)
return result.split(",")
async def get_deathlink(self) -> str:
"""returns either "false" or the name of the unit that killed the player's unit"""
command = "ClientGetDeathLink()"
result = await self.tuner.send_game_command(command)
return result
async def kill_unit(self, message: str) -> None:
command = f"KillUnit(\"{message}\")"
await self.tuner.send_game_command(command)
async def get_last_received_index(self) -> int:
command = "ClientGetLastReceivedIndex()"
result = await self.tuner.send_game_command(command)
return int(result)
async def send_notification(self, item: CivVIItemData, sender: str = "someone") -> None:
command = f"GameCore.NotificationManager:SendNotification(GameCore.NotificationTypes.USER_DEFINED_2, \"{item.name} Received\", \"You have received {item.name} from \" .. \"{sender}\", 0, {item.civ_vi_id})"
await self.tuner.send_command(command)
async def decrease_gold_by_percent(self, percent: int, message: str) -> None:
command = f"DecreaseGoldByPercent({percent}, \"{message}\")"
await self.tuner.send_game_command(command)
async def decrease_faith_by_percent(self, percent: int, message: str) -> None:
command = f"DecreaseFaithByPercent({percent}, \"{message}\")"
await self.tuner.send_game_command(command)
async def decrease_era_score_by_amount(self, amount: int, message: str) -> None:
command = f"DecreaseEraScoreByAmount({amount}, \"{message}\")"
await self.tuner.send_game_command(command)
async def set_max_allowed_era(self, count: int) -> None:
command = f"SetMaxAllowedEra(\"{count}\")"
await self.tuner.send_game_command(command)
async def get_max_allowed_era(self) -> int:
command = "ClientGetMaxAllowedEra()"
result = await self.tuner.send_game_command(command)
if result == "":
return -1
return int(result)

219
worlds/civ_6/Container.py Normal file
View File

@@ -0,0 +1,219 @@
from dataclasses import dataclass
import os
from typing import TYPE_CHECKING, Dict, List, Optional, cast
import zipfile
from BaseClasses import Location
from worlds.Files import APContainer
from .Enum import CivVICheckType
from .Locations import CivVILocation, CivVILocationData
if TYPE_CHECKING:
from . import CivVIWorld
# Python fstrings don't allow backslashes, so we use this workaround
nl = "\n"
tab = "\t"
apo = "\'"
@dataclass
class CivTreeItem:
name: str
cost: int
ui_tree_row: int
class CivVIContainer(APContainer):
"""
Responsible for generating the dynamic mod files for the Civ VI multiworld
"""
game: Optional[str] = "Civilization VI"
def __init__(self, patch_data: Dict[str, str], base_path: str, output_directory: str,
player: Optional[int] = None, player_name: str = "", server: str = ""):
self.patch_data = patch_data
self.file_path = base_path
container_path = os.path.join(output_directory, base_path + ".apcivvi")
super().__init__(container_path, player, player_name, server)
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
for filename, yml in self.patch_data.items():
opened_zipfile.writestr(filename, yml)
super().write_contents(opened_zipfile)
def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int:
"""
Returns the cost of the item based on the game options
"""
# Research cost is between 50 and 150 where 100 equals the default cost
multiplier = world.options.research_cost_multiplier / 100
return int(world.location_table[location.name].cost * multiplier)
def get_formatted_player_name(world: 'CivVIWorld', player: int) -> str:
"""
Returns the name of the player in the world
"""
if player != world.player:
return f"{world.multiworld.player_name[player]}{apo}s"
return "Your"
def get_advisor_type(world: 'CivVIWorld', location: Location) -> str:
if world.options.advisor_show_progression_items and location.item and location.item.advancement:
return "ADVISOR_PROGRESSIVE"
return "ADVISOR_GENERIC"
def generate_new_items(world: 'CivVIWorld') -> str:
"""
Generates the XML for the new techs/civics as well as the blockers used to prevent players from researching their own items
"""
locations: List[CivVILocation] = cast(List[CivVILocation], world.multiworld.get_filled_locations(world.player))
techs = [location for location in locations if location.location_type ==
CivVICheckType.TECH]
civics = [location for location in locations if location.location_type ==
CivVICheckType.CIVIC]
boost_techs = []
boost_civics = []
if world.options.boostsanity:
boost_techs = [location for location in locations if location.location_type == CivVICheckType.BOOST and location.name.split("_")[1] == "TECH"]
boost_civics = [location for location in locations if location.location_type == CivVICheckType.BOOST and location.name.split("_")[1] == "CIVIC"]
techs += boost_techs
civics += boost_civics
return f"""<?xml version="1.0" encoding="utf-8"?>
<GameInfo>
<Types>
<Row Type="TECH_BLOCKER" Kind="KIND_TECH" />
<Row Type="CIVIC_BLOCKER" Kind="KIND_CIVIC" />
{"".join([f'{tab}<Row Type="{tech.name}" Kind="KIND_TECH" />{nl}' for
tech in techs])}
{"".join([f'{tab}<Row Type="{civic.name}" Kind="KIND_CIVIC" />{nl}' for
civic in civics])}
</Types>
<Technologies>
<Row TechnologyType="TECH_BLOCKER" Name="TECH_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Tech created to prevent players from researching their own tech. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row TechnologyType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{location.item.name}" '
f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" '
f'Description="{location.name}" '
f'AdvisorType="{get_advisor_type(world, location)}"'
f'/>{nl}'
for location in techs if location.item])}
</Technologies>
<TechnologyPrereqs>
{"".join([f'{tab}<Row Technology="{location.name}" PrereqTech="TECH_BLOCKER" />{nl}' for location in boost_techs])}
</TechnologyPrereqs>
<Civics>
<Row CivicType="CIVIC_BLOCKER" Name="CIVIC_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Civic created to prevent players from researching their own civics. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row CivicType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{location.item.name}" '
f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" '
f'Description="{location.name}" '
f'AdvisorType="{get_advisor_type(world, location)}"'
f'/>{nl}'
for location in civics if location.item])}
</Civics>
<CivicPrereqs>
{"".join([f'{tab}<Row Civic="{location.name}" PrereqCivic="CIVIC_BLOCKER" />{nl}' for location in boost_civics])}
</CivicPrereqs>
<Civics_XP2>
{"".join([f'{tab}<Row CivicType="{location.name}" HiddenUntilPrereqComplete="true" RandomPrereqs="false"/>{nl}' for location in civics if world.options.hide_item_names])}
</Civics_XP2>
<Technologies_XP2>
{"".join([f'{tab}<Row TechnologyType="{location.name}" HiddenUntilPrereqComplete="true" RandomPrereqs="false"/>{nl}' for location in techs if world.options.hide_item_names])}
</Technologies_XP2>
</GameInfo>
"""
def generate_setup_file(world: 'CivVIWorld') -> str:
"""
Generates the Lua for the setup file. This sets initial variables and state that affect gameplay around Progressive Eras
"""
setup = "-- Setup"
if world.options.progression_style == "eras_and_districts":
setup += f"""
-- Init Progressive Era Value if it hasn't been set already
if Game.GetProperty("MaxAllowedEra") == nil then
print("Setting MaxAllowedEra to 0")
Game.SetProperty("MaxAllowedEra", 0)
end
"""
if world.options.boostsanity:
setup += f"""
-- Init Boosts
if Game.GetProperty("BoostsAsChecks") == nil then
print("Setting Boosts As Checks to True")
Game.SetProperty("BoostsAsChecks", true)
end
"""
return setup
def generate_goody_hut_sql(world: 'CivVIWorld') -> str:
"""
Generates the SQL for the goody huts or an empty string if they are disabled since the mod expects the file to be there
"""
if world.options.shuffle_goody_hut_rewards:
return f"""
UPDATE GoodyHutSubTypes SET Description = NULL WHERE GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND Weight > 0;
INSERT INTO Modifiers
(ModifierId, ModifierType, RunOnce, Permanent, SubjectRequirementSetId)
SELECT ModifierID||'_AI', ModifierType, RunOnce, Permanent, 'PLAYER_IS_AI'
FROM Modifiers
WHERE EXISTS (
SELECT ModifierId
FROM GoodyHutSubTypes
WHERE Modifiers.ModifierId = GoodyHutSubTypes.ModifierId AND GoodyHutSubTypes.GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND GoodyHutSubTypes.Weight > 0);
INSERT INTO ModifierArguments
(ModifierId, Name, Type, Value)
SELECT ModifierID||'_AI', Name, Type, Value
FROM ModifierArguments
WHERE EXISTS (
SELECT ModifierId
FROM GoodyHutSubTypes
WHERE ModifierArguments.ModifierId = GoodyHutSubTypes.ModifierId AND GoodyHutSubTypes.GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND GoodyHutSubTypes.Weight > 0);
UPDATE GoodyHutSubTypes
SET ModifierID = ModifierID||'_AI'
WHERE GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND Weight > 0;
"""
return "-- Goody Huts are disabled, no changes needed"
def generate_update_boosts_sql(world: 'CivVIWorld') -> str:
"""
Generates the SQL for existing boosts in boostsanity or an empty string if they are disabled since the mod expects the file to be there
"""
if world.options.boostsanity:
return f"""
UPDATE Boosts
SET TechnologyType = 'BOOST_' || TechnologyType
WHERE TechnologyType IS NOT NULL;
UPDATE Boosts
SET CivicType = 'BOOST_' || CivicType
WHERE CivicType IS NOT NULL AND CivicType NOT IN ('CIVIC_CORPORATE_LIBERTARIANISM', 'CIVIC_DIGITAL_DEMOCRACY', 'CIVIC_SYNTHETIC_TECHNOCRACY', 'CIVIC_NEAR_FUTURE_GOVERNANCE');
"""
return "-- Boostsanity is disabled, no changes needed"

70
worlds/civ_6/Data.py Normal file
View File

@@ -0,0 +1,70 @@
from typing import Dict, List
from .ItemData import (
CivVIBoostData,
CivicPrereqData,
ExistingItemData,
GoodyHutRewardData,
NewItemData,
TechPrereqData,
)
def get_boosts_data() -> List[CivVIBoostData]:
from .data.boosts import boosts
return boosts
def get_era_required_items_data() -> Dict[str, List[str]]:
from .data.era_required_items import era_required_items
return era_required_items
def get_existing_civics_data() -> List[ExistingItemData]:
from .data.existing_civics import existing_civics
return existing_civics
def get_existing_techs_data() -> List[ExistingItemData]:
from .data.existing_tech import existing_tech
return existing_tech
def get_goody_hut_rewards_data() -> List[GoodyHutRewardData]:
from .data.goody_hut_rewards import reward_data
return reward_data
def get_new_civic_prereqs_data() -> List[CivicPrereqData]:
from .data.new_civic_prereqs import new_civic_prereqs
return new_civic_prereqs
def get_new_civics_data() -> List[NewItemData]:
from .data.new_civics import new_civics
return new_civics
def get_new_tech_prereqs_data() -> List[TechPrereqData]:
from .data.new_tech_prereqs import new_tech_prereqs
return new_tech_prereqs
def get_new_techs_data() -> List[NewItemData]:
from .data.new_tech import new_tech
return new_tech
def get_progressive_districts_data() -> Dict[str, List[str]]:
from .data.progressive_districts import progressive_districts
return progressive_districts

74
worlds/civ_6/DeathLink.py Normal file
View File

@@ -0,0 +1,74 @@
import random
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from .Civ6Client import CivVIContext
# any is also an option but should not be considered an effect
DEATH_LINK_EFFECTS = ["Gold", "Faith", "Era Score", "Unit Killed"]
async def handle_receive_deathlink(ctx: 'CivVIContext', message: str):
"""Resolves the effects of a deathlink received from the multiworld based on the options selected by the player"""
chosen_effects: List[str] = ctx.slot_data["death_link_effect"]
effect = random.choice(chosen_effects)
percent = ctx.slot_data["death_link_effect_percent"]
if effect == "Gold":
ctx.logger.info(f"Decreasing gold by {percent}%")
await ctx.game_interface.decrease_gold_by_percent(percent, message)
elif effect == "Faith":
ctx.logger.info(f"Decreasing faith by {percent}%")
await ctx.game_interface.decrease_faith_by_percent(percent, message)
elif effect == "Era Score":
ctx.logger.info("Decreasing era score by 1")
await ctx.game_interface.decrease_era_score_by_amount(1, message)
elif effect == "Unit Killed":
ctx.logger.info("Destroying a random unit")
await ctx.game_interface.kill_unit(message)
async def handle_check_deathlink(ctx: 'CivVIContext'):
"""Checks if the local player should send out a deathlink to the multiworld as well as if we should respond to any pending deathlinks sent to us """
# check if we received a death link
if ctx.received_death_link:
ctx.received_death_link = False
await handle_receive_deathlink(ctx, ctx.death_link_message)
# Check if we should send out a death link
result = await ctx.game_interface.get_deathlink()
if ctx.death_link_just_changed:
ctx.death_link_just_changed = False
return
if result != "false":
messages = [f"lost a unit to a {result}",
f"offered a sacrifice to the great {result}",
f"was killed by a {result}",
f"made a donation to the {result} fund",
f"made a tactical error",
f"picked a fight with a {result} and lost",
f"tried to befriend an enemy {result}",
f"used a {result} to reduce their military spend",
f"was defeated by a {result} in combat",
f"bravely struck a {result} and paid the price",
f"had a lapse in judgement against a {result}",
f"learned at the hands of a {result}",
f"attempted to non peacefully negotiate with a {result}",
f"was outsmarted by a {result}",
f"received a lesson from a {result}",
f"now understands the importance of not fighting a {result}",
f"let a {result} get the better of them",
f"allowed a {result} to show them the error of their ways",
f"heard the tragedy of Darth Plagueis the Wise from a {result}",
f"refused to join a {result} in their quest for power",
f"was tired of sitting in BK and decided to fight a {result} instead",
f"purposely lost to a {result} as a cry for help",
f"is wanting to remind everyone that they are here to have fun and not to win",
f"is reconsidering their pursuit of a domination victory",
f"had their plans toppled by a {result}",
]
if ctx.slot is not None:
player = ctx.player_names[ctx.slot]
message = random.choice(messages)
await ctx.send_death(f"{player} {message}")

39
worlds/civ_6/Enum.py Normal file
View File

@@ -0,0 +1,39 @@
from enum import Enum
from BaseClasses import ItemClassification
class EraType(Enum):
ERA_ANCIENT = "ERA_ANCIENT"
ERA_CLASSICAL = "ERA_CLASSICAL"
ERA_MEDIEVAL = "ERA_MEDIEVAL"
ERA_RENAISSANCE = "ERA_RENAISSANCE"
ERA_INDUSTRIAL = "ERA_INDUSTRIAL"
ERA_MODERN = "ERA_MODERN"
ERA_ATOMIC = "ERA_ATOMIC"
ERA_INFORMATION = "ERA_INFORMATION"
ERA_FUTURE = "ERA_FUTURE"
class CivVICheckType(Enum):
TECH = "TECH"
CIVIC = "CIVIC"
PROGRESSIVE_DISTRICT = "PROGRESSIVE_DISTRICT"
ERA = "ERA"
GOODY = "GOODY"
BOOST = "BOOST"
EVENT = "EVENT"
class CivVIHintClassification(Enum):
PROGRESSION = "Progression"
USEFUL = "Useful"
FILLER = "Filler"
def to_item_classification(self) -> ItemClassification:
if self == CivVIHintClassification.PROGRESSION:
return ItemClassification.progression
if self == CivVIHintClassification.USEFUL:
return ItemClassification.useful
if self == CivVIHintClassification.FILLER:
return ItemClassification.filler
assert False

38
worlds/civ_6/ItemData.py Normal file
View File

@@ -0,0 +1,38 @@
from dataclasses import dataclass
from typing import List, TypedDict
class NewItemData(TypedDict):
Type: str
Cost: int
UITreeRow: int
EraType: str
class ExistingItemData(NewItemData):
Name: str
@dataclass
class CivVIBoostData:
Type: str
EraType: str
Prereq: List[str]
PrereqRequiredCount: int
Classification: str
class GoodyHutRewardData(TypedDict):
Type: str
Name: str
Rarity: str
class CivicPrereqData(TypedDict):
Civic: str
PrereqTech: str
class TechPrereqData(TypedDict):
Technology: str
PrereqTech: str

353
worlds/civ_6/Items.py Normal file
View File

@@ -0,0 +1,353 @@
from enum import Enum
from typing import Dict, Optional, TYPE_CHECKING, List
from BaseClasses import Item, ItemClassification
from .Data import (
GoodyHutRewardData,
get_era_required_items_data,
get_existing_civics_data,
get_existing_techs_data,
get_goody_hut_rewards_data,
get_progressive_districts_data,
)
from .Enum import CivVICheckType, EraType
from .ProgressiveDistricts import get_flat_progressive_districts
if TYPE_CHECKING:
from . import CivVIWorld
CIV_VI_AP_ITEM_ID_BASE = 5041000
NON_PROGRESSION_DISTRICTS = ["PROGRESSIVE_PRESERVE", "PROGRESSIVE_NEIGHBORHOOD"]
# Items required as progression for boostsanity mode
BOOSTSANITY_PROGRESSION_ITEMS = [
"TECH_THE_WHEEL",
"TECH_MASONRY",
"TECH_ARCHERY",
"TECH_ENGINEERING",
"TECH_CONSTRUCTION",
"TECH_GUNPOWDER",
"TECH_MACHINERY",
"TECH_SIEGE_TACTICS",
"TECH_STIRRUPS",
"TECH_ASTRONOMY",
"TECH_BALLISTICS",
"TECH_STEAM_POWER",
"TECH_SANITATION",
"TECH_COMPUTERS",
"TECH_COMBUSTION",
"TECH_TELECOMMUNICATIONS",
"TECH_ROBOTICS",
"CIVIC_FEUDALISM",
"CIVIC_GUILDS",
"CIVIC_THE_ENLIGHTENMENT",
"CIVIC_MERCANTILISM",
"CIVIC_CONSERVATION",
"CIVIC_CIVIL_SERVICE",
"CIVIC_GLOBALIZATION",
"CIVIC_COLD_WAR",
"CIVIC_URBANIZATION",
"CIVIC_NATIONALISM",
"CIVIC_MOBILIZATION",
"PROGRESSIVE_NEIGHBORHOOD",
"PROGRESSIVE_PRESERVE",
]
class FillerItemRarity(Enum):
COMMON = "COMMON"
UNCOMMON = "UNCOMMON"
RARE = "RARE"
FILLER_DISTRIBUTION: Dict[FillerItemRarity, float] = {
FillerItemRarity.RARE: 0.025,
FillerItemRarity.UNCOMMON: 0.2,
FillerItemRarity.COMMON: 0.775,
}
class FillerItemData:
name: str
type: str
rarity: FillerItemRarity
civ_name: str
def __init__(self, data: GoodyHutRewardData):
self.name = data["Name"]
self.rarity = FillerItemRarity(data["Rarity"])
self.civ_name = data["Type"]
filler_data: Dict[str, FillerItemData] = {
item["Name"]: FillerItemData(item) for item in get_goody_hut_rewards_data()
}
class CivVIItemData:
civ_vi_id: int
classification: ItemClassification
name: str
code: int
cost: int
item_type: CivVICheckType
progressive_name: Optional[str]
civ_name: Optional[str]
era: Optional[EraType]
def __init__(
self,
name: str,
civ_vi_id: int,
cost: int,
item_type: CivVICheckType,
id_offset: int,
classification: ItemClassification,
progressive_name: Optional[str],
civ_name: Optional[str] = None,
era: Optional[EraType] = None,
):
self.classification = classification
self.civ_vi_id = civ_vi_id
self.name = name
self.code = civ_vi_id + CIV_VI_AP_ITEM_ID_BASE + id_offset
self.cost = cost
self.item_type = item_type
self.progressive_name = progressive_name
self.civ_name = civ_name
self.era = era
class CivVIEvent(Item):
game: str = "Civilization VI"
class CivVIItem(Item):
game: str = "Civilization VI"
civ_vi_id: int
item_type: CivVICheckType
def __init__(
self,
item: CivVIItemData,
player: int,
classification: Optional[ItemClassification] = None,
):
super().__init__(
item.name, classification or item.classification, item.code, player
)
self.civ_vi_id = item.civ_vi_id
self.item_type = item.item_type
def format_item_name(name: str) -> str:
name_parts = name.split("_")
return " ".join([part.capitalize() for part in name_parts])
_items_by_civ_name: Dict[str, CivVIItemData] = {}
def get_item_by_civ_name(
item_name: str, item_table: Dict[str, "CivVIItemData"]
) -> "CivVIItemData":
"""Gets the names of the items in the item_table"""
if not _items_by_civ_name:
for item in item_table.values():
if item.civ_name:
_items_by_civ_name[item.civ_name] = item
try:
return _items_by_civ_name[item_name]
except KeyError as e:
raise KeyError(f"Item {item_name} not found in item_table") from e
def _generate_tech_items(
id_base: int, required_items: List[str], progressive_items: Dict[str, str]
) -> Dict[str, CivVIItemData]:
# Generate Techs
existing_techs = get_existing_techs_data()
tech_table: Dict[str, CivVIItemData] = {}
tech_id = 0
for tech in existing_techs:
classification = ItemClassification.useful
name = tech["Name"]
civ_name = tech["Type"]
if civ_name in required_items:
classification = ItemClassification.progression
progressive_name = None
check_type = CivVICheckType.TECH
if civ_name in progressive_items.keys():
progressive_name = format_item_name(progressive_items[civ_name])
tech_table[name] = CivVIItemData(
name=name,
civ_vi_id=tech_id,
cost=tech["Cost"],
item_type=check_type,
id_offset=id_base,
classification=classification,
progressive_name=progressive_name,
civ_name=civ_name,
era=EraType(tech["EraType"]),
)
tech_id += 1
return tech_table
def _generate_civics_items(
id_base: int, required_items: List[str], progressive_items: Dict[str, str]
) -> Dict[str, CivVIItemData]:
civic_id = 0
civic_table: Dict[str, CivVIItemData] = {}
existing_civics = get_existing_civics_data()
for civic in existing_civics:
name = civic["Name"]
civ_name = civic["Type"]
progressive_name = None
check_type = CivVICheckType.CIVIC
if civ_name in progressive_items.keys():
progressive_name = format_item_name(progressive_items[civ_name])
classification = ItemClassification.useful
if civ_name in required_items:
classification = ItemClassification.progression
civic_table[name] = CivVIItemData(
name=name,
civ_vi_id=civic_id,
cost=civic["Cost"],
item_type=check_type,
id_offset=id_base,
classification=classification,
progressive_name=progressive_name,
civ_name=civ_name,
era=EraType(civic["EraType"]),
)
civic_id += 1
return civic_table
def _generate_progressive_district_items(id_base: int) -> Dict[str, CivVIItemData]:
progressive_table: Dict[str, CivVIItemData] = {}
progressive_id_base = 0
progressive_items = get_progressive_districts_data()
for item_name in progressive_items.keys():
classification = (
ItemClassification.useful
if item_name in NON_PROGRESSION_DISTRICTS
else ItemClassification.progression
)
name = format_item_name(item_name)
progressive_table[name] = CivVIItemData(
name=name,
civ_vi_id=progressive_id_base,
cost=0,
item_type=CivVICheckType.PROGRESSIVE_DISTRICT,
id_offset=id_base,
classification=classification,
progressive_name=None,
civ_name=item_name,
)
progressive_id_base += 1
return progressive_table
def _generate_progressive_era_items(id_base: int) -> Dict[str, CivVIItemData]:
"""Generates the single progressive district item"""
era_table: Dict[str, CivVIItemData] = {}
# Generate progressive eras
progressive_era_name = format_item_name("PROGRESSIVE_ERA")
era_table[progressive_era_name] = CivVIItemData(
name=progressive_era_name,
civ_vi_id=0,
cost=0,
item_type=CivVICheckType.ERA,
id_offset=id_base,
classification=ItemClassification.progression,
progressive_name=None,
civ_name="PROGRESSIVE_ERA",
)
return era_table
def _generate_goody_hut_items(id_base: int) -> Dict[str, CivVIItemData]:
# Generate goody hut items
goody_huts = {
item["Name"]: FillerItemData(item) for item in get_goody_hut_rewards_data()
}
goody_table: Dict[str, CivVIItemData] = {}
goody_base = 0
for value in goody_huts.values():
goody_table[value.name] = CivVIItemData(
name=value.name,
civ_vi_id=goody_base,
cost=0,
item_type=CivVICheckType.GOODY,
id_offset=id_base,
classification=ItemClassification.filler,
progressive_name=None,
civ_name=value.civ_name,
)
goody_base += 1
return goody_table
def generate_item_table() -> Dict[str, CivVIItemData]:
era_required_items = get_era_required_items_data()
required_items: List[str] = []
for value in era_required_items.values():
required_items += value
progressive_items = get_flat_progressive_districts()
item_table: Dict[str, CivVIItemData] = {}
def get_id_base():
return len(item_table.keys())
item_table.update(
**_generate_tech_items(get_id_base(), required_items, progressive_items)
)
item_table.update(
**_generate_civics_items(get_id_base(), required_items, progressive_items)
)
item_table.update(**_generate_progressive_district_items(get_id_base()))
item_table.update(**_generate_progressive_era_items(get_id_base()))
item_table.update(**_generate_goody_hut_items(get_id_base()))
return item_table
def get_items_by_type(
item_type: CivVICheckType, item_table: Dict[str, CivVIItemData]
) -> List[CivVIItemData]:
"""
Returns a list of items that match the given item type
"""
return [item for item in item_table.values() if item.item_type == item_type]
fillers_by_rarity: Dict[FillerItemRarity, List[FillerItemData]] = {
rarity: [item for item in filler_data.values() if item.rarity == rarity]
for rarity in FillerItemRarity
}
def get_random_filler_by_rarity(
world: "CivVIWorld", rarity: FillerItemRarity
) -> FillerItemData:
"""
Returns a random filler item by rarity
"""
return world.random.choice(fillers_by_rarity[rarity])

21
worlds/civ_6/LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright © 2024 tanjo3
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

156
worlds/civ_6/Locations.py Normal file
View File

@@ -0,0 +1,156 @@
from collections import defaultdict
from dataclasses import dataclass
from typing import Optional, Dict
from BaseClasses import Location, Region
from .Data import get_boosts_data, get_new_civics_data, get_new_techs_data
from .Enum import CivVICheckType, EraType
CIV_VI_AP_LOCATION_ID_BASE = 5041000
# Locs that should not have progression items
GOODY_HUT_LOCATION_NAMES = [
"GOODY_HUT_1",
"GOODY_HUT_2",
"GOODY_HUT_3",
"GOODY_HUT_4",
"GOODY_HUT_5",
"GOODY_HUT_6",
"GOODY_HUT_7",
"GOODY_HUT_8",
"GOODY_HUT_9",
"GOODY_HUT_10",
]
@dataclass
class CivVILocationData:
name: str
cost: int
uiTreeRow: int
civ_id: int
era_type: str
location_type: CivVICheckType
game: str = "Civilization VI"
@property
def code(self):
return self.civ_id + CIV_VI_AP_LOCATION_ID_BASE
class CivVILocation(Location):
game: str = "Civilization VI"
location_type: CivVICheckType
def __init__(
self,
player: int,
name: str = "",
address: Optional[int] = None,
parent: Optional[Region] = None,
):
super().__init__(player, name, address, parent)
category = name.split("_")[0]
if "victory" in category:
self.location_type = CivVICheckType.EVENT
else:
self.location_type = CivVICheckType(category)
def generate_flat_location_table() -> Dict[str, CivVILocationData]:
"""
Generates a flat location table in the following format:
{
"TECH_AP_ANCIENT_00": CivVILocationData,
"TECH_AP_ANCIENT_01": CivVILocationData,
"CIVIC_AP_ANCIENT_00": CivVILocationData,
...
}
"""
era_locations = generate_era_location_table()
flat_locations: Dict[str, CivVILocationData] = {}
for locations in era_locations.values():
for location_id, location_data in locations.items():
flat_locations[location_id] = location_data
return flat_locations
def generate_era_location_table() -> Dict[str, Dict[str, CivVILocationData]]:
"""
Uses the data from existing_tech.json to generate a location table in the following format:
{
"ERA_ANCIENT": {
"TECH_AP_ANCIENT_00": CivVILocationData,
"TECH_AP_ANCIENT_01": CivVILocationData,
"CIVIC_AP_ANCIENT_00": CivVILocationData,
},
...
}
"""
new_techs = get_new_techs_data()
era_locations: Dict[str, Dict[str, CivVILocationData]] = defaultdict(dict)
id_base = 0
# Techs
for data in new_techs:
era_type = data["EraType"]
era_locations[era_type][data["Type"]] = CivVILocationData(
data["Type"],
data["Cost"],
data["UITreeRow"],
id_base,
era_type,
CivVICheckType.TECH,
)
id_base += 1
# Civics
new_civics = get_new_civics_data()
for data in new_civics:
era_type = data["EraType"]
era_locations[era_type][data["Type"]] = CivVILocationData(
data["Type"],
data["Cost"],
data["UITreeRow"],
id_base,
era_type,
CivVICheckType.CIVIC,
)
id_base += 1
# Eras
for era in EraType:
if era == EraType.ERA_ANCIENT:
continue
era_locations[era.name][era.name] = CivVILocationData(
era.name, 0, 0, id_base, era.name, CivVICheckType.ERA
)
id_base += 1
# Goody Huts, defaults to 10 goody huts as location checks (rarely will a player get more than this)
for i in range(10):
era_locations[EraType.ERA_ANCIENT.value]["GOODY_HUT_" + str(i + 1)] = (
CivVILocationData(
"GOODY_HUT_" + str(i + 1),
0,
0,
id_base,
EraType.ERA_ANCIENT.value,
CivVICheckType.GOODY,
)
)
id_base += 1
# Boosts
boosts = get_boosts_data()
for boost in boosts:
location = CivVILocationData(
boost.Type, 0, 0, id_base, boost.EraType, CivVICheckType.BOOST
)
era_locations["ERA_ANCIENT"][boost.Type] = location
id_base += 1
return era_locations

130
worlds/civ_6/Options.py Normal file
View File

@@ -0,0 +1,130 @@
from dataclasses import dataclass
from Options import (
Choice,
DefaultOnToggle,
OptionSet,
PerGameCommonOptions,
Range,
StartInventoryPool,
Toggle,
)
from .Enum import CivVIHintClassification
class ProgressionStyle(Choice):
"""
**Districts Only**: Each tech/civic that would normally unlock a district or building now has a logical progression.
Example: TECH_BRONZE_WORKING is now PROGRESSIVE_ENCAMPMENT
**Eras and Districts**: Players will be defeated if they play until the world era advances beyond the currently unlocked maximum era.
Unlocked eras can be seen in both the tech and civic trees. Includes all progressive districts.
**None**: No progressive items will be included. This means you can get district upgrades that won't be usable until the relevant district is unlocked.
"""
rich_text_doc = True
display_name = "Progression Style"
option_districts_only = 0
option_eras_and_districts = 1
option_none = 2
default = option_districts_only
class ShuffleGoodyHuts(DefaultOnToggle):
"""Shuffles the goody hut rewards.
Goody huts will only contain junk items and locations are checked sequentially (First goody hut gives GOODY_HUT_1, second gives GOODY_HUT_2, etc.).
"""
display_name = "Shuffle Goody Hut Rewards"
class BoostSanity(Toggle):
"""Boosts for Civics/Techs are location checks. Boosts can now be triggered even if the item has already been
researched.
**Note**: If a boost is dependent upon a unit that is now obsolete, you can click to toggle on/off the relevant tech in
the tech tree."""
rich_text_doc = True
display_name = "Boostsanity"
class ResearchCostMultiplier(Range):
"""Multiplier for research cost of techs and civics, higher values make research more expensive."""
display_name = "Tech/Civic Cost Multiplier"
range_start = 50
range_end = 150
default = 100
class PreHintItems(OptionSet):
"""Controls what items from the tech/civics trees are pre-hinted for the multiworld.
**Progression**: Include Progression items in hints
**Useful**: Include Useful items in hints
**Filler**: Include Filler items in hints
"""
display_name = "Tech/Civic Tree pre-hinted Items"
valid_keys = {classification.value for classification in CivVIHintClassification} # type: ignore
class HideItemNames(Toggle):
"""Each Tech and Civic Location will have a title of 'Unrevealed' until its prereqs have been researched. Note that
hints will still be precollected if that option is enabled."""
display_name = "Hide Item Names"
class InGameFlagProgressionItems(DefaultOnToggle):
"""If enabled, an advisor icon will be added to any location that contains a progression item."""
display_name = "Advisor Indicates Progression Items"
class CivDeathLink(Toggle):
"""If enabled, losing a unit will trigger a death link effect on other players in the multiworld. When a death link is received, the player will receive the effect specified in 'Death Link Effect'."""
display_name = "Death Link"
class DeathLinkEffect(OptionSet):
"""What happens when a unit dies.
**Unit Killed**: A random unit will be killed when a death link is received.
**Faith**: Faith will be decreased by the amount specified in 'Death Link Effect Percent'.
**Gold**: Gold will be decreased by the amount specified in 'Death Link Effect Percent'.
**Era Score**: Era score is decreased by 1.
"""
rich_text_doc = True
display_name = "Death Link Effect"
valid_keys = ["Unit Killed", "Faith", "Gold", "Era Score"] # type: ignore
default = frozenset({"Unit Killed"})
class DeathLinkEffectPercent(Range):
"""The percentage of the effect that will be applied. Only applicable for Gold and Faith effects."""
display_name = "Death Link Effect Percent"
default = 20
range_start = 1
range_end = 100
@dataclass
class CivVIOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
progression_style: ProgressionStyle
shuffle_goody_hut_rewards: ShuffleGoodyHuts
boostsanity: BoostSanity
research_cost_multiplier: ResearchCostMultiplier
pre_hint_items: PreHintItems
hide_item_names: HideItemNames
advisor_show_progression_items: InGameFlagProgressionItems
death_link: CivDeathLink
death_link_effect: DeathLinkEffect
death_link_effect_percent: DeathLinkEffectPercent

View File

@@ -0,0 +1,35 @@
from typing import Dict, List, Optional
from .Data import get_progressive_districts_data
_flat_progressive_districts: Optional[Dict[str, str]] = {}
def get_flat_progressive_districts() -> Dict[str, str]:
"""Returns a dictionary of all items that are associated with a progressive item.
Key is the item name ("TECH_WRITING") and the value is the associated progressive
item ("PROGRESSIVE_CAMPUS")"""
if _flat_progressive_districts:
return _flat_progressive_districts
progressive_districts = get_progressive_districts_data()
flat_progressive_districts: Dict[str, str] = {}
for key, value in progressive_districts.items():
for item in value:
flat_progressive_districts[item] = key
return flat_progressive_districts
def convert_items_to_progressive_items(items: List[str]):
"""converts a list of items to instead be their associated progressive item if
they have one. ["TECH_MINING", "TECH_WRITING"] -> ["TECH_MINING", "PROGRESSIVE_CAMPUS]
"""
flat_progressive_districts = get_flat_progressive_districts()
return [flat_progressive_districts.get(item, item) for item in items]
def convert_item_to_progressive_item(item: str):
"""converts an items to instead be its associated progressive item if
it has one. "TECH_WRITING" -> "PROGRESSIVE_CAMPUS"""
flat_progressive_districts = get_flat_progressive_districts()
return flat_progressive_districts.get(item, item)

128
worlds/civ_6/Regions.py Normal file
View File

@@ -0,0 +1,128 @@
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union
from BaseClasses import CollectionState, LocationProgressType, Region
from worlds.generic.Rules import add_rule, set_rule
from .Data import (
get_boosts_data,
)
from .Enum import EraType
from .Locations import GOODY_HUT_LOCATION_NAMES, CivVILocation
if TYPE_CHECKING:
from . import CivVIWorld
def has_progressive_eras(
state: CollectionState, era: EraType, world: "CivVIWorld"
) -> bool:
return state.has(
"Progressive Era", world.player, world.era_required_progressive_era_counts[era]
)
def has_non_progressive_items(
state: CollectionState, era: EraType, world: "CivVIWorld"
) -> bool:
return state.has_all(world.era_required_non_progressive_items[era], world.player)
def has_progressive_items(
state: CollectionState, era: EraType, world: "CivVIWorld"
) -> bool:
return state.has_all_counts(
world.era_required_progressive_items_counts[era], world.player
)
def create_regions(world: "CivVIWorld"):
menu = Region("Menu", world.player, world.multiworld)
world.multiworld.regions.append(menu)
optional_location_inclusions: Dict[str, Union[bool, int]] = {
"ERA": world.options.progression_style
== world.options.progression_style.option_eras_and_districts,
"GOODY": world.options.shuffle_goody_hut_rewards.value,
"BOOST": world.options.boostsanity.value,
}
regions: List[Region] = []
previous_era: EraType = EraType.ERA_ANCIENT
for era in EraType:
era_region = Region(era.value, world.player, world.multiworld)
era_locations: Dict[str, Optional[int]] = {}
for key, location in world.location_by_era[era.value].items():
category = key.split("_")[0]
if optional_location_inclusions.get(category, True):
era_locations[location.name] = location.code
era_region.add_locations(era_locations, CivVILocation)
regions.append(era_region)
world.multiworld.regions.append(era_region)
# Connect era to previous era if not ancient era
if era == EraType.ERA_ANCIENT:
menu.connect(world.get_region(EraType.ERA_ANCIENT.value))
continue
connection = world.get_region(previous_era.value).connect(
world.get_region(era.value)
)
# Access rules for eras
add_rule(
connection,
lambda state, previous_era=previous_era, world=world: has_non_progressive_items(
state, previous_era, world
),
)
if world.options.progression_style == "eras_and_districts":
add_rule(
connection,
lambda state, previous_era=previous_era, world=world: has_progressive_eras(
state, previous_era, world
),
)
if world.options.progression_style != "none":
add_rule(
connection,
lambda state, previous_era=previous_era, world=world: has_progressive_items(
state, previous_era, world
),
)
previous_era = era
future_era = world.get_region(EraType.ERA_FUTURE.value)
victory = CivVILocation(world.player, "Complete a victory type", None, future_era)
victory.place_locked_item(world.create_event("Victory"))
future_era.locations.append(victory)
set_rule(
victory,
lambda state: state.can_reach_region(EraType.ERA_FUTURE.value, world.player),
)
world.multiworld.completion_condition[world.player] = lambda state: state.has(
"Victory", world.player
)
exclude_necessary_locations(world)
def exclude_necessary_locations(world: "CivVIWorld"):
forced_excluded_location_names: Set[str] = set()
if world.options.shuffle_goody_hut_rewards:
forced_excluded_location_names.update(GOODY_HUT_LOCATION_NAMES)
if world.options.boostsanity:
boost_data_list = get_boosts_data()
excluded_boosts = {
boost_data.Type
for boost_data in boost_data_list
if boost_data.Classification == "EXCLUDED"
}
forced_excluded_location_names.update(excluded_boosts)
for location_name in forced_excluded_location_names:
location = world.get_location(location_name)
location.progress_type = LocationProgressType.EXCLUDED

109
worlds/civ_6/Rules.py Normal file
View File

@@ -0,0 +1,109 @@
from typing import TYPE_CHECKING, List, Tuple
from BaseClasses import CollectionState
from .ItemData import CivVIBoostData
from .Items import format_item_name
from .Data import get_boosts_data, get_progressive_districts_data
from .Enum import CivVICheckType
from .ProgressiveDistricts import convert_item_to_progressive_item
from worlds.generic.Rules import forbid_item, set_rule
if TYPE_CHECKING:
from . import CivVIWorld
def generate_requirements_for_boosts(
world: "CivVIWorld", boost_data: CivVIBoostData
) -> Tuple[List[str], List[Tuple[str, int]]]:
required_non_progressive_items: List[str] = []
required_progressive_item_counts: List[Tuple[str, int]] = []
for item in boost_data.Prereq:
progressive_item_name = convert_item_to_progressive_item(item)
if (
world.options.progression_style != "none"
and "PROGRESSIVE" in progressive_item_name
):
required_progressive_item_counts.append(
(
format_item_name(progressive_item_name),
get_progressive_districts_data()[progressive_item_name].index(item)
+ 1,
)
)
else:
ap_item_name = world.item_by_civ_name[item]
required_non_progressive_items.append(ap_item_name)
return required_non_progressive_items, required_progressive_item_counts
def create_boost_rules(world: "CivVIWorld"):
boost_data_list = get_boosts_data()
boost_locations = [
location
for location in world.location_table.values()
if location.location_type == CivVICheckType.BOOST
]
for location in boost_locations:
boost_data = next(
(boost for boost in boost_data_list if boost.Type == location.name), None
)
world_location = world.get_location(location.name)
forbid_item(world_location, "Progressive Era", world.player)
if boost_data and boost_data.PrereqRequiredCount > 0:
required_non_progressive_items, required_progressive_item_counts = (
generate_requirements_for_boosts(world, boost_data)
)
if world.options.progression_style != "none":
set_rule(
world_location,
lambda state, non_progressive_prereqs=required_non_progressive_items, progressive_prereq_counts=required_progressive_item_counts, required_count=boost_data.PrereqRequiredCount: has_required_items_progressive(
state,
non_progressive_prereqs,
progressive_prereq_counts,
required_count,
world,
),
)
else:
set_rule(
world_location,
lambda state, prereqs=required_non_progressive_items, required_count=boost_data.PrereqRequiredCount: has_required_items_non_progressive(
state, prereqs, required_count, world
),
)
def has_required_items_progressive(
state: CollectionState,
non_progressive_prereqs: List[str],
progressive_prereq_counts: List[Tuple[str, int]],
required_count: int,
world: "CivVIWorld",
) -> bool:
collected_count = 0
for item, count in progressive_prereq_counts:
if state.has(item, world.player, count):
collected_count += 1
# early out if we've already gotten enough
if collected_count >= required_count:
return True
for item in non_progressive_prereqs:
if state.has(item, world.player):
collected_count += 1
# early out if we've already gotten enough
if collected_count >= required_count:
return True
return False
def has_required_items_non_progressive(
state: CollectionState, prereqs: List[str], required_count: int, world: "CivVIWorld"
) -> bool:
return state.has_from_list_unique(
prereqs,
world.player,
required_count,
)

105
worlds/civ_6/TunerClient.py Normal file
View File

@@ -0,0 +1,105 @@
import asyncio
from logging import Logger
import socket
from typing import Any
ADDRESS = "127.0.0.1"
PORT = 4318
CLIENT_PREFIX = "APSTART:"
CLIENT_POSTFIX = ":APEND"
def decode_mixed_string(data: bytes) -> str:
return "".join(chr(b) if 32 <= b < 127 else "?" for b in data)
class TunerException(Exception):
pass
class TunerTimeoutException(TunerException):
pass
class TunerErrorException(TunerException):
pass
class TunerConnectionException(TunerException):
pass
class TunerClient:
"""Interfaces with Civilization via the tuner socket"""
logger: Logger
def __init__(self, logger: Logger):
self.logger = logger
def __parse_response(self, response: str) -> str:
"""Parses the response from the tuner socket"""
split = response.split(CLIENT_PREFIX)
if len(split) > 1:
start = split[1]
end = start.split(CLIENT_POSTFIX)[0]
return end
elif "ERR:" in response:
raise TunerErrorException(response.replace("?", ""))
else:
return ""
async def send_game_command(self, command_string: str, size: int = 64):
"""Small helper that prefixes a command with GameCore.Game."""
return await self.send_command("GameCore.Game." + command_string, size)
async def send_command(self, command_string: str, size: int = 64):
"""Send a raw commannd"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
b_command_string = command_string.encode("utf-8")
# Send data to the server
command_prefix = b"CMD:0:"
delimiter = b"\x00"
full_command = b_command_string
message = command_prefix + full_command + delimiter
message_length = len(message).to_bytes(1, byteorder="little")
# game expects this to be added before any command that is sent, indicates payload size
message_header = message_length + b"\x00\x00\x00\x03\x00\x00\x00"
data = message_header + command_prefix + full_command + delimiter
server_address = (ADDRESS, PORT)
loop = asyncio.get_event_loop()
try:
await loop.sock_connect(sock, server_address)
await loop.sock_sendall(sock, data)
# Add a delay before receiving data
await asyncio.sleep(.02)
received_data = await self.async_recv(sock)
response = decode_mixed_string(received_data)
return self.__parse_response(response)
except socket.timeout:
self.logger.debug("Timeout occurred while receiving data")
raise TunerTimeoutException()
except Exception as e:
self.logger.debug(f"Error occurred while receiving data: {str(e)}")
# check if No connection could be made is present in the error message
connection_errors = [
"The remote computer refused the network connection",
]
if any(error in str(e) for error in connection_errors):
raise TunerConnectionException(e)
else:
raise TunerErrorException(e)
finally:
sock.close()
async def async_recv(self, sock: Any, timeout: float = 2.0, size: int = 4096):
response = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(sock, size), timeout)
return response

326
worlds/civ_6/__init__.py Normal file
View File

@@ -0,0 +1,326 @@
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()

919
worlds/civ_6/data/boosts.py Normal file
View File

@@ -0,0 +1,919 @@
from typing import List
from ..ItemData import CivVIBoostData
boosts: List[CivVIBoostData] = [
CivVIBoostData("BOOST_TECH_SAILING", "ERA_ANCIENT", [], 0, "DEFAULT"),
CivVIBoostData(
"BOOST_TECH_ASTROLOGY",
"ERA_ANCIENT",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_IRRIGATION",
"ERA_ANCIENT",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_ARCHERY",
"ERA_ANCIENT",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_WRITING",
"ERA_ANCIENT",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_MASONRY",
"ERA_ANCIENT",
["TECH_MINING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_BRONZE_WORKING",
"ERA_ANCIENT",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_THE_WHEEL",
"ERA_ANCIENT",
["TECH_MINING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_CELESTIAL_NAVIGATION",
"ERA_CLASSICAL",
["TECH_SAILING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_CURRENCY",
"ERA_CLASSICAL",
["CIVIC_FOREIGN_TRADE"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_HORSEBACK_RIDING",
"ERA_CLASSICAL",
["TECH_ANIMAL_HUSBANDRY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_IRON_WORKING",
"ERA_CLASSICAL",
["TECH_MINING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_SHIPBUILDING",
"ERA_CLASSICAL",
["TECH_SAILING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_MATHEMATICS",
"ERA_CLASSICAL",
[
"TECH_CURRENCY",
"TECH_BRONZE_WORKING",
"TECH_CELESTIAL_NAVIGATION",
"TECH_WRITING",
"TECH_APPRENTICESHIP",
"TECH_FLIGHT",
"CIVIC_GAMES_RECREATION",
"CIVIC_DRAMA_POETRY",
],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_CONSTRUCTION",
"ERA_CLASSICAL",
["TECH_THE_WHEEL"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_ENGINEERING",
"ERA_CLASSICAL",
["TECH_MASONRY"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_MILITARY_TACTICS",
"ERA_MEDIEVAL",
["TECH_BRONZE_WORKING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_APPRENTICESHIP",
"ERA_MEDIEVAL",
["TECH_MINING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_MACHINERY",
"ERA_MEDIEVAL",
["TECH_ARCHERY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_EDUCATION",
"ERA_MEDIEVAL",
["TECH_WRITING"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_STIRRUPS",
"ERA_MEDIEVAL",
["CIVIC_FEUDALISM"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_MILITARY_ENGINEERING",
"ERA_MEDIEVAL",
["TECH_ENGINEERING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_CASTLES",
"ERA_MEDIEVAL",
[
"CIVIC_DIVINE_RIGHT",
"CIVIC_EXPLORATION",
"CIVIC_REFORMED_CHURCH",
"CIVIC_SUFFRAGE",
"CIVIC_TOTALITARIANISM",
"CIVIC_CLASS_STRUGGLE",
"CIVIC_DIGITAL_DEMOCRACY",
"CIVIC_CORPORATE_LIBERTARIANISM",
"CIVIC_SYNTHETIC_TECHNOCRACY",
],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_CARTOGRAPHY",
"ERA_RENAISSANCE",
["TECH_CELESTIAL_NAVIGATION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_MASS_PRODUCTION",
"ERA_RENAISSANCE",
["TECH_CONSTRUCTION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_BANKING",
"ERA_RENAISSANCE",
["CIVIC_GUILDS"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_GUNPOWDER",
"ERA_RENAISSANCE",
["TECH_BRONZE_WORKING", "TECH_MILITARY_ENGINEERING"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_PRINTING",
"ERA_RENAISSANCE",
["TECH_WRITING", "TECH_EDUCATION"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_SQUARE_RIGGING",
"ERA_RENAISSANCE",
["TECH_GUNPOWDER"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_ASTRONOMY",
"ERA_RENAISSANCE",
["TECH_EDUCATION"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_METAL_CASTING",
"ERA_RENAISSANCE",
["TECH_MACHINERY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_SIEGE_TACTICS",
"ERA_RENAISSANCE",
["TECH_MILITARY_ENGINEERING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_INDUSTRIALIZATION",
"ERA_INDUSTRIAL",
["TECH_APPRENTICESHIP"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_SCIENTIFIC_THEORY",
"ERA_INDUSTRIAL",
["CIVIC_THE_ENLIGHTENMENT"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_BALLISTICS",
"ERA_INDUSTRIAL",
["TECH_SIEGE_TACTICS", "TECH_MILITARY_ENGINEERING"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_MILITARY_SCIENCE",
"ERA_INDUSTRIAL",
["TECH_STIRRUPS"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_STEAM_POWER",
"ERA_INDUSTRIAL",
["TECH_MASS_PRODUCTION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_SANITATION",
"ERA_INDUSTRIAL",
["CIVIC_URBANIZATION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_ECONOMICS",
"ERA_INDUSTRIAL",
["TECH_CURRENCY", "TECH_BANKING"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_RIFLING",
"ERA_INDUSTRIAL",
["TECH_MINING", "TECH_MILITARY_ENGINEERING"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_FLIGHT",
"ERA_MODERN",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_REPLACEABLE_PARTS",
"ERA_MODERN",
["TECH_MILITARY_SCIENCE"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_STEEL",
"ERA_MODERN",
["TECH_MINING", "TECH_STEAM_POWER", "TECH_INDUSTRIALIZATION"],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_ELECTRICITY",
"ERA_MODERN",
["CIVIC_MERCANTILISM", "TECH_CELESTIAL_NAVIGATION"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_RADIO",
"ERA_MODERN",
["CIVIC_CONSERVATION"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_CHEMISTRY",
"ERA_MODERN",
["CIVIC_CIVIL_SERVICE"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_COMBUSTION",
"ERA_MODERN",
["CIVIC_NATURAL_HISTORY", "CIVIC_HUMANISM"],
2,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_ADVANCED_FLIGHT",
"ERA_ATOMIC",
["TECH_FLIGHT"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_ROCKETRY",
"ERA_ATOMIC",
["CIVIC_DIPLOMATIC_SERVICE"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_ADVANCED_BALLISTICS",
"ERA_ATOMIC",
[
"TECH_ELECTRICITY",
"TECH_REFINING",
"TECH_APPRENTICESHIP",
"TECH_INDUSTRIALIZATION",
],
4,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_COMBINED_ARMS",
"ERA_ATOMIC",
["CIVIC_MOBILIZATION", "CIVIC_NATIONALISM"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_PLASTICS",
"ERA_ATOMIC",
["TECH_REFINING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_COMPUTERS",
"ERA_ATOMIC",
[
"CIVIC_SUFFRAGE",
"CIVIC_TOTALITARIANISM",
"CIVIC_CLASS_STRUGGLE",
"CIVIC_DIGITAL_DEMOCRACY",
"CIVIC_CORPORATE_LIBERTARIANISM",
"CIVIC_SYNTHETIC_TECHNOCRACY",
],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_NUCLEAR_FISSION",
"ERA_ATOMIC",
["CIVIC_DIPLOMATIC_SERVICE"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_SYNTHETIC_MATERIALS",
"ERA_ATOMIC",
["TECH_FLIGHT"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_TELECOMMUNICATIONS",
"ERA_INFORMATION",
["CIVIC_DIPLOMATIC_SERVICE"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_SATELLITES",
"ERA_INFORMATION",
["CIVIC_DRAMA_POETRY", "CIVIC_HUMANISM", "TECH_RADIO"],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_GUIDANCE_SYSTEMS",
"ERA_INFORMATION",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_LASERS",
"ERA_INFORMATION",
["TECH_COMPUTERS"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_COMPOSITES",
"ERA_INFORMATION",
["TECH_COMBUSTION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_STEALTH_TECHNOLOGY",
"ERA_INFORMATION",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_ROBOTICS",
"ERA_INFORMATION",
["CIVIC_GLOBALIZATION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_NANOTECHNOLOGY",
"ERA_INFORMATION",
["TECH_MINING", "TECH_RADIO"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_NUCLEAR_FUSION",
"ERA_INFORMATION",
[
"TECH_APPRENTICESHIP",
"TECH_INDUSTRIALIZATION",
"TECH_ELECTRICITY",
"TECH_NUCLEAR_FISSION",
],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_BUTTRESS",
"ERA_MEDIEVAL",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_REFINING",
"ERA_MODERN",
["TECH_INDUSTRIALIZATION", "TECH_MINING", "TECH_APPRENTICESHIP"],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_TECH_SEASTEADS",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_ADVANCED_AI",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_ADVANCED_POWER_CELLS",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_CYBERNETICS",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_SMART_MATERIALS",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_PREDICTIVE_SYSTEMS",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_TECH_OFFWORLD_MISSION",
"ERA_FUTURE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_CRAFTSMANSHIP",
"ERA_ANCIENT",
[
"TECH_IRRIGATION",
"TECH_MINING",
"TECH_CONSTRUCTION",
"TECH_ANIMAL_HUSBANDRY",
"TECH_SAILING",
],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_FOREIGN_TRADE",
"ERA_ANCIENT",
["TECH_CARTOGRAPHY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_MILITARY_TRADITION",
"ERA_ANCIENT",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_STATE_WORKFORCE",
"ERA_ANCIENT",
[
"TECH_CURRENCY",
"TECH_BRONZE_WORKING",
"TECH_CELESTIAL_NAVIGATION",
"TECH_WRITING",
"TECH_APPRENTICESHIP",
"TECH_FLIGHT",
"CIVIC_GAMES_RECREATION",
"CIVIC_DRAMA_POETRY",
],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_EARLY_EMPIRE",
"ERA_ANCIENT",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_MYSTICISM",
"ERA_ANCIENT",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_GAMES_RECREATION",
"ERA_CLASSICAL",
["TECH_CONSTRUCTION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_POLITICAL_PHILOSOPHY",
"ERA_CLASSICAL",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_DRAMA_POETRY",
"ERA_CLASSICAL",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_MILITARY_TRAINING",
"ERA_CLASSICAL",
["TECH_BRONZE_WORKING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_DEFENSIVE_TACTICS",
"ERA_CLASSICAL",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_RECORDED_HISTORY",
"ERA_CLASSICAL",
["TECH_WRITING"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_THEOLOGY",
"ERA_CLASSICAL",
["TECH_ASTROLOGY"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_NAVAL_TRADITION",
"ERA_MEDIEVAL",
["TECH_SHIPBUILDING"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_FEUDALISM",
"ERA_MEDIEVAL",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_CIVIL_SERVICE",
"ERA_MEDIEVAL",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_MERCENARIES",
"ERA_MEDIEVAL",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_MEDIEVAL_FAIRES",
"ERA_MEDIEVAL",
["CIVIC_FOREIGN_TRADE", "TECH_CURRENCY"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_GUILDS",
"ERA_MEDIEVAL",
["TECH_CURRENCY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_DIVINE_RIGHT",
"ERA_MEDIEVAL",
["CIVIC_THEOLOGY", "TECH_ASTROLOGY"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_EXPLORATION",
"ERA_RENAISSANCE",
["TECH_CARTOGRAPHY", "TECH_CELESTIAL_NAVIGATION"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_HUMANISM",
"ERA_RENAISSANCE",
["CIVIC_DRAMA_POETRY"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_DIPLOMATIC_SERVICE",
"ERA_RENAISSANCE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_REFORMED_CHURCH",
"ERA_RENAISSANCE",
["TECH_ASTROLOGY"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_MERCANTILISM",
"ERA_RENAISSANCE",
["TECH_CURRENCY"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_THE_ENLIGHTENMENT",
"ERA_RENAISSANCE",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_COLONIALISM",
"ERA_INDUSTRIAL",
["TECH_ASTRONOMY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_CIVIL_ENGINEERING",
"ERA_INDUSTRIAL",
[
"TECH_CURRENCY",
"TECH_BRONZE_WORKING",
"TECH_CELESTIAL_NAVIGATION",
"TECH_WRITING",
"TECH_APPRENTICESHIP",
"TECH_FLIGHT",
"CIVIC_GAMES_RECREATION",
"CIVIC_DRAMA_POETRY",
],
8,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_NATIONALISM",
"ERA_INDUSTRIAL",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_OPERA_BALLET",
"ERA_INDUSTRIAL",
["CIVIC_HUMANISM", "CIVIC_DRAMA_POETRY"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_NATURAL_HISTORY",
"ERA_INDUSTRIAL",
["CIVIC_HUMANISM", "CIVIC_DRAMA_POETRY"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_SCORCHED_EARTH",
"ERA_INDUSTRIAL",
["TECH_BALLISTICS"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_URBANIZATION",
"ERA_INDUSTRIAL",
[],
0,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_CONSERVATION",
"ERA_MODERN",
["CIVIC_URBANIZATION"],
1,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_CAPITALISM",
"ERA_MODERN",
["TECH_CURRENCY", "TECH_BANKING", "TECH_ECONOMICS"],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_NUCLEAR_PROGRAM",
"ERA_MODERN",
["TECH_WRITING", "TECH_EDUCATION", "TECH_CHEMISTRY"],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_MASS_MEDIA",
"ERA_MODERN",
["TECH_RADIO"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_MOBILIZATION",
"ERA_MODERN",
["CIVIC_NATIONALISM"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_SUFFRAGE",
"ERA_MODERN",
["TECH_SANITATION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_TOTALITARIANISM",
"ERA_MODERN",
[
"TECH_BRONZE_WORKING",
"TECH_MILITARY_ENGINEERING",
"TECH_MILITARY_SCIENCE",
],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_CLASS_STRUGGLE",
"ERA_MODERN",
["TECH_APPRENTICESHIP", "TECH_INDUSTRIALIZATION"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_COLD_WAR",
"ERA_ATOMIC",
["TECH_NUCLEAR_FISSION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_PROFESSIONAL_SPORTS",
"ERA_ATOMIC",
["CIVIC_GAMES_RECREATION"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_CULTURAL_HERITAGE",
"ERA_ATOMIC",
[],
0,
"EXCLUDED",
),
CivVIBoostData(
"BOOST_CIVIC_RAPID_DEPLOYMENT",
"ERA_ATOMIC",
["TECH_FLIGHT", "TECH_CARTOGRAPHY", "TECH_SHIPBUILDING"],
3,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_SPACE_RACE",
"ERA_ATOMIC",
["TECH_ROCKETRY"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_GLOBALIZATION",
"ERA_INFORMATION",
["TECH_FLIGHT", "TECH_ADVANCED_FLIGHT"],
2,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_SOCIAL_MEDIA",
"ERA_INFORMATION",
["TECH_TELECOMMUNICATIONS"],
1,
"DEFAULT",
),
CivVIBoostData(
"BOOST_CIVIC_ENVIRONMENTALISM",
"ERA_INFORMATION",
["TECH_SATELLITES"],
1,
"DEFAULT",
),
]

View File

@@ -0,0 +1,75 @@
from typing import Dict, List
era_required_items: Dict[str, List[str]] = {
"ERA_ANCIENT": [
"TECH_MINING",
"TECH_BRONZE_WORKING",
"TECH_ASTROLOGY",
"TECH_WRITING",
"TECH_IRRIGATION",
"TECH_SAILING",
"TECH_ANIMAL_HUSBANDRY",
"CIVIC_STATE_WORKFORCE",
"CIVIC_FOREIGN_TRADE",
],
"ERA_CLASSICAL": [
"TECH_CELESTIAL_NAVIGATION",
"TECH_CURRENCY",
"TECH_MATHEMATICS",
"TECH_SHIPBUILDING",
"CIVIC_GAMES_RECREATION",
"CIVIC_POLITICAL_PHILOSOPHY",
"CIVIC_DRAMA_POETRY",
"CIVIC_THEOLOGY",
],
"ERA_MEDIEVAL": [
"TECH_APPRENTICESHIP",
"TECH_EDUCATION",
"TECH_MILITARY_ENGINEERING",
"CIVIC_DIVINE_RIGHT",
],
"ERA_RENAISSANCE": [
"TECH_MASS_PRODUCTION",
"TECH_BANKING",
"CIVIC_EXPLORATION",
"CIVIC_HUMANISM",
"CIVIC_REFORMED_CHURCH",
"CIVIC_DIPLOMATIC_SERVICE",
"TECH_CARTOGRAPHY",
],
"ERA_INDUSTRIAL": [
"TECH_INDUSTRIALIZATION",
"TECH_MILITARY_SCIENCE",
"TECH_ECONOMICS",
"CIVIC_NATIONALISM",
"CIVIC_NATURAL_HISTORY",
],
"ERA_MODERN": [
"TECH_FLIGHT",
"TECH_REFINING",
"TECH_ELECTRICITY",
"TECH_RADIO",
"TECH_CHEMISTRY",
"CIVIC_SUFFRAGE",
"CIVIC_TOTALITARIANISM",
"CIVIC_CLASS_STRUGGLE",
],
"ERA_ATOMIC": [
"TECH_ADVANCED_FLIGHT",
"TECH_ROCKETRY",
"TECH_COMBINED_ARMS",
"TECH_PLASTICS",
"TECH_NUCLEAR_FISSION",
"CIVIC_PROFESSIONAL_SPORTS",
],
"ERA_INFORMATION": [
"TECH_SATELLITES",
"TECH_NANOTECHNOLOGY",
"TECH_SMART_MATERIALS",
"CIVIC_CORPORATE_LIBERTARIANISM",
"CIVIC_DIGITAL_DEMOCRACY",
"CIVIC_SYNTHETIC_TECHNOCRACY",
],
"ERA_FUTURE": [],
}

View File

@@ -0,0 +1,435 @@
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from ..Data import ExistingItemData
existing_civics: List["ExistingItemData"] = [
{
"Type": "CIVIC_CODE_OF_LAWS",
"Name": "Code of Laws",
"Cost": 20,
"EraType": "ERA_ANCIENT",
"UITreeRow": 0,
},
{
"Type": "CIVIC_CRAFTSMANSHIP",
"Name": "Craftsmanship",
"Cost": 40,
"EraType": "ERA_ANCIENT",
"UITreeRow": -2,
},
{
"Type": "CIVIC_FOREIGN_TRADE",
"Name": "Foreign Trade",
"Cost": 40,
"EraType": "ERA_ANCIENT",
"UITreeRow": 2,
},
{
"Type": "CIVIC_MILITARY_TRADITION",
"Name": "Military Tradition",
"Cost": 50,
"EraType": "ERA_ANCIENT",
"UITreeRow": -3,
},
{
"Type": "CIVIC_STATE_WORKFORCE",
"Name": "State Workforce",
"Cost": 70,
"EraType": "ERA_ANCIENT",
"UITreeRow": 0,
},
{
"Type": "CIVIC_EARLY_EMPIRE",
"Name": "Early Empire",
"Cost": 70,
"EraType": "ERA_ANCIENT",
"UITreeRow": 1,
},
{
"Type": "CIVIC_MYSTICISM",
"Name": "Mysticism",
"Cost": 50,
"EraType": "ERA_ANCIENT",
"UITreeRow": 3,
},
{
"Type": "CIVIC_GAMES_RECREATION",
"Name": "Games Recreation",
"Cost": 110,
"EraType": "ERA_CLASSICAL",
"UITreeRow": -2,
},
{
"Type": "CIVIC_POLITICAL_PHILOSOPHY",
"Name": "Political Philosophy",
"Cost": 110,
"EraType": "ERA_CLASSICAL",
"UITreeRow": 0,
},
{
"Type": "CIVIC_DRAMA_POETRY",
"Name": "Drama and Poetry",
"Cost": 110,
"EraType": "ERA_CLASSICAL",
"UITreeRow": 2,
},
{
"Type": "CIVIC_MILITARY_TRAINING",
"Name": "Military Training",
"Cost": 120,
"EraType": "ERA_CLASSICAL",
"UITreeRow": -3,
},
{
"Type": "CIVIC_DEFENSIVE_TACTICS",
"Name": "Defensive Tactics",
"Cost": 175,
"EraType": "ERA_CLASSICAL",
"UITreeRow": -1,
},
{
"Type": "CIVIC_RECORDED_HISTORY",
"Name": "Recorded History",
"Cost": 175,
"EraType": "ERA_CLASSICAL",
"UITreeRow": 1,
},
{
"Type": "CIVIC_THEOLOGY",
"Name": "Theology",
"Cost": 120,
"EraType": "ERA_CLASSICAL",
"UITreeRow": 3,
},
{
"Type": "CIVIC_NAVAL_TRADITION",
"Name": "Naval Tradition",
"Cost": 220,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": -2,
},
{
"Type": "CIVIC_FEUDALISM",
"Name": "Feudalism",
"Cost": 300,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": -1,
},
{
"Type": "CIVIC_CIVIL_SERVICE",
"Name": "Civil Service",
"Cost": 300,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": 1,
},
{
"Type": "CIVIC_MERCENARIES",
"Name": "Mercenaries",
"Cost": 340,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": -3,
},
{
"Type": "CIVIC_MEDIEVAL_FAIRES",
"Name": "Medieval Faires",
"Cost": 420,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": -1,
},
{
"Type": "CIVIC_GUILDS",
"Name": "Guilds",
"Cost": 420,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": 1,
},
{
"Type": "CIVIC_DIVINE_RIGHT",
"Name": "Divine Right",
"Cost": 340,
"EraType": "ERA_MEDIEVAL",
"UITreeRow": 3,
},
{
"Type": "CIVIC_EXPLORATION",
"Name": "Exploration",
"Cost": 440,
"EraType": "ERA_RENAISSANCE",
"UITreeRow": -3,
},
{
"Type": "CIVIC_HUMANISM",
"Name": "Humanism",
"Cost": 600,
"EraType": "ERA_RENAISSANCE",
"UITreeRow": -1,
},
{
"Type": "CIVIC_DIPLOMATIC_SERVICE",
"Name": "Diplomatic Service",
"Cost": 600,
"EraType": "ERA_RENAISSANCE",
"UITreeRow": 1,
},
{
"Type": "CIVIC_REFORMED_CHURCH",
"Name": "Reformed Church",
"Cost": 440,
"EraType": "ERA_RENAISSANCE",
"UITreeRow": 3,
},
{
"Type": "CIVIC_MERCANTILISM",
"Name": "Mercantilism",
"Cost": 720,
"EraType": "ERA_RENAISSANCE",
"UITreeRow": -1,
},
{
"Type": "CIVIC_THE_ENLIGHTENMENT",
"Name": "The Enlightenment",
"Cost": 720,
"EraType": "ERA_RENAISSANCE",
"UITreeRow": 1,
},
{
"Type": "CIVIC_COLONIALISM",
"Name": "Colonialism",
"Cost": 800,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": -3,
},
{
"Type": "CIVIC_CIVIL_ENGINEERING",
"Name": "Civil Engineering",
"Cost": 1010,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": -1,
},
{
"Type": "CIVIC_NATIONALISM",
"Name": "Nationalism",
"Cost": 1010,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": 0,
},
{
"Type": "CIVIC_OPERA_BALLET",
"Name": "Opera and Ballet",
"Cost": 800,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": 2,
},
{
"Type": "CIVIC_NATURAL_HISTORY",
"Name": "Natural History",
"Cost": 1050,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": -3,
},
{
"Type": "CIVIC_SCORCHED_EARTH",
"Name": "Scorched Earth",
"Cost": 1210,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": 2,
},
{
"Type": "CIVIC_URBANIZATION",
"Name": "Urbanization",
"Cost": 1210,
"EraType": "ERA_INDUSTRIAL",
"UITreeRow": -1,
},
{
"Type": "CIVIC_CONSERVATION",
"Name": "Conservation",
"Cost": 1540,
"EraType": "ERA_MODERN",
"UITreeRow": -3,
},
{
"Type": "CIVIC_CAPITALISM",
"Name": "Capitalism",
"Cost": 1580,
"EraType": "ERA_MODERN",
"UITreeRow": -2,
},
{
"Type": "CIVIC_NUCLEAR_PROGRAM",
"Name": "Nuclear Program",
"Cost": 1715,
"EraType": "ERA_MODERN",
"UITreeRow": -2,
},
{
"Type": "CIVIC_MASS_MEDIA",
"Name": "Mass Media",
"Cost": 1540,
"EraType": "ERA_MODERN",
"UITreeRow": -1,
},
{
"Type": "CIVIC_MOBILIZATION",
"Name": "Mobilization",
"Cost": 1540,
"EraType": "ERA_MODERN",
"UITreeRow": 1,
},
{
"Type": "CIVIC_IDEOLOGY",
"Name": "Ideology",
"Cost": 1640,
"EraType": "ERA_MODERN",
"UITreeRow": -1,
},
{
"Type": "CIVIC_SUFFRAGE",
"Name": "Suffrage",
"Cost": 1640,
"EraType": "ERA_MODERN",
"UITreeRow": 0,
},
{
"Type": "CIVIC_TOTALITARIANISM",
"Name": "Totalitarianism",
"Cost": 1640,
"EraType": "ERA_MODERN",
"UITreeRow": 2,
},
{
"Type": "CIVIC_CLASS_STRUGGLE",
"Name": "Class Struggle",
"Cost": 1640,
"EraType": "ERA_MODERN",
"UITreeRow": 3,
},
{
"Type": "CIVIC_COLD_WAR",
"Name": "Cold War",
"Cost": 2185,
"EraType": "ERA_ATOMIC",
"UITreeRow": -1,
},
{
"Type": "CIVIC_PROFESSIONAL_SPORTS",
"Name": "Professional Sports",
"Cost": 2185,
"EraType": "ERA_ATOMIC",
"UITreeRow": 2,
},
{
"Type": "CIVIC_CULTURAL_HERITAGE",
"Name": "Cultural Heritage",
"Cost": 1955,
"EraType": "ERA_ATOMIC",
"UITreeRow": -3,
},
{
"Type": "CIVIC_RAPID_DEPLOYMENT",
"Name": "Rapid Deployment",
"Cost": 2415,
"EraType": "ERA_ATOMIC",
"UITreeRow": -1,
},
{
"Type": "CIVIC_SPACE_RACE",
"Name": "Space Race",
"Cost": 2415,
"EraType": "ERA_ATOMIC",
"UITreeRow": 1,
},
{
"Type": "CIVIC_GLOBALIZATION",
"Name": "Globalization",
"Cost": 2880,
"EraType": "ERA_INFORMATION",
"UITreeRow": 0,
},
{
"Type": "CIVIC_SOCIAL_MEDIA",
"Name": "Social Media",
"Cost": 2880,
"EraType": "ERA_INFORMATION",
"UITreeRow": 2,
},
{
"Type": "CIVIC_FUTURE_CIVIC",
"Name": "Future Civic",
"Cost": 3500,
"EraType": "ERA_FUTURE",
"UITreeRow": 1,
},
{
"Type": "CIVIC_ENVIRONMENTALISM",
"Name": "Environmentalism",
"Cost": 2880,
"EraType": "ERA_INFORMATION",
"UITreeRow": -2,
},
{
"Type": "CIVIC_CORPORATE_LIBERTARIANISM",
"Name": "Corporate Libertarianism",
"Cost": 3000,
"EraType": "ERA_INFORMATION",
"UITreeRow": 0,
},
{
"Type": "CIVIC_DIGITAL_DEMOCRACY",
"Name": "Digital Democracy",
"Cost": 3000,
"EraType": "ERA_INFORMATION",
"UITreeRow": 1,
},
{
"Type": "CIVIC_SYNTHETIC_TECHNOCRACY",
"Name": "Synthetic Technocracy",
"Cost": 3000,
"EraType": "ERA_INFORMATION",
"UITreeRow": 2,
},
{
"Type": "CIVIC_NEAR_FUTURE_GOVERNANCE",
"Name": "Near Future Governance",
"Cost": 3100,
"EraType": "ERA_INFORMATION",
"UITreeRow": -1,
},
{
"Type": "CIVIC_GLOBAL_WARMING_MITIGATION",
"Name": "Global Warming Mitigation",
"Cost": 3200,
"EraType": "ERA_FUTURE",
"UITreeRow": -2,
},
{
"Type": "CIVIC_SMART_POWER_DOCTRINE",
"Name": "Smart Power Doctrine",
"Cost": 3200,
"EraType": "ERA_FUTURE",
"UITreeRow": -1,
},
{
"Type": "CIVIC_INFORMATION_WARFARE",
"Name": "Information Warfare",
"Cost": 3200,
"EraType": "ERA_FUTURE",
"UITreeRow": 0,
},
{
"Type": "CIVIC_EXODUS_IMPERATIVE",
"Name": "Exodus Imperative",
"Cost": 3200,
"EraType": "ERA_FUTURE",
"UITreeRow": 1,
},
{
"Type": "CIVIC_CULTURAL_HEGEMONY",
"Name": "Cultural Hegemony",
"Cost": 3200,
"EraType": "ERA_FUTURE",
"UITreeRow": 2,
},
]

View File

@@ -0,0 +1,546 @@
from typing import List
from ..ItemData import ExistingItemData
existing_tech: List[ExistingItemData] = [
{
"Type": "TECH_POTTERY",
"Cost": 25,
"UITreeRow": 0,
"EraType": "ERA_ANCIENT",
"Name": "Pottery",
},
{
"Type": "TECH_ANIMAL_HUSBANDRY",
"Cost": 25,
"UITreeRow": 1,
"EraType": "ERA_ANCIENT",
"Name": "Animal Husbandry",
},
{
"Type": "TECH_MINING",
"Cost": 25,
"UITreeRow": 3,
"EraType": "ERA_ANCIENT",
"Name": "Mining",
},
{
"Type": "TECH_SAILING",
"Cost": 50,
"UITreeRow": -3,
"EraType": "ERA_ANCIENT",
"Name": "Sailing",
},
{
"Type": "TECH_ASTROLOGY",
"Cost": 50,
"UITreeRow": -2,
"EraType": "ERA_ANCIENT",
"Name": "Astrology",
},
{
"Type": "TECH_IRRIGATION",
"Cost": 50,
"UITreeRow": -1,
"EraType": "ERA_ANCIENT",
"Name": "Irrigation",
},
{
"Type": "TECH_ARCHERY",
"Cost": 50,
"UITreeRow": 1,
"EraType": "ERA_ANCIENT",
"Name": "Archery",
},
{
"Type": "TECH_WRITING",
"Cost": 50,
"UITreeRow": 0,
"EraType": "ERA_ANCIENT",
"Name": "Writing",
},
{
"Type": "TECH_MASONRY",
"Cost": 80,
"UITreeRow": 2,
"EraType": "ERA_ANCIENT",
"Name": "Masonry",
},
{
"Type": "TECH_BRONZE_WORKING",
"Cost": 80,
"UITreeRow": 3,
"EraType": "ERA_ANCIENT",
"Name": "Bronze Working",
},
{
"Type": "TECH_THE_WHEEL",
"Cost": 80,
"UITreeRow": 4,
"EraType": "ERA_ANCIENT",
"Name": "The Wheel",
},
{
"Type": "TECH_CELESTIAL_NAVIGATION",
"Cost": 120,
"UITreeRow": -2,
"EraType": "ERA_CLASSICAL",
"Name": "Celestial Navigation",
},
{
"Type": "TECH_CURRENCY",
"Cost": 120,
"UITreeRow": 0,
"EraType": "ERA_CLASSICAL",
"Name": "Currency",
},
{
"Type": "TECH_HORSEBACK_RIDING",
"Cost": 120,
"UITreeRow": 1,
"EraType": "ERA_CLASSICAL",
"Name": "Horseback Riding",
},
{
"Type": "TECH_IRON_WORKING",
"Cost": 120,
"UITreeRow": 3,
"EraType": "ERA_CLASSICAL",
"Name": "Iron Working",
},
{
"Type": "TECH_SHIPBUILDING",
"Cost": 200,
"UITreeRow": -3,
"EraType": "ERA_CLASSICAL",
"Name": "Shipbuilding",
},
{
"Type": "TECH_MATHEMATICS",
"Cost": 200,
"UITreeRow": -1,
"EraType": "ERA_CLASSICAL",
"Name": "Mathematics",
},
{
"Type": "TECH_CONSTRUCTION",
"Cost": 200,
"UITreeRow": 2,
"EraType": "ERA_CLASSICAL",
"Name": "Construction",
},
{
"Type": "TECH_ENGINEERING",
"Cost": 200,
"UITreeRow": 4,
"EraType": "ERA_CLASSICAL",
"Name": "Engineering",
},
{
"Type": "TECH_MILITARY_TACTICS",
"Cost": 300,
"UITreeRow": -2,
"EraType": "ERA_MEDIEVAL",
"Name": "Military Tactics",
},
{
"Type": "TECH_APPRENTICESHIP",
"Cost": 300,
"UITreeRow": 0,
"EraType": "ERA_MEDIEVAL",
"Name": "Apprenticeship",
},
{
"Type": "TECH_MACHINERY",
"Cost": 300,
"UITreeRow": 4,
"EraType": "ERA_MEDIEVAL",
"Name": "Machinery",
},
{
"Type": "TECH_EDUCATION",
"Cost": 390,
"UITreeRow": -1,
"EraType": "ERA_MEDIEVAL",
"Name": "Education",
},
{
"Type": "TECH_STIRRUPS",
"Cost": 390,
"UITreeRow": 1,
"EraType": "ERA_MEDIEVAL",
"Name": "Stirrups",
},
{
"Type": "TECH_MILITARY_ENGINEERING",
"Cost": 390,
"UITreeRow": 2,
"EraType": "ERA_MEDIEVAL",
"Name": "Military Engineering",
},
{
"Type": "TECH_CASTLES",
"Cost": 390,
"UITreeRow": 3,
"EraType": "ERA_MEDIEVAL",
"Name": "Castles",
},
{
"Type": "TECH_CARTOGRAPHY",
"Cost": 600,
"UITreeRow": -3,
"EraType": "ERA_RENAISSANCE",
"Name": "Cartography",
},
{
"Type": "TECH_MASS_PRODUCTION",
"Cost": 600,
"UITreeRow": -2,
"EraType": "ERA_RENAISSANCE",
"Name": "Mass Production",
},
{
"Type": "TECH_BANKING",
"Cost": 600,
"UITreeRow": 0,
"EraType": "ERA_RENAISSANCE",
"Name": "Banking",
},
{
"Type": "TECH_GUNPOWDER",
"Cost": 600,
"UITreeRow": 1,
"EraType": "ERA_RENAISSANCE",
"Name": "Gunpowder",
},
{
"Type": "TECH_PRINTING",
"Cost": 600,
"UITreeRow": 4,
"EraType": "ERA_RENAISSANCE",
"Name": "Printing",
},
{
"Type": "TECH_SQUARE_RIGGING",
"Cost": 730,
"UITreeRow": -3,
"EraType": "ERA_RENAISSANCE",
"Name": "Square Rigging",
},
{
"Type": "TECH_ASTRONOMY",
"Cost": 730,
"UITreeRow": -1,
"EraType": "ERA_RENAISSANCE",
"Name": "Astronomy",
},
{
"Type": "TECH_METAL_CASTING",
"Cost": 730,
"UITreeRow": 1,
"EraType": "ERA_RENAISSANCE",
"Name": "Metal Casting",
},
{
"Type": "TECH_SIEGE_TACTICS",
"Cost": 730,
"UITreeRow": 3,
"EraType": "ERA_RENAISSANCE",
"Name": "Siege Tactics",
},
{
"Type": "TECH_INDUSTRIALIZATION",
"Cost": 930,
"UITreeRow": -2,
"EraType": "ERA_INDUSTRIAL",
"Name": "Industrialization",
},
{
"Type": "TECH_SCIENTIFIC_THEORY",
"Cost": 930,
"UITreeRow": -1,
"EraType": "ERA_INDUSTRIAL",
"Name": "Scientific Theory",
},
{
"Type": "TECH_BALLISTICS",
"Cost": 930,
"UITreeRow": 1,
"EraType": "ERA_INDUSTRIAL",
"Name": "Ballistics",
},
{
"Type": "TECH_MILITARY_SCIENCE",
"Cost": 930,
"UITreeRow": 3,
"EraType": "ERA_INDUSTRIAL",
"Name": "Military Science",
},
{
"Type": "TECH_STEAM_POWER",
"Cost": 1070,
"UITreeRow": -3,
"EraType": "ERA_INDUSTRIAL",
"Name": "Steam Power",
},
{
"Type": "TECH_SANITATION",
"Cost": 1070,
"UITreeRow": -1,
"EraType": "ERA_INDUSTRIAL",
"Name": "Sanitation",
},
{
"Type": "TECH_ECONOMICS",
"Cost": 1070,
"UITreeRow": 0,
"EraType": "ERA_INDUSTRIAL",
"Name": "Economics",
},
{
"Type": "TECH_RIFLING",
"Cost": 1070,
"UITreeRow": 2,
"EraType": "ERA_INDUSTRIAL",
"Name": "Rifling",
},
{
"Type": "TECH_FLIGHT",
"Cost": 1250,
"UITreeRow": -2,
"EraType": "ERA_MODERN",
"Name": "Flight",
},
{
"Type": "TECH_REPLACEABLE_PARTS",
"Cost": 1250,
"UITreeRow": 0,
"EraType": "ERA_MODERN",
"Name": "Replaceable Parts",
},
{
"Type": "TECH_STEEL",
"Cost": 1250,
"UITreeRow": 1,
"EraType": "ERA_MODERN",
"Name": "Steel",
},
{
"Type": "TECH_ELECTRICITY",
"Cost": 1370,
"UITreeRow": -3,
"EraType": "ERA_MODERN",
"Name": "Electricity",
},
{
"Type": "TECH_RADIO",
"Cost": 1370,
"UITreeRow": -2,
"EraType": "ERA_MODERN",
"Name": "Radio",
},
{
"Type": "TECH_CHEMISTRY",
"Cost": 1370,
"UITreeRow": -1,
"EraType": "ERA_MODERN",
"Name": "Chemistry",
},
{
"Type": "TECH_COMBUSTION",
"Cost": 1370,
"UITreeRow": 2,
"EraType": "ERA_MODERN",
"Name": "Combustion",
},
{
"Type": "TECH_ADVANCED_FLIGHT",
"Cost": 1480,
"UITreeRow": -2,
"EraType": "ERA_ATOMIC",
"Name": "Advanced Flight",
},
{
"Type": "TECH_ROCKETRY",
"Cost": 1480,
"UITreeRow": -1,
"EraType": "ERA_ATOMIC",
"Name": "Rocketry",
},
{
"Type": "TECH_ADVANCED_BALLISTICS",
"Cost": 1480,
"UITreeRow": 0,
"EraType": "ERA_ATOMIC",
"Name": "Advanced Ballistics",
},
{
"Type": "TECH_COMBINED_ARMS",
"Cost": 1480,
"UITreeRow": 1,
"EraType": "ERA_ATOMIC",
"Name": "Combined Arms",
},
{
"Type": "TECH_PLASTICS",
"Cost": 1480,
"UITreeRow": 2,
"EraType": "ERA_ATOMIC",
"Name": "Plastics",
},
{
"Type": "TECH_COMPUTERS",
"Cost": 1660,
"UITreeRow": -3,
"EraType": "ERA_ATOMIC",
"Name": "Computers",
},
{
"Type": "TECH_NUCLEAR_FISSION",
"Cost": 1660,
"UITreeRow": 1,
"EraType": "ERA_ATOMIC",
"Name": "Nuclear Fission",
},
{
"Type": "TECH_SYNTHETIC_MATERIALS",
"Cost": 1660,
"UITreeRow": 2,
"EraType": "ERA_ATOMIC",
"Name": "Synthetic Materials",
},
{
"Type": "TECH_TELECOMMUNICATIONS",
"Cost": 1850,
"UITreeRow": -3,
"EraType": "ERA_INFORMATION",
"Name": "Telecommunications",
},
{
"Type": "TECH_SATELLITES",
"Cost": 1850,
"UITreeRow": -1,
"EraType": "ERA_INFORMATION",
"Name": "Satellites",
},
{
"Type": "TECH_GUIDANCE_SYSTEMS",
"Cost": 1850,
"UITreeRow": 0,
"EraType": "ERA_INFORMATION",
"Name": "Guidance Systems",
},
{
"Type": "TECH_LASERS",
"Cost": 1850,
"UITreeRow": 1,
"EraType": "ERA_INFORMATION",
"Name": "Lasers",
},
{
"Type": "TECH_COMPOSITES",
"Cost": 1850,
"UITreeRow": 2,
"EraType": "ERA_INFORMATION",
"Name": "Composites",
},
{
"Type": "TECH_STEALTH_TECHNOLOGY",
"Cost": 1850,
"UITreeRow": 3,
"EraType": "ERA_INFORMATION",
"Name": "Stealth Technology",
},
{
"Type": "TECH_ROBOTICS",
"Cost": 2155,
"UITreeRow": -2,
"EraType": "ERA_INFORMATION",
"Name": "Robotics",
},
{
"Type": "TECH_NANOTECHNOLOGY",
"Cost": 2155,
"UITreeRow": 2,
"EraType": "ERA_INFORMATION",
"Name": "Nanotechnology",
},
{
"Type": "TECH_NUCLEAR_FUSION",
"Cost": 2155,
"UITreeRow": 1,
"EraType": "ERA_INFORMATION",
"Name": "Nuclear Fusion",
},
{
"Type": "TECH_BUTTRESS",
"Cost": 300,
"UITreeRow": -3,
"EraType": "ERA_MEDIEVAL",
"Name": "Buttress",
},
{
"Type": "TECH_REFINING",
"Cost": 1250,
"UITreeRow": 3,
"EraType": "ERA_MODERN",
"Name": "Refining",
},
{
"Type": "TECH_SEASTEADS",
"Cost": 2200,
"UITreeRow": -3,
"EraType": "ERA_FUTURE",
"Name": "Seasteads",
},
{
"Type": "TECH_ADVANCED_AI",
"Cost": 2200,
"UITreeRow": -2,
"EraType": "ERA_FUTURE",
"Name": "Advanced AI",
},
{
"Type": "TECH_ADVANCED_POWER_CELLS",
"Cost": 2200,
"UITreeRow": -1,
"EraType": "ERA_FUTURE",
"Name": "Advanced Power Cells",
},
{
"Type": "TECH_CYBERNETICS",
"Cost": 2200,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
"Name": "Cybernetics",
},
{
"Type": "TECH_SMART_MATERIALS",
"Cost": 2200,
"UITreeRow": 1,
"EraType": "ERA_FUTURE",
"Name": "Smart Materials",
},
{
"Type": "TECH_PREDICTIVE_SYSTEMS",
"Cost": 2200,
"UITreeRow": 2,
"EraType": "ERA_FUTURE",
"Name": "Predictive Systems",
},
{
"Type": "TECH_OFFWORLD_MISSION",
"Cost": 2500,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
"Name": "Offworld Mission",
},
{
"Type": "TECH_FUTURE_TECH",
"Cost": 2600,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
"Name": "Future Tech",
},
]

View File

@@ -0,0 +1,81 @@
from typing import List
from ..ItemData import GoodyHutRewardData
reward_data: List[GoodyHutRewardData] = [
{
"Type": "GOODY_GOLD_SMALL_MODIFIER",
"Rarity": "COMMON",
"Name": "Gold: Small"
},
{
"Type": "GOODY_GOLD_MEDIUM_MODIFIER",
"Rarity": "COMMON",
"Name": "Gold: Medium"
},
{
"Type": "GOODY_GOLD_LARGE_MODIFIER",
"Rarity": "UNCOMMON",
"Name": "Gold: Large"
},
{
"Type": "GOODY_FAITH_SMALL_MODIFIER",
"Rarity": "COMMON",
"Name": "Faith: Small"
},
{
"Type": "GOODY_FAITH_MEDIUM_MODIFIER",
"Rarity": "COMMON",
"Name": "Faith: Medium"
},
{
"Type": "GOODY_FAITH_LARGE_MODIFIER",
"Rarity": "UNCOMMON",
"Name": "Faith: Large"
},
{
"Type": "GOODY_DIPLOMACY_GRANT_FAVOR",
"Rarity": "COMMON",
"Name": "Diplomatic Favor"
},
{
"Type": "GOODY_DIPLOMACY_GRANT_GOVERNOR_TITLE",
"Rarity": "RARE",
"Name": "Governor Title"
},
{
"Type": "GOODY_DIPLOMACY_GRANT_ENVOY",
"Rarity": "UNCOMMON",
"Name": "Envoy"
},
{
"Type": "GOODY_CULTURE_GRANT_ONE_RELIC",
"Rarity": "RARE",
"Name": "Relic"
},
{
"Type": "GOODY_MILITARY_GRANT_SCOUT",
"Rarity": "UNCOMMON",
"Name": "Scout"
},
{
"Type": "GOODY_SURVIVORS_ADD_POPULATION",
"Rarity": "UNCOMMON",
"Name": "Additional Population"
},
{
"Type": "GOODY_SURVIVORS_GRANT_BUILDER",
"Rarity": "UNCOMMON",
"Name": "Builder"
},
{
"Type": "GOODY_SURVIVORS_GRANT_TRADER",
"Rarity": "UNCOMMON",
"Name": "Trader"
},
{
"Type": "GOODY_SURVIVORS_GRANT_SETTLER",
"Rarity": "UNCOMMON",
"Name": "Settler"
}
]

View File

@@ -0,0 +1,92 @@
from typing import List
from ..ItemData import CivicPrereqData
new_civic_prereqs: List[CivicPrereqData] = [
{"Civic": "CIVIC_AP_ANCIENT_01", "PrereqCivic": "CIVIC_AP_ANCIENT_00"},
{"Civic": "CIVIC_AP_ANCIENT_02", "PrereqCivic": "CIVIC_AP_ANCIENT_00"},
{"Civic": "CIVIC_AP_ANCIENT_03", "PrereqCivic": "CIVIC_AP_ANCIENT_01"},
{"Civic": "CIVIC_AP_ANCIENT_04", "PrereqCivic": "CIVIC_AP_ANCIENT_01"},
{"Civic": "CIVIC_AP_ANCIENT_05", "PrereqCivic": "CIVIC_AP_ANCIENT_02"},
{"Civic": "CIVIC_AP_ANCIENT_06", "PrereqCivic": "CIVIC_AP_ANCIENT_02"},
{"Civic": "CIVIC_AP_CLASSICAL_07", "PrereqCivic": "CIVIC_AP_ANCIENT_04"},
{"Civic": "CIVIC_AP_CLASSICAL_08", "PrereqCivic": "CIVIC_AP_ANCIENT_04"},
{"Civic": "CIVIC_AP_CLASSICAL_08", "PrereqCivic": "CIVIC_AP_ANCIENT_05"},
{"Civic": "CIVIC_AP_CLASSICAL_09", "PrereqCivic": "CIVIC_AP_ANCIENT_05"},
{"Civic": "CIVIC_AP_CLASSICAL_10", "PrereqCivic": "CIVIC_AP_ANCIENT_03"},
{"Civic": "CIVIC_AP_CLASSICAL_10", "PrereqCivic": "CIVIC_AP_CLASSICAL_07"},
{"Civic": "CIVIC_AP_CLASSICAL_11", "PrereqCivic": "CIVIC_AP_CLASSICAL_07"},
{"Civic": "CIVIC_AP_CLASSICAL_11", "PrereqCivic": "CIVIC_AP_CLASSICAL_08"},
{"Civic": "CIVIC_AP_CLASSICAL_12", "PrereqCivic": "CIVIC_AP_CLASSICAL_08"},
{"Civic": "CIVIC_AP_CLASSICAL_12", "PrereqCivic": "CIVIC_AP_CLASSICAL_09"},
{"Civic": "CIVIC_AP_CLASSICAL_13", "PrereqCivic": "CIVIC_AP_CLASSICAL_09"},
{"Civic": "CIVIC_AP_CLASSICAL_13", "PrereqCivic": "CIVIC_AP_ANCIENT_06"},
{"Civic": "CIVIC_AP_MEDIEVAL_14", "PrereqCivic": "CIVIC_AP_CLASSICAL_11"},
{"Civic": "CIVIC_AP_MEDIEVAL_15", "PrereqCivic": "CIVIC_AP_CLASSICAL_11"},
{"Civic": "CIVIC_AP_MEDIEVAL_16", "PrereqCivic": "CIVIC_AP_CLASSICAL_11"},
{"Civic": "CIVIC_AP_MEDIEVAL_16", "PrereqCivic": "CIVIC_AP_CLASSICAL_12"},
{"Civic": "CIVIC_AP_MEDIEVAL_17", "PrereqCivic": "CIVIC_AP_CLASSICAL_10"},
{"Civic": "CIVIC_AP_MEDIEVAL_17", "PrereqCivic": "CIVIC_AP_MEDIEVAL_15"},
{"Civic": "CIVIC_AP_MEDIEVAL_18", "PrereqCivic": "CIVIC_AP_MEDIEVAL_15"},
{"Civic": "CIVIC_AP_MEDIEVAL_19", "PrereqCivic": "CIVIC_AP_MEDIEVAL_15"},
{"Civic": "CIVIC_AP_MEDIEVAL_19", "PrereqCivic": "CIVIC_AP_MEDIEVAL_16"},
{"Civic": "CIVIC_AP_MEDIEVAL_20", "PrereqCivic": "CIVIC_AP_MEDIEVAL_16"},
{"Civic": "CIVIC_AP_MEDIEVAL_20", "PrereqCivic": "CIVIC_AP_CLASSICAL_13"},
{"Civic": "CIVIC_AP_RENAISSANCE_21", "PrereqCivic": "CIVIC_AP_MEDIEVAL_17"},
{"Civic": "CIVIC_AP_RENAISSANCE_21", "PrereqCivic": "CIVIC_AP_MEDIEVAL_18"},
{"Civic": "CIVIC_AP_RENAISSANCE_22", "PrereqCivic": "CIVIC_AP_MEDIEVAL_18"},
{"Civic": "CIVIC_AP_RENAISSANCE_22", "PrereqCivic": "CIVIC_AP_MEDIEVAL_19"},
{"Civic": "CIVIC_AP_RENAISSANCE_23", "PrereqCivic": "CIVIC_AP_MEDIEVAL_19"},
{"Civic": "CIVIC_AP_RENAISSANCE_24", "PrereqCivic": "CIVIC_AP_MEDIEVAL_19"},
{"Civic": "CIVIC_AP_RENAISSANCE_24", "PrereqCivic": "CIVIC_AP_MEDIEVAL_20"},
{"Civic": "CIVIC_AP_RENAISSANCE_25", "PrereqCivic": "CIVIC_AP_RENAISSANCE_22"},
{"Civic": "CIVIC_AP_RENAISSANCE_26", "PrereqCivic": "CIVIC_AP_RENAISSANCE_22"},
{"Civic": "CIVIC_AP_RENAISSANCE_26", "PrereqCivic": "CIVIC_AP_RENAISSANCE_23"},
{"Civic": "CIVIC_AP_INDUSTRIAL_27", "PrereqCivic": "CIVIC_AP_RENAISSANCE_25"},
{"Civic": "CIVIC_AP_INDUSTRIAL_28", "PrereqCivic": "CIVIC_AP_RENAISSANCE_25"},
{"Civic": "CIVIC_AP_INDUSTRIAL_29", "PrereqCivic": "CIVIC_AP_RENAISSANCE_26"},
{"Civic": "CIVIC_AP_INDUSTRIAL_30", "PrereqCivic": "CIVIC_AP_RENAISSANCE_26"},
{"Civic": "CIVIC_AP_INDUSTRIAL_31", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_27"},
{"Civic": "CIVIC_AP_INDUSTRIAL_32", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_29"},
{"Civic": "CIVIC_AP_INDUSTRIAL_33", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_28"},
{"Civic": "CIVIC_AP_INDUSTRIAL_33", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_29"},
{"Civic": "CIVIC_AP_MODERN_34", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_31"},
{"Civic": "CIVIC_AP_MODERN_37", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_31"},
{"Civic": "CIVIC_AP_MODERN_37", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_33"},
{"Civic": "CIVIC_AP_MODERN_35", "PrereqCivic": "CIVIC_AP_MODERN_37"},
{"Civic": "CIVIC_AP_MODERN_38", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_33"},
{"Civic": "CIVIC_AP_MODERN_39", "PrereqCivic": "CIVIC_AP_MODERN_37"},
{"Civic": "CIVIC_AP_MODERN_39", "PrereqCivic": "CIVIC_AP_MODERN_38"},
{"Civic": "CIVIC_AP_MODERN_36", "PrereqCivic": "CIVIC_AP_MODERN_39"},
{"Civic": "CIVIC_AP_MODERN_40", "PrereqCivic": "CIVIC_AP_MODERN_39"},
{"Civic": "CIVIC_AP_MODERN_41", "PrereqCivic": "CIVIC_AP_MODERN_39"},
{"Civic": "CIVIC_AP_MODERN_42", "PrereqCivic": "CIVIC_AP_MODERN_39"},
{"Civic": "CIVIC_AP_ATOMIC_43", "PrereqCivic": "CIVIC_AP_MODERN_39"},
{"Civic": "CIVIC_AP_ATOMIC_44", "PrereqCivic": "CIVIC_AP_MODERN_39"},
{"Civic": "CIVIC_AP_ATOMIC_45", "PrereqCivic": "CIVIC_AP_MODERN_34"},
{"Civic": "CIVIC_AP_ATOMIC_46", "PrereqCivic": "CIVIC_AP_ATOMIC_43"},
{"Civic": "CIVIC_AP_ATOMIC_47", "PrereqCivic": "CIVIC_AP_ATOMIC_43"},
{"Civic": "CIVIC_AP_INFORMATION_48", "PrereqCivic": "CIVIC_AP_ATOMIC_46"},
{"Civic": "CIVIC_AP_INFORMATION_48", "PrereqCivic": "CIVIC_AP_ATOMIC_47"},
{"Civic": "CIVIC_AP_INFORMATION_49", "PrereqCivic": "CIVIC_AP_ATOMIC_47"},
{"Civic": "CIVIC_AP_INFORMATION_49", "PrereqCivic": "CIVIC_AP_ATOMIC_44"},
{"Civic": "CIVIC_AP_FUTURE_50", "PrereqCivic": "CIVIC_AP_INFORMATION_48"},
{"Civic": "CIVIC_AP_FUTURE_50", "PrereqCivic": "CIVIC_AP_INFORMATION_49"},
{"Civic": "CIVIC_AP_MODERN_38", "PrereqCivic": "CIVIC_AP_INDUSTRIAL_32"},
{"Civic": "CIVIC_AP_INFORMATION_51", "PrereqCivic": "CIVIC_AP_ATOMIC_45"},
{"Civic": "CIVIC_AP_INFORMATION_51", "PrereqCivic": "CIVIC_AP_ATOMIC_46"},
{"Civic": "CIVIC_AP_INFORMATION_52", "PrereqCivic": "CIVIC_AP_INFORMATION_48"},
{"Civic": "CIVIC_AP_INFORMATION_52", "PrereqCivic": "CIVIC_AP_INFORMATION_49"},
{"Civic": "CIVIC_AP_INFORMATION_53", "PrereqCivic": "CIVIC_AP_INFORMATION_48"},
{"Civic": "CIVIC_AP_INFORMATION_53", "PrereqCivic": "CIVIC_AP_INFORMATION_49"},
{"Civic": "CIVIC_AP_INFORMATION_54", "PrereqCivic": "CIVIC_AP_INFORMATION_48"},
{"Civic": "CIVIC_AP_INFORMATION_54", "PrereqCivic": "CIVIC_AP_INFORMATION_49"},
{"Civic": "CIVIC_AP_INFORMATION_55", "PrereqCivic": "CIVIC_AP_INFORMATION_51"},
{"Civic": "CIVIC_AP_INFORMATION_55", "PrereqCivic": "CIVIC_AP_INFORMATION_48"},
{"Civic": "CIVIC_AP_FUTURE_56", "PrereqCivic": "CIVIC_AP_FUTURE_50"},
{"Civic": "CIVIC_AP_FUTURE_57", "PrereqCivic": "CIVIC_AP_FUTURE_50"},
{"Civic": "CIVIC_AP_FUTURE_58", "PrereqCivic": "CIVIC_AP_FUTURE_50"},
{"Civic": "CIVIC_AP_FUTURE_59", "PrereqCivic": "CIVIC_AP_FUTURE_50"},
{"Civic": "CIVIC_AP_FUTURE_60", "PrereqCivic": "CIVIC_AP_FUTURE_50"},
]

View File

@@ -0,0 +1,372 @@
from typing import List
from ..ItemData import NewItemData
new_civics: List[NewItemData] = [
{
"Type": "CIVIC_AP_ANCIENT_00",
"Cost": 20,
"UITreeRow": 0,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_ANCIENT_01",
"Cost": 40,
"UITreeRow": -2,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_ANCIENT_02",
"Cost": 40,
"UITreeRow": 2,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_ANCIENT_03",
"Cost": 50,
"UITreeRow": -3,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_ANCIENT_04",
"Cost": 70,
"UITreeRow": 0,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_ANCIENT_05",
"Cost": 70,
"UITreeRow": 1,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_ANCIENT_06",
"Cost": 50,
"UITreeRow": 3,
"EraType": "ERA_ANCIENT",
},
{
"Type": "CIVIC_AP_CLASSICAL_07",
"Cost": 110,
"UITreeRow": -2,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_CLASSICAL_08",
"Cost": 110,
"UITreeRow": 0,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_CLASSICAL_09",
"Cost": 110,
"UITreeRow": 2,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_CLASSICAL_10",
"Cost": 120,
"UITreeRow": -3,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_CLASSICAL_11",
"Cost": 175,
"UITreeRow": -1,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_CLASSICAL_12",
"Cost": 175,
"UITreeRow": 1,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_CLASSICAL_13",
"Cost": 120,
"UITreeRow": 3,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_14",
"Cost": 220,
"UITreeRow": -2,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_15",
"Cost": 300,
"UITreeRow": -1,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_16",
"Cost": 300,
"UITreeRow": 1,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_17",
"Cost": 340,
"UITreeRow": -3,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_18",
"Cost": 420,
"UITreeRow": -1,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_19",
"Cost": 420,
"UITreeRow": 1,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_MEDIEVAL_20",
"Cost": 340,
"UITreeRow": 3,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "CIVIC_AP_RENAISSANCE_21",
"Cost": 440,
"UITreeRow": -3,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "CIVIC_AP_RENAISSANCE_22",
"Cost": 600,
"UITreeRow": -1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "CIVIC_AP_RENAISSANCE_23",
"Cost": 600,
"UITreeRow": 1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "CIVIC_AP_RENAISSANCE_24",
"Cost": 440,
"UITreeRow": 3,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "CIVIC_AP_RENAISSANCE_25",
"Cost": 720,
"UITreeRow": -1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "CIVIC_AP_RENAISSANCE_26",
"Cost": 720,
"UITreeRow": 1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_27",
"Cost": 800,
"UITreeRow": -3,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_28",
"Cost": 1010,
"UITreeRow": -1,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_29",
"Cost": 1010,
"UITreeRow": 0,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_30",
"Cost": 800,
"UITreeRow": 2,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_31",
"Cost": 1050,
"UITreeRow": -3,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_32",
"Cost": 1210,
"UITreeRow": 2,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_INDUSTRIAL_33",
"Cost": 1210,
"UITreeRow": -1,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "CIVIC_AP_MODERN_34",
"Cost": 1540,
"UITreeRow": -3,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_35",
"Cost": 1580,
"UITreeRow": -2,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_36",
"Cost": 1715,
"UITreeRow": -2,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_37",
"Cost": 1540,
"UITreeRow": -1,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_38",
"Cost": 1540,
"UITreeRow": 1,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_39",
"Cost": 1640,
"UITreeRow": -1,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_40",
"Cost": 1640,
"UITreeRow": 0,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_41",
"Cost": 1640,
"UITreeRow": 2,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_MODERN_42",
"Cost": 1640,
"UITreeRow": 3,
"EraType": "ERA_MODERN",
},
{
"Type": "CIVIC_AP_ATOMIC_43",
"Cost": 2185,
"UITreeRow": -1,
"EraType": "ERA_ATOMIC",
},
{
"Type": "CIVIC_AP_ATOMIC_44",
"Cost": 2185,
"UITreeRow": 2,
"EraType": "ERA_ATOMIC",
},
{
"Type": "CIVIC_AP_ATOMIC_45",
"Cost": 1955,
"UITreeRow": -3,
"EraType": "ERA_ATOMIC",
},
{
"Type": "CIVIC_AP_ATOMIC_46",
"Cost": 2415,
"UITreeRow": -1,
"EraType": "ERA_ATOMIC",
},
{
"Type": "CIVIC_AP_ATOMIC_47",
"Cost": 2415,
"UITreeRow": 1,
"EraType": "ERA_ATOMIC",
},
{
"Type": "CIVIC_AP_INFORMATION_48",
"Cost": 2880,
"UITreeRow": 0,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_INFORMATION_49",
"Cost": 2880,
"UITreeRow": 2,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_FUTURE_50",
"Cost": 3200,
"UITreeRow": 3,
"EraType": "ERA_FUTURE",
},
{
"Type": "CIVIC_AP_INFORMATION_51",
"Cost": 2880,
"UITreeRow": -2,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_INFORMATION_52",
"Cost": 3000,
"UITreeRow": 0,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_INFORMATION_53",
"Cost": 3000,
"UITreeRow": 1,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_INFORMATION_54",
"Cost": 3000,
"UITreeRow": 2,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_INFORMATION_55",
"Cost": 3100,
"UITreeRow": -1,
"EraType": "ERA_INFORMATION",
},
{
"Type": "CIVIC_AP_FUTURE_56",
"Cost": 3200,
"UITreeRow": -2,
"EraType": "ERA_FUTURE",
},
{
"Type": "CIVIC_AP_FUTURE_57",
"Cost": 3200,
"UITreeRow": -1,
"EraType": "ERA_FUTURE",
},
{
"Type": "CIVIC_AP_FUTURE_58",
"Cost": 3200,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
},
{
"Type": "CIVIC_AP_FUTURE_59",
"Cost": 3200,
"UITreeRow": 1,
"EraType": "ERA_FUTURE",
},
{
"Type": "CIVIC_AP_FUTURE_60",
"Cost": 3200,
"UITreeRow": 2,
"EraType": "ERA_FUTURE",
},
]

View File

@@ -0,0 +1,468 @@
from typing import List
from ..ItemData import NewItemData
new_tech: List[NewItemData] = [
{
"Type": "TECH_AP_ANCIENT_00",
"Cost": 25,
"UITreeRow": 0,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_01",
"Cost": 25,
"UITreeRow": 1,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_02",
"Cost": 25,
"UITreeRow": 3,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_03",
"Cost": 50,
"UITreeRow": -3,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_04",
"Cost": 50,
"UITreeRow": -2,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_05",
"Cost": 50,
"UITreeRow": -1,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_06",
"Cost": 50,
"UITreeRow": 1,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_07",
"Cost": 50,
"UITreeRow": 0,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_08",
"Cost": 80,
"UITreeRow": 2,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_09",
"Cost": 80,
"UITreeRow": 3,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_ANCIENT_10",
"Cost": 80,
"UITreeRow": 4,
"EraType": "ERA_ANCIENT",
},
{
"Type": "TECH_AP_CLASSICAL_11",
"Cost": 120,
"UITreeRow": -2,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_12",
"Cost": 120,
"UITreeRow": 0,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_13",
"Cost": 120,
"UITreeRow": 1,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_14",
"Cost": 120,
"UITreeRow": 3,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_15",
"Cost": 200,
"UITreeRow": -3,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_16",
"Cost": 200,
"UITreeRow": -1,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_17",
"Cost": 200,
"UITreeRow": 2,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_CLASSICAL_18",
"Cost": 200,
"UITreeRow": 4,
"EraType": "ERA_CLASSICAL",
},
{
"Type": "TECH_AP_MEDIEVAL_19",
"Cost": 300,
"UITreeRow": -2,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MEDIEVAL_20",
"Cost": 300,
"UITreeRow": 0,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MEDIEVAL_21",
"Cost": 300,
"UITreeRow": 4,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MEDIEVAL_22",
"Cost": 390,
"UITreeRow": -1,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MEDIEVAL_23",
"Cost": 390,
"UITreeRow": 1,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MEDIEVAL_24",
"Cost": 390,
"UITreeRow": 2,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MEDIEVAL_25",
"Cost": 390,
"UITreeRow": 3,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_RENAISSANCE_26",
"Cost": 600,
"UITreeRow": -3,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_27",
"Cost": 600,
"UITreeRow": -2,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_28",
"Cost": 600,
"UITreeRow": 0,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_29",
"Cost": 600,
"UITreeRow": 1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_30",
"Cost": 600,
"UITreeRow": 4,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_31",
"Cost": 730,
"UITreeRow": -3,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_32",
"Cost": 730,
"UITreeRow": -1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_33",
"Cost": 730,
"UITreeRow": 1,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_RENAISSANCE_34",
"Cost": 730,
"UITreeRow": 3,
"EraType": "ERA_RENAISSANCE",
},
{
"Type": "TECH_AP_INDUSTRIAL_35",
"Cost": 930,
"UITreeRow": -2,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_36",
"Cost": 930,
"UITreeRow": -1,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_37",
"Cost": 930,
"UITreeRow": 1,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_38",
"Cost": 930,
"UITreeRow": 3,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_39",
"Cost": 1070,
"UITreeRow": -3,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_40",
"Cost": 1070,
"UITreeRow": -1,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_41",
"Cost": 1070,
"UITreeRow": 0,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_INDUSTRIAL_42",
"Cost": 1070,
"UITreeRow": 2,
"EraType": "ERA_INDUSTRIAL",
},
{
"Type": "TECH_AP_MODERN_43",
"Cost": 1250,
"UITreeRow": -2,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_MODERN_44",
"Cost": 1250,
"UITreeRow": 0,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_MODERN_45",
"Cost": 1250,
"UITreeRow": 1,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_MODERN_46",
"Cost": 1370,
"UITreeRow": -3,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_MODERN_47",
"Cost": 1370,
"UITreeRow": -2,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_MODERN_48",
"Cost": 1370,
"UITreeRow": -1,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_MODERN_49",
"Cost": 1370,
"UITreeRow": 2,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_ATOMIC_50",
"Cost": 1480,
"UITreeRow": -2,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_51",
"Cost": 1480,
"UITreeRow": -1,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_52",
"Cost": 1480,
"UITreeRow": 0,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_53",
"Cost": 1480,
"UITreeRow": 1,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_54",
"Cost": 1480,
"UITreeRow": 2,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_55",
"Cost": 1660,
"UITreeRow": -3,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_56",
"Cost": 1660,
"UITreeRow": 1,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_ATOMIC_57",
"Cost": 1660,
"UITreeRow": 2,
"EraType": "ERA_ATOMIC",
},
{
"Type": "TECH_AP_INFORMATION_58",
"Cost": 1850,
"UITreeRow": -3,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_59",
"Cost": 1850,
"UITreeRow": -1,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_60",
"Cost": 1850,
"UITreeRow": 0,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_61",
"Cost": 1850,
"UITreeRow": 1,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_62",
"Cost": 1850,
"UITreeRow": 2,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_63",
"Cost": 1850,
"UITreeRow": 3,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_64",
"Cost": 2155,
"UITreeRow": -2,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_65",
"Cost": 2155,
"UITreeRow": 2,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_INFORMATION_66",
"Cost": 2155,
"UITreeRow": 1,
"EraType": "ERA_INFORMATION",
},
{
"Type": "TECH_AP_MEDIEVAL_67",
"Cost": 300,
"UITreeRow": -3,
"EraType": "ERA_MEDIEVAL",
},
{
"Type": "TECH_AP_MODERN_68",
"Cost": 1250,
"UITreeRow": 3,
"EraType": "ERA_MODERN",
},
{
"Type": "TECH_AP_FUTURE_69",
"Cost": 2200,
"UITreeRow": -3,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_70",
"Cost": 2200,
"UITreeRow": -2,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_71",
"Cost": 2200,
"UITreeRow": -1,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_72",
"Cost": 2200,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_73",
"Cost": 2200,
"UITreeRow": 1,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_74",
"Cost": 2200,
"UITreeRow": 2,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_75",
"Cost": 2500,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
},
{
"Type": "TECH_AP_FUTURE_76",
"Cost": 2600,
"UITreeRow": 0,
"EraType": "ERA_FUTURE",
},
]

View File

@@ -0,0 +1,110 @@
from typing import List
from ..ItemData import TechPrereqData
new_tech_prereqs: List[TechPrereqData] = [
{"Technology": "TECH_AP_ANCIENT_06", "PrereqTech": "TECH_AP_ANCIENT_01"},
{"Technology": "TECH_AP_ANCIENT_07", "PrereqTech": "TECH_AP_ANCIENT_00"},
{"Technology": "TECH_AP_ANCIENT_05", "PrereqTech": "TECH_AP_ANCIENT_00"},
{"Technology": "TECH_AP_ANCIENT_08", "PrereqTech": "TECH_AP_ANCIENT_02"},
{"Technology": "TECH_AP_ANCIENT_09", "PrereqTech": "TECH_AP_ANCIENT_02"},
{"Technology": "TECH_AP_ANCIENT_10", "PrereqTech": "TECH_AP_ANCIENT_02"},
{"Technology": "TECH_AP_CLASSICAL_15", "PrereqTech": "TECH_AP_ANCIENT_03"},
{"Technology": "TECH_AP_CLASSICAL_11", "PrereqTech": "TECH_AP_ANCIENT_03"},
{"Technology": "TECH_AP_CLASSICAL_11", "PrereqTech": "TECH_AP_ANCIENT_04"},
{"Technology": "TECH_AP_CLASSICAL_12", "PrereqTech": "TECH_AP_ANCIENT_07"},
{"Technology": "TECH_AP_CLASSICAL_13", "PrereqTech": "TECH_AP_ANCIENT_06"},
{"Technology": "TECH_AP_CLASSICAL_14", "PrereqTech": "TECH_AP_ANCIENT_09"},
{"Technology": "TECH_AP_CLASSICAL_16", "PrereqTech": "TECH_AP_CLASSICAL_12"},
{"Technology": "TECH_AP_CLASSICAL_17", "PrereqTech": "TECH_AP_ANCIENT_08"},
{"Technology": "TECH_AP_CLASSICAL_17", "PrereqTech": "TECH_AP_CLASSICAL_13"},
{"Technology": "TECH_AP_CLASSICAL_18", "PrereqTech": "TECH_AP_ANCIENT_10"},
{"Technology": "TECH_AP_MEDIEVAL_19", "PrereqTech": "TECH_AP_CLASSICAL_16"},
{"Technology": "TECH_AP_MEDIEVAL_20", "PrereqTech": "TECH_AP_CLASSICAL_12"},
{"Technology": "TECH_AP_MEDIEVAL_20", "PrereqTech": "TECH_AP_CLASSICAL_13"},
{"Technology": "TECH_AP_MEDIEVAL_23", "PrereqTech": "TECH_AP_CLASSICAL_13"},
{"Technology": "TECH_AP_MEDIEVAL_21", "PrereqTech": "TECH_AP_CLASSICAL_14"},
{"Technology": "TECH_AP_MEDIEVAL_21", "PrereqTech": "TECH_AP_CLASSICAL_18"},
{"Technology": "TECH_AP_MEDIEVAL_22", "PrereqTech": "TECH_AP_CLASSICAL_16"},
{"Technology": "TECH_AP_MEDIEVAL_22", "PrereqTech": "TECH_AP_MEDIEVAL_20"},
{"Technology": "TECH_AP_MEDIEVAL_25", "PrereqTech": "TECH_AP_CLASSICAL_17"},
{"Technology": "TECH_AP_MEDIEVAL_24", "PrereqTech": "TECH_AP_CLASSICAL_17"},
{"Technology": "TECH_AP_RENAISSANCE_27", "PrereqTech": "TECH_AP_MEDIEVAL_22"},
{"Technology": "TECH_AP_RENAISSANCE_28", "PrereqTech": "TECH_AP_MEDIEVAL_22"},
{"Technology": "TECH_AP_RENAISSANCE_28", "PrereqTech": "TECH_AP_MEDIEVAL_23"},
{"Technology": "TECH_AP_RENAISSANCE_29", "PrereqTech": "TECH_AP_MEDIEVAL_20"},
{"Technology": "TECH_AP_RENAISSANCE_29", "PrereqTech": "TECH_AP_MEDIEVAL_23"},
{"Technology": "TECH_AP_RENAISSANCE_29", "PrereqTech": "TECH_AP_MEDIEVAL_24"},
{"Technology": "TECH_AP_RENAISSANCE_30", "PrereqTech": "TECH_AP_MEDIEVAL_21"},
{"Technology": "TECH_AP_RENAISSANCE_31", "PrereqTech": "TECH_AP_RENAISSANCE_26"},
{"Technology": "TECH_AP_RENAISSANCE_32", "PrereqTech": "TECH_AP_MEDIEVAL_22"},
{"Technology": "TECH_AP_RENAISSANCE_33", "PrereqTech": "TECH_AP_RENAISSANCE_29"},
{"Technology": "TECH_AP_RENAISSANCE_34", "PrereqTech": "TECH_AP_MEDIEVAL_25"},
{"Technology": "TECH_AP_INDUSTRIAL_35", "PrereqTech": "TECH_AP_RENAISSANCE_31"},
{"Technology": "TECH_AP_INDUSTRIAL_35", "PrereqTech": "TECH_AP_RENAISSANCE_27"},
{"Technology": "TECH_AP_INDUSTRIAL_36", "PrereqTech": "TECH_AP_RENAISSANCE_32"},
{"Technology": "TECH_AP_INDUSTRIAL_36", "PrereqTech": "TECH_AP_RENAISSANCE_28"},
{"Technology": "TECH_AP_INDUSTRIAL_41", "PrereqTech": "TECH_AP_INDUSTRIAL_36"},
{"Technology": "TECH_AP_INDUSTRIAL_41", "PrereqTech": "TECH_AP_RENAISSANCE_33"},
{"Technology": "TECH_AP_INDUSTRIAL_38", "PrereqTech": "TECH_AP_RENAISSANCE_34"},
{"Technology": "TECH_AP_INDUSTRIAL_38", "PrereqTech": "TECH_AP_RENAISSANCE_30"},
{"Technology": "TECH_AP_INDUSTRIAL_39", "PrereqTech": "TECH_AP_INDUSTRIAL_35"},
{"Technology": "TECH_AP_INDUSTRIAL_40", "PrereqTech": "TECH_AP_INDUSTRIAL_36"},
{"Technology": "TECH_AP_INDUSTRIAL_37", "PrereqTech": "TECH_AP_RENAISSANCE_33"},
{"Technology": "TECH_AP_INDUSTRIAL_42", "PrereqTech": "TECH_AP_INDUSTRIAL_37"},
{"Technology": "TECH_AP_INDUSTRIAL_42", "PrereqTech": "TECH_AP_INDUSTRIAL_38"},
{"Technology": "TECH_AP_MODERN_43", "PrereqTech": "TECH_AP_INDUSTRIAL_35"},
{"Technology": "TECH_AP_MODERN_43", "PrereqTech": "TECH_AP_INDUSTRIAL_36"},
{"Technology": "TECH_AP_MODERN_44", "PrereqTech": "TECH_AP_INDUSTRIAL_41"},
{"Technology": "TECH_AP_MODERN_45", "PrereqTech": "TECH_AP_INDUSTRIAL_42"},
{"Technology": "TECH_AP_MODERN_46", "PrereqTech": "TECH_AP_INDUSTRIAL_39"},
{"Technology": "TECH_AP_MODERN_47", "PrereqTech": "TECH_AP_INDUSTRIAL_39"},
{"Technology": "TECH_AP_MODERN_47", "PrereqTech": "TECH_AP_MODERN_43"},
{"Technology": "TECH_AP_MODERN_48", "PrereqTech": "TECH_AP_INDUSTRIAL_40"},
{"Technology": "TECH_AP_MODERN_49", "PrereqTech": "TECH_AP_MODERN_45"},
{"Technology": "TECH_AP_ATOMIC_55", "PrereqTech": "TECH_AP_MODERN_46"},
{"Technology": "TECH_AP_ATOMIC_55", "PrereqTech": "TECH_AP_MODERN_47"},
{"Technology": "TECH_AP_ATOMIC_50", "PrereqTech": "TECH_AP_MODERN_47"},
{"Technology": "TECH_AP_ATOMIC_51", "PrereqTech": "TECH_AP_MODERN_47"},
{"Technology": "TECH_AP_ATOMIC_51", "PrereqTech": "TECH_AP_MODERN_48"},
{"Technology": "TECH_AP_ATOMIC_52", "PrereqTech": "TECH_AP_MODERN_44"},
{"Technology": "TECH_AP_ATOMIC_52", "PrereqTech": "TECH_AP_MODERN_45"},
{"Technology": "TECH_AP_ATOMIC_53", "PrereqTech": "TECH_AP_MODERN_45"},
{"Technology": "TECH_AP_ATOMIC_53", "PrereqTech": "TECH_AP_MODERN_49"},
{"Technology": "TECH_AP_ATOMIC_56", "PrereqTech": "TECH_AP_ATOMIC_52"},
{"Technology": "TECH_AP_ATOMIC_56", "PrereqTech": "TECH_AP_ATOMIC_53"},
{"Technology": "TECH_AP_ATOMIC_54", "PrereqTech": "TECH_AP_MODERN_49"},
{"Technology": "TECH_AP_ATOMIC_57", "PrereqTech": "TECH_AP_ATOMIC_54"},
{"Technology": "TECH_AP_INFORMATION_58", "PrereqTech": "TECH_AP_ATOMIC_55"},
{"Technology": "TECH_AP_INFORMATION_64", "PrereqTech": "TECH_AP_ATOMIC_55"},
{"Technology": "TECH_AP_INFORMATION_59", "PrereqTech": "TECH_AP_ATOMIC_50"},
{"Technology": "TECH_AP_INFORMATION_59", "PrereqTech": "TECH_AP_ATOMIC_51"},
{"Technology": "TECH_AP_INFORMATION_60", "PrereqTech": "TECH_AP_ATOMIC_51"},
{"Technology": "TECH_AP_INFORMATION_60", "PrereqTech": "TECH_AP_ATOMIC_52"},
{"Technology": "TECH_AP_INFORMATION_61", "PrereqTech": "TECH_AP_ATOMIC_56"},
{"Technology": "TECH_AP_INFORMATION_62", "PrereqTech": "TECH_AP_ATOMIC_57"},
{"Technology": "TECH_AP_INFORMATION_63", "PrereqTech": "TECH_AP_ATOMIC_57"},
{"Technology": "TECH_AP_INFORMATION_65", "PrereqTech": "TECH_AP_INFORMATION_62"},
{"Technology": "TECH_AP_INFORMATION_66", "PrereqTech": "TECH_AP_INFORMATION_61"},
{"Technology": "TECH_AP_MEDIEVAL_67", "PrereqTech": "TECH_AP_CLASSICAL_15"},
{"Technology": "TECH_AP_MEDIEVAL_67", "PrereqTech": "TECH_AP_CLASSICAL_16"},
{"Technology": "TECH_AP_MEDIEVAL_23", "PrereqTech": "TECH_AP_MEDIEVAL_20"},
{"Technology": "TECH_AP_MODERN_68", "PrereqTech": "TECH_AP_INDUSTRIAL_42"},
{"Technology": "TECH_AP_MODERN_49", "PrereqTech": "TECH_AP_MODERN_68"},
{"Technology": "TECH_AP_RENAISSANCE_26", "PrereqTech": "TECH_AP_MEDIEVAL_67"},
{"Technology": "TECH_AP_RENAISSANCE_27", "PrereqTech": "TECH_AP_MEDIEVAL_67"},
{"Technology": "TECH_AP_RENAISSANCE_27", "PrereqTech": "TECH_AP_MEDIEVAL_19"},
{"Technology": "TECH_AP_MODERN_48", "PrereqTech": "TECH_AP_MODERN_44"},
{"Technology": "TECH_AP_INFORMATION_64", "PrereqTech": "TECH_AP_INFORMATION_59"},
{"Technology": "TECH_AP_INFORMATION_64", "PrereqTech": "TECH_AP_INFORMATION_60"},
{"Technology": "TECH_AP_INFORMATION_64", "PrereqTech": "TECH_AP_INFORMATION_61"},
{"Technology": "TECH_AP_FUTURE_69", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_70", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_71", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_72", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_73", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_74", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_75", "PrereqTech": "TECH_AP_AP60"},
{"Technology": "TECH_AP_FUTURE_76", "PrereqTech": "TECH_AP_AP60"},
]

View File

@@ -0,0 +1,41 @@
from typing import Dict, List
progressive_districts: Dict[str, List[str]] = {
"PROGRESSIVE_CAMPUS": ["TECH_WRITING", "TECH_EDUCATION", "TECH_CHEMISTRY"],
"PROGRESSIVE_THEATER": ["CIVIC_DRAMA_POETRY", "CIVIC_HUMANISM", "TECH_RADIO"],
"PROGRESSIVE_HOLY_SITE": ["TECH_ASTROLOGY", "CIVIC_THEOLOGY"],
"PROGRESSIVE_ENCAMPMENT": [
"TECH_BRONZE_WORKING",
"TECH_MILITARY_ENGINEERING",
"TECH_MILITARY_SCIENCE",
],
"PROGRESSIVE_COMMERCIAL_HUB": ["TECH_CURRENCY", "TECH_BANKING", "TECH_ECONOMICS"],
"PROGRESSIVE_HARBOR": ["TECH_CELESTIAL_NAVIGATION", "TECH_MASS_PRODUCTION"],
"PROGRESSIVE_INDUSTRIAL_ZONE": [
"TECH_APPRENTICESHIP",
"TECH_INDUSTRIALIZATION",
"TECH_ELECTRICITY",
"TECH_NUCLEAR_FISSION",
],
"PROGRESSIVE_PRESERVE": ["CIVIC_MYSTICISM", "CIVIC_CONSERVATION"],
"PROGRESSIVE_ENTERTAINMENT_COMPLEX": [
"CIVIC_GAMES_RECREATION",
"CIVIC_NATURAL_HISTORY",
"CIVIC_PROFESSIONAL_SPORTS",
],
"PROGRESSIVE_NEIGHBORHOOD": [
"CIVIC_URBANIZATION",
"TECH_REPLACEABLE_PARTS",
"CIVIC_CAPITALISM",
],
"PROGRESSIVE_AERODROME": ["TECH_FLIGHT", "TECH_ADVANCED_FLIGHT"],
"PROGRESSIVE_DIPLOMATIC_QUARTER": ["TECH_MATHEMATICS", "CIVIC_DIPLOMATIC_SERVICE"],
"PROGRESSIVE_SPACE_PORT": [
"TECH_ROCKETRY",
"TECH_SATELLITES",
"TECH_NANOTECHNOLOGY",
"TECH_SMART_MATERIALS",
"TECH_OFFWORLD_MISSION",
],
}

View File

@@ -0,0 +1,59 @@
# Civilization 6 Archipelago
## What does randomization do to this game?
In Civilization VI, the tech and civic trees are both shuffled. This presents some interesting ways to play the game in a non-standard way. If you are feeling adventurous, you can enable the "boostsanity" option in order to really change up the way you normally would play a Civ game. Details on the option can be found in the [Boostsanity](#boostsanity) section below.
There are a few changes that the Archipelago mod introduces in order to make this playable/fun. These are detailed in the [__FAQ__](#faqs) section below.
## What is the goal of Civilization VI when randomized?
The goal of randomized Civilization VI remains the same. Pursue any victory type you have enabled in your game settings, the one you normally go for may or may not be feasible based on how things have been changed up!
## Which items can be in another player's world?
All technologies and civics can be found in another player's world.
## What does another world's item look like in Civilization VI?
Each item from another world is represented as a researchable tech/civic in your normal tech/civic trees.
## When the player receives an item, what happens?
A short period after receiving an item, you will get a notification indicating you have discovered the relevant tech/civic. You will also get the regular popup that details what the given item has unlocked for you.
## FAQs
- Do I need the DLC to play this?
- Yes, you need both Rise & Fall and Gathering Storm.
- Does this work with Multiplayer?
- It does not and, despite my best efforts, probably won't until there's a new way for external programs to be able to interact with the game.
- Does my mod that reskins Barbarians as various Pro Wrestlers work with this?
- Only one way to find out! Any mods that modify techs/civics will most likely cause issues, though.
- "Help! I can't see any of the items that have been sent to me!"
- Both trees by default will show you the researchable Archipelago locations. To view the normal tree, you can click "Toggle Archipelago Tree" in the top-left corner of the tree view.
- "Oh no! I received the Machinery tech and now instead of getting an Archer next turn, I have to wait an additional 10 turns to get a Crossbowman!"
- Vanilla prevents you from building units of the same class from an earlier tech level after you have researched a later variant. For example, this could be problematic if someone unlocks Crossbowmen for you right out the gate since you won't be able to make Archers (which have a much lower production cost).
Solution: You can now go in to the tech tree, click "Toggle Archipelago Tree" to view your unlocked techs, and then can click any tech you have unlocked to toggle whether it is currently active or not.
- "How does DeathLink work? Am I going to have to start a new game every time one of my friends dies?"
- Heavens no, my fellow Archipelago appreciator. When configuring your Archipelago options for Civilization on the options page, there are several choices available for you to fine tune the way you'd like to be punished for the follies of your friends. These include: Having a random unit destroyed, losing a percentage of gold or faith, or even losing a point on your era score. If you can't make up your mind, you can elect to have any of them be selected every time a death link is sent your way.
In the event you lose one of your units in combat (this means captured units don't count), then you will send a death link event to the rest of your friends.
- I enabled `progressive districts` but I have no idea what tech or civic a progressive district unlocks for me!
- Any technology or civic that grants you a new building in a district (or grants you the district itself) is now locked behind a progressive item. For example, `PROGRESSIVE_CAMPUS` would give you these items in the following order:
1. `TECH_WRITING`
2. `TECH_EDUCATION`
3. `TECH_CHEMISTRY`
- If you want to see the details around each item, you can review [this file](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/civ_6/data/progressive_districts.json).
## Boostsanity
Boostsanity takes all of the Eureka & Inspiration events and makes them location checks. This feature is the one to change up the way Civilization is played in an AP multiworld/randomizer. What normally are mundane tasks that are passively collected now become a novel and interesting bucket list that you need to pay attention to in order to unlock items for yourself and others!
Boosts have logic associated with them in order to verify you can always reach the ones you need to, when you need to. One side effect of this is that when boostsanity is enabled, some previously "Useful" items are now flagged as "Progression" (Urbanization, Pottery, The Wheel, to name a few).
### Boostsanity FAQs
- Someone sent me a tech/civic, and I'm worried I won't be able to boost it anymore!
- Fear not! Through a lot of wizardry 🧙‍♂️ you can boost civics/techs that have already been received. Additionally, the UI has been updated to show you whether they have been boosted or not after receiving them.
- I need to kill a unit with a slinger/archer/musketman or some other obsolete unit I can't build anymore, how can I do this?
- Don't forget you can go into the Tech Tree and click on a Vanilla tech you've received in order to toggle it on/off. This is necessary in order to pursue some of the boosts if you receive techs in certain orders.
- Something happened, and I'm not able to unlock the boost due to game rules!
- A few scenarios you may worry about: "Found a religion", "Make an alliance with another player", "Develop an alliance to level 2", "Build a wonder from X Era", to name a few. Any boost that is "miss-able" has been flagged as an "Excluded" location and will not ever receive a progression item. For a list of how each boost is flagged, take a look [here](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/civ_6/data/boosts.json).
- I'm worried that my `PROGRESSIVE_ERA` item is going to be stuck in a boost I won't have time to complete before my maximum unlocked era ends!
- The unpredictable timing of boosts and unlocking them can occasionally lead to scenarios where you'll have to first encounter a locked era defeat and then load a previous save. To help reduce the frequency of this, local `PROGRESSIVE_ERA` items will never be located at a boost check.
- There's too many boosts, how will I know which one's I should focus on?!
- In order to give a little more focus to all the boosts rather than just arbitrarily picking them at random, items in both of the vanilla trees will now have an advisor icon on them if its associated boost contains a progression item.

View File

@@ -0,0 +1,51 @@
# Setup Guide for Civilization VI Archipelago
This guide is meant to help you get up and running with Civilization VI in Archipelago. Note that this requires you to have both Rise & Fall and Gathering Storm installed. This will not work unless both of those DLCs are enabled.
## Requirements
The following are required in order to play Civ VI in Archipelago:
- Windows OS (Firaxis does not support the necessary tooling for Mac, or Linux)
- Installed [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) v0.4.5 or higher.
- The latest version of the [Civ VI AP Mod](https://github.com/hesto2/civilization_archipelago_mod/releases/latest).
## Enabling the tuner
Depending on how you installed Civ 6 you will have to navigate to one of the following:
- `YOUR_USER/Documents/My Games/Sid Meier's Civilization VI/AppOptions.txt`
- `YOUR_USER/AppData/Local/Firaxis Games/Sid Meier's Civilization VI/AppOptions.txt`
Once you have located your `AppOptions.txt`, do a search for `Enable FireTuner`. Set `EnableTuner` to `1` instead of `0`. **NOTE**: While this is active, achievements will be disabled.
## Mod Installation
1. Download and unzip the latest release of the mod from [GitHub](https://github.com/hesto2/civilization_archipelago_mod/releases/latest).
2. Copy the folder containing the mod files to your Civ VI mods folder. On Windows, this is usually located at `C:\Users\YOUR_USER\Documents\My Games\Sid Meier's Civilization VI\Mods`.
3. After the Archipelago host generates a game, you should be given a `.apcivvi` file. Associate the file with the Archipelago Launcher and double click it.
4. Copy the contents of the new folder it generates (it will have the same name as the `.apcivvi` file) into your Civilization VI Archipelago Mod folder.
5. Your finished mod folder should look something like this:
- Civ VI Mods Directory
- civilization_archipelago_mod
- NewItems.xml
- InitOptions.lua
- Archipelago.modinfo
- All the other mod files, etc.
## Configuring your game
When configuring your game, make sure to start the game in the Ancient Era and leave all settings related to starting technologies and civics as the defaults. Other than that, configure difficulty, AI, etc. as you normally would.
## Troubleshooting
- If you are getting an error: "The remote computer refused the network connection", or something else related to the client (or tuner) not being able to connect, it likely indicates the tuner is not actually enabled. One simple way to verify that it is enabled is, after completing the setup steps, go to Main Menu &rarr; Options &rarr; Look for an option named "Tuner" and verify it is set to "Enabled"
- If your game gets in a state where someone has sent you items or you have sent locations but these are not correctly sent to the multiworld, you can run `/resync` from the Civ 6 client. This may take up to a minute depending on how many items there are.

View File

@@ -0,0 +1,107 @@
from Fill import distribute_items_restrictive
from ..Data import get_boosts_data
from . import CivVITestBase
class TestBoostsanityIncluded(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"boostsanity": "true",
"progression_style": "none",
"shuffle_goody_hut_rewards": "false",
}
def test_boosts_get_included(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
locations = self.multiworld.get_locations(self.player)
found_locations = 0
for location in locations:
if "BOOST" in location.name:
found_locations += 1
num_boost_locations = len(get_boosts_data())
self.assertEqual(found_locations, num_boost_locations)
def test_boosts_require_prereqs_no_progressives(self) -> None:
self.world_setup()
location = "BOOST_TECH_ADVANCED_BALLISTICS"
items_to_give = ["Refining", "Electricity", "Apprenticeship", "Industrialization"]
self.assertFalse(self.can_reach_location(location))
for prereq in items_to_give:
self.collect_by_name(prereq)
is_last_prereq = prereq == items_to_give[-1]
self.assertEqual(self.can_reach_location(location), is_last_prereq)
class TestBoostsanityIncludedNoProgressiveDistricts(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"boostsanity": "true",
"progression_style": "districts_only",
"shuffle_goody_hut_rewards": "false",
}
def test_boosts_get_included(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
locations = self.multiworld.get_locations(self.player)
found_locations = 0
for location in locations:
if "BOOST" in location.name:
found_locations += 1
num_boost_locations = len(get_boosts_data())
self.assertEqual(found_locations, num_boost_locations)
class TestBoostsanityPrereqsWithProgressiveDistricts(CivVITestBase):
options = {
"progressive_eras": "true",
"boostsanity": "true",
"progression_style": "districts_only",
"shuffle_goody_hut_rewards": "false",
}
def test_boosts_require_progressive_prereqs_optional(self) -> None:
location = "BOOST_TECH_NUCLEAR_FUSION"
items_to_give = ["Progressive Industrial Zone", "Progressive Industrial Zone"]
self.assertFalse(self.can_reach_location(location))
for prereq in items_to_give:
self.collect_by_name(prereq)
is_last_prereq = prereq == items_to_give[-1]
self.assertEqual(self.can_reach_location(location), is_last_prereq)
def tests_boosts_require_correct_progressive_district_count(self) -> None:
location = "BOOST_TECH_RIFLING"
items_to_give = ["Mining", "Progressive Encampment", "Progressive Encampment"]
self.assertFalse(self.can_reach_location(location))
for prereq in items_to_give:
self.collect_by_name(prereq)
is_last_prereq = prereq == items_to_give[-1]
self.assertEqual(self.can_reach_location(location), is_last_prereq)
class TestBoostsanityExcluded(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"death_link": "true",
"boostsanity": "false",
"death_link_effect": "unit_killed",
"progressive_districts": "true",
"shuffle_goody_hut_rewards": "false",
}
def test_boosts_are_not_included(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
locations = self.multiworld.get_locations(self.player)
found_locations = 0
for location in locations:
if "BOOST" in location.name:
found_locations += 1
self.assertEqual(found_locations, 0)

View File

@@ -0,0 +1,114 @@
from typing import Dict
from BaseClasses import ItemClassification
from Fill import distribute_items_restrictive
from ..Items import FillerItemRarity, filler_data
from . import CivVITestBase
class TestGoodyHutsIncluded(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"progressive_districts": "true",
"shuffle_goody_hut_rewards": "true",
}
def test_goody_huts_get_included(self) -> None:
self.world_setup()
self.world.generate_early()
distribute_items_restrictive(self.multiworld)
expected_goody_huts = 10
found = 0
for location in self.multiworld.get_locations(self.player):
if location.name.startswith("GOODY_HUT_"):
found += 1
self.assertEqual(found, expected_goody_huts)
class TestGoodyHutsExcluded(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"progressive_districts": "true",
"shuffle_goody_hut_rewards": "false",
}
def test_goody_huts_are_not_included(self) -> None:
self.world_setup()
self.world.generate_early()
distribute_items_restrictive(self.multiworld)
found_goody_huts = 0
for location in self.multiworld.get_locations(self.player):
if location.name.startswith("GOODY_HUT_"):
found_goody_huts += 1
self.assertEqual(found_goody_huts, 0)
class TestFillerItemsIncludedByRarity(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"progressive_districts": "true",
"shuffle_goody_hut_rewards": "true",
"boostsanity": "true"
}
def test_filler_items_are_included_by_rarity(self) -> None:
self.world_setup()
self.world.generate_early()
distribute_items_restrictive(self.multiworld)
rarity_counts: Dict[FillerItemRarity, int] = {
FillerItemRarity.COMMON: 0,
FillerItemRarity.UNCOMMON: 0,
FillerItemRarity.RARE: 0,
}
total_filler_items = 0
for item in self.multiworld.itempool:
if item.classification == ItemClassification.filler:
rarity = filler_data[item.name].rarity
rarity_counts[rarity] += 1
total_filler_items += 1
expected_counts = {
FillerItemRarity.COMMON: 101,
FillerItemRarity.UNCOMMON: 27,
FillerItemRarity.RARE: 4,
}
for rarity, expected in expected_counts.items():
self.assertEqual(rarity_counts[rarity], expected, f"Expected {expected} {rarity} items, found {rarity_counts[rarity]}")
class TestFillerItemsIncludedByRarityWithoutBoostsanity(CivVITestBase):
auto_construct = False
options = {
"progressive_eras": "true",
"progressive_districts": "true",
"shuffle_goody_hut_rewards": "true",
"boostsanity": "false"
}
def test_filler_items_are_included_by_rarity_without_boostsanity(self) -> None:
self.world_setup()
self.world.generate_early()
distribute_items_restrictive(self.multiworld)
rarity_counts: Dict[FillerItemRarity, int] = {
FillerItemRarity.COMMON: 0,
FillerItemRarity.UNCOMMON: 0,
FillerItemRarity.RARE: 0,
}
total_filler_items = 0
for item in self.multiworld.itempool:
if item.classification == ItemClassification.filler:
rarity = filler_data[item.name].rarity
rarity_counts[rarity] += 1
total_filler_items += 1
expected_counts = {
FillerItemRarity.COMMON: 7,
FillerItemRarity.UNCOMMON: 2,
FillerItemRarity.RARE: 1,
}
for rarity, expected in expected_counts.items():
self.assertEqual(rarity_counts[rarity], expected, f"Expected {expected} {rarity} items, found {rarity_counts[rarity]}")

View File

@@ -0,0 +1,234 @@
from typing import Callable, List
from BaseClasses import CollectionState
from ..Data import get_era_required_items_data
from ..Enum import EraType
from ..ProgressiveDistricts import convert_items_to_progressive_items
from ..Items import get_item_by_civ_name
from . import CivVITestBase
def collect_items_for_era(test: CivVITestBase, era: EraType) -> None:
era_required_items = get_era_required_items_data()
items = [
get_item_by_civ_name(item, test.world.item_table).name
for item in era_required_items[era.value]
]
test.collect_by_name(items)
def collect_items_for_era_progressive(test: CivVITestBase, era: EraType) -> None:
era_progression_items = get_era_required_items_data()
progressive_items = convert_items_to_progressive_items(
era_progression_items[era.value]
)
items = [
get_item_by_civ_name(item, test.world.item_table).name
for item in progressive_items
]
for item in items:
test.collect(test.get_item_by_name(item))
def verify_eras_accessible(
test: CivVITestBase,
state: CollectionState,
collect_func: Callable[[CivVITestBase, EraType], None],
) -> None:
"""Collect for an era, then check if the next era is accessible and the one after that is not"""
for era in EraType:
if era == EraType.ERA_ANCIENT:
test.assertTrue(state.can_reach(era.value, "Region", test.player))
else:
test.assertFalse(state.can_reach(era.value, "Region", test.player))
eras = [
EraType.ERA_ANCIENT,
EraType.ERA_CLASSICAL,
EraType.ERA_MEDIEVAL,
EraType.ERA_RENAISSANCE,
EraType.ERA_INDUSTRIAL,
EraType.ERA_MODERN,
EraType.ERA_ATOMIC,
EraType.ERA_INFORMATION,
EraType.ERA_FUTURE,
]
for i in range(len(eras) - 1):
collect_func(test, eras[i])
test.assertTrue(state.can_reach(eras[i + 1].value, "Region", test.player))
if i + 2 < len(eras):
test.assertFalse(state.can_reach(eras[i + 2].value, "Region", test.player))
class TestNonProgressiveRegionRequirements(CivVITestBase):
options = {
"progression_style": "none",
"boostsanity": "false",
}
def test_eras_are_accessible_without_progressive_districts(self) -> None:
state = self.multiworld.state
verify_eras_accessible(self, state, collect_items_for_era)
class TestNonProgressiveRegionRequirementsWithBoostsanity(CivVITestBase):
options = {
"progression_style": "none",
"boostsanity": "true",
}
def test_eras_are_accessible_without_progressive_districts(self) -> None:
state = self.multiworld.state
verify_eras_accessible(self, state, collect_items_for_era)
class TestProgressiveDistrictRequirementsWithBoostsanity(CivVITestBase):
options = {
"progression_style": "districts_only",
"boostsanity": "true",
}
def test_eras_are_accessible_with_progressive_districts(self) -> None:
state = self.multiworld.state
verify_eras_accessible(self, state, collect_items_for_era_progressive)
class TestProgressiveDistrictRequirements(CivVITestBase):
options = {
"progression_style": "districts_only",
"boostsanity": "false",
}
def test_eras_are_accessible_with_progressive_districts(self) -> None:
state = self.multiworld.state
verify_eras_accessible(self, state, collect_items_for_era_progressive)
def test_progressive_districts_are_required(self) -> None:
state = self.multiworld.state
self.collect_all_but(["Progressive Encampment"])
self.assertFalse(state.can_reach("ERA_CLASSICAL", "Region", self.player))
self.assertFalse(state.can_reach("ERA_RENAISSANCE", "Region", self.player))
self.assertFalse(state.can_reach("ERA_MODERN", "Region", self.player))
self.collect(self.get_item_by_name("Progressive Encampment"))
self.assertTrue(state.can_reach("ERA_CLASSICAL", "Region", self.player))
self.assertFalse(state.can_reach("ERA_RENAISSANCE", "Region", self.player))
self.assertFalse(state.can_reach("ERA_MODERN", "Region", self.player))
self.collect(self.get_item_by_name("Progressive Encampment"))
self.assertTrue(state.can_reach("ERA_RENAISSANCE", "Region", self.player))
self.assertFalse(state.can_reach("ERA_MODERN", "Region", self.player))
self.collect(self.get_item_by_name("Progressive Encampment"))
self.assertTrue(state.can_reach("ERA_MODERN", "Region", self.player))
class TestProgressiveEraRequirements(CivVITestBase):
options = {
"progression_style": "eras_and_districts",
}
def test_eras_are_accessible_with_progressive_eras(self) -> None:
state = self.multiworld.state
self.collect_all_but(["Progressive Era"])
def check_eras_accessible(eras: List[EraType]):
for era in EraType:
if era in eras:
self.assertTrue(state.can_reach(era.value, "Region", self.player))
else:
self.assertFalse(state.can_reach(era.value, "Region", self.player))
progresive_era_item = self.get_item_by_name("Progressive Era")
accessible_eras = [EraType.ERA_ANCIENT]
check_eras_accessible(accessible_eras)
# Classical era requires 2 progressive era items
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_CLASSICAL]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_MEDIEVAL]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_RENAISSANCE]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_INDUSTRIAL]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_MODERN]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_ATOMIC]
check_eras_accessible(accessible_eras)
# Since we collect 2 in the ancient era, information and future era have same logic requirement
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_INFORMATION]
accessible_eras += [EraType.ERA_FUTURE]
check_eras_accessible(accessible_eras)
class TestProgressiveEraRequirementsWithBoostsanity(CivVITestBase):
options = {
"progression_style": "eras_and_districts",
"boostsanity": "true",
}
def test_eras_are_accessible_with_progressive_eras(self) -> None:
state = self.multiworld.state
self.collect_all_but(["Progressive Era"])
def check_eras_accessible(eras: List[EraType]):
for era in EraType:
if era in eras:
self.assertTrue(
state.can_reach(era.value, "Region", self.player),
"Failed for era: " + era.value,
)
else:
self.assertFalse(
state.can_reach(era.value, "Region", self.player),
"Failed for era: " + era.value,
)
progresive_era_item = self.get_item_by_name("Progressive Era")
accessible_eras = [EraType.ERA_ANCIENT]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_CLASSICAL]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_MEDIEVAL]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_RENAISSANCE]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_INDUSTRIAL]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_MODERN]
check_eras_accessible(accessible_eras)
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_ATOMIC]
check_eras_accessible(accessible_eras)
# Since we collect 2 in the ancient era, information and future era have same logic requirement
self.collect(progresive_era_item)
accessible_eras += [EraType.ERA_INFORMATION]
accessible_eras += [EraType.ERA_FUTURE]
check_eras_accessible(accessible_eras)

View File

@@ -0,0 +1,125 @@
from BaseClasses import ItemClassification
from Fill import distribute_items_restrictive
from ..Enum import CivVICheckType
from . import CivVITestBase
class TestStartingHints(CivVITestBase):
run_default_tests = False # type: ignore
auto_construct = False
options = {
"progressive_eras": "true",
"death_link": "true",
"death_link_effect": "unit_killed",
"progressive_districts": "true",
"pre_hint_items": set({"Progression", "Useful", "Filler"}),
}
def test_all_tech_civic_items_are_hinted_default(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
self.world.post_fill()
start_location_hints = self.world.options.start_location_hints.value
for location_name, location_data in self.world.location_table.items():
if location_data.location_type == CivVICheckType.CIVIC or location_data.location_type == CivVICheckType.TECH:
self.assertIn(location_name, start_location_hints)
else:
self.assertNotIn(location_name, start_location_hints)
class TestOnlyProgressionItemsHinted(CivVITestBase):
run_default_tests = False # type: ignore
auto_construct = False
options = {
"progressive_eras": "true",
"death_link": "true",
"death_link_effect": "unit_killed",
"progressive_districts": "true",
"pre_hint_items": set({"Progression"}),
}
def test_only_progression_items_are_hinted(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
self.world.post_fill()
start_location_hints = self.world.options.start_location_hints.value
self.assertTrue(len(start_location_hints) > 0)
for hint in start_location_hints:
location_data = self.world.get_location(hint)
if location_data.item:
self.assertTrue(location_data.item.classification == ItemClassification.progression)
else:
self.assertTrue(False, "Location has no item")
class TestNoJunkItemsHinted(CivVITestBase):
run_default_tests = False # type: ignore
auto_construct = False
options = {
"progressive_eras": "true",
"death_link": "true",
"death_link_effect": "unit_killed",
"progressive_districts": "true",
"pre_hint_items": set({"Progression", "Useful"}),
"boostsanity": "true",
"shuffle_goody_hut_rewards": "true",
}
def test_no_junk_items_are_hinted(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
item = self.multiworld.get_location("TECH_AP_ANCIENT_01", self.player).item
self.assertIsNotNone(item)
if item:
item.classification = ItemClassification.filler
self.world.post_fill()
start_location_hints = self.world.options.start_location_hints.value
self.assertTrue(len(start_location_hints) > 0)
self.assertNotIn("TECH_AP_ANCIENT_01", start_location_hints)
class TestOnlyJunkItemsHinted(CivVITestBase):
run_default_tests = False # type: ignore
auto_construct = False
options = {
"progressive_eras": "true",
"death_link": "true",
"death_link_effect": "unit_killed",
"progressive_districts": "true",
"pre_hint_items": set({"Filler"}),
}
def test_only_junk_items_are_hinted(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
item = self.multiworld.get_location("TECH_AP_ANCIENT_01", self.player).item
self.assertIsNotNone(item)
if item:
item.classification = ItemClassification.filler
self.world.post_fill()
start_location_hints = self.world.options.start_location_hints.value
self.assertEqual(len(start_location_hints), 1)
self.assertIn("TECH_AP_ANCIENT_01", start_location_hints)
class TestNoItemsHinted(CivVITestBase):
run_default_tests = False # type: ignore
auto_construct = False
options = {
"progressive_eras": "true",
"death_link": "true",
"death_link_effect": "unit_killed",
"progressive_districts": "true",
"pre_hint_items": set({}),
}
def test_no_items_are_hinted(self) -> None:
self.world_setup()
distribute_items_restrictive(self.multiworld)
self.world.post_fill()
start_location_hints = self.world.options.start_location_hints.value
self.assertEqual(len(start_location_hints), 0)

View File

@@ -0,0 +1,8 @@
from typing import ClassVar
from test.bases import WorldTestBase
class CivVITestBase(WorldTestBase):
game = "Civilization VI"
player: ClassVar[int] = 1