import math
import random
from VegansDeluxe.core.Actions.Action import Action, FreeAction, DecisiveAction
from VegansDeluxe.core.Actions.ActionTags import ActionTag
from VegansDeluxe.core.Entities import Entity
from VegansDeluxe.core.Events.DamageEvents import PostAttackGameEvent, AttackGameEvent
from VegansDeluxe.core.Session import Session
from VegansDeluxe.core.TargetType import Enemies, Distance
from VegansDeluxe.core.Translator.LocalizedString import ls
from VegansDeluxe.core.Weapons import Weapon
[docs]class DamageData:
def __init__(self, calculated, displayed, dealt):
self.calculated = calculated
self.displayed = displayed
self.dealt = dealt
def __int__(self):
return self.dealt
[docs]class WeaponAction[T: Weapon](Action):
def __init__(self, session: Session, source: Entity, weapon: T):
super().__init__(session, source)
self.weapon: T = weapon
[docs]class FreeWeaponAction(WeaponAction, FreeAction):
pass
[docs]class DecisiveWeaponAction(WeaponAction, DecisiveAction):
pass
[docs]class Attack(DecisiveWeaponAction):
id = 'attack'
name = ls("core.base_attack.name")
target_type = Enemies()
priority = 0
def __init__(self, *args):
super().__init__(*args)
self.ATTACK_TEXT = ls("core.base_attack.text_ranged") if self.weapon.ranged else ls("core.base_attack.text_melee")
self.ATTACK_EMOJI = ls("core.base_attack.emoji_ranged") if self.weapon.ranged else ls("core.base_attack.emoji_melee")
self.ATTACK_MESSAGE = ls("core.base_attack.hit")
self.MISS_MESSAGE = ls("core.base_attack.miss")
self.SELF_TARGET_NAME = ls("core.self_target_name")
self.tags += [ActionTag.ATTACK, ActionTag.HARMFUL]
[docs] async def func(self, source, target):
return await self.attack(source, target)
[docs] def calculate_damage(self, source: Entity, target: Entity) -> int:
"""
Calculate the damage based on weapon's damage bonus and accuracy
"""
if source.energy <= 0:
return 0
total_accuracy = (source.energy + self.weapon.accuracy_bonus + target.inbound_accuracy_bonus
+ source.outbound_accuracy_bonus)
damage = sum(1 for _ in range(self.weapon.cubes) if random.randint(1, 10) <= total_accuracy)
if total_accuracy > 10:
damage = int(math.floor(damage * total_accuracy / 10))
return damage + self.weapon.damage_bonus if damage else 0
[docs] async def attack(self, source, target, pay_energy=True) -> DamageData:
"""
Actually performs attack on target, dealing damage.
"""
calculated_damage = self.calculate_damage(source, target)
if pay_energy:
source.energy = max(source.energy - self.weapon.energy_cost, 0)
displayed_damage = await self.publish_attack_event(source, target, calculated_damage)
self.send_attack_message(source, target, displayed_damage)
dealt_damage = await self.publish_post_attack_event(source, target, displayed_damage)
target.inbound_dmg.add(source, dealt_damage, self.session.turn)
source.outbound_dmg.add(target, dealt_damage, self.session.turn)
return DamageData(calculated_damage, displayed_damage, dealt_damage)
[docs] async def publish_attack_event(self, source, target, damage):
message = AttackGameEvent(self.session.id, self.session.turn, source, target, damage)
await self.event_manager.publish(message) # 7.1 Pre-Attack stage
return message.damage
[docs] async def publish_post_attack_event(self, source, target, damage):
message = PostAttackGameEvent(self.session.id, self.session.turn, source, target, damage)
await self.event_manager.publish(message) # 7.2 Post-Attack stage
return message.damage
[docs] def send_attack_message(self, source, target, damage):
target_name = self.SELF_TARGET_NAME if source == target else target.name
if damage:
message = self.ATTACK_MESSAGE.format(attack_emoji=self.ATTACK_EMOJI, source_name=source.name,
attack_text=self.ATTACK_TEXT,
target_name=target_name, weapon_name=self.weapon.name, damage=damage)
else:
message = self.MISS_MESSAGE.format(source_name=source.name, attack_text=self.ATTACK_TEXT,
target_name=target_name,
weapon_name=self.weapon.name)
self.session.say(message)
[docs]class MeleeAttack(Attack):
target_type = Enemies(distance=Distance.NEARBY_ONLY)
[docs]class RangedAttack(MeleeAttack):
target_type = Enemies(distance=Distance.ANY)