CrashOverrideX
Sealing v8.1 Subjectively Aware Standard for Hugging Face. Clean Model & Knowledge release.
a3e5f70
from settings import *
from sprites import MonsterSprite, MonsterNameSprite, MonsterLevelSprite, MonsterStatsSprite, MonsterOutlineSprite, AttackSprite, TimedSprite
from groups import BattleSprites
from game_data import ATTACK_DATA
from support import draw_bar
from timer import Timer
from random import choice
class Battle:
# main
def __init__(self, player_monsters, opponent_monsters, monster_frames, bg_surf, fonts, end_battle, character, sounds):
# general
self.display_surface = pygame.display.get_surface()
self.bg_surf = bg_surf
self.monster_frames = monster_frames
self.fonts = fonts
self.monster_data = {'player': player_monsters, 'opponent': opponent_monsters}
self.battle_over = False
self.end_battle = end_battle
self.character = character
self.sounds = sounds
# timers
self.timers = {
'opponent delay': Timer(600, func = self.opponent_attack)
}
# groups
self.battle_sprites = BattleSprites()
self.player_sprites = pygame.sprite.Group()
self.opponent_sprites = pygame.sprite.Group()
# control
self.current_monster = None
self.selection_mode = None
self.selected_attack = None
self.selection_side = 'player'
self.indexes = {
'general': 0,
'monster': 0,
'attacks': 0,
'switch' : 0,
'target' : 0,
}
self.setup()
def setup(self):
for entity, monster in self.monster_data.items():
for index, monster in {k:v for k,v in monster.items() if k <= 2}.items():
self.create_monster(monster, index, index, entity)
# remove opponent monster data
for i in range(len(self.opponent_sprites)):
del self.monster_data['opponent'][i]
def create_monster(self, monster, index, pos_index, entity):
monster.paused = False
frames = self.monster_frames['monsters'][monster.name]
outline_frames = self.monster_frames['outlines'][monster.name]
if entity == 'player':
pos = list(BATTLE_POSITIONS['left'].values())[pos_index]
groups = (self.battle_sprites, self.player_sprites)
frames = {state: [pygame.transform.flip(frame, True, False) for frame in frames] for state, frames in frames.items()}
outline_frames = {state: [pygame.transform.flip(frame, True, False) for frame in frames] for state, frames in outline_frames.items()}
else:
pos = list(BATTLE_POSITIONS['right'].values())[pos_index]
groups = (self.battle_sprites, self.opponent_sprites)
monster_sprite = MonsterSprite(pos, frames, groups, monster, index, pos_index, entity, self.apply_attack, self.create_monster)
MonsterOutlineSprite(monster_sprite, self.battle_sprites, outline_frames)
# ui
name_pos = monster_sprite.rect.midleft + vector(16,-70) if entity == 'player' else monster_sprite.rect.midright + vector(-40,-70)
name_sprite = MonsterNameSprite(name_pos, monster_sprite, self.battle_sprites, self.fonts['regular'])
level_pos = name_sprite.rect.bottomleft if entity == 'player' else name_sprite.rect.bottomright
MonsterLevelSprite(entity, level_pos, monster_sprite, self.battle_sprites, self.fonts['small'])
MonsterStatsSprite(monster_sprite.rect.midbottom + vector(0,20), monster_sprite, (150,48), self.battle_sprites, self.fonts['small'])
def input(self):
if self.selection_mode and self.current_monster:
keys = pygame.key.get_just_pressed()
match self.selection_mode:
case 'general': limiter = len(BATTLE_CHOICES['full'])
case 'attacks': limiter = len(self.current_monster.monster.get_abilities(all = False))
case 'switch': limiter = len(self.available_monsters)
case 'target': limiter = len(self.opponent_sprites) if self.selection_side == 'opponent' else len(self.player_sprites)
if keys[pygame.K_DOWN]:
self.indexes[self.selection_mode] = (self.indexes[self.selection_mode] + 1) % limiter
if keys[pygame.K_UP]:
self.indexes[self.selection_mode] = (self.indexes[self.selection_mode] - 1) % limiter
if keys[pygame.K_SPACE]:
if self.selection_mode == 'switch':
index, new_monster = list(self.available_monsters.items())[self.indexes['switch']]
self.current_monster.kill()
self.create_monster(new_monster, index, self.current_monster.pos_index, 'player')
self.selection_mode = None
self.update_all_monsters('resume')
if self.selection_mode == 'target':
sprite_group = self.opponent_sprites if self.selection_side == 'opponent' else self.player_sprites
sprites = {sprite.pos_index: sprite for sprite in sprite_group}
monster_sprite = sprites[list(sprites.keys())[self.indexes['target']]]
if self.selected_attack:
self.current_monster.activate_attack(monster_sprite, self.selected_attack)
self.selected_attack, self.current_monster, self.selection_mode = None, None, None
else:
if monster_sprite.monster.health < monster_sprite.monster.get_stat('max_health') * 0.9:
self.monster_data['player'][len(self.monster_data['player'])] = monster_sprite.monster
monster_sprite.delayed_kill(None)
self.update_all_monsters('resume')
else:
TimedSprite(monster_sprite.rect.center, self.monster_frames['ui']['cross'], self.battle_sprites, 1000)
if self.selection_mode == 'attacks':
self.selection_mode = 'target'
self.selected_attack = self.current_monster.monster.get_abilities(all = False)[self.indexes['attacks']]
self.selection_side = ATTACK_DATA[self.selected_attack]['target']
if self.selection_mode == 'general':
if self.indexes['general'] == 0:
self.selection_mode = 'attacks'
if self.indexes['general'] == 1:
self.current_monster.monster.defending = True
self.update_all_monsters('resume')
self.current_monster, self.selection_mode = None, None
self.indexes['general'] = 0
if self.indexes['general'] == 2:
self.selection_mode = 'switch'
if self.indexes['general'] == 3:
self.selection_mode = 'target'
self.selection_side = 'opponent'
self.indexes = {k: 0 for k in self.indexes}
if keys[pygame.K_ESCAPE]:
if self.selection_mode in ('attacks', 'switch', 'target'):
self.selection_mode = 'general'
def update_timers(self):
for timer in self.timers.values():
timer.update()
# battle system
def check_active(self):
for monster_sprite in self.player_sprites.sprites() + self.opponent_sprites.sprites():
if monster_sprite.monster.initiative >= 100:
monster_sprite.monster.defending = False
self.update_all_monsters('pause')
monster_sprite.monster.initiative = 0
monster_sprite.set_highlight(True)
self.current_monster = monster_sprite
if self.player_sprites in monster_sprite.groups():
self.selection_mode = 'general'
else:
self.timers['opponent delay'].activate()
def update_all_monsters(self, option):
for monster_sprite in self.player_sprites.sprites() + self.opponent_sprites.sprites():
monster_sprite.monster.paused = True if option == 'pause' else False
def apply_attack(self, target_sprite, attack, amount):
AttackSprite(target_sprite.rect.center, self.monster_frames['attacks'][ATTACK_DATA[attack]['animation']], self.battle_sprites)
self.sounds[ATTACK_DATA[attack]['animation']].play()
# get correct attack damage amount (defense, element)
attack_element = ATTACK_DATA[attack]['element']
target_element = target_sprite.monster.element
# double attack
if attack_element == 'fire' and target_element == 'plant' or \
attack_element == 'water' and target_element == 'fire' or \
attack_element == 'plant' and target_element == 'water':
amount *= 2
# halve attack
if attack_element == 'fire' and target_element == 'water' or \
attack_element == 'water' and target_element == 'plant' or \
attack_element == 'plant' and target_element == 'fire':
amount *= 0.5
target_defense = 1 - target_sprite.monster.get_stat('defense') / 2000
if target_sprite.monster.defending:
target_defense -= 0.2
target_defense = max(0, min(1, target_defense))
# update the monster health
target_sprite.monster.health -= amount * target_defense
self.check_death()
# resume
self.update_all_monsters('resume')
def check_death(self):
for monster_sprite in self.opponent_sprites.sprites() + self.player_sprites.sprites():
if monster_sprite.monster.health <= 0:
if self.player_sprites in monster_sprite.groups(): # player
active_monsters = [(monster_sprite.index, monster_sprite.monster) for monster_sprite in self.player_sprites.sprites()]
available_monsters = [(index, monster) for index, monster in self.monster_data['player'].items() if monster.health > 0 and (index, monster) not in active_monsters]
if available_monsters:
new_monster_data = [(monster, index, monster_sprite.pos_index, 'player') for index, monster in available_monsters][0]
else:
new_monster_data = None
else:
new_monster_data = (list(self.monster_data['opponent'].values())[0], monster_sprite.index, monster_sprite.pos_index, 'opponent') if self.monster_data['opponent'] else None
if self.monster_data['opponent']:
del self.monster_data['opponent'][min(self.monster_data['opponent'])]
# xp
xp_amount = monster_sprite.monster.level * 100 / len(self.player_sprites)
for player_sprite in self.player_sprites:
player_sprite.monster.update_xp(xp_amount)
monster_sprite.delayed_kill(new_monster_data)
def opponent_attack(self):
ability = choice(self.current_monster.monster.get_abilities())
random_target = choice(self.opponent_sprites.sprites()) if ATTACK_DATA[ability]['target'] == 'player' else choice(self.player_sprites.sprites())
self.current_monster.activate_attack(random_target, ability)
def check_end_battle(self):
# opponents have been defeated
if len(self.opponent_sprites) == 0 and not self.battle_over:
self.battle_over = True
self.end_battle(self.character)
for monster in self. monster_data['player'].values():
monster.initiative = 0
# player has been defeated
if len(self.player_sprites) == 0:
pygame.quit()
exit()
# ui
def draw_ui(self):
if self.current_monster:
if self.selection_mode == 'general':
self.draw_general()
if self.selection_mode == 'attacks':
self.draw_attacks()
if self.selection_mode == 'switch':
self.draw_switch()
def draw_general(self):
for index, (option, data_dict) in enumerate(BATTLE_CHOICES['full'].items()):
if index == self.indexes['general']:
surf = self.monster_frames['ui'][f"{data_dict['icon']}_highlight"]
else:
surf = pygame.transform.grayscale(self.monster_frames['ui'][data_dict['icon']])
rect = surf.get_frect(center = self.current_monster.rect.midright + data_dict['pos'])
self.display_surface.blit(surf, rect)
def draw_attacks(self):
# data
abilities = self.current_monster.monster.get_abilities(all = False)
width, height = 150, 200
visible_attacks = 4
item_height = height / visible_attacks
v_offset = 0 if self.indexes['attacks'] < visible_attacks else -(self.indexes['attacks'] - visible_attacks + 1) * item_height
# bg
bg_rect = pygame.FRect((0,0), (width,height)).move_to(midleft = self.current_monster.rect.midright + vector(20,0))
pygame.draw.rect(self.display_surface, COLORS['white'], bg_rect, 0, 5)
for index, ability in enumerate(abilities):
selected = index == self.indexes['attacks']
# text
if selected:
element = ATTACK_DATA[ability]['element']
text_color = COLORS[element] if element!= 'normal' else COLORS['black']
else:
text_color = COLORS['light']
text_surf = self.fonts['regular'].render(ability, False, text_color)
# rect
text_rect = text_surf.get_frect(center = bg_rect.midtop + vector(0, item_height / 2 + index * item_height + v_offset))
text_bg_rect = pygame.FRect((0,0), (width, item_height)).move_to(center = text_rect.center)
# draw
if bg_rect.collidepoint(text_rect.center):
if selected:
if text_bg_rect.collidepoint(bg_rect.topleft):
pygame.draw.rect(self.display_surface, COLORS['dark white'], text_bg_rect,0,0,5,5)
elif text_bg_rect.collidepoint(bg_rect.midbottom + vector(0,-1)):
pygame.draw.rect(self.display_surface, COLORS['dark white'], text_bg_rect,0,0,0,0,5,5)
else:
pygame.draw.rect(self.display_surface, COLORS['dark white'], text_bg_rect)
self.display_surface.blit(text_surf, text_rect)
def draw_switch(self):
# data
width, height = 300, 320
visible_monsters = 4
item_height = height / visible_monsters
v_offset = 0 if self.indexes['switch'] < visible_monsters else -(self.indexes['switch'] - visible_monsters + 1) * item_height
bg_rect = pygame.FRect((0,0), (width, height)).move_to(midleft = self.current_monster.rect.midright + vector(20,0))
pygame.draw.rect(self.display_surface, COLORS['white'], bg_rect, 0, 5)
# monsters
active_monsters = [(monster_sprite.index, monster_sprite.monster) for monster_sprite in self.player_sprites]
self.available_monsters = {index: monster for index, monster in self.monster_data['player'].items() if (index, monster) not in active_monsters and monster.health > 0}
for index, monster in enumerate(self.available_monsters.values()):
selected = index == self.indexes['switch']
item_bg_rect = pygame.FRect((0,0), (width, item_height)).move_to(midleft = (bg_rect.left, bg_rect.top + item_height / 2 + index * item_height + v_offset))
icon_surf = self.monster_frames['icons'][monster.name]
icon_rect = icon_surf.get_frect(midleft = bg_rect.topleft + vector(10,item_height / 2 + index * item_height + v_offset))
text_surf = self.fonts['regular'].render(f'{monster.name} ({monster.level})', False, COLORS['red'] if selected else COLORS['black'])
text_rect = text_surf.get_frect(topleft = (bg_rect.left + 90, icon_rect.top))
# selection bg
if selected:
if item_bg_rect.collidepoint(bg_rect.topleft):
pygame.draw.rect(self.display_surface, COLORS['dark white'], item_bg_rect, 0, 0, 5, 5)
elif item_bg_rect.collidepoint(bg_rect.midbottom + vector(0,-1)):
pygame.draw.rect(self.display_surface, COLORS['dark white'], item_bg_rect, 0, 0, 0, 0, 5, 5)
else:
pygame.draw.rect(self.display_surface, COLORS['dark white'], item_bg_rect)
if bg_rect.collidepoint(item_bg_rect.center):
for surf, rect in ((icon_surf, icon_rect), (text_surf, text_rect)):
self.display_surface.blit(surf, rect)
health_rect = pygame.FRect((text_rect.bottomleft + vector(0,4)), (100,4))
energy_rect = pygame.FRect((health_rect.bottomleft + vector(0,2)), (80,4))
draw_bar(self.display_surface, health_rect, monster.health, monster.get_stat('max_health'), COLORS['red'], COLORS['black'])
draw_bar(self.display_surface, energy_rect, monster.energy, monster.get_stat('max_energy'), COLORS['blue'], COLORS['black'])
def update(self, dt):
self.check_end_battle()
# updates
self.input()
self.update_timers()
self.battle_sprites.update(dt)
self.check_active()
# drawing
self.display_surface.blit(self.bg_surf, (0,0))
self.battle_sprites.draw(self.current_monster, self.selection_side, self.selection_mode, self.indexes['target'], self.player_sprites, self.opponent_sprites)
self.draw_ui()