mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00

* 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>
220 lines
9.3 KiB
Python
220 lines
9.3 KiB
Python
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"
|