I just realized that making a Dimensional III spell that lets you create a Portal between 2 plots of your choosing actually isn't that hard, if you split it up into 2 stages. The actual Dimensional III spell should summon a SPECIALUNIT_SPELL ICBM-like unit, which can move instantly to any tile on the map. (If you wanted to limit it more, you could instead make it like a tactical nuke with limited range.) When that summon has moved to the tile of your choosing, it can cast the spell that actually creates a portal between its tile and the tile of its caster, which can be found the same way here as it is in the Severed Soul's Call Form spell. To reduce micromanagement, you could put the spell's actual effect in its <PyRequirement> instead of its <PyResult. That kind of coding is generally considered bad form, but I've found it to be quite useful.
I'm thinking I may use this in my version instead of the Tesseract summon that can move only a few units at a time, and which risks killing them all if the tile it moves to is inhabited. I decided to post it here too, in case anyone else wants to use it. I recall Kael saying that he would consider reimplementing the Dimensional sphere if he had 3 good spells and that he liked Escape for Dimensional I and a portal creating spell for Dimensional III, so maybe posting this code will move him closer to reimplementing the sphere.
Code:
<UnitInfo>
<Class>UNITCLASS_SEVERED_SOUL</Class>
<Type>UNIT_RIFT</Type>
<UniqueNames>
</UniqueNames>
<Special>SPECIALUNIT_SPELL</Special>
<Capture>NONE</Capture>
<Combat>NONE</Combat>
<Domain>DOMAIN_LAND</Domain>
<DefaultUnitAI>UNITAI_ATTACK</DefaultUnitAI>
<Invisible>INVISIBLE_ALL</Invisible>
<SeeInvisible>NONE</SeeInvisible>
<Description>TXT_KEY_UNIT_RIFT</Description>
<Civilopedia>TXT_KEY_UNIT_PLACEHOLDER_PEDIA</Civilopedia>
<Strategy>TXT_KEY_UNIT_RIFT_STRATEGY</Strategy>
<Advisor>ADVISOR_MILITARY</Advisor>
<bAnimal>0</bAnimal>
<bFood>0</bFood>
<bNoBadGoodies>0</bNoBadGoodies>
<bOnlyDefensive>1</bOnlyDefensive>
<bNoCapture>0</bNoCapture>
<bQuickCombat>0</bQuickCombat>
<bRivalTerritory>0</bRivalTerritory>
<bMilitaryHappiness>0</bMilitaryHappiness>
<bMilitarySupport>0</bMilitarySupport>
<bMilitaryProduction>0</bMilitaryProduction>
<bPillage>0</bPillage>
<bSpy>0</bSpy>
<bSabotage>0</bSabotage>
<bDestroy>0</bDestroy>
<bStealPlans>0</bStealPlans>
<bInvestigate>0</bInvestigate>
<bCounterSpy>0</bCounterSpy>
<bFound>0</bFound>
<bGoldenAge>0</bGoldenAge>
<bInvisible>0</bInvisible>
<bFirstStrikeImmune>0</bFirstStrikeImmune>
<bNoDefensiveBonus>0</bNoDefensiveBonus>
<bIgnoreBuildingDefense>0</bIgnoreBuildingDefense>
<bCanMoveImpassable>0</bCanMoveImpassable>
<bCanMoveAllTerrain>0</bCanMoveAllTerrain>
<bFlatMovementCost>0</bFlatMovementCost>
<bIgnoreTerrainCost>0</bIgnoreTerrainCost>
<bNukeImmune>0</bNukeImmune>
<bPrereqBonuses>0</bPrereqBonuses>
<bPrereqReligion>0</bPrereqReligion>
<bMechanized>0</bMechanized>
<bSuicide>0</bSuicide>
<bHiddenNationality>0</bHiddenNationality>
<bAlwaysHostile>0</bAlwaysHostile>
<UnitClassUpgrades>
</UnitClassUpgrades>
<UnitClassTargets>
</UnitClassTargets>
<UnitCombatTargets>
</UnitCombatTargets>
<UnitClassDefenders>
</UnitClassDefenders>
<UnitCombatDefenders>
</UnitCombatDefenders>
<FlankingStrikes>
</FlankingStrikes>
<UnitAIs>
<UnitAI>
<UnitAIType>UNITAI_EXPLORE</UnitAIType>
<bUnitAI>1</bUnitAI>
</UnitAI>
</UnitAIs>
<NotUnitAIs>
</NotUnitAIs>
<Builds>
</Builds>
<ReligionSpreads>
</ReligionSpreads>
<CorporationSpreads>
</CorporationSpreads>
<GreatPeoples>
</GreatPeoples>
<Buildings>
</Buildings>
<ForceBuildings>
</ForceBuildings>
<HolyCity>NONE</HolyCity>
<ReligionType>NONE</ReligionType>
<StateReligion>NONE</StateReligion>
<PrereqReligion>NONE</PrereqReligion>
<PrereqCorporation>NONE</PrereqCorporation>
<PrereqBuilding>NONE</PrereqBuilding>
<PrereqTech>NONE</PrereqTech>
<TechTypes>
</TechTypes>
<BonusType>NONE</BonusType>
<PrereqBonuses>
</PrereqBonuses>
<ProductionTraits>
</ProductionTraits>
<Flavors>
</Flavors>
<iAIWeight>0</iAIWeight>
<iCost>-1</iCost>
<iHurryCostModifier>0</iHurryCostModifier>
<iAdvancedStartCost>-1</iAdvancedStartCost>
<iAdvancedStartCostIncrease>0</iAdvancedStartCostIncrease>
<iMinAreaSize>-1</iMinAreaSize>
<iMoves>1</iMoves>
<bNoRevealMap>0</bNoRevealMap>
<iAirRange>0</iAirRange>
<iAirUnitCap>0</iAirUnitCap>
<iDropRange>0</iDropRange>
<iNukeRange>1</iNukeRange>
<iWorkRate>0</iWorkRate>
<iBaseDiscover>0</iBaseDiscover>
<iDiscoverMultiplier>0</iDiscoverMultiplier>
<iBaseHurry>0</iBaseHurry>
<iHurryMultiplier>0</iHurryMultiplier>
<iBaseTrade>0</iBaseTrade>
<iTradeMultiplier>0</iTradeMultiplier>
<iGreatWorkCulture>0</iGreatWorkCulture>
<iEspionagePoints>0</iEspionagePoints>
<TerrainImpassables>
</TerrainImpassables>
<FeatureImpassables>
</FeatureImpassables>
<TerrainPassableTechs>
</TerrainPassableTechs>
<FeaturePassableTechs>
</FeaturePassableTechs>
<iCombat>0</iCombat>
<iCombatLimit>100</iCombatLimit>
<iAirCombat>0</iAirCombat>
<iAirCombatLimit>0</iAirCombatLimit>
<iXPValueAttack>8</iXPValueAttack>
<iXPValueDefense>4</iXPValueDefense>
<iFirstStrikes>0</iFirstStrikes>
<iChanceFirstStrikes>0</iChanceFirstStrikes>
<iInterceptionProbability>0</iInterceptionProbability>
<iEvasionProbability>0</iEvasionProbability>
<iWithdrawalProb>0</iWithdrawalProb>
<iCollateralDamage>0</iCollateralDamage>
<iCollateralDamageLimit>0</iCollateralDamageLimit>
<iCollateralDamageMaxUnits>0</iCollateralDamageMaxUnits>
<iCityAttack>0</iCityAttack>
<iCityDefense>0</iCityDefense>
<iAnimalCombat>0</iAnimalCombat>
<iHillsAttack>0</iHillsAttack>
<iHillsDefense>0</iHillsDefense>
<TerrainNatives>
</TerrainNatives>
<FeatureNatives>
</FeatureNatives>
<TerrainAttacks>
</TerrainAttacks>
<TerrainDefenses>
</TerrainDefenses>
<FeatureAttacks>
</FeatureAttacks>
<FeatureDefenses>
</FeatureDefenses>
<UnitClassAttackMods>
</UnitClassAttackMods>
<UnitClassDefenseMods>
</UnitClassDefenseMods>
<UnitCombatMods>
</UnitCombatMods>
<UnitCombatCollateralImmunes>
</UnitCombatCollateralImmunes>
<DomainMods>
</DomainMods>
<BonusProductionModifiers>
</BonusProductionModifiers>
<iBombRate>0</iBombRate>
<iBombardRate>0</iBombardRate>
<SpecialCargo>NONE</SpecialCargo>
<DomainCargo>NONE</DomainCargo>
<iCargo>0</iCargo>
<iConscription>0</iConscription>
<iCultureGarrison>0</iCultureGarrison>
<iExtraCost>0</iExtraCost>
<iAsset>2</iAsset>
<iPower>4</iPower>
<UnitMeshGroups>
<iGroupSize>1</iGroupSize>
<fMaxSpeed>1.75</fMaxSpeed>
<fPadTime>1</fPadTime>
<iMeleeWaveSize>1</iMeleeWaveSize>
<iRangedWaveSize>0</iRangedWaveSize>
<UnitMeshGroup>
<iRequired>1</iRequired>
<EarlyArtDefineTag>ART_DEF_UNIT_SEVERED_SOUL</EarlyArtDefineTag>
</UnitMeshGroup>
</UnitMeshGroups>
<FormationType>FORMATION_TYPE_DEFAULT</FormationType>
<HotKey></HotKey>
<bAltDown>0</bAltDown>
<bShiftDown>0</bShiftDown>
<bCtrlDown>0</bCtrlDown>
<bGraphicalOnly>0</bGraphicalOnly>
<iHotKeyPriority>0</iHotKeyPriority>
<FreePromotions>
<FreePromotion>
<PromotionType>PROMOTION_ELEMENTAL</PromotionType>
<bFreePromotion>1</bFreePromotion>
</FreePromotion>
</FreePromotions>
<LeaderPromotion>NONE</LeaderPromotion>
<iLeaderExperience>0</iLeaderExperience>
<iTier>2</iTier>
</UnitInfo>
Code:
<SpellInfo>
<Type>SPELL_SUMMON_RIFT</Type>
<Description>TXT_KEY_SPELL_RIFT</Description>
<Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
<PromotionPrereq1>PROMOTION_DIMENSIONAL3</PromotionPrereq1>
<bAllowAI>1</bAllowAI>
<bDisplayWhenDisabled>1</bDisplayWhenDisabled>
<bHasCasted>1</bHasCasted>
<CreateUnitType>UNIT_RIFT</CreateUnitType>
<iCreateUnitNum>1</iCreateUnitNum>
<Effect>EFFECT_ENTROPY_SUMMON</Effect>
<Sound>AS3D_SPELL_DEFILE</Sound>
<Button>Art/Interface/Buttons/Units/Severed Soul.dds</Button>
</SpellInfo>
<SpellInfo>
<Type>SPELL_OPEN_PORTAL</Type>
<Description>TXT_KEY_SPELL_OPEN_PORTAL</Description>
<Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
<UnitPrereq>UNIT_RIFT</UnitPrereq>
<bAllowAI>1</bAllowAI>
<iAIWeight>1000</iAIWeight>
<bIgnoreHasCasted>1</bIgnoreHasCasted>
<bAbility>1</bAbility>
<PyRequirement>reqOpenPortal(pCaster)</PyRequirement>
<Effect>EFFECT_ENTROPY_SUMMON</Effect>
<Sound>AS3D_SPELL_DEFILE</Sound>
<Button>Art/Interface/Buttons/Improvements/Portal.dds</Button>
</SpellInfo>
Code:
def reqOpenPortal(caster):
if caster.getSummoner() == -1:
return false
pPlayer = gc.getPlayer(caster.getOwner())
pUnit = pPlayer.getUnit(caster.getSummoner())
pPlot1 = caster.plot()
pPlot2 = pUnit.plot()
if pPlot1.getX() == pPlot2.getX() and pPlot1.getY() == pPlot2.getY():
return false
if pPlot1.getImprovementType() != -1:
if gc.getImprovementInfo(pPlot1.getImprovementType()).isPermanent():
return false
if pPlot2.getImprovementType() != -1:
if gc.getImprovementInfo(pPlot2.getImprovementType()).isPermanent():
return false
iPortal = gc.getInfoTypeForString('IMPROVEMENT_PORTAL')
pPlot1.setImprovementType(iPortal)
pPlot1.setPortalExitX(pPlot2.getX())
pPlot1.setPortalExitY(pPlot2.getY())
pPlot2.setImprovementType(iPortal)
pPlot2.setPortalExitX(pPlot1.getX())
pPlot2.setPortalExitY(pPlot1.getY())
caster.kill(True, PlayerTypes.NO_PLAYER)
return true
(Actually, I'll probably alter it slightly, to use the TempImprovementType function I added in my DLL, based on Kael's TempTerrainType function. That way you wouldn't risk permanently filling up the whole world with portals. Having these portals close after a random number of turns seems more interesting, and more balanced. That way would also prevent you from permanently destroying unique features, without stopping you from using the spell there.)
Code:
def reqOpenPortal(caster):
if caster.getSummoner() == -1:
return false
pPlayer = gc.getPlayer(caster.getOwner())
pUnit = pPlayer.getUnit(caster.getSummoner())
pPlot1 = caster.plot()
pPlot2 = pUnit.plot()
iPortal = gc.getInfoTypeForString('IMPROVEMENT_PORTAL')
if pPlot1.getImprovementType() == iPortal or pPlot2.getImprovementType() == iPortal:
return false
if pPlot1.getX() == pPlot2.getX() and pPlot1.getY() == pPlot2.getY():
return false
iRnd = CyGame().getSorenRandNum(8, "Portal")
pPlot1.setTempImprovementType(iPortal, iRnd)
pPlot1.setPortalExitX(pPlot2.getX())
pPlot1.setPortalExitY(pPlot2.getY())
pPlot2.setTempImprovementType(iPortal, iRnd)
pPlot2.setPortalExitX(pPlot1.getX())
pPlot2.setPortalExitY(pPlot1.getY())
caster.kill(True, PlayerTypes.NO_PLAYER)
return true
I may also add a <PythonPostCombatLost> function, for in case the Rift unit tries to enter a tile occupied by an enemy and thus dies since it has no combat strength. It would probably be best to make it find the nearest open plot, rather than make a portal there and let your units move straight to that tile, knocking their units off. Adding a block preventing portals from being created on city tiles is probably a good idea too.
Nota bene:
1. Nukes like this still cause diplomatic penalties, and display the text about the unit exploding when it hits it target. You'd probably want to change the text in CIV4GameText_FFH2.xml.
2. Also bear in mind that it cannot be used to close to neutral units or territory.
3. Nukes ignore the level prereqs for entering tiles. If you use a spell like this, you should probably change how freeing Brigit works. I'd recommend getting rid of UNIT_BRIGIT_HELD (possibly adding her graphics to the Ring of Carcer) and making it so that Brigit s created though a <iCasterMinLevel>15</iCasterMinLevel> spell that can only be cast on the Ring of Carcer improvement.
4. The way I have the spell autocasting now would cause the portals to be created if the dimensional III unit that summoned the rift moved. I'm considering changing it so that the original caster is immobilized by the spell, and that the portal's duration would be equal to the original caster's immobility counter. If immobile units could cast spells and promotions could have <PythonPostCombatLost> (or, better yet, in FF, <PythonOnDeath>) functions, I'd recommend making the spell grant a "Maintaining Portal" promotion that immobilizes the caster, allows another spell to close the portal and remove the promotion, and had a <PythonPostCombatLost> (or <PythonOnDeath>) call that would close the portal when the unit was killed. (Actually, I'm thinking that is already possible in FF.)
Edit: I just found it rather easy to edit the dll to make it so that you could nuke neutral lands (making you declare war) but be unable to nuke tiles without the prerequisite minimum level.
Also, since the <PyRequirement> functions are only run when the unit is selected, you can manage to get the caster several tiles away before creating a portal to the summon. If you fortify the unit there then you could go several turns before making the portal, or rather you would be able to if the rift unit would stck around that long, but it won't as FfH's tracking mechanism (unlike FF's) can't track it that long.