123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- from core.db import Spells
- class SpellStatus:
- none = 0
- casting = 1
- fire = 2
- cooling = 3
- done = 4
- cancelled = 5
- class CasterCheckStatus:
- clear = 0 ## Ready to cast
- busy = 1 ## Already casting
- lowEnergy = 2 ## Not enough energy
- dead = 3 ## Caster is dead
- cooling = 4 ## Spell is cooling
- class TargetCheckStatus:
- clear = 0
- notFacing = 1
- notInRange = 2
- dead = 3
- maxHealth = 4
- selfAttack = 5
- casterDead = 6
- class BasicCast:
- def __init__(self, caster, spell, targetSpawnId):
- self._caster = caster
- self._spell = spell
- self._targetSpawnId = targetSpawnId
- self._targets = []
- self._targetCheckTask = None
- self._casterCheckTask = None
- self._castingTask = None
- self.startCast()
- @property
- def caster(self): return self._caster
- @property
- def spell(self): return self._spell
- @property
- def targetSpawnId(self): return self._targetSpawnId
- @property
- def target(self):
- if self._targets: return self._targets[0]
- @property
- def targets(self): return self._targets
- @targets.setter
- def targets(self, targets):
- """
- @type target: core.character.Character
- """
- self._targets = targets
- def casterCheck(self):
- """ Checks the following:
- - If the caster isn't already casting.
- - If the caster has enough energy.
- - If the caster is dead.
- - If the spell isn't in cooldown.
- @rtype: int
- @return: See CasterCheckStatus
- """
- if self.caster.characterData.spells.isCasting:
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("Already busy!", 2)
- return CasterCheckStatus.busy
- if self.spell.isCooling():
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("Spell is cooling..", 2)
- return CasterCheckStatus.cooling
- if self.caster.characterData.stats.energy.value < self.spell.data.energyCost:
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("Not enough energy", 2)
- return CasterCheckStatus.lowEnergy
- if self.caster.isDead(): return CasterCheckStatus.dead
- return CasterCheckStatus.clear
- def targetCheck(self, target):
- """
- - target is not dead
- - caster is not dead
- - caster is facing target
- - caster is in range of target
- - if impactPoints are positive
- - - check if target doesn't have max health
- - if impactPoints are negative
- - - check if target is not caster (cannot attack self)
- """
- if target.isDead(): return TargetCheckStatus.dead
- if self.caster.isDead(): return TargetCheckStatus.casterDead
- if self.spell.data.impactPoints < 0: # Attacking
- if (target == self.caster):
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("Cannot attack self.", 2)
- return TargetCheckStatus.selfAttack
- elif self.spell.data.impactPoints > 0: # healing
- if (target.characterData.stats.health.value ==
- target.characterData.stats.health.max
- ):
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("Max health reached!", 2)
- return TargetCheckStatus.maxHealth
- if (self.spell.data.facingAngle and not
- self.caster.canSee(
- target,
- self.spell.data.facingAngle
- )
- ):
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("You are not facing the NPC.", 2)
- return TargetCheckStatus.notFacing
- if self.spell.data.rangeEnd and not self.inRange(target):
- if self.caster == base.world.playerController.character:
- base.uiManager.actionMessenger.print("Not in range.", 2)
- return TargetCheckStatus.notInRange
- return TargetCheckStatus.clear
- def targetIntervalCheck(self, task):
- if self.targetCheck(self.target) != TargetCheckStatus.clear:
- self.cancelCast()
- return task.done
- return task.again
- def inRange(self, target):
- diffVec = self.caster.getGlobalPos() - target.getGlobalPos()
- if (diffVec.getXy().length() >= self.spell.data.rangeStart and
- diffVec.getXy().length() <= self.spell.data.rangeEnd):
- return True
- return False
- def stopAllTasks(self):
- if self._targetCheckTask:
- taskMgr.remove(self._targetCheckTask)
- self._targetCheckTask = None
- if self._casterCheckTask:
- taskMgr.remove(self._casterCheckTask)
- self._casterCheckTask = None
- if self._castingTask:
- taskMgr.remove(self._castingTask)
- self._castingTask = None
- def cancelCast(self, spawnId=None):
- self.caster.hasDied.disconnect(self.cancelCast)
- if self.target and self.spell.data.targetType == Spells.targetTypes.selected:
- self.target.hasDied.disconnect(self.cancelCast)
- self.spell.setStatus(SpellStatus.cancelled)
- self.stopAllTasks()
- self.caster.characterData.spells.isCasting = False
- self.caster.actorNP.loop("idle")
- self.finalizeCast()
- def getTarget(self):
- target = base.world.player
- if self.targetSpawnId > 0: # None or player selected
- target = base.world.npcsManager.getSpawn(self.targetSpawnId)
- return target
- def getTargets(self):
- """
- Get multiple targets in range of the spell, excluding the caster.
- """
- targets = []
- if not self.caster.isDead():
- for character in base.world.npcsManager.spawns + [base.world.player]:
- if (character != self.caster and
- self.caster.characterData.fraction in character.characterData.enemies and
- not character.isDead() and
- self.inRange(character)
- ):
- targets.append(character)
- return targets
- def startCast(self):
- if self.casterCheck() != CasterCheckStatus.clear: return
- if self.spell.data.targetType in [Spells.targetTypes.selected, Spells.targetTypes.self]:
- if self.spell.data.targetType == Spells.targetTypes.selected:
- self.targets = [self.getTarget()]
- else:
- self.targets = [self.caster]
- if self.targetCheck(self.target) != TargetCheckStatus.clear:
- return
- if self.spell.data.targetType == Spells.targetTypes.selected:
- self.target.hasDied.connect(self.cancelCast)
- self._targetCheckTask = taskMgr.doMethodLater(
- .1,
- self.targetIntervalCheck,
- '{}_castTargetCheck'.format(self.caster.characterData.spawnData.id),
- )
- self.caster.hasDied.connect(self.cancelCast)
- self.doCastingTime()
- def doCastingTime(self):
- self.caster.characterData.spells.isCasting = True # TODO spells should know this by watching it's spell's
- if self.spell.data.castTime:
- self._castingTask = taskMgr.doMethodLater(
- self.spell.data.castTime,
- self.applySpell,
- '{}_cast'.format(self.caster.characterData.spawnData.id),
- extraArgs=[],
- )
- self.spell.setStatus(SpellStatus.casting)
- ## Start casting animation.
- self.caster.actorNP.loop("casting")
- else: self.applySpell()
- def applySpell(self):
- """ Apply spell (after casting-time)
- """
- self.caster.hasDied.disconnect(self.cancelCast)
- self.stopAllTasks()
- ## Reset animation.
- self.caster.actorNP.loop("idle")
- if self.caster.isDead():
- self.cancelCast()
- return
- ## Subtract used energy from caster.
- if self.spell.data.energyCost:
- self.caster.characterData.stats.energy.value -= self.spell.data.energyCost
- if self.spell.data.targetType == Spells.targetTypes.range:
- # Get all targets in range
- self.targets = self.getTargets()
- elif self.target and self.spell.data.targetType == Spells.targetTypes.selected:
- self.target.hasDied.disconnect(self.cancelCast)
- if self.spell.data.impactPoints:
- for target in self.targets:
- target.characterData.stats.health.value += self.spell.data.impactPoints
- if self.spell.data.impactPoints < 0 and not target.isDead():
- target.underAttackBy(int(self.caster.characterData.spawnData.id))
- #else: # healing
- self.spell.setStatus(SpellStatus.fire)
- self.caster.characterData.spells.isCasting = False # TODO spells should know this by watching it's spell's
- self.targets.clear()
- self.doCooldown()
- def doCooldown(self):
- """ Wait for cooldown time before calling self.finalizeCast()
- """
- if self.spell.data.coolDown:
- self.spell.setStatus(SpellStatus.cooling)
- taskMgr.doMethodLater(
- self.spell.data.coolDown,
- self.finalizeCast,
- '{}_finalizeCast'.format(self.caster.characterData.spawnData.id),
- extraArgs=[]
- )
- else: self.finalizeCast()
- def finalizeCast(self):
- """ Finalize the cast.
- """
- self.spell.setStatus(SpellStatus.done)
- ## Note: make sure made connections are disconnected, else
- ## they leave a reference and this instance won't get destructed.
- # ..
|