class Corpse(Projectile): STATIONARY = CharacterParameters(0., 0., 0., 0., 0.) STANDARD = CharacterParameters(0., mstpvv('04200'), mstpva('00280'), mstpvv('04000'), mstpvv('04000')) """Stays on-screen for a while, then disappears.""" def __init__(self, level, animation, parameters, duration, ignore_ground=False): super().__init__(animation, level, parameters, (0, 0), animation.rect.size, 0, 0) self.level = level self.duration = duration self.animation = animation # kind of kludgy, but we need to edit GravityMovement's colliders if the corpse # is to ignore the ground if ignore_ground: self.movement.vertical_movement.airborne_collider.mask = 0 # will always be "airborne" def update(self, dt, view_rect): super().update(dt, view_rect) self.duration -= dt if self.duration < 0.: self.destroy() @property def layer(self): return constants.Active def destroy(self): self.level.entity_manager.unregister(self) def on_movement_collision(self, collision): pass def on_hit(self, collision): pass @staticmethod def create_corpse_animation(animation): # creates a simple corpse by flipping current animation frame current_frame = animation.image corpse = pygame.transform.flip(current_frame, False, True) return StaticAnimation(corpse) @staticmethod def create_ghost_corpse_from_entity(entity, entity_animation, level, duration, parameters=STATIONARY, initial_y=0.): # creates a special type of corpse which is inverted and falls off the screen, ignoring blocks corpse_animation = Corpse.create_corpse_animation(entity_animation) corpse = Corpse(level, corpse_animation, parameters, duration, ignore_ground=True) corpse.position = get_aligned_foot_position(entity.rect, corpse.rect) corpse.movement.velocity = make_vector(0., initial_y or -parameters.jump_velocity) return corpse
def __init__(self, level): super().__init__(level) ca = level.asset_manager.character_atlas self.left_animation = ca.load_animation("koopa_red_winged_left") self.right_animation = ca.load_animation("koopa_red_winged_right") from .behaviors.koopa_floating import KoopaFloating self.movement.destroy() self.movement = KoopaFloating(level, self, WingedKoopaTroopaRed.FLY_HEIGHT, WingedKoopaTroopaRed.FLY_FREQUENCY, make_vector(0, 0), mstpvv('08000'), self.on_squashed, self._on_mario_invincible) self.squashable.on_squashed = self.on_squashed
class BowserFireball(Projectile): """Bowser's tracking fireball""" TRACK_VELOCITY = mstpvv( '01200') # how quickly the fireball can alter position to track mario TARGET_ACQUIRE_TIME = 0.1 # have to be within lock-on distance for this amount of time before stop tracking LOCK_ON_DISTANCE = config.base_tile_dimensions[ 1] * config.rescale_factor * 0.75 def __init__(self, level): self.animation = level.asset_manager.interactive_atlas.load_animation( "bowser_fireball") super().__init__(self.animation, level, bowser_fb_parameters, (6, 2), (6, 6), constants.Block, constants.Mario) self.level = level self.ground_detector = Collider.from_entity(self, level.collider_manager, constants.Block) self.movement.velocity = make_vector( -bowser_fb_parameters.max_horizontal_velocity, 0.) # state self._is_tracking = True self._locked_on_time = 0. self._dead = False def update(self, dt, view_rect): super().update(dt, view_rect) if self._is_tracking: mario = self.level.mario delta_y = BowserFireball.TRACK_VELOCITY * dt mario_y = mario.movement.get_center_of_mass().y dist = math.fabs(self.position.y - mario_y) if dist < BowserFireball.LOCK_ON_DISTANCE: self._locked_on_time += dt if self._locked_on_time >= BowserFireball.TARGET_ACQUIRE_TIME: self._is_tracking = False else: self._locked_on_time = 0. if self._is_tracking: # approach mario's coordinate if dist < delta_y: # we're close enough to just match it self.position = make_vector(self.position.x, mario_y) else: our_pos = self.position our_pos.y += delta_y * (1. if our_pos.y < mario_y else -1.) # move to this position if we can (if there's a block in the way, stay put and stop tracking) if self.ground_detector.test(our_pos, False): # collided! self._is_tracking = False else: self.position = our_pos def die(self): self.level.entity_manager.unregister(self) self.movement.destroy() self.level.asset_manager.sounds['stomp'].play() self._dead = True def on_movement_collision(self, collision): if not self._dead: self.die() def on_hit(self, collision): from .behaviors import DamageMario mario = self.level.mario if mario.is_starman: self.die() elif not mario.is_invincible: DamageMario.hurt_mario(self.level, self.level.mario)
import math from util import make_vector import constants from .projectile import Projectile from .parameters import CharacterParameters from util import mario_str_to_pixel_value_velocity as mstpvv from ..collider import Collider import config bowser_fb_parameters = CharacterParameters(mstpvv('00800'), mstpvv('01600'), 0., 0., 0.) class BowserFireball(Projectile): """Bowser's tracking fireball""" TRACK_VELOCITY = mstpvv( '01200') # how quickly the fireball can alter position to track mario TARGET_ACQUIRE_TIME = 0.1 # have to be within lock-on distance for this amount of time before stop tracking LOCK_ON_DISTANCE = config.base_tile_dimensions[ 1] * config.rescale_factor * 0.75 def __init__(self, level): self.animation = level.asset_manager.interactive_atlas.load_animation( "bowser_fireball") super().__init__(self.animation, level, bowser_fb_parameters, (6, 2), (6, 6), constants.Block, constants.Mario) self.level = level self.ground_detector = Collider.from_entity(self, level.collider_manager,
from .enemy import Enemy from .parameters import CharacterParameters from util import world_to_screen, mario_str_to_pixel_value_velocity as mstpvv from util import mario_str_to_pixel_value_acceleration as mstpva from .level_entity import LevelEntity import constants bowser_parameters = CharacterParameters(mstpvv('00500'), mstpvv('04000'), mstpva('00200'), mstpvv('03000'), mstpvv('02000')) class FakeBowser(Enemy): BOWSER_HITPOINTS = 5 # number of fireballs needed to kill DELAY_LEVEL_END = 3. # how long to wait after bowser dies to end the level def __init__(self, level): ca = level.asset_manager.character_atlas self.animation_open = ca.load_animation("bowser_left_mouth_open") self.animation_close = ca.load_animation("bowser_left_mouth_closed") super().__init__(level, self.animation_close.get_rect()) from .behaviors.bowser_logic import BowserLogic from .behaviors.simple_movement import SimpleMovement self.level = level self.movement = SimpleMovement(self, level.collider_manager, bowser_parameters) self.logic = BowserLogic(self, level, self.movement)
class MarioDeath(Entity, EventHandler): FREEZE_LENGTH = 0.25 # in seconds VELOCITY = mstpvv('04000') GRAVITY = mstpva('00200') def __init__(self, level, position): animation_name = "mario_fire_dead" if level.mario.effects & entities.characters.mario.MarioEffectFire \ else "mario_dead" self.animation = level.asset_manager.character_atlas.load_static( animation_name) super().__init__(self.animation.get_rect()) self.level = level self.position = position self._elapsed = 0. self._applied_jump = False self._velocity = make_vector(0, 0) self._finished = False # play death music pygame.mixer_music.load('sounds/music/smb_mariodie.wav') pygame.mixer_music.set_endevent(pygame.USEREVENT) pygame.mixer_music.play() state_stack.top.game_events.register(self) def update(self, dt, view_rect): self._elapsed += dt if not self._applied_jump and self._elapsed > MarioDeath.FREEZE_LENGTH: # apply jump self._velocity.y = -MarioDeath.VELOCITY self._applied_jump = True if self._applied_jump: self._velocity.y += MarioDeath.GRAVITY * dt self.position = self.position + self._velocity * dt if self._finished: self.level.entity_manager.unregister(self) # Decrement live counter self.level.stats.lives -= 1 # if have more lives, display world start again if self.level.stats.lives > 0: # reset any state mario might have had self.level.mario.effects = entities.characters.mario.MarioEffectSmall # kludgy :( no time to do it the nice way though run_session = state_stack.top while run_session is not None and not isinstance( run_session, state.run_session.RunSession): run_session = state_stack.get_next(run_session) state_stack.push( state.level_begin.LevelBegin(self.level.asset_manager, self.level, run_session.scoring_labels, self.level.stats)) # also resetore level state... self.level.reset() def draw(self, screen, view_rect): screen.blit(self.animation.image, world_to_screen(self.position, view_rect)) def handle_event(self, evt, game_events): # only event we care about is the end of sound one if evt.type == pygame.USEREVENT: self._finished = True pygame.mixer_music.set_endevent() state_stack.top.game_events.unregister(self) @property def layer(self): return constants.Active
from event import PlayerInputHandler from ..parameters import CharacterParameters from util import mario_str_to_pixel_value_velocity as mstpvv from util import mario_str_to_pixel_value_acceleration as mstpva from util import make_vector from ..fireball import Fireball fireball_parameters = CharacterParameters(mstpvv('03900'), mstpvv('02400'), mstpva('00300'), mstpvv('02400'), 0.) class FireballThrow: DELAY = 0.25 def __init__(self, level, input_state): super().__init__() self.input_state = input_state # type: PlayerInputHandler self.level = level # state self._fired = False self._cooldown = 0. def update(self, dt): from .mario import MarioEffectFire, MarioEffectStar mario = self.level.mario # only let mario throw fire if: # button pressed
from entities.entity import Entity import entities.characters.behaviors from .parameters import CharacterParameters from util import world_to_screen, mario_str_to_pixel_value_acceleration as mstpva, \ mario_str_to_pixel_value_velocity as mstpvv import entities.effects import constants from util import make_vector from .floaty_points import FloatyPoints # todo: scale this by rescale factor? mushroom_movement = CharacterParameters(50, mstpvv('03800'), mstpva('00300'), 0., mstpva('00300')) class Mushroom(Entity): POINT_VALUE = 1000 def __init__(self, level, position): self.animation = level.asset_manager.pickup_atlas.load_static( "mushroom_red") super().__init__(self.animation.image.get_rect()) self.level = level self.pickup = entities.characters.behaviors.interactive.Interactive( level, self, (0, 0), (16, 16), self.on_collected) self.movement = entities.characters.behaviors.simple_movement.SimpleMovement( self, level.collider_manager, mushroom_movement) self.movement.horizontal_movement_collider.mask = constants.Block # exclude enemies
import pygame from .corpse import Corpse from util import make_vector from util import mario_str_to_pixel_value_velocity as mstpvv from ..entity import Entity from .parameters import CharacterParameters floaty_font = None floaty_parameters = CharacterParameters(0., mstpvv('01500'), 0., mstpvv('00950'), 0.) class FloatyPoints(Corpse): DURATION = 0.33 def __init__(self, level, points): if isinstance(points, int): points = str(points) global floaty_font from animation import StaticAnimation # lazy load font floaty_font = floaty_font or pygame.font.Font( "scoring/super_mario_font.ttf", 12) frame = floaty_font.render(points, True, pygame.Color('white')) animation = StaticAnimation(frame) super().__init__(level, animation, floaty_parameters,
from ..entity import Entity import constants from util import world_to_screen from .parameters import CharacterParameters from util import mario_str_to_pixel_value_velocity as mstpvv from util import mario_str_to_pixel_value_acceleration as mstpva from util import make_vector from .floaty_points import FloatyPoints starman_parameters = CharacterParameters(mstpvv('00750'), mstpvv('04000'), mstpva('00200'), mstpvv('02500'), 0.) class Starman(Entity): DURATION = 15. POINT_VALUE = 2000 def __init__(self, level, position): self.animation = level.asset_manager.pickup_atlas.load_animation( "star") super().__init__(self.animation.get_rect()) from .behaviors import Interactive from .behaviors import JumpingMovement self.collect = Interactive(level, self, (4, 4), (9, 9), self.on_collected) self.level = level self.position = position self.movement = JumpingMovement(self, level.collider_manager,
import math from .projectile import Projectile from util import mario_str_to_pixel_value_velocity as mstpvv from util import mario_str_to_pixel_value_acceleration as mstpva from .parameters import CharacterParameters from util import make_vector from .behaviors import DamageMario from . import Enemy import constants deadly_shell_parameters = CharacterParameters(mstpvv('03500'), mstpvv('04000'), mstpva('00300'), 0., mstpvv('00700')) class Shell(Projectile): # todo: mario stomping shell in motion # todo: richochet off pipes # todo: don't kill offscreen enemies """Deadly version of the shell""" def __init__(self, level, direction, shell_animation): self.level = level self.sounds = level.asset_manager.sounds from animation import StaticAnimation self.shell = StaticAnimation(shell_animation.image) super().__init__(self.shell, level, deadly_shell_parameters, (1, 1), (14, 14), constants.Block, constants.Enemy | constants.Mario)
class Platform(LevelEntity): FALL_RATE = mstpvv('01500') """A horizontal platform that moves downward as mario stands on it""" def __init__(self, level): self.animation = level.asset_manager.interactive_atlas.load_static("horizontal_platform") super().__init__(self.animation.get_rect()) self.level = level from entities.collider import Collider from entities.characters.behaviors import Interactive # need to register a collider in the world for mario to stand on self.collider = Collider.from_entity(self, level.collider_manager, constants.Block) level.collider_manager.register(self.collider) # now another collider to let us know when to drop the platform # remember: it assumes unscaled values, while animation has been scaled self.platform_tester = Interactive(level, self, (0, -3), (self.rect.width / config.rescale_factor, self.rect.height / config.rescale_factor), self.attach_mario, self.detach_mario) # state self._attached = False self._pushed = False def attach_mario(self, collision): mario = self.level.mario mario.glued = mario.vertical_speed >= 0 and air_max_vertical_velocity >= Platform.FALL_RATE def detach_mario(self): self.level.mario.glued = False def update(self, dt, view_rect): self.collider.position = self.position # is mario standing on the platform? self.platform_tester.update(dt) mario = self.level.mario mario_foot_y = mario.movement.get_foot_position().y if mario.glued or self._pushed: # todo: platform can drag mario into solid blocks, fix pos = self.position pos.y += Platform.FALL_RATE * dt for collision in self.collider.try_move(pos, True): if collision.hit_collider and collision.hit_collider.entity: thing_hit = collision.hit_collider.entity if isinstance(thing_hit, Platform): # shove it out of the way thing_hit.position = make_vector(thing_hit.position.x, thing_hit.position.y + Platform.FALL_RATE * dt) else: self.collider.position = self.position # todo: better way to handle this? hit a block self.position = self.collider.position # want to align mario's feet with our top if he's glued onto the platform if mario.glued: # mario has been glued onto the platform, we're responsible for moving him (ypos) now mario.movement.set_foot_y_coord(self.position.y) def draw(self, screen, view_rect): screen.blit(self.animation.image, world_to_screen(self.position, view_rect)) self.platform_tester.draw(screen, view_rect) @property def layer(self): return constants.Block def destroy(self): self.level.collider_manager.unregister(self.collider) self.level.entity_manager.unregister(self) def create_preview(self): return self.animation.image.copy() @property def position(self): return super().position @position.setter def position(self, val): super(Platform, self.__class__).position.fset(self, val) self.collider.position = val
from entities.characters.level_entity import LevelEntity from entities.characters import Corpse from entities.entity import Entity from entities.characters.enemy import Enemy from entities.characters.behaviors import EnemyGroundMovement, Squashable from . import CharacterParameters import constants from util import mario_str_to_pixel_value_velocity as mstpvv from util import mario_str_to_pixel_value_acceleration as mstpva from entities.collider import Collider from .behaviors import SimpleMovement from util import get_aligned_foot_position, world_to_screen, make_vector from .shell import Shell from .floaty_points import FloatyPoints koopa_parameters = CharacterParameters(35, mstpvv('04800'), mstpva('00300'), 100, mstpvv('04200')) # todo: red koopa, patrols a set area and doesn't suicide off ledges class KoopaTroopa(Enemy): POINT_VALUE = 100 # when stomped into shell """Actively walking koopa, green, walks left until defeated or falls""" def __init__(self, level): self.level = level ca = level.asset_manager.character_atlas self.left_animation = ca.load_animation("koopa_green_left") self.right_animation = ca.load_animation("koopa_green_right")
from .koopa_troopa import KoopaTroopa, StunnedKoopaTroopa from entities.characters.level_entity import LevelEntity from entities.characters import Corpse from . import CharacterParameters from util import mario_str_to_pixel_value_velocity as mstpvv from util import mario_str_to_pixel_value_acceleration as mstpva from util import get_aligned_foot_position from .behaviors.smart_enemy_ground_movement import SmartEnemyGroundMovement import config from util import make_vector, copy_vector from .floaty_points import FloatyPoints # todo: tweak movement characteristics koopa_red_parameters = CharacterParameters(40, mstpvv('04800'), mstpva('00300'), 100, mstpvv('04200')) winged_koopa_red_parameters = CharacterParameters(40, mstpvv('04800'), mstpva('00300'), 100, mstpvv('08000')) class KoopaTroopaRed(KoopaTroopa): PATROL_RANGE = 300. * config.rescale_factor def __init__(self, level): super().__init__(level) ca = level.asset_manager.character_atlas self.left_animation = ca.load_animation("koopa_red_left") self.right_animation = ca.load_animation("koopa_red_right")