def loadModel(self):
     self.loadSfx()
     self.navyCannon = DefenseCannon(self.cr)
     self.navyCannon.cannonPost = self.navyCannon
     self.navyCannon.shipId = self.shipId
     self.navyCannon.doId = self.doId
     self.navyCannon.loadModel(None, ShipGlobals.Cannons.Navy)
     self.navyCannon.deleteCollisions()
     self.repeaterCannon = DefenseRepeaterCannon(self.cr)
     self.repeaterCannon.cannonPost = self.repeaterCannon
     self.repeaterCannon.shipId = self.shipId
     self.repeaterCannon.doId = self.doId
     self.repeaterCannon.loadModel(None, ShipGlobals.Cannons.Repeater)
     self.setCannon(self.navyCannon)
 def loadModel(self):
     self.loadSfx()
     self.navyCannon = DefenseCannon(self.cr)
     self.navyCannon.cannonPost = self.navyCannon
     self.navyCannon.shipId = self.shipId
     self.navyCannon.doId = self.doId
     self.navyCannon.loadModel(None, ShipGlobals.Cannons.Navy)
     self.navyCannon.deleteCollisions()
     self.repeaterCannon = DefenseRepeaterCannon(self.cr)
     self.repeaterCannon.cannonPost = self.repeaterCannon
     self.repeaterCannon.shipId = self.shipId
     self.repeaterCannon.doId = self.doId
     self.repeaterCannon.loadModel(None, ShipGlobals.Cannons.Repeater)
     self.setCannon(self.navyCannon)
class DistributedDefenseCannon(DistributedIslandCannon):
    ignoreGround = True
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedDefenseCannon')
    sfxRepeater = None
    sfxNavy = None
    powderKegSfx = None
    
    def __init__(self, cr):
        DistributedIslandCannon.__init__(self, cr)
        self._DistributedDefenseCannon__shouldUnloadSfx = False
        self._DistributedDefenseCannon__exitEvent = None
        self._DistributedDefenseCannon__readyEvent = None
        self._cameraState = 'CannonDefense'
        self.isRepeaterCannon = False
        self.proximityAmmo = { }
        self.usedShotNums = { }
        self.lastCannonShot = { }
        self.shipsHitByShotNum = { }
        self.removeShotNumTasks = []

    
    def loadCannonEmptySound(self):
        self.emptySound = loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_AMMO_OUT)

    
    def announceGenerate(self):
        DistributedIslandCannon.announceGenerate(self)
        self.accept(self.uniqueName('proximityAmmoDestroyed'), self.sendRemoveProximityAmmo)
        self.d_requestProximityAmmo(base.localAvatar.doId)
        self.d_requestShotNum(base.localAvatar.doId)

    
    def delete(self):
        DistributedIslandCannon.delete(self)
        if self._DistributedDefenseCannon__shouldUnloadSfx:
            self.unloadSfx()
        
        for task in self.removeShotNumTasks:
            taskMgr.remove(task.name)
        

    
    def loadModel(self):
        self.loadSfx()
        self.navyCannon = DefenseCannon(self.cr)
        self.navyCannon.cannonPost = self.navyCannon
        self.navyCannon.shipId = self.shipId
        self.navyCannon.doId = self.doId
        self.navyCannon.loadModel(None, ShipGlobals.Cannons.Navy)
        self.navyCannon.deleteCollisions()
        self.repeaterCannon = DefenseRepeaterCannon(self.cr)
        self.repeaterCannon.cannonPost = self.repeaterCannon
        self.repeaterCannon.shipId = self.shipId
        self.repeaterCannon.doId = self.doId
        self.repeaterCannon.loadModel(None, ShipGlobals.Cannons.Repeater)
        self.setCannon(self.navyCannon)

    
    def loadSfx(self):
        if self.sfxRepeater == None:
            DistributedDefenseCannon.sfxRepeater = loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_TRANSFORM_REPEATER)
        
        if self.sfxNavy == None:
            DistributedDefenseCannon.sfxNavy = loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_TRANSFORM_NAVY)
        
        if self.powderKegSfx == None:
            DistributedDefenseCannon.powderKegSfx = loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_POWDERKEG_EXPLODE)
        

    
    def unloadSfx(self):
        if self.sfxRepeater:
            loader.unloadSfx(self.sfxRepeater)
            self.sfxRepeater = None
        
        if self.sfxNavy:
            loader.unloadSfx(self.sfxNavy)
            self.sfxNavy = None
        
        if self.powderKegSfx:
            loader.unloadSfx(self.powderKegSfx)
            self.powderKegSfx = None
        

    
    def addDestructableCollision(self):
        pass

    
    def cleanupProp(self):
        if self.prop:
            self.prop = None
        
        if self.navyCannon:
            self.navyCannon.delete()
            self.navyCannon = None
        
        if self.repeaterCannon:
            self.repeaterCannon.delete()
            self.repeaterCannon = None
        

    
    def setCannon(self, cannon):
        if self.prop:
            cannon.hNode.setHpr(self.prop.hNode.getHpr())
            cannon.pivot.setHpr(self.prop.pivot.getHpr())
            cannon.currentHpr = list(self.prop.currentHpr)
        
        if self.prop:
            self.prop.detachNode()
        
        self.prop = cannon
        self.prop.reparentTo(self)

    
    def currentCannonType(self, type):
        if type == 0:
            self.downgradeToNavy()
        elif type == 1:
            self.upgradeToRepeater()
        

    
    def requestUpgradeToRepeater(self):
        self.sendUpdate('requestUpgradeToRepeater')

    
    def upgradeToRepeater(self):
        if self.isRepeaterCannon == False:
            self.isRepeaterCannon = True
            self.doTransitionEffect(self.repeaterCannon, self.sfxRepeater)
        

    
    def downgradeToNavy(self):
        if self.isRepeaterCannon == True:
            self.isRepeaterCannon = False
            self.doTransitionEffect(self.navyCannon, self.sfxNavy)
        

    
    def doTransitionEffect(self, cannonModel, sfx):
        smokeEffect = RepeaterCannonUpgradeEffect.getEffect()
        if smokeEffect:
            smokeEffect.reparentTo(self)
            smokeEffect.setPos(self, 0, 0, 0)
            smokeEffect.spriteScale = 1.0
            smokeEffect.play()
        
        if sfx:
            base.playSfx(sfx, node = self.prop, cutoff = 3000)
        
        taskMgr.doMethodLater(0.20000000000000001, self._switchToCannon, name = self.uniqueName('SwitchToCannon'), extraArgs = [
            cannonModel])

    
    def _switchToCannon(self, cannonModel):
        self.setCannon(cannonModel)
        if self.av == localAvatar and localAvatar.cameraFSM.state == self._cameraState:
            self.prop.av = self.av
            localAvatar.cameraFSM.cannonDefenseCamera.changeModel(cannonModel)
        

    
    def setCannonGUI(self):
        self.cgui = DefenseCannonGUI.DefenseCannonGUI(self)
        self.cgui.exitEvent = self._DistributedDefenseCannon__exitEvent

    
    def updateCannonItems(self):
        base.localAvatar.guiMgr.combatTray.initCombatTray(InventoryType.DefenseCannonRep)
        base.localAvatar.guiMgr.combatTray.skillTray.updateSkillTray(InventoryType.DefenseCannonRep, WeaponGlobals.DEFENSE_CANNON)

    
    def enableCannonFireInput(self):
        self.accept('mouse1', self.fireCannon)
        self.accept('control', self.fireCannon)
        self.accept('wheel_up', self.changeAmmoMouseWheel, extraArgs = [
            1])
        self.accept('wheel_down', self.changeAmmoMouseWheel, extraArgs = [
            -1])

    
    def startWeapon(self, av):
        DistributedIslandCannon.startWeapon(self, av)
        if av:
            av.setPos(self.prop, 0, -6.0, 0)
            if av == localAvatar:
                self.repeaterCannon.setLocalAvatarUsingWeapon(1)
                self.navyCannon.setLocalAvatarUsingWeapon(1)
                localAvatar.sendCurrentPosition()
                localAvatar.cameraFSM.cannonDefenseCamera.setRotation(CannonDefenseGlobals.CANNON_HPR[0], CannonDefenseGlobals.CANNON_HPR[1])
            
        
        if self._DistributedDefenseCannon__readyEvent:
            self._DistributedDefenseCannon__readyEvent()
        

    
    def enterFireCannon(self):
        DistributedIslandCannon.enterFireCannon(self)
        self.ammoPanel = AmmoPanel(self)
        localAvatar.guiMgr.radarGui.hide()
        localAvatar.guiMgr.hideTrackedQuestInfo()
        localAvatar.guiMgr.gameGui.hide()
        localAvatar.guiMgr.crewHUD.setHUDOff()

    
    def exitFireCannon(self):
        if self.av == localAvatar:
            self._DistributedDefenseCannon__shouldUnloadSfx = True
        
        DistributedIslandCannon.exitFireCannon(self)
        self.ammoPanel.destroy()
        render.findAllMatches('**/=objType=%s' % PiratesGlobals.COLL_DEFENSE_AMMO).detach()
        localAvatar.guiMgr.radarGui.show()
        localAvatar.guiMgr.showTrackedQuestInfo()
        localAvatar.guiMgr.gameGui.show()
        localAvatar.guiMgr.crewHUD.setHUDOn()

    
    def _doFireCannon(self):
        if localAvatar.isDazed:
            base.playSfx(self.emptySound)
            return None
        
        if not self.ammoPanel.hasCurrentAmmo():
            return None
        
        canFire = self.volley > 0
        DistributedIslandCannon._doFireCannon(self)
        if canFire:
            self.sendUpdate('recordFireEvent')
            self.lastCannonShot[self.getAmmoSkillId()] = globalClock.getFrameTime()
        
        self.ammoPanel.decreaseAmmoAmount(canFire)

    
    def useProjectileSkill(self, skillId, ammoSkillId, posHpr, timestamp, charge):
        DistributedIslandCannon.useProjectileSkill(self, skillId, ammoSkillId, posHpr, timestamp, charge)
        self.shotNum += 1

    
    def handleEndInteractKey(self):
        self.cgui.showExitDialog()

    
    def applyRechargeTimeModifier(self, rechargeTime):
        if not self.isRepeaterCannon:
            return rechargeTime
        
        newRecharge = rechargeTime + rechargeTime * CannonDefenseGlobals.REPEATER_RELOAD_MODIFIER
        return newRecharge

    
    def changeAmmoMouseWheel(self, amt = 1):
        ammoSkillId = self.getAmmoSkillId()
        index = -1
        for i in range(len(PiratesGlobals.CANNON_DEFENSE_SKILLS)):
            if PiratesGlobals.CANNON_DEFENSE_SKILLS[i] == ammoSkillId:
                index = i
                break
                continue
        
        if index < 0:
            return None
        
        if amt > 0:
            recursionChange = 1
            if index + amt > len(PiratesGlobals.CANNON_DEFENSE_SKILLS) - 1:
                return None
            
            newAmmoId = PiratesGlobals.CANNON_DEFENSE_SKILLS[index + amt]
        else:
            recursionChange = -1
            if index + amt < 0:
                return None
            
            newAmmoId = PiratesGlobals.CANNON_DEFENSE_SKILLS[index + amt]
        if newAmmoId == InventoryType.DefenseCannonEmpty:
            self.changeAmmoMouseWheel(amt + recursionChange)
            return None
        
        self.changeAmmo(newAmmoId - ammoSkillId)

    
    def changeAmmo(self, amt = 1):
        if amt == 0:
            return None
        
        keepChanging = True
        ammoSkillId = self.getAmmoSkillId()
        while keepChanging:
            ammoSkillId += amt
            if ammoSkillId > InventoryType.DefenseCannonEmpty:
                ammoSkillId = InventoryType.DefenseCannonRoundShot
            
            if ammoSkillId < InventoryType.begin_WeaponSkillCannonDefense + 1:
                ammoSkillId = InventoryType.DefenseCannonEmpty
            
            inv = base.localAvatar.getInventory()
            if ammoSkillId > InventoryType.CannonBullet:
                keepChanging = False
            
            if inv.getStackQuantity(ammoSkillId) >= 2:
                keepChanging = False
            
            if WeaponGlobals.isInfiniteAmmo(ammoSkillId):
                keepChanging = False
                continue
        self.setAmmoSkillId(ammoSkillId)
        del ammoSkillId
        if WeaponGlobals.isInfiniteAmmo(self.getAmmoSkillId()):
            self.cgui.setAmmoLeft(-1, -1)
        else:
            ammoInvId = WeaponGlobals.getSkillAmmoInventoryId(self.getAmmoSkillId())
            self.numShots = inv.getStackQuantity(ammoInvId)
            maxShots = inv.getStackLimit(ammoInvId)
            self.cgui.setAmmoLeft(self.numShots, maxShots)
        self.cgui.setAmmoId(self.getAmmoSkillId())
        self.cgui.showCannonControls()
        self.updateCannonDressing()
        self.hideCannonDressing()
        self.setVolley(0)

    
    def simpleShipHit(self, hitObject, entry, skillId, ammoSkillId):
        ship = hitObject.getNetPythonTag('ship')
        if not ship:
            return None
        
        fromNodePath = entry.getFromNodePath()
        shotNum = int(fromNodePath.getNetTag('shotNum'))
        if shotNum not in self.shipsHitByShotNum:
            self.shipsHitByShotNum[shotNum] = []
            self.removeShotNumTasks.append(taskMgr.doMethodLater(5.0, self.removeShotNumTask, self.uniqueName('removeShotNum%s' % shotNum), extraArgs = [
                shotNum]))
        
        if ship.doId in self.shipsHitByShotNum[shotNum]:
            return None
        
        self.shipsHitByShotNum[shotNum].append(ship.doId)
        return DistributedIslandCannon.simpleShipHit(self, hitObject, entry, skillId, ammoSkillId)

    
    def removeShotNumTask(self, shotNum):
        if shotNum in self.shipsHitByShotNum:
            del self.shipsHitByShotNum[shotNum]
        
        self.removeShotNumTasks.pop()

    
    def projectileHitObject(self, entry):
        DistributedIslandCannon.projectileHitObject(self, entry)
        if self.localAvatarUsingWeapon:
            fromNodePath = entry.getFromNodePath()
            ammo = fromNodePath.getNetPythonTag('ammo')
            if not ammo:
                return None
            
            ammo.setHitByAmmoEvent(self.uniqueName('proximityAmmoDestroyed'))
            hitObject = entry.getIntoNodePath()
            objType = hitObject.getNetTag('objType')
            if not objType:
                return None
            
            ammoSkillId = int(fromNodePath.getNetTag('ammoSkillId'))
            shotNum = int(fromNodePath.getNetTag('shotNum'))
            objType = int(objType)
            if objType == PiratesGlobals.COLL_DEFENSE_AMMO:
                hitammo = hitObject.getNetPythonTag('ammo')
                if not hitammo:
                    return None
                
                if ammo.hasTag('noAmmoCollide') or hitammo.hasTag('noAmmoCollide'):
                    return None
                
                ammo.dispatchHitByAmmoEvent()
                hitammo.dispatchHitByAmmoEvent()
            elif (ammoSkillId in [
                InventoryType.DefenseCannonMine,
                InventoryType.DefenseCannonPowderKeg,
                InventoryType.DefenseCannonColdShot] or objType == PiratesGlobals.COLL_SEA or ammoSkillId == InventoryType.DefenseCannonSmokePowder) and objType == PiratesGlobals.COLL_NEWSHIP:
                if shotNum not in self.proximityAmmo:
                    pos = entry.getSurfacePoint(render)
                    self.sendAddProximityAmmo(shotNum, ammoSkillId, pos, localAvatar.doId)
                
            
            if ammoSkillId in [
                InventoryType.DefenseCannonMine,
                InventoryType.DefenseCannonPowderKeg]:
                if objType == PiratesGlobals.COLL_NEWSHIP and shotNum in self.proximityAmmo:
                    ammo.dispatchHitByAmmoEvent(ammoSkillId == InventoryType.DefenseCannonMine)
                
            
        

    
    def updateReload(self, task):
        self.rechargeTime = self.cr.battleMgr.getModifiedReloadTime(localAvatar, self.skillId, self.getAmmoSkillId())
        self.rechargeTime = self.applyRechargeTimeModifier(self.rechargeTime)
        if self.rechargeTime and self.getAmmoSkillId() in self.lastCannonShot:
            percentDone = min((globalClock.getFrameTime() - self.lastCannonShot[self.getAmmoSkillId()]) / self.rechargeTime, 1.0)
        else:
            percentDone = 1
        if percentDone == 1:
            self.finishReload()
        
        self.cgui.updateReload(percentDone, self.volley)
        return Task.cont

    
    def sendAddProximityAmmo(self, shotNum, ammoSkillId, pos, attackerId):
        if shotNum in self.usedShotNums:
            return None
        
        self.usedShotNums[shotNum] = True
        self.sendUpdate('addProximityAmmo', [
            shotNum,
            ammoSkillId,
            pos,
            attackerId])

    
    def sendRemoveProximityAmmo(self, shotNum, playEffect = True):
        if shotNum not in self.usedShotNums:
            self.usedShotNums[shotNum] = True
            return None
        
        self.sendUpdate('removeProximityAmmo', [
            shotNum,
            playEffect])

    
    def setRemovedProximityAmmo(self, shotNum, playHitEffect):
        if shotNum in self.proximityAmmo:
            ammo = self.proximityAmmo[shotNum]
            if ammo and not (ammo.destroyed):
                if playHitEffect:
                    if ammo.ammoSkillId == InventoryType.DefenseCannonPowderKeg:
                        base.playSfx(self.powderKegSfx, node = ammo, cutoff = 2000)
                    
                    ammo.playProximityAmmoEffect()
                else:
                    ammo.sinkFloatingAmmo()
            
            del self.proximityAmmo[shotNum]
        

    
    def d_requestProximityAmmo(self, avId):
        self.sendUpdate('requestProximityAmmo', [
            avId])

    
    def d_requestShotNum(self, avId):
        self.sendUpdate('requestShotNum', [
            avId])

    
    def setProximityAmmo(self, ammo):
        if not self.prop:
            return None
        
        for ammoInfo in ammo:
            shotNum = ammoInfo[0]
            ammoSkillId = ammoInfo[1]
            pos = ammoInfo[2]
            attackerId = ammoInfo[3]
            timeRemaining = ammoInfo[4]
            cannonball = self.prop.addProximityAmmoFromAI(shotNum, pos, ammoSkillId, attackerId, timeRemaining, self.projectileHitEvent)
            cannonball.setHitByAmmoEvent(self.uniqueName('proximityAmmoDestroyed'))
            self.proximityAmmo[shotNum] = cannonball
            self.usedShotNums[shotNum] = True
        

    
    def setShotNum(self, shotNum):
        self.shotNum = shotNum
        self.shipsHitByShotNum[self.shotNum] = []

    
    def enableInput(self):
        self.enableCannonFireInput()
        localAvatar.cameraFSM.cannonDefenseCamera.enableInput()

    
    def disableInput(self):
        self.disableCannonFireInput()
        localAvatar.cameraFSM.cannonDefenseCamera.disableInput()

    
    def setExitEvent(self, eventCallBack):
        self._DistributedDefenseCannon__exitEvent = eventCallBack

    
    def setReadyEvent(self, eventCallBack):
        self._DistributedDefenseCannon__readyEvent = eventCallBack

    
    def simpleShipHit(self, hitObject, entry, skillId, ammoSkillId):
        ship = hitObject.getNetPythonTag('ship')
        if not ship:
            return None
        
        fromNodePath = entry.getFromNodePath()
        shotNum = int(fromNodePath.getNetTag('shotNum'))
        if shotNum not in self.shipsHitByShotNum:
            self.shipsHitByShotNum[shotNum] = []
            self.removeShotNumTasks.append(taskMgr.doMethodLater(5.0, self.removeShotNumTask, self.uniqueName('removeShotNum%s' % shotNum), extraArgs = [
                shotNum]))
        
        if ship.doId in self.shipsHitByShotNum[shotNum]:
            return None
        
        self.shipsHitByShotNum[shotNum].append(ship.doId)
        timestamp32 = globalClockDelta.getFrameNetworkTime(bits = 32)
        if not ship:
            self.notify.warning('ignoring attack on destroyed ship')
            return None
        
        hitSail = hitObject.getNetTag('Sail')
        if not hitSail:
            hitSail = 0
        else:
            hitSail = 1
        pos = entry.getSurfacePoint(ship)
        normal = entry.getSurfaceNormal(render)
        attackerId = 0
        attackerStr = entry.getFromNodePath().getNetTag('attackerId')
        if attackerStr:
            attackerId = int(attackerStr)
        
        attacker = self.cr.doId2do.get(attackerId)
        if not attacker:
            return None
        
        areaList = self.getAreaList(skillId, ammoSkillId, ship, pos, attackerId)
        result = self.cr.battleMgr.doAttack(attacker, skillId, ammoSkillId, ship.doId, areaList, pos)
        ammo = entry.getFromNodePath().getNetPythonTag('ammo')
        if ammo:
            if ammo.hasTag('newAmmoId'):
                ammoSkillId = int(ammo.getTag('newAmmoId'))
                pos = (0, 0, 0)
            
        
        self.sendSuggestProjectileSkillResult(skillId, ammoSkillId, result, ship.doId, areaList, [
            pos[0],
            pos[1],
            pos[2]], [
            normal[0],
            normal[1],
            normal[2]], [
            0,
            0,
            hitSail], timestamp32)
        (attackerEffects, targetEffects, itemEffects) = self.cr.battleMgr.getModifiedSkillEffects(attacker, ship, skillId, ammoSkillId)
        ship.projectileWeaponHit(skillId, ammoSkillId, result, targetEffects, pos, normal, (0, 0, hitSail), attacker, itemEffects)
        return True

    
    def getAreaList(self, skillId, ammoSkillId, target, pos, attackerId, isBoss = False):
        targets = set()
        areaShape = WeaponGlobals.getAttackAreaShape(skillId, ammoSkillId)
        bitmask = PiratesGlobals.GenericShipBitmask
        if areaShape == WeaponGlobals.AREA_SPHERE:
            self.runSphereAreaCollisions(skillId, ammoSkillId, target, pos, bitmask)
        elif areaShape == WeaponGlobals.AREA_TUBE:
            self.runTubeAreaCollisions(skillId, ammoSkillId, target, pos, bitmask)
        elif areaShape == WeaponGlobals.AREA_CONE:
            self.runConeAreaCollisions(skillId, ammoSkillId, target, pos, bitmask)
        elif areaShape == WeaponGlobals.AREA_OFF:
            return []
        
        numEntries = self.areaCollQueue.getNumEntries()
        if numEntries == 0:
            return []
        
        for i in range(numEntries):
            areaTarget = self.areaCollQueue.getEntry(i).getIntoNodePath().getNetPythonTag('ship')
            if areaTarget.gameFSM.state in ('Sinking', 'Sunk'):
                continue
            
            targets.add(areaTarget.doId)
        
        if target in targets:
            targets.remove(target)
        
        return list(targets)

    
    def _DistributedDefenseCannon__grounHit(self, groundId, hitObject, entry, skillId, ammoSkillId):
        return False
class DistributedDefenseCannon(DistributedIslandCannon):
    ignoreGround = True
    notify = DirectNotifyGlobal.directNotify.newCategory(
        'DistributedDefenseCannon')
    sfxRepeater = None
    sfxNavy = None
    powderKegSfx = None

    def __init__(self, cr):
        DistributedIslandCannon.__init__(self, cr)
        self._DistributedDefenseCannon__shouldUnloadSfx = False
        self._DistributedDefenseCannon__exitEvent = None
        self._DistributedDefenseCannon__readyEvent = None
        self._cameraState = 'CannonDefense'
        self.isRepeaterCannon = False
        self.proximityAmmo = {}
        self.usedShotNums = {}
        self.lastCannonShot = {}
        self.shipsHitByShotNum = {}
        self.removeShotNumTasks = []

    def loadCannonEmptySound(self):
        self.emptySound = loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_AMMO_OUT)

    def announceGenerate(self):
        DistributedIslandCannon.announceGenerate(self)
        self.accept(self.uniqueName('proximityAmmoDestroyed'),
                    self.sendRemoveProximityAmmo)
        self.d_requestProximityAmmo(base.localAvatar.doId)
        self.d_requestShotNum(base.localAvatar.doId)

    def delete(self):
        DistributedIslandCannon.delete(self)
        if self._DistributedDefenseCannon__shouldUnloadSfx:
            self.unloadSfx()

        for task in self.removeShotNumTasks:
            taskMgr.remove(task.name)

    def loadModel(self):
        self.loadSfx()
        self.navyCannon = DefenseCannon(self.cr)
        self.navyCannon.cannonPost = self.navyCannon
        self.navyCannon.shipId = self.shipId
        self.navyCannon.doId = self.doId
        self.navyCannon.loadModel(None, ShipGlobals.Cannons.Navy)
        self.navyCannon.deleteCollisions()
        self.repeaterCannon = DefenseRepeaterCannon(self.cr)
        self.repeaterCannon.cannonPost = self.repeaterCannon
        self.repeaterCannon.shipId = self.shipId
        self.repeaterCannon.doId = self.doId
        self.repeaterCannon.loadModel(None, ShipGlobals.Cannons.Repeater)
        self.setCannon(self.navyCannon)

    def loadSfx(self):
        if self.sfxRepeater == None:
            DistributedDefenseCannon.sfxRepeater = loadSfx(
                SoundGlobals.SFX_MINIGAME_CANNON_TRANSFORM_REPEATER)

        if self.sfxNavy == None:
            DistributedDefenseCannon.sfxNavy = loadSfx(
                SoundGlobals.SFX_MINIGAME_CANNON_TRANSFORM_NAVY)

        if self.powderKegSfx == None:
            DistributedDefenseCannon.powderKegSfx = loadSfx(
                SoundGlobals.SFX_MINIGAME_CANNON_POWDERKEG_EXPLODE)

    def unloadSfx(self):
        if self.sfxRepeater:
            loader.unloadSfx(self.sfxRepeater)
            self.sfxRepeater = None

        if self.sfxNavy:
            loader.unloadSfx(self.sfxNavy)
            self.sfxNavy = None

        if self.powderKegSfx:
            loader.unloadSfx(self.powderKegSfx)
            self.powderKegSfx = None

    def addDestructableCollision(self):
        pass

    def cleanupProp(self):
        if self.prop:
            self.prop = None

        if self.navyCannon:
            self.navyCannon.delete()
            self.navyCannon = None

        if self.repeaterCannon:
            self.repeaterCannon.delete()
            self.repeaterCannon = None

    def setCannon(self, cannon):
        if self.prop:
            cannon.hNode.setHpr(self.prop.hNode.getHpr())
            cannon.pivot.setHpr(self.prop.pivot.getHpr())
            cannon.currentHpr = list(self.prop.currentHpr)

        if self.prop:
            self.prop.detachNode()

        self.prop = cannon
        self.prop.reparentTo(self)

    def currentCannonType(self, type):
        if type == 0:
            self.downgradeToNavy()
        elif type == 1:
            self.upgradeToRepeater()

    def requestUpgradeToRepeater(self):
        self.sendUpdate('requestUpgradeToRepeater')

    def upgradeToRepeater(self):
        if self.isRepeaterCannon == False:
            self.isRepeaterCannon = True
            self.doTransitionEffect(self.repeaterCannon, self.sfxRepeater)

    def downgradeToNavy(self):
        if self.isRepeaterCannon == True:
            self.isRepeaterCannon = False
            self.doTransitionEffect(self.navyCannon, self.sfxNavy)

    def doTransitionEffect(self, cannonModel, sfx):
        smokeEffect = RepeaterCannonUpgradeEffect.getEffect()
        if smokeEffect:
            smokeEffect.reparentTo(self)
            smokeEffect.setPos(self, 0, 0, 0)
            smokeEffect.spriteScale = 1.0
            smokeEffect.play()

        if sfx:
            base.playSfx(sfx, node=self.prop, cutoff=3000)

        taskMgr.doMethodLater(0.20000000000000001,
                              self._switchToCannon,
                              name=self.uniqueName('SwitchToCannon'),
                              extraArgs=[cannonModel])

    def _switchToCannon(self, cannonModel):
        self.setCannon(cannonModel)
        if self.av == localAvatar and localAvatar.cameraFSM.state == self._cameraState:
            self.prop.av = self.av
            localAvatar.cameraFSM.cannonDefenseCamera.changeModel(cannonModel)

    def setCannonGUI(self):
        self.cgui = DefenseCannonGUI.DefenseCannonGUI(self)
        self.cgui.exitEvent = self._DistributedDefenseCannon__exitEvent

    def updateCannonItems(self):
        base.localAvatar.guiMgr.combatTray.initCombatTray(
            InventoryType.DefenseCannonRep)
        base.localAvatar.guiMgr.combatTray.skillTray.updateSkillTray(
            InventoryType.DefenseCannonRep, WeaponGlobals.DEFENSE_CANNON)

    def enableCannonFireInput(self):
        self.accept('mouse1', self.fireCannon)
        self.accept('control', self.fireCannon)
        self.accept('wheel_up', self.changeAmmoMouseWheel, extraArgs=[1])
        self.accept('wheel_down', self.changeAmmoMouseWheel, extraArgs=[-1])

    def startWeapon(self, av):
        DistributedIslandCannon.startWeapon(self, av)
        if av:
            av.setPos(self.prop, 0, -6.0, 0)
            if av == localAvatar:
                self.repeaterCannon.setLocalAvatarUsingWeapon(1)
                self.navyCannon.setLocalAvatarUsingWeapon(1)
                localAvatar.sendCurrentPosition()
                localAvatar.cameraFSM.cannonDefenseCamera.setRotation(
                    CannonDefenseGlobals.CANNON_HPR[0],
                    CannonDefenseGlobals.CANNON_HPR[1])

        if self._DistributedDefenseCannon__readyEvent:
            self._DistributedDefenseCannon__readyEvent()

    def enterFireCannon(self):
        DistributedIslandCannon.enterFireCannon(self)
        self.ammoPanel = AmmoPanel(self)
        localAvatar.guiMgr.radarGui.hide()
        localAvatar.guiMgr.hideTrackedQuestInfo()
        localAvatar.guiMgr.gameGui.hide()
        localAvatar.guiMgr.crewHUD.setHUDOff()

    def exitFireCannon(self):
        if self.av == localAvatar:
            self._DistributedDefenseCannon__shouldUnloadSfx = True

        DistributedIslandCannon.exitFireCannon(self)
        self.ammoPanel.destroy()
        render.findAllMatches('**/=objType=%s' %
                              PiratesGlobals.COLL_DEFENSE_AMMO).detach()
        localAvatar.guiMgr.radarGui.show()
        localAvatar.guiMgr.showTrackedQuestInfo()
        localAvatar.guiMgr.gameGui.show()
        localAvatar.guiMgr.crewHUD.setHUDOn()

    def _doFireCannon(self):
        if localAvatar.isDazed:
            base.playSfx(self.emptySound)
            return None

        if not self.ammoPanel.hasCurrentAmmo():
            return None

        canFire = self.volley > 0
        DistributedIslandCannon._doFireCannon(self)
        if canFire:
            self.sendUpdate('recordFireEvent')
            self.lastCannonShot[
                self.getAmmoSkillId()] = globalClock.getFrameTime()

        self.ammoPanel.decreaseAmmoAmount(canFire)

    def useProjectileSkill(self, skillId, ammoSkillId, posHpr, timestamp,
                           charge):
        DistributedIslandCannon.useProjectileSkill(self, skillId, ammoSkillId,
                                                   posHpr, timestamp, charge)
        self.shotNum += 1

    def handleEndInteractKey(self):
        self.cgui.showExitDialog()

    def applyRechargeTimeModifier(self, rechargeTime):
        if not self.isRepeaterCannon:
            return rechargeTime

        newRecharge = rechargeTime + rechargeTime * CannonDefenseGlobals.REPEATER_RELOAD_MODIFIER
        return newRecharge

    def changeAmmoMouseWheel(self, amt=1):
        ammoSkillId = self.getAmmoSkillId()
        index = -1
        for i in range(len(PiratesGlobals.CANNON_DEFENSE_SKILLS)):
            if PiratesGlobals.CANNON_DEFENSE_SKILLS[i] == ammoSkillId:
                index = i
                break
                continue

        if index < 0:
            return None

        if amt > 0:
            recursionChange = 1
            if index + amt > len(PiratesGlobals.CANNON_DEFENSE_SKILLS) - 1:
                return None

            newAmmoId = PiratesGlobals.CANNON_DEFENSE_SKILLS[index + amt]
        else:
            recursionChange = -1
            if index + amt < 0:
                return None

            newAmmoId = PiratesGlobals.CANNON_DEFENSE_SKILLS[index + amt]
        if newAmmoId == InventoryType.DefenseCannonEmpty:
            self.changeAmmoMouseWheel(amt + recursionChange)
            return None

        self.changeAmmo(newAmmoId - ammoSkillId)

    def changeAmmo(self, amt=1):
        if amt == 0:
            return None

        keepChanging = True
        ammoSkillId = self.getAmmoSkillId()
        while keepChanging:
            ammoSkillId += amt
            if ammoSkillId > InventoryType.DefenseCannonEmpty:
                ammoSkillId = InventoryType.DefenseCannonRoundShot

            if ammoSkillId < InventoryType.begin_WeaponSkillCannonDefense + 1:
                ammoSkillId = InventoryType.DefenseCannonEmpty

            inv = base.localAvatar.getInventory()
            if ammoSkillId > InventoryType.CannonBullet:
                keepChanging = False

            if inv.getStackQuantity(ammoSkillId) >= 2:
                keepChanging = False

            if WeaponGlobals.isInfiniteAmmo(ammoSkillId):
                keepChanging = False
                continue
        self.setAmmoSkillId(ammoSkillId)
        del ammoSkillId
        if WeaponGlobals.isInfiniteAmmo(self.getAmmoSkillId()):
            self.cgui.setAmmoLeft(-1, -1)
        else:
            ammoInvId = WeaponGlobals.getSkillAmmoInventoryId(
                self.getAmmoSkillId())
            self.numShots = inv.getStackQuantity(ammoInvId)
            maxShots = inv.getStackLimit(ammoInvId)
            self.cgui.setAmmoLeft(self.numShots, maxShots)
        self.cgui.setAmmoId(self.getAmmoSkillId())
        self.cgui.showCannonControls()
        self.updateCannonDressing()
        self.hideCannonDressing()
        self.setVolley(0)

    def simpleShipHit(self, hitObject, entry, skillId, ammoSkillId):
        ship = hitObject.getNetPythonTag('ship')
        if not ship:
            return None

        fromNodePath = entry.getFromNodePath()
        shotNum = int(fromNodePath.getNetTag('shotNum'))
        if shotNum not in self.shipsHitByShotNum:
            self.shipsHitByShotNum[shotNum] = []
            self.removeShotNumTasks.append(
                taskMgr.doMethodLater(5.0,
                                      self.removeShotNumTask,
                                      self.uniqueName('removeShotNum%s' %
                                                      shotNum),
                                      extraArgs=[shotNum]))

        if ship.doId in self.shipsHitByShotNum[shotNum]:
            return None

        self.shipsHitByShotNum[shotNum].append(ship.doId)
        return DistributedIslandCannon.simpleShipHit(self, hitObject, entry,
                                                     skillId, ammoSkillId)

    def removeShotNumTask(self, shotNum):
        if shotNum in self.shipsHitByShotNum:
            del self.shipsHitByShotNum[shotNum]

        self.removeShotNumTasks.pop()

    def projectileHitObject(self, entry):
        DistributedIslandCannon.projectileHitObject(self, entry)
        if self.localAvatarUsingWeapon:
            fromNodePath = entry.getFromNodePath()
            ammo = fromNodePath.getNetPythonTag('ammo')
            if not ammo:
                return None

            ammo.setHitByAmmoEvent(self.uniqueName('proximityAmmoDestroyed'))
            hitObject = entry.getIntoNodePath()
            objType = hitObject.getNetTag('objType')
            if not objType:
                return None

            ammoSkillId = int(fromNodePath.getNetTag('ammoSkillId'))
            shotNum = int(fromNodePath.getNetTag('shotNum'))
            objType = int(objType)
            if objType == PiratesGlobals.COLL_DEFENSE_AMMO:
                hitammo = hitObject.getNetPythonTag('ammo')
                if not hitammo:
                    return None

                if ammo.hasTag('noAmmoCollide') or hitammo.hasTag(
                        'noAmmoCollide'):
                    return None

                ammo.dispatchHitByAmmoEvent()
                hitammo.dispatchHitByAmmoEvent()
            elif (ammoSkillId in [
                    InventoryType.DefenseCannonMine,
                    InventoryType.DefenseCannonPowderKeg,
                    InventoryType.DefenseCannonColdShot
            ] or objType == PiratesGlobals.COLL_SEA
                  or ammoSkillId == InventoryType.DefenseCannonSmokePowder
                  ) and objType == PiratesGlobals.COLL_NEWSHIP:
                if shotNum not in self.proximityAmmo:
                    pos = entry.getSurfacePoint(render)
                    self.sendAddProximityAmmo(shotNum, ammoSkillId, pos,
                                              localAvatar.doId)

            if ammoSkillId in [
                    InventoryType.DefenseCannonMine,
                    InventoryType.DefenseCannonPowderKeg
            ]:
                if objType == PiratesGlobals.COLL_NEWSHIP and shotNum in self.proximityAmmo:
                    ammo.dispatchHitByAmmoEvent(
                        ammoSkillId == InventoryType.DefenseCannonMine)

    def updateReload(self, task):
        self.rechargeTime = self.cr.battleMgr.getModifiedReloadTime(
            localAvatar, self.skillId, self.getAmmoSkillId())
        self.rechargeTime = self.applyRechargeTimeModifier(self.rechargeTime)
        if self.rechargeTime and self.getAmmoSkillId() in self.lastCannonShot:
            percentDone = min((globalClock.getFrameTime() -
                               self.lastCannonShot[self.getAmmoSkillId()]) /
                              self.rechargeTime, 1.0)
        else:
            percentDone = 1
        if percentDone == 1:
            self.finishReload()

        self.cgui.updateReload(percentDone, self.volley)
        return Task.cont

    def sendAddProximityAmmo(self, shotNum, ammoSkillId, pos, attackerId):
        if shotNum in self.usedShotNums:
            return None

        self.usedShotNums[shotNum] = True
        self.sendUpdate('addProximityAmmo',
                        [shotNum, ammoSkillId, pos, attackerId])

    def sendRemoveProximityAmmo(self, shotNum, playEffect=True):
        if shotNum not in self.usedShotNums:
            self.usedShotNums[shotNum] = True
            return None

        self.sendUpdate('removeProximityAmmo', [shotNum, playEffect])

    def setRemovedProximityAmmo(self, shotNum, playHitEffect):
        if shotNum in self.proximityAmmo:
            ammo = self.proximityAmmo[shotNum]
            if ammo and not (ammo.destroyed):
                if playHitEffect:
                    if ammo.ammoSkillId == InventoryType.DefenseCannonPowderKeg:
                        base.playSfx(self.powderKegSfx, node=ammo, cutoff=2000)

                    ammo.playProximityAmmoEffect()
                else:
                    ammo.sinkFloatingAmmo()

            del self.proximityAmmo[shotNum]

    def d_requestProximityAmmo(self, avId):
        self.sendUpdate('requestProximityAmmo', [avId])

    def d_requestShotNum(self, avId):
        self.sendUpdate('requestShotNum', [avId])

    def setProximityAmmo(self, ammo):
        if not self.prop:
            return None

        for ammoInfo in ammo:
            shotNum = ammoInfo[0]
            ammoSkillId = ammoInfo[1]
            pos = ammoInfo[2]
            attackerId = ammoInfo[3]
            timeRemaining = ammoInfo[4]
            cannonball = self.prop.addProximityAmmoFromAI(
                shotNum, pos, ammoSkillId, attackerId, timeRemaining,
                self.projectileHitEvent)
            cannonball.setHitByAmmoEvent(
                self.uniqueName('proximityAmmoDestroyed'))
            self.proximityAmmo[shotNum] = cannonball
            self.usedShotNums[shotNum] = True

    def setShotNum(self, shotNum):
        self.shotNum = shotNum
        self.shipsHitByShotNum[self.shotNum] = []

    def enableInput(self):
        self.enableCannonFireInput()
        localAvatar.cameraFSM.cannonDefenseCamera.enableInput()

    def disableInput(self):
        self.disableCannonFireInput()
        localAvatar.cameraFSM.cannonDefenseCamera.disableInput()

    def setExitEvent(self, eventCallBack):
        self._DistributedDefenseCannon__exitEvent = eventCallBack

    def setReadyEvent(self, eventCallBack):
        self._DistributedDefenseCannon__readyEvent = eventCallBack

    def simpleShipHit(self, hitObject, entry, skillId, ammoSkillId):
        ship = hitObject.getNetPythonTag('ship')
        if not ship:
            return None

        fromNodePath = entry.getFromNodePath()
        shotNum = int(fromNodePath.getNetTag('shotNum'))
        if shotNum not in self.shipsHitByShotNum:
            self.shipsHitByShotNum[shotNum] = []
            self.removeShotNumTasks.append(
                taskMgr.doMethodLater(5.0,
                                      self.removeShotNumTask,
                                      self.uniqueName('removeShotNum%s' %
                                                      shotNum),
                                      extraArgs=[shotNum]))

        if ship.doId in self.shipsHitByShotNum[shotNum]:
            return None

        self.shipsHitByShotNum[shotNum].append(ship.doId)
        timestamp32 = globalClockDelta.getFrameNetworkTime(bits=32)
        if not ship:
            self.notify.warning('ignoring attack on destroyed ship')
            return None

        hitSail = hitObject.getNetTag('Sail')
        if not hitSail:
            hitSail = 0
        else:
            hitSail = 1
        pos = entry.getSurfacePoint(ship)
        normal = entry.getSurfaceNormal(render)
        attackerId = 0
        attackerStr = entry.getFromNodePath().getNetTag('attackerId')
        if attackerStr:
            attackerId = int(attackerStr)

        attacker = self.cr.doId2do.get(attackerId)
        if not attacker:
            return None

        areaList = self.getAreaList(skillId, ammoSkillId, ship, pos,
                                    attackerId)
        result = self.cr.battleMgr.doAttack(attacker, skillId, ammoSkillId,
                                            ship.doId, areaList, pos)
        ammo = entry.getFromNodePath().getNetPythonTag('ammo')
        if ammo:
            if ammo.hasTag('newAmmoId'):
                ammoSkillId = int(ammo.getTag('newAmmoId'))
                pos = (0, 0, 0)

        self.sendSuggestProjectileSkillResult(
            skillId, ammoSkillId, result, ship.doId, areaList,
            [pos[0], pos[1], pos[2]], [normal[0], normal[1], normal[2]],
            [0, 0, hitSail], timestamp32)
        (attackerEffects, targetEffects,
         itemEffects) = self.cr.battleMgr.getModifiedSkillEffects(
             attacker, ship, skillId, ammoSkillId)
        ship.projectileWeaponHit(skillId, ammoSkillId, result, targetEffects,
                                 pos, normal, (0, 0, hitSail), attacker,
                                 itemEffects)
        return True

    def getAreaList(self,
                    skillId,
                    ammoSkillId,
                    target,
                    pos,
                    attackerId,
                    isBoss=False):
        targets = set()
        areaShape = WeaponGlobals.getAttackAreaShape(skillId, ammoSkillId)
        bitmask = PiratesGlobals.GenericShipBitmask
        if areaShape == WeaponGlobals.AREA_SPHERE:
            self.runSphereAreaCollisions(skillId, ammoSkillId, target, pos,
                                         bitmask)
        elif areaShape == WeaponGlobals.AREA_TUBE:
            self.runTubeAreaCollisions(skillId, ammoSkillId, target, pos,
                                       bitmask)
        elif areaShape == WeaponGlobals.AREA_CONE:
            self.runConeAreaCollisions(skillId, ammoSkillId, target, pos,
                                       bitmask)
        elif areaShape == WeaponGlobals.AREA_OFF:
            return []

        numEntries = self.areaCollQueue.getNumEntries()
        if numEntries == 0:
            return []

        for i in range(numEntries):
            areaTarget = self.areaCollQueue.getEntry(
                i).getIntoNodePath().getNetPythonTag('ship')
            if areaTarget.gameFSM.state in ('Sinking', 'Sunk'):
                continue

            targets.add(areaTarget.doId)

        if target in targets:
            targets.remove(target)

        return list(targets)

    def _DistributedDefenseCannon__grounHit(self, groundId, hitObject, entry,
                                            skillId, ammoSkillId):
        return False