diff --git a/scripts/dragonactorscript.pex b/scripts/dragonactorscript.pex new file mode 100644 index 00000000..ea7a699a Binary files /dev/null and b/scripts/dragonactorscript.pex differ diff --git a/source/scripts/dragonactorscript.psc b/source/scripts/dragonactorscript.psc new file mode 100644 index 00000000..5234afc7 --- /dev/null +++ b/source/scripts/dragonactorscript.psc @@ -0,0 +1,304 @@ +scriptname dragonActorSCRIPT extends actor +{Base Actor Script for all Dragons} +; _____________ +;| | +;| SETUP | +;|_____________| +ImageSpaceModifier property dragonFOVfx auto +{FX played for various impacts in dragon combat} +float property deathFXrange = 1024.0 auto +{max range to play death FX on player} +quest property MQkillDragon auto +{used to invoke deathSequence() function in MQKillDragonScript.psc} +actor property player auto hidden +{For quick reference and clean-looking script} +float property FOVfalloff auto hidden +{choosing not to expose this and clutter the prop list in CS, since it won't be touched often} +sound property NPCDragonFlyby auto +{Sound played when dragon passes by a target} +explosion property knockBackExplosion auto +{explosion used to knock back enemies} +; NOTE - the dragon breed/skin settings are deprecated and have no effect. +armor property SnowDragonSkin auto +{deprecated - do not use} +armor property TundraDragonSkin auto +{deprecated - do not use} +armor property forestDragonSkin auto +{deprecated - do not use} +int property dragonBreed = 0 auto +{deprecated - do not use} + +WIFunctionsScript Property WI Auto ;added by jduvall +{Pointer to WIFunctionsScript on WI quest. Used to create script event to get nearby NPCs to react to the death of the dragon} + +ImpactDataSet Property FXDragonTakeoffImpactSet Auto +{Impact data set to use for the dragon takeoff} +ImpactDataSet Property FXDragonLandingImpactSet Auto +{Impact data set to use for the dragon landing} +ImpactDataSet Property FXDragonTailstompImpactSet Auto +{Impact data set to use for the tailstomp} + + +;*****ADDED FOR DLC2-------------- +bool MiraakIntroductionHappened +bool MiraakAppeared +Location Property DLC2ApocryphaLocation Auto +WorldSpace Property DLC2ApocryphaWorld auto +;*****---------------------------- + +; _____________ +;| | +;| EVENTS | +;|_____________| + EVENT onInit() + ; just initialize any variables, etc. + player = game.getPlayer() ; store player reference + FOVfalloff = 1600 ; range at which impact doesn't play FOV + + if deathFXrange == 0 + deathFXrange = 1000 + endif + + if !isDead() && isGhost() + ; redundancy check to prevent invincible, "ghosted" dragons from respawning. + setGhost(FALSE) + endif + + gotoState("alive") + endEVENT + + EVENT onReset() + ; if we're resetting a previously-killed dragon, make sure it's not a ghost. + setGhost(FALSE) + endEVENT + + EVENT onLoad() + ; change markings based on breed of this dragon + ; first, support random selection + ; if dragonBreed == 4 + ; dragonBreed = utility.randomInt(0,3) + ; endif + ; then assign the markings, either way. + ; if dragonBreed == 1 + ; equipItem(snowDragonSkin) + ; elseif dragonBreed == 2 + ; equipItem(tundraDragonSkin) + ; elseif dragonBreed == 3 + ; equipItem(forestDragonSkin) + ; endif + + ; Block rewritten by USKP which fixes bug of respawned dragons not burning up or giving the player a soul + if !isDead() + + if isGhost() + ; redundancy check to prevent invincible, "ghosted" dragons from respawning. + setGhost(FALSE) + endif + + ; Block rewritten by tarlazo to fix a possible rare glitch as consequence of animation events registered for a dead dragon + registerForAnimationEvent(self, "DragonLandEffect") + registerForAnimationEvent(self, "DragonForcefulLandEffect") + registerForAnimationEvent(self, "DragonTakeoffEffect") + registerForAnimationEvent(self, "DragonBiteEffect") + registerForAnimationEvent(self, "DragonTailAttackEffect") + registerForAnimationEvent(self, "DragonLeftWingAttackEffect") + registerForAnimationEvent(self, "DragonRightWingAttackEffect") + registerForAnimationEvent(self, "DragonPassByEffect") + registerForAnimationEvent(self, "flightCrashLandStart") ; Considered temp for showing injury FX test + registerForAnimationEVENT(self, "DragonKnockbackEvent") + + gotoState("alive") + endif + + ; registerForAnimationEvent(self, "DragonLandEffect") + ; registerForAnimationEvent(self, "DragonForcefulLandEffect") + ; registerForAnimationEvent(self, "DragonTakeoffEffect") + ; registerForAnimationEvent(self, "DragonBiteEffect") + ; registerForAnimationEvent(self, "DragonTailAttackEffect") + ; registerForAnimationEvent(self, "DragonLeftWingAttackEffect") + ; registerForAnimationEvent(self, "DragonRightWingAttackEffect") + ; registerForAnimationEvent(self, "DragonPassByEffect") + ; registerForAnimationEvent(self, "flightCrashLandStart") ; Considered temp for showing injury FX test + ; registerForAnimationEVENT(self, "DragonKnockbackEvent") + endEVENT + + +Event OnLocationChange(Location akOldLoc, Location akNewLoc) + ;USED TO GET DIALOGUE CONDITIONED ON DRAGON HAVING ATTACKED A TOWN -- only happens if he lands on the ground in a location - ie death, or to land to fight (not on a perch) + ;see also DragonPerchScript + +; debug.trace(self + "OnLocationChange() calling WI.RegisterDragonAttack(" +akNewLoc + ")") + ;USKP 2.0.3 - So. Now it seems like you won't even call this out of here with a None? Ok..... + if( akNewLoc != None ) + WI.RegisterDragonAttack(akNewLoc, self) + EndIf + + if !isDead() && isGhost() + ; redundancy check to prevent invincible, "ghosted" dragons from respawning. + setGhost(FALSE) + endif + +EndEvent + +STATE alive + Event OnCombatStateChanged(Actor akTarget, int aeCombatState) + + if !isDead() && isGhost() + ; redundancy check to prevent invincible, "ghosted" dragons from respawning. + setGhost(FALSE) + endif + + if akTarget == Game.GetPlayer() + WI.updateWIDragonTimer() ;used to prevent dragons from appearing too frequently + endif + endEvent + + EVENT onAnimationEvent(objectReference deliverator, string eventName) +; debug.trace("AnimEvent: " + eventName + " delivered by Dragon ("+self+") ") + if (eventName == "DragonLandEffect") +; ;debug.trace("Dragon AnimPayLoad: DragonLandEffect") + game.shakeCamera(self, 1) + game.shakeController(95, 95, 2) + KnockAreaEffect(1, getLength()) + animateFOV() ; call the FOV function + PlayImpactEffect(FXDragonTakeoffImpactSet, "NPC Pelvis", 0, 0, -1, 512) + elseIf (eventName == "DragonForcefulLandEffect") +; ;debug.trace("Dragon AnimPayLoad: DragonForcefulLandEffect") + PlayImpactEffect(FXDragonLandingImpactSet, "NPC Pelvis", 0, 0, -1, 512) + KnockAreaEffect(1, 2*getLength()) + ;notification ("Dragon Forceful Land Effect") + elseIf (eventName == "DragonTakeoffEffect") +; debug.trace("Dragon AnimPayLoad: DragonTakeoffEffect") + PlayImpactEffect(FXDragonTakeoffImpactSet, "NPC Tail8", 0, 0, -1, 2048) + elseIf (eventName == "DragonBiteEffect") + ;Removed spit effect on 6/1/10. Leaving logic in case we need to sub in other fx or sounds. +; ;debug.trace("Dragon ("+self+") AnimPayLoad: DragonBiteEffect") + ;DragonBiteFX.Play(Self, 2) + ;notification ("Dragon Bite Effect") + elseIf (eventName == "DragonTailAttackEffect") +; ;debug.trace(" Dragon AnimPayoload: DragonTailAttackEffect") + PlayImpactEffect(FXDragonTailstompImpactSet, "NPC Tail8", 0, 0, -1, 512) + ;notification("Dragon Tail Attack Effect") + elseIf (eventName == "DragonLeftWingAttackEffect") +; ;debug.trace("Dragon AnimPayLoad: DragonLeftWingAttackEffect") + PlayImpactEffect(FXDragonTailstompImpactSet, "NPC LHand", 0, 0, -1, 512) + ;notification ("Dragon Left Wing Attack Effect") + elseIf (eventName == "DragonRightWingAttackEffect") +; ;debug.trace("Dragon AnimPayLoad: DragonRightWingAttackEffect") + PlayImpactEffect(FXDragonTailstompImpactSet, "NPC RHand", 0, 0, -1, 512) + ;notification ("Dragon Right Wing Attack Effect") + elseIf (eventName == "DragonPassByEffect") + NPCDragonFlyby.Play(self) + game.shakeCamera(self, 0.85) + game.shakeController(0.65, 0.65, 0.5) + ;elseIf (eventName == "flightCrashLandStart") + ;Removed by mark since leaking magic is a bust. May replace with leaking blood at some point as a second test. + ; apply injury FX art when I crash land. + ;FXinjuryLeaks.play(self, -1) + elseIf (eventName == "DragonKnockbackEvent") + ; dragon needs to stagger everyone in radius a bit larger than my length +; ;debug.trace(self + " is knocking back actors within " + 1.5*getLength() + " of self @ " + self.x + ", " + self.y) + ;self.placeAtMe(knockBackExplosion) + KnockAreaEffect(1, 1.5*getLength()) + animateFOV(1.5*getLength()) + endif + endEVENT + + EVENT onDeath(actor killer) + ;debug.trace("Remove mouth/injury FX art, begin death FX sequence for " + self) + ;USKP 2.1.3 Bug #19214 - Dialogue in the quest has options that get skipped with this line below the gotoState call. + WI.startWIDragonKillQuest(self) ;used to create a scene if any NPCs are nearby when the dragon dies. See WIFunctionsScript attached to WI quest which creates a story manager script event, and WIDragonKilled quest which handles the scene. + gotoState("deadAndWaiting") + + ; removing update registration in favor of a while() in the STATE below + ;registerForUpdate(1) + endEVENT +endSTATE + +STATE deadAndWaiting + + ;-------------------CHANGED FOR DLC2--------------------------- + EVENT onBeginState() + + MQKillDragonScript MQKillDragonS = MQkillDragon as MQKillDragonScript + + ;If in Apocrypha, this is the boss fight so do not wait for distance + if DLC2ApocryphaLocation && DLC2ApocryphaWorld && (game.getPlayer().isInLocation(DLC2ApocryphaLocation) || game.getPlayer().getWorldSpace() == DLC2ApocryphaWorld) + gotoState("deadDisintegrated") + MQkillDragonS.deathSequence(self) + RegisterForSingleUpdateGameTime(0.5) + elseif MQKillDragonS.ShouldMiraakAppear(self) && MiraakAppeared == false + gotoState("deadDisintegrated") +; Debug.Trace(self + "MiraakAppears") + MiraakAppeared = true + MQkillDragonS.deathSequence(self, MiraakAppears = true) + RegisterForSingleUpdateGameTime(0.5) + else + ;NORMAL BASE GAME BEHAVIOR + while getDistance(player) > deathFXrange + utility.wait(1.0) + endWhile +; debug.trace("player close enough to absorb" + self) + gotoState("deadDisintegrated") + MQkillDragonS.deathSequence(self) + RegisterForSingleUpdateGameTime(0.5) + + endif + + endEVENT + ;-------------------------------------------------------------------------------------------- + + ; EVENT onUpdate() + ; if getDistance(player) < deathFXrange +; ; debug.trace("player close enough to absorb" + self) + ; gotoState("deadDisintegrated") + ; unRegisterforUpdate() + ; (MQkillDragon as MQKillDragonScript).deathSequence(self) + ; endif + ;endEVENT +endSTATE + +STATE deadDisintegrated + ; nothing happens here - dead and just bones. + event OnUpdateGameTime() + cell myDragonCell = self.getparentcell() + if !myDragonCell.IsAttached() + NoStalking() + else + RegisterForSingleUpdateGameTime(0.5) + endIf + endEvent +endSTATE + +; _____________ +;| | +;| FUNCTIONS | +;|_____________| +function AnimateFOV(float fFOVfalloff = 1600.0) + ; Function that animates FOV with an ismod. Declaring here in case needed frequently + float playerDist = player.getDistance(self) ; get and store the player's current distance from me + if playerDist < fFOVfalloff + float FOVpower = (1- (1/(fFOVfalloff/(playerDist)))) ; consider offset to compensate for collision size + if FOVpower > 1.0 + FOVpower = 1.0 ; clamp to prevent wacky values + endif +; ;debug.trace("player is " + playerDist + " from landing dragon") +; ;debug.trace("dragon FOV fx power is" + FOVpower) + dragonFOVfx.apply(FOVpower) ;animated FOV effect. Strength based on distance from player + endif +endFunction + +Bool function IsDurnehviir() + + return self.GetActorBase() as form == game.GetFormFromFile(0x000030D8, "Dawnguard.esm") +endFunction + +function NoStalking() + + if self.IsDurnehviir() + return + else + self.DispelAllSpells() + self.SetCriticalStage(self.CritStage_DisintegrateEnd) + endIf +endFunction \ No newline at end of file