As I said earlier, I am going to release some of the individual items I came up with for this mod. My hope is that they will benefit the community in some way -- since it will be a while before a DA rebuild gets done.
And so, allow me to introduce "DA mod giblet 1: Claim flags."
By request, I am posting this first. Claim flags create a zone of persistent culture around them, without needing a city! This is great for claiming, and being able to get the benefits of, improved resources outside of ones normal cultural boundaries.
I created claim flags by modding the starbase code from Final Frontier. The essentials are:
1. A new unit, which I called "STARBASE_I" just to avoid more edits in the modded python code.
2. Changes and additions to the CvEventManager.py file.
3. Some method of getting the new unit into the game. In Final Frontier, starbases are built by constructor ships. In my mod, the ability to plant claim flags is restricted to a special unit called a "Dungeoneer," and required researching a special tech. The basis of this is a new spell called "stake claim." More on that later.
First, the new unit. I literally just ripped the starbase unitclass and unit XML from FF, as well as the unit arts. You could get away with any new unit actually, so long as you call it "STARBASE_I" or whatever name you use consistently in the modded python. Because I wanted the look of a planted flag, I just kept the starbase art, but scaled the NIF for the actual unit down to zero. That makes the unit itself invisible, but still shows the player flag. (Sneaky, huh?) The flag is what is important to see anyway.
Second, you must change CvEventManager.py file. Find the onEndGameTurn def and change the code block to match the stuff below. Also, add in all of the "starbase" related defs. Looks like this:
Code:
def onEndGameTurn(self, argsList):
'Called at the end of the end of each turn'
iGameTurn = argsList[0]
self.updateAllStarbases()
def doMakeStarbase(self, iPlayer, iX, iY):
pPlayer = gc.getPlayer(iPlayer)
pPlot = CyMap().plot(iX, iY)
# Create Starbase Unit
iUnitStarbaseID = CvUtil.findInfoTypeNum(gc.getUnitInfo,gc.getNumUnitInfos(),'UNIT_STARBASE_I')
pPlayer.initUnit(iUnitStarbaseID, iX, iY, UnitAITypes.UNITAI_ATTACK, DirectionTypes.NO_DIRECTION)
self.updateStarbaseCulture(iPlayer, iX, iY)
def updateStarbaseCulture(self, iPlayer, iX, iY):
# Create culture around unit
for iXLoop in range(iX-2, iX+3):
for iYLoop in range(iY-2, iY+3):
iActiveX = iXLoop
iActiveY = iYLoop
if (iActiveX < 0):
iActiveX = CyMap().getGridWidth() + iActiveX
if (iActiveY < 0):
iActiveY = CyMap().getGridHeight() + iActiveY
pLoopPlot = CyMap().plot(iActiveX, iActiveY)
if (pLoopPlot.getOwner() == -1):
pLoopPlot.setOwner(iPlayer)
pLoopPlot.changeCulture(iPlayer, 10, False)
def updateAllStarbases(self):
# Update Starbase culture
iUnitStarbaseID = CvUtil.findInfoTypeNum(gc.getUnitInfo,gc.getNumUnitInfos(),'UNIT_STARBASE_I')
# List made to preserve culture of units built first
aaiStarbaseList = []
for iPlayerLoop in range(gc.getMAX_CIV_PLAYERS()):
pPlayer = gc.getPlayer(iPlayerLoop)
pTeam = gc.getTeam(pPlayer.getTeam())
pyPlayer = PyPlayer(iPlayerLoop)
apUnitList = pyPlayer.getUnitList()
for pUnitLoop in apUnitList:
if (pUnitLoop.getUnitType() == iUnitStarbaseID):
aaiStarbaseList.append([pUnitLoop.getGameTurnCreated(), iPlayerLoop, pUnitLoop.getX(), pUnitLoop.getY()])
if (len(aaiStarbaseList) > 0):
# Make order such that units built first get culture preference
aaiStarbaseList.sort()
# aaiStarbaseList.reverse()
for iStarbaseLoop in range(len(aaiStarbaseList)):
self.updateStarbaseCulture(aaiStarbaseList[iStarbaseLoop][1], aaiStarbaseList[iStarbaseLoop][2], aaiStarbaseList[iStarbaseLoop][3])
Finally, you will need a way to get the unit into the game. Keep in mind that the unit must be DOMAIN_IMMOBILE or things will get buggy on you quick. That is because the python creates an array that keeps track of where flags were planted and when. Earlier flags take precedence over later ones, and this is tied to position. Having mobile flags makes things buggy.
So given that the flags must be immobile, you need a way get them into the game that creates them at the spot they are intended to claim.
I created a spell called "stake claim" (internally called "crumbs"). Oddly enough, simply treating it like a permanent summon of a flag unit didn't seem to work. At least I couldn't make that work, which is a shame because it would have been the easiest way.
It wouldn't be too hard to change the code to run off of an improvement rather than a unit. You can also change it so that the cultural zone will not penetrate certain terrains or features. For example, in my DA mod, I made it so the claim flag culture would not penetrate dungeon walls, but just the halls and rooms.
Anyway, enjoy! Let me know if it works, or you have additional questions.
The problem when trying to run shafers code of an improvement instead of a unit is actually how do I get an owner of the imrovement if it's build outside borders?
The problem when trying to run shafers code of an improvement instead of a unit is actually how do I get an owner of the imrovement if it's build outside borders?
When I was playing with the idea for an improvement instead of a unit, I just had a unique improvement for each civ. E.g., BANNOR_FLAG, SHAEIM_FLAG, etc.
In my DA modmod, the tall dungeon walls often made it hard to see items on the floor. To fix this, I imported a modified Afterworld style of camera freedom. I should quickly note that this mod giblet does NOT lock the player into a close-up view like AW does. (I found that really annoying).
Instead, this simple code addition to your CvEventManager.py file allows full 360 spinning of the worldview.
If you open up your CvEventManager.py file and scroll down a bit, you will come to the "def onKbdEvent" code. The code should be altered to look like this:
Code:
def onKbdEvent(self, argsList):
'keypress handler - return 1 if the event was consumed'
eventType,key,mx,my,px,py = argsList
game = gc.getGame()
if (self.bAllowCheats):
# notify debug tools of input to allow it to override the control
argsList = (eventType,key,self.bCtrl,self.bShift,self.bAlt,mx,my,px,py,gc.getGame().isNetworkMultiPlayer())
if ( CvDebugTools.g_CvDebugTools.notifyInput(argsList) ):
return 0
if ( eventType == self.EventKeyDown ):
theKey=int(key)
#begin add AW camera
if (theKey == int(InputTypes.KB_LEFT)):
if self.bCtrl:
CyCamera().SetBaseTurn(CyCamera().GetBaseTurn() - 45.0)
return 1
elif self.bShift:
CyCamera().SetBaseTurn(CyCamera().GetBaseTurn() - 10.0)
return 1
if (theKey == int(InputTypes.KB_RIGHT)):
if self.bCtrl:
CyCamera().SetBaseTurn(CyCamera().GetBaseTurn() + 45.0)
return 1
elif self.bShift:
CyCamera().SetBaseTurn(CyCamera().GetBaseTurn() + 10.0)
return 1
#end add AW camera
CvCameraControls.g_CameraControls.handleInput( theKey )
if (self.bAllowCheats):
# Shift - T (Debug - No MP)
if (theKey == int(InputTypes.KB_T)):
if ( self.bShift ):
self.beginEvent(CvUtil.EventAwardTechsAndGold)
#self.beginEvent(CvUtil.EventCameraControlPopup)
return 1
elif (theKey == int(InputTypes.KB_W)):
if ( self.bShift and self.bCtrl):
self.beginEvent(CvUtil.EventShowWonder)
return 1
# Shift - ] (Debug - currently mouse-overd unit, health += 10
elif (theKey == int(InputTypes.KB_LBRACKET) and self.bShift ):
unit = CyMap().plot(px, py).getUnit(0)
if ( not unit.isNone() ):
d = min( unit.maxHitPoints()-1, unit.getDamage() + 10 )
unit.setDamage( d, PlayerTypes.NO_PLAYER )
# Shift - [ (Debug - currently mouse-overd unit, health -= 10
elif (theKey == int(InputTypes.KB_RBRACKET) and self.bShift ):
unit = CyMap().plot(px, py).getUnit(0)
if ( not unit.isNone() ):
d = max( 0, unit.getDamage() - 10 )
unit.setDamage( d, PlayerTypes.NO_PLAYER )
elif (theKey == int(InputTypes.KB_F1)):
if ( self.bShift ):
CvScreensInterface.replayScreen.showScreen(False)
return 1
# don't return 1 unless you want the input consumed
elif (theKey == int(InputTypes.KB_F2)):
if ( self.bShift ):
import CvDebugInfoScreen
CvScreensInterface.showDebugInfoScreen()
return 1
elif (theKey == int(InputTypes.KB_F3)):
if ( self.bShift ):
CvScreensInterface.showDanQuayleScreen(())
return 1
elif (theKey == int(InputTypes.KB_F4)):
if ( self.bShift ):
CvScreensInterface.showUnVictoryScreen(())
return 1
return 0
This important bit to add in is between the "begin add AW camera" comments and the "end add AW camera" comment.
Once added, this code will allow you to spin the camera a full 360 degrees. Pressing the Control key and the left or right arrow, will spin the world 45 degrees counterclockwise (widdershins) and clockwise (deasil). Pressing the Shift key and the left or right arrow will spin 10 degrees.
In my opinion, this type of camera movement should be default in all Civ games, but that's just me!
An example of the python code shown above can be downloaded from here:
Yes, the full camera movement is very handy for checking textures and NIFs. When I was first working on my dungeon walls, this was how I discovered that some of the NIFs didn't mesh well with their neighbors. I was able to deactivate those particular NIFs and make the walls look a lot better.
Eventually, I was going to figure out an "over the shoulder" cam option. This would have given an option to make DA into almost a FPS type of experience. By fiddling with the Globals in XML I could get the positioning right, but hadn't figured out how to make the camera track with the unit yet. Meh... not really a priority.
Yes, the full camera movement is very handy for checking textures and NIFs. When I was first working on my dungeon walls, this was how I discovered that some of the NIFs didn't mesh well with their neighbors. I was able to deactivate those particular NIFs and make the walls look a lot better.
Eventually, I was going to figure out an "over the shoulder" cam option. This would have given an option to make DA into almost a FPS type of experience. By fiddling with the Globals in XML I could get the positioning right, but hadn't figured out how to make the camera track with the unit yet. Meh... not really a priority.
is it just me or are you refering to the new 'Spore' Game comming out soon? i think im going to go mad waiting for its release CANT...WAIT...TILL...SEPTEMBER...
The Creature Creator is part of Spore that came out recently. $10 for the full version, or there's a free version that's missing 75% of the content. Basically, the full version is a $10 demo of a tiny part of the game. The free version is a demo of the demo.
[Edit] That said, it's a nifty little toy, but it's frustrating that you can't do anything with whatever you make, besides running it through some animations.
[Edit 2] Oh, I should probably say something about Dungeon Mod, huh? I think it might be better to wait on the over-the-shoulder camera for now. It's a nifty concept and might add a lot to the mod, but seems like it would be a . .. .. .. .. . to get up and running, and it might be better to add some more "meat" to the mod.
[Edit 3] One more thing... I just watched the videos... I get a total Roguelike vibe from the mod. If it does end up being a Roguelike, it'll definitely be the best-looking one out there Would it be possible to use map scripts to make random dungeons? Also, how will combat work? I'm not sure Civ's "uninterruptable fight to the death" method would work so well. Even if you had a 95% chance of winning, that means that there's a 50% chance you'd die by the 20th fight.
Sorry if any of this has been mentioned before. I skimmed through the thread, but 26 pages is a bit much to get caught up on.
The Creature Creator is part of Spore that came out recently. $10 for the full version, or there's a free version that's missing 75% of the content. Basically, the full version is a $10 demo of a tiny part of the game. The free version is a demo of the demo.
[Edit] That said, it's a nifty little toy, but it's frustrating that you can't do anything with whatever you make, besides running it through some animations.
[Edit 2] Oh, I should probably say something about Dungeon Mod, huh? I think it might be better to wait on the over-the-shoulder camera for now. It's a nifty concept and might add a lot to the mod, but seems like it would be a . .. .. .. .. . to get up and running, and it might be better to add some more "meat" to the mod.
[Edit 3] One more thing... I just watched the videos... I get a total Roguelike vibe from the mod. If it does end up being a Roguelike, it'll definitely be the best-looking one out there Would it be possible to use map scripts to make random dungeons? Also, how will combat work? I'm not sure Civ's "uninterruptable fight to the death" method would work so well. Even if you had a 95% chance of winning, that means that there's a 50% chance you'd die by the 20th fight.
Sorry if any of this has been mentioned before. I skimmed through the thread, but 26 pages is a bit much to get caught up on.
I have a feeling Spore may finally break my Civ addiction. Of course, then I'll need Civ V to break my Spore addiction, and Spore 2 to break my Civ V/FFH3 addiction, and then...
Soren (who is working on Spore now incidently) checked out the Modcast and even posted about it and this mod mod on his blog: http://www.designer-notes.com/?p=98.
Wow. Thank you for posting that! That is really motivating!
[feels guilty for getting distracted by Spore...]
In the podcast, it was mentioned that the plan is to have the "final" release of FfH2 around December. Is that still the target?
I ask because one of my challenges is trying to keep up with a platform that gets refined so often. It would be nice to build the mod mod off of a version that won't morph too much afterwards. At least knowing for sure that there will be no DLL changes or Schema changes would be a huge help.
In the meantime, I hope to keep dropping DA mod Giblets out there for people to enjoy and use in their own mods. Looks like the claim flags and 360 camera were well received.
This giblet is for more advanced modders, because I am leaving out a lot of supporting information.
In the DA mod mod, doors, traps, and glyphs all make use of the onUnitMove function in the CvEventManager.py file. Lots of fun things can be done with onUnitMove. I was originally going to post these as separate giblets, but as you will see, too much of the code is shared and co-dependent.
Anyway, the code in question is below:
Spoiler:
Code:
def onUnitMove(self, argsList):
'unit move'
pPlot,pUnit,pOldPlot = argsList
player = PyPlayer(pUnit.getOwner())
unitInfo = PyInfo.UnitInfo(pUnit.getUnitType())
pPlayer = gc.getPlayer(pUnit.getOwner())
iX = pPlot.getX()
iY = pPlot.getY()
for iiX in range(iX-1, iX+2, 1):
for iiY in range(iY-1, iY+2, 1):
p2Plot = CyMap().plot(iiX,iiY)
for i in range(p2Plot.getNumUnits()):
iTrap = gc.getInfoTypeForString('UNITCLASS_TRAP')
iGlyph = gc.getInfoTypeForString('UNITCLASS_GLYPH')
p2Unit = p2Plot.getUnit(i)
if p2Unit.getUnitClassType() == iTrap:
if p2Unit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AREA_TRAP')):
pPlayer = gc.getPlayer(p2Unit.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
if eTeam.isAtWar(pUnit.getTeam()):
p2Unit.cast(gc.getInfoTypeForString('SPELL_ACTIVATE_AREA_TRAP'))
if p2Unit.getUnitClassType() == iGlyph:
if p2Unit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_GLYPH')):
pPlayer = gc.getPlayer(p2Unit.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
if eTeam.isAtWar(pUnit.getTeam()):
p2Unit.cast(gc.getInfoTypeForString('SPELL_ACTIVATE_GLYPH'))
if pPlot.getNumUnits() != 0:
iTrap = gc.getInfoTypeForString('UNITCLASS_TRAP')
iNoise = gc.getInfoTypeForString('UNITCLASS_NOISE')
for i in range(pPlot.getNumUnits()):
p2Unit = pPlot.getUnit(i)
if p2Unit.getUnitClassType() == iTrap:
if p2Unit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_TRAP')):
pPlayer = gc.getPlayer(p2Unit.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
if eTeam.isAtWar(pUnit.getTeam()):
p2Unit.cast(gc.getInfoTypeForString('SPELL_ACTIVATE_TRAP'))
if p2Unit.getUnitClassType() == iNoise:
p2Unit.cast(gc.getInfoTypeForString('SPELL_NOISE'))
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_SOUTH_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_SOUTH_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_NORTH_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_NORTH_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_WEST_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_WEST_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_EAST_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_EAST_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
Wow. What a big chunk. So what does it all mean?
Let's look at the first code block:
Spoiler:
Code:
iX = pPlot.getX()
iY = pPlot.getY()
for iiX in range(iX-1, iX+2, 1):
for iiY in range(iY-1, iY+2, 1):
p2Plot = CyMap().plot(iiX,iiY)
for i in range(p2Plot.getNumUnits()):
iTrap = gc.getInfoTypeForString('UNITCLASS_TRAP')
iGlyph = gc.getInfoTypeForString('UNITCLASS_GLYPH')
p2Unit = p2Plot.getUnit(i)
if p2Unit.getUnitClassType() == iTrap:
if p2Unit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AREA_TRAP')):
pPlayer = gc.getPlayer(p2Unit.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
if eTeam.isAtWar(pUnit.getTeam()):
p2Unit.cast(gc.getInfoTypeForString('SPELL_ACTIVATE_AREA_TRAP'))
if p2Unit.getUnitClassType() == iGlyph:
if p2Unit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_GLYPH')):
pPlayer = gc.getPlayer(p2Unit.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
if eTeam.isAtWar(pUnit.getTeam()):
p2Unit.cast(gc.getInfoTypeForString('SPELL_ACTIVATE_GLYPH'))
This code looks at the area in a one tile radius around the plot a unit just moved into. If that 3x3 area contains a trap or glyph unit, the rest of the code check those units for promotions. (My traps and Glyphs are actual units that are normally invisible.) If the trap is an area trap (as determined by promotion) or a glyph (also determined by promotion -- I did this to allow for larger area glyphs later on) it triggers the appropriate spell effect.
The area trap and glyph spells are normally non-castable (they have a NEVER type promotion as a prerequisite). But the onUnitMove code forces the effect.
The area trap spell python is below:
Spoiler:
Code:
def spellActivateAreaTrap(caster):
iX = caster.getX()
iY = caster.getY()
iDamage = gc.getInfoTypeForString('DAMAGE_PHYSICAL')
iEffect = gc.getInfoTypeForString('EFFECT_MININGSPARK')
szText = "AS3D_UN_CANNON_FIRE"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FIRE1')):
iDamage = gc.getInfoTypeForString('DAMAGE_FIRE')
iEffect = gc.getInfoTypeForString('EFFECT_PILLAR_OF_FIRE')
szText = "AS3D_SPELL_FIREBALL"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEATH1')):
iDamage = gc.getInfoTypeForString('DAMAGE_DEATH')
iEffect = gc.getInfoTypeForString('EFFECT_HELLFIRE')
szText = "AS3D_SPELL_RAISE_SKELETON"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NATURE1')):
iDamage = gc.getInfoTypeForString('DAMAGE_POISON')
iEffect = gc.getInfoTypeForString('EFFECT_SPORES')
szText = "AS3D_SPELL_CONTAGION"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENTROPY1')):
iDamage = gc.getInfoTypeForString('DAMAGE_UNHOLY')
iEffect = gc.getInfoTypeForString('EFFECT_ENTROPY_SUMMON')
szText = "AS3D_SPELL_DEFILE"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_WATER1')):
iDamage = gc.getInfoTypeForString('DAMAGE_COLD')
iEffect = gc.getInfoTypeForString('EFFECT_WHIRLWIND')
szText = "AS3D_SPELL_WHIRLWIND"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LIFE1')):
iDamage = gc.getInfoTypeForString('DAMAGE_HOLY')
iEffect = gc.getInfoTypeForString('EFFECT_LIFE_SUMMON')
szText = "AS3D_SPELL_DESTROY_UNDEAD"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AIR1')):
iDamage = gc.getInfoTypeForString('DAMAGE_LIGHTNING')
iEffect = gc.getInfoTypeForString('EFFECT_SWORDS_SPARKS')
szText = "AS3D_SPELL_LIGHTNING_ELEMENTAL"
pPlayer = gc.getPlayer(caster.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
iBestValue = 0
pBestPlot = -1
for iiX in range(iX-1, iX+2, 1):
for iiY in range(iY-1, iY+2, 1):
iValue = 0
pPlot = CyMap().plot(iiX,iiY)
for i in range(pPlot.getNumUnits()):
pUnit = pPlot.getUnit(i)
if eTeam.isAtWar(pUnit.getTeam()):
iValue = iValue + 10
if (iValue > iBestValue):
iBestValue = iValue
pBestPlot = pPlot
if pBestPlot != -1:
for i in range(pBestPlot.getNumUnits()):
pUnit = pBestPlot.getUnit(i)
pUnit.doDamage(50, 75, caster, iDamage, true)
CyInterface().addMessage(pUnit.getOwner(),true,25,CyTranslator().getText("TXT_KEY_MESSAGE_AREA_TRAP_SPRUNG",()),'',1,'Art/Interface/Buttons/spells/sprungtrap.dds',ColorTypes(7),caster.getX(),caster.getY(),True,True)
point = pBestPlot.getPoint()
CyEngine().triggerEffect(iEffect,point)
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEFENSIVE')):
caster.setHasPromotion(gc.getInfoTypeForString('PROMOTION_HIDDEN'), True)
szText = "AS3D_UN_TRAP_RESET"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_RUSTED')):
caster.kill(True, 0)
Oooookay. So as you can see, area traps look at the other pomotions of a trap unit to determine the type of damage dealt out. The default is physical damage. By adding different promotions (I reused existing ones) a designer can change the trap visual effects, sound, and damage type. Also, at the end of the code block, the code looks for the DEFENSIVE and RUSTED promotions. Again, these were just reusing already existing promotions. If the unit has the DEFENSIVE promotion, it regains the HIDDEN promotion, so that the trap unit stays invisible to the player. Otherwise, the unit is revealed and can be "attacked." The RUSTED promotion turns the trap into a "one-shot trap. That is, the unit is destroyed after it triggers once.
The code for glyphs looks much the same, except that glyphs cast spells, instead of dealing out a form of damage directly. Code example below:
Spoiler:
Code:
def spellActivateGlyph(caster):
iSpell = (gc.getInfoTypeForString('SPELL_BLINDING_LIGHT'))
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FIRE1')):
iSpell = (gc.getInfoTypeForString('SPELL_FIREBALL'))
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NATURE1')):
iSpell = (gc.getInfoTypeForString('SPELL_CONTAGION'))
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AIR1')):
iSpell = (gc.getInfoTypeForString('SPELL_SUMMON_LIGHTNING_ELEMENTAL'))
iX = caster.getX()
iY = caster.getY()
pPlayer = gc.getPlayer(caster.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
for iiX in range(iX-1, iX+2, 1):
for iiY in range(iY-1, iY+2, 1):
pPlot = CyMap().plot(iiX,iiY)
for i in range(pPlot.getNumUnits()):
pUnit = pPlot.getUnit(i)
if eTeam.isAtWar(pUnit.getTeam()):
caster.cast(iSpell)
CyInterface().addMessage(pUnit.getOwner(),true,25,CyTranslator().getText("TXT_KEY_MESSAGE_GLYPH_SPRUNG", ()),'',1,'Art/Interface/Buttons/sprungglyph.dds',ColorTypes(7),caster.getX(),caster.getY(),True,True)
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEFENSIVE')):
caster.setHasPromotion(gc.getInfoTypeForString('PROMOTION_HIDDEN'), True)
point = pPlot.getPoint()
szText = "AS3D_UN_GLYPH_RESET"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_RUSTED')):
caster.kill(True, 0)
Glyphs weren't fully implemented, yet. I gotta tell ya, though, those blinding light glyphs were great for paralyzing player's units while monsters pounded on them.
I should also note the custom sounds I created for these units, the ***_RESET sound files. These were sounds that I added a second or two of silence to at the beginning. Thus, if the trap or glyph was a persistent one, the player would hear a "reset" sound after the unit triggered its primary effect. In the case of a trap, a player stumbling into one would hear the initial "bang" or whatever, followed momentarily by a "click-click." For glyphs, the reset sound is more of a "whoosh." I did this to give players an auditory clue that the trap or glyph, even though possibly still unseen, was still around and a potential danger.
The next code block in the CVEventManager.py file deals with regular "one tile" traps and noise units. Regular traps trigger only if a unit steps on the same tile as the trap unit. Same for noise units. The noise units are a big part of how I got my dungeon doors to work, but thay can also trigger other sound effects throughout the dungeon. Code below:
Spoiler:
Code:
if pPlot.getNumUnits() != 0:
iTrap = gc.getInfoTypeForString('UNITCLASS_TRAP')
iNoise = gc.getInfoTypeForString('UNITCLASS_NOISE')
for i in range(pPlot.getNumUnits()):
p2Unit = pPlot.getUnit(i)
if p2Unit.getUnitClassType() == iTrap:
if p2Unit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_TRAP')):
pPlayer = gc.getPlayer(p2Unit.getOwner())
iTeam = pPlayer.getTeam()
eTeam = gc.getTeam(iTeam)
if eTeam.isAtWar(pUnit.getTeam()):
p2Unit.cast(gc.getInfoTypeForString('SPELL_ACTIVATE_TRAP'))
if p2Unit.getUnitClassType() == iNoise:
p2Unit.cast(gc.getInfoTypeForString('SPELL_NOISE'))
I forgot to mention that all traps and glyphs only trigger if the player entering the zone of activation is at war with the playing owning the trap or glyph unit. Noise units activate regardless of who enters.
The spell results code for regular traps is pretty much like area traps. The spell code for noise units is below:
Spoiler:
Code:
def spellNoise(caster):
pPlot = caster.plot()
point = pPlot.getPoint()
szText = "AS3D_DOOR_OPEN"
if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEATH1')):
szText = "AS3D_SPELL_RAISE_SKELETON"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
At this point, I only have the custom "door opening" noise and a skeleton rattle noise as options. Again, the specific noise generated is driven by a check for promotions. The default is the door noise, with others being possible if the noise unit is given certain promotions.
So what's up with doors? Glad you asked. From the remainder of the CvEventManager.py entry for onUnitMove:
Spoiler:
Code:
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_SOUTH_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_SOUTH_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_NORTH_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_NORTH_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_WEST_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_WEST_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
if pOldPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_DOOR_EAST_OPEN'):
iFeature = gc.getInfoTypeForString('FEATURE_DOOR_EAST_CLOSED')
if pOldPlot.getNumUnits() == 0:
pOldPlot.setFeatureType(iFeature,0)
point = pOldPlot.getPoint()
szText = "AS3D_DOOR_CLOSE"
CyAudioGame().Play3DSound(szText,point.x,point.y,point.z)
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
newUnit = bPlayer.initUnit(gc.getInfoTypeForString('UNIT_NOISE'), pOldPlot.getX(), pOldPlot.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
This code looks at the plot a unit just left. If that plot had an open door, and there are no units remaining on that door, the code changes the door to a closed door, and creates a new noise unit on the closed door. It also plays a custom "door slam" sound.
I should note that noise units "die" after making their noise. Having them be sacrificed like this made it way easier to do the code checks for the dynamic changes on doors.
More about doors -- as a feature, they are set to convert to the correct facing of an "open door" once a unit moves onto the tile. As the unit moves onto the tile, it activates the noise unit, which makes the "door opening" sound. The Noise unit then dies. At this point, the door will remain open and make no further noises until all non-door related units have moved off of the door. This way, one unit can "hold open the door" so to speak, as other unit pass through. (I had plans to have a type of unit/equipment called a "door spike" that could be manufactured in camp to do exactly this.) This is important because the move cost on a closed door is very high (you have to stop and open the door, logically), but the move cost for an open door is the same as the underlying terrain.
Once all non-door related units move off the door, the last leaving unit triggers the onUnitMove effects that convert the door back into a closed door, play the "door slam" sound, and generate a new noise unit.
Taken all together, this creates much more realistic door effects, and are a huge improvement over the doors in Afterworld. (The Afterworld doors would open when the first player moved onto them, but would stay open afterwards.)
Clumsy work-around? Absolutely. But as they say, "If it's stupid and it works, then it's not stupid."
*******************
Yikes, long post. I'll close it off for now. Keep in mind that all of this code requires the creation of new units and some custom sounds that are NOT in standard FfH2. Accordingly, this DA modmod Giblet is of more use to those who are more experienced modders, who already know how to add those missing pieces.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.