UNPKG

civ7-modding-tools

Version:
466 lines (433 loc) 19.9 kB
import * as lodash from "lodash"; import { TClassProperties, TObjectValues, TPartialRequired } from "../types"; import { CityNameNode, CivilizationItemNode, CivilizationNode, CivilizationTagNode, CivilizationTraitNode, CivilizationUnlockNode, DatabaseNode, GameCivilizationNodeSlice, GameEffectNode, IconDefinitionNode, LeaderCivilizationBiasNode, LeaderUnlockNode, LegacyCivilizationNode, LegacyCivilizationTraitNode, ModifierNode, ProgressionTreeNodeUnlockNode, ShellCivilizationNodeSlice, StartBiasAdjacentToCoastNode, StartBiasBiomeNode, StartBiasFeatureClassNode, StartBiasResourceNode, StartBiasRiverNode, StartBiasTerrainNode, TCivilizationItemNode, TCivilizationNode, TIconDefinitionNode, TLegacyCivilizationNode, TraitModifierNode, TraitNode, TStartBiasBiomeNode, TStartBiasFeatureClassNode, TStartBiasResourceNode, TStartBiasTerrainNode, TTraitNode, TypeNode, VisArtCivilizationBuildingCultureNode, VisArtCivilizationUnitCultureNode } from "../nodes"; import { ACTION_GROUP_ACTION, AGE, BUILDING_CULTURES, CIVILIZATION_DOMAIN, EFFECT, KIND, REQUIREMENT, TAG_TRAIT, TRAIT, UNIT_CULTURE } from "../constants"; import { locale, trim } from "../utils"; import { XmlFile } from "../files"; import { CivilizationLocalization, TCivilizationLocalization } from "../localizations"; import { BaseBuilder } from "./BaseBuilder"; import { UnitBuilder } from "./UnitBuilder"; import { ConstructibleBuilder } from "./ConstructibleBuilder"; import { ProgressionTreeBuilder } from "./ProgressionTreeBuilder"; import { ModifierBuilder } from "./ModifierBuilder"; import { UniqueQuarterBuilder } from "./UniqueQuarterBuilder"; import { CivilizationUnlockBuilder } from "./CivilizationUnlockBuilder"; import { LeaderUnlockBuilder } from "./LeaderUnlockBuilder"; type TCivilizationBuilder = TClassProperties<CivilizationBuilder> export class CivilizationBuilder extends BaseBuilder<TCivilizationBuilder> { _current: DatabaseNode = new DatabaseNode(); _shell: DatabaseNode = new DatabaseNode(); _legacy: DatabaseNode = new DatabaseNode(); _localizations: DatabaseNode = new DatabaseNode(); _icons: DatabaseNode = new DatabaseNode(); _gameEffects: GameEffectNode = new GameEffectNode(); civilizationTraits: (TObjectValues<typeof TRAIT> | string)[] = []; civilizationTags: TObjectValues<typeof TAG_TRAIT>[] = []; trait: TPartialRequired<TTraitNode, 'traitType'> = { traitType: 'TRAIT_' }; traitAbility: TPartialRequired<TraitNode, 'traitType'> = { traitType: 'TRAIT_ABILITY_' }; civilization: TPartialRequired<TCivilizationNode, 'civilizationType' | 'domain'> = { civilizationType: 'CIVILIZATION_CUSTOM', domain: 'AntiquityAgeCivilizations' } civilizationLegacy: TPartialRequired<TLegacyCivilizationNode, 'age'> = { age: AGE.ANTIQUITY } localizations: Partial<TCivilizationLocalization>[] = []; icon: TPartialRequired<TIconDefinitionNode, 'path'> = { path: 'fs://game/civ_sym_han' } civilizationItems: TPartialRequired<TCivilizationItemNode, "type" | "kind">[] = []; startBiasBiomes: TPartialRequired<TStartBiasBiomeNode, 'biomeType'>[] = []; startBiasResources: TPartialRequired<TStartBiasResourceNode, 'resourceType'>[] = []; startBiasTerrains: TPartialRequired<TStartBiasTerrainNode, 'terrainType'>[] = []; startBiasRiver: number | null = null; startBiasFeatureClasses: TPartialRequired<TStartBiasFeatureClassNode, 'featureClassType'>[] = []; startBiasAdjacentToCoast: number | null = null; visArtCivilizationBuildingCultures: TObjectValues<typeof BUILDING_CULTURES>[] = []; visArtCivilizationUnitCulture: TObjectValues<typeof UNIT_CULTURE> | null = null; constructor(payload: Partial<TCivilizationBuilder> = {}) { super(); this.fill(payload); } migrate() { if (this.trait.traitType === 'TRAIT_') { this.trait = { traitType: this.civilization.civilizationType.replace('CIVILIZATION_', 'TRAIT_'), } } if (this.traitAbility.traitType === 'TRAIT_ABILITY_') { this.traitAbility = { traitType: this.trait.traitType + '_ABILITY', } } const civilization = new CivilizationNode({ adjective: locale(this.civilization.civilizationType, 'adjective'), capitalName: locale(this.civilization.civilizationType, 'cityNames_1'), description: locale(this.civilization.civilizationType, 'description'), fullName: locale(this.civilization.civilizationType, 'fullName'), name: locale(this.civilization.civilizationType, 'name'), ...this.civilization, }); const trait = new TraitNode({ internalOnly: true, ...this.trait }); const traitAbility = new TraitNode({ name: locale(this.civilization.civilizationType + '_ABILITY', 'name'), description: locale(this.civilization.civilizationType + '_ABILITY', 'description'), internalOnly: true, ...this.traitAbility }); this._current.fill({ types: [ new TypeNode({ kind: KIND.TRAIT, type: this.trait.traitType }), new TypeNode({ kind: KIND.TRAIT, type: this.traitAbility.traitType }), ], traits: [trait, traitAbility], civilizations: [GameCivilizationNodeSlice.from(civilization)], civilizationTraits: [ new CivilizationTraitNode({ ...civilization, ...this.trait }), new CivilizationTraitNode({ ...civilization, ...this.traitAbility }), ...this.civilizationTraits.map(item => { return new CivilizationTraitNode({ ...civilization, traitType: item, }) }) ], startBiasBiomes: this.startBiasBiomes.map(item => { return new StartBiasBiomeNode({ ...civilization, ...item }) }), startBiasTerrains: this.startBiasTerrains.map(item => { return new StartBiasTerrainNode({ ...civilization, ...item }) }), startBiasFeatureClasses: this.startBiasFeatureClasses.map(item => { return new StartBiasFeatureClassNode({ ...civilization, ...item }) }), startBiasResources: this.startBiasResources.map(item => { return new StartBiasResourceNode({ ...civilization, ...item }) }), startBiasRivers: this.startBiasRiver !== null ? [ new StartBiasRiverNode({ ...civilization, score: this.startBiasRiver }) ] : [], startBiasAdjacentToCoasts: this.startBiasAdjacentToCoast !== null ? [ new StartBiasAdjacentToCoastNode({ ...civilization, score: this.startBiasAdjacentToCoast }) ] : [], visArtCivilizationBuildingCultures: this.visArtCivilizationBuildingCultures.map(item => { return new VisArtCivilizationBuildingCultureNode({ ...civilization, buildingCulture: item }) }), visArtCivilizationUnitCultures: this.visArtCivilizationUnitCulture !== null ? [ new VisArtCivilizationUnitCultureNode({ ...civilization, unitCulture: this.visArtCivilizationUnitCulture }) ] : [], }); this._shell.fill({ civilizations: [ShellCivilizationNodeSlice.from(civilization)], civilizationTags: this.civilizationTags.map(item => { return new CivilizationTagNode({ civilizationDomain: this.civilization.domain, tagType: item, ...civilization, }) }), civilizationItems: [ new CivilizationItemNode({ ...civilization, ...traitAbility, civilizationDomain: this.civilization.domain, type: this.traitAbility.traitType, kind: KIND.TRAIT, }), ...this.civilizationItems.map(item => { return new CivilizationItemNode({ civilizationDomain: this.civilization.domain, ...civilization, ...item }) }) ], }); this._legacy.fill({ types: [ new TypeNode({ type: this.civilization.civilizationType, kind: KIND.CIVILIZATION }).insertOrIgnore(), new TypeNode({ kind: KIND.TRAIT, type: this.trait.traitType }).insertOrIgnore(), new TypeNode({ kind: KIND.TRAIT, type: this.traitAbility.traitType }).insertOrIgnore(), ], traits: [new TraitNode(trait).insertOrIgnore()], legacyCivilizations: [ new LegacyCivilizationNode({ ...civilization, ...this.civilizationLegacy }) ], legacyCivilizationTraits: [ new LegacyCivilizationTraitNode({ ...civilization, ...this.trait }) ] }); this._icons.fill({ iconDefinitions: [new IconDefinitionNode({ id: this.civilization.civilizationType, ...this.icon, })] }); this._localizations.fill({ englishText: this.localizations.map(item => { return new CivilizationLocalization({ prefix: this.civilization.civilizationType, ...item }); }).flatMap(item => item.getNodes()) }); const cityNamesCount = lodash.maxBy(this.localizations, loc => loc.cityNames?.length || 0)?.cityNames?.length || 0; for (let i = 1; i <= cityNamesCount; i++) { this._current.cityNames.push(new CityNameNode({ civilizationType: this.civilization.civilizationType, cityName: locale(this.civilization.civilizationType, `cityNames_${i}`) })); } return this; } /** * @description Bind entity as unique to this civilization * @param items */ bind(items: (UnitBuilder | ConstructibleBuilder | ProgressionTreeBuilder | ModifierBuilder | UniqueQuarterBuilder | CivilizationUnlockBuilder | LeaderUnlockBuilder)[] = []) { items.forEach(item => { if (item instanceof UnitBuilder) { item._current.units.forEach(unit => { unit.traitType = this.trait.traitType; this._shell.civilizationItems.push( new CivilizationItemNode({ civilizationDomain: this.civilization.domain, civilizationType: this.civilization.civilizationType, type: unit.unitType, kind: KIND.UNIT, icon: unit.unitType, ...unit, }) ) }); } if (item instanceof ModifierBuilder) { item._gameEffects.modifiers.forEach(modifier => { this._gameEffects.modifiers.push(modifier); if (!item.isDetached) { this._current.traitModifiers.push(new TraitModifierNode({ traitType: this.traitAbility.traitType, modifierId: modifier.id })); } }) } if (item instanceof UniqueQuarterBuilder) { item._always.uniqueQuarters.forEach(uniqueQuarter => { uniqueQuarter.traitType = this.trait.traitType; this._shell.civilizationItems.push( new CivilizationItemNode({ civilizationDomain: this.civilization.domain, civilizationType: this.civilization.civilizationType, type: uniqueQuarter.uniqueQuarterType, kind: KIND.QUARTER, icon: uniqueQuarter.uniqueQuarterType, ...uniqueQuarter, }) ) }); } if (item instanceof ConstructibleBuilder) { item._always.buildings.forEach(item => { item.traitType = this.trait.traitType; }); item._always.improvements.forEach(item => { item.traitType = this.trait.traitType; }); item._always.constructibles.forEach(constructible => { this._shell.civilizationItems.push( new CivilizationItemNode({ civilizationDomain: this.civilization.domain, civilizationType: this.civilization.civilizationType, type: constructible.constructibleType, kind: KIND.IMPROVEMENT, icon: '', ...constructible, }) ) }); } if (item instanceof ProgressionTreeBuilder) { item._current.progressionTrees.forEach(progressionTree => { this._gameEffects.modifiers.push(new ModifierNode({ id: `MOD_${progressionTree.progressionTreeType}_REVEAL`, effect: EFFECT.PLAYER_REVEAL_CULTURE_TREE, requirements: [{ type: REQUIREMENT.PLAYER_HAS_CIVILIZATION_OR_LEADER_TRAIT, arguments: [{ name: 'TraitType', value: this.trait.traitType }] }], arguments: [{ name: 'ProgressionTreeType', value: progressionTree.progressionTreeType }] })); item._current.progressionTreeNodeUnlocks.push(new ProgressionTreeNodeUnlockNode({ progressionTreeNodeType: 'NODE_CIVIC_AQ_MAIN_CHIEFDOM', targetKind: KIND.MODIFIER, targetType: `MOD_${progressionTree.progressionTreeType}_REVEAL`, unlockDepth: 1, hidden: true })); }) } if (item instanceof CivilizationUnlockBuilder) { this._shell.civilizationUnlocks.push(new CivilizationUnlockNode({ ageDomain: 'StandardAges', civilizationDomain: CIVILIZATION_DOMAIN.from(item.from.ageType), civilizationType: item.from.civilizationType, type: item.to.civilizationType, ageType: item.to.ageType, kind: KIND.CIVILIZATION, name: locale(item.to.civilizationType, 'NAME'), description: locale(item.to.civilizationType, 'DESCRIPTION'), icon: item.to.civilizationType })) } if (item instanceof LeaderUnlockBuilder) { this._shell.leaderUnlocks.push(new LeaderUnlockNode({ leaderDomain: 'StandardLeaders', ageDomain: 'StandardAges', kind: KIND.CIVILIZATION, name: locale(item.leaderUnlock.type, 'name'), description: locale(item.leaderUnlock.type, 'description'), icon: item.leaderUnlock.type, ...item.leaderUnlock })) this._shell.leaderCivilizationBias.push(new LeaderCivilizationBiasNode({ civilizationDomain: CIVILIZATION_DOMAIN.from(item.leaderUnlock.ageType), civilizationType: item.leaderUnlock.type, reasonType: locale(`PLAY_AS_${trim(item.leaderUnlock.leaderType)}_${trim(item.leaderUnlock.type)}`, 'TOOLTIP'), ...item.leaderUnlock, ...item.leaderCivilizationBias })) } }); return this; } build() { const path = `/civilizations/${lodash.kebabCase(trim(this.civilization.civilizationType))}/`; return [ new XmlFile({ path, name: 'current.xml', content: this._current.toXmlElement(), actionGroups: [this.actionGroupBundle.current], actionGroupActions: [ACTION_GROUP_ACTION.UPDATE_DATABASE] }), new XmlFile({ path, name: 'legacy.xml', content: this._legacy.toXmlElement(), actionGroups: [this.actionGroupBundle.always], actionGroupActions: [ACTION_GROUP_ACTION.UPDATE_DATABASE] }), new XmlFile({ path, name: 'shell.xml', content: this._shell.toXmlElement(), actionGroups: [this.actionGroupBundle.shell], actionGroupActions: [ACTION_GROUP_ACTION.UPDATE_DATABASE] }), new XmlFile({ path, name: 'icons.xml', content: this._icons.toXmlElement(), actionGroups: [this.actionGroupBundle.shell, this.actionGroupBundle.always], actionGroupActions: [ACTION_GROUP_ACTION.UPDATE_ICONS] }), new XmlFile({ path, name: 'localization.xml', content: this._localizations.toXmlElement(), actionGroups: [this.actionGroupBundle.shell, this.actionGroupBundle.always], actionGroupActions: [ACTION_GROUP_ACTION.UPDATE_TEXT] }), new XmlFile({ path, name: 'game-effects.xml', content: this._gameEffects?.toXmlElement(), actionGroups: [this.actionGroupBundle.current], actionGroupActions: [ACTION_GROUP_ACTION.UPDATE_DATABASE] }), ] } }