Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions pyboy/plugins/game_wrapper_castlevania_the_adventure.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# License: See LICENSE.md file
# GitHub: https://github.com/Baekalfen/PyBoy
#
cimport cython
from libc.stdint cimport uint8_t

from pyboy.logging.logging cimport Logger
from pyboy.plugins.base_plugin cimport PyBoyGameWrapper


cdef Logger logger

cdef class GameWrapperCastlevaniaTheAdventure(PyBoyGameWrapper):
cdef readonly int level_score
cdef readonly int time_left
cdef readonly int lives_left
cdef readonly int health
cdef readonly int whipe_level
cdef readonly int invincible_timer
122 changes: 122 additions & 0 deletions pyboy/plugins/game_wrapper_castlevania_the_adventure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#
# License: See LICENSE.md file
# GitHub: https://github.com/Baekalfen/PyBoy
#

__pdoc__ = {
"GameWrapperCastlevaniaTheAdventure.cartridge_title": False,
"GameWrapperCastlevaniaTheAdventure.post_tick": False,
}

import numpy as np

import pyboy
from pyboy.utils import bcd_to_dec

from .base_plugin import PyBoyGameWrapper

logger = pyboy.logging.get_logger(__name__)

ADDR_LEVEL_SCORE = 0xC034 # BCD
LEVEL_SCORE_BYTE_WIDTH = 3
MAX_LEVEL_SCORE = 99999

ADDR_TIME_LEFT_SECONDS = 0xC436 # BCD
ADDR_TIME_LEFT_MINUTES = 0xC437 # BCD

ADDR_LIVES_LEFT = 0xC040 # BCD
MAX_LIVES = 0x99

ADDR_HEALTH = 0xC519
MAX_HEALTH = 10

ADDR_WHIPE_LEVEL = 0xC51C # 00, 01 or 02
ADDR_WHIPE_THROW_BULLET = 0xC51D # 00 False, 0x80 True (if ADDR_WHIPE_LEVEL >= 02)
MAX_WHIPE_LEVEL = 2

ADDR_INVINCIBLE_TIMER = 0xC02C


class GameWrapperCastlevaniaTheAdventure(PyBoyGameWrapper):
"""
This class wraps Castlevania The Adventure, and provides easy access for AIs.

If you call `print` on an instance of this object, it will show an overview of everything this object provides.
"""

cartridge_title = "CASTLEVANIA AD"

def __init__(self, *args, **kwargs):
self.level_score = 0
"""The level score provided by the game"""
self.time_left = 0
"""Time remaining (in seconds) provided by the game"""
self.lives_left = 0
"""The lives remaining provided by the game"""
self.health = 0
"""The health provided by the game"""
self.whipe_level = 0
"""The whipe level provided by the game. Can be 0, 1 or 2."""
self.invincible_timer = 0
"""The timer for invincible mode provided by the game. Player is invincible if timer is higher than 0"""

super().__init__(*args, game_area_section=(0, 0, 19, 16), game_area_follow_scxy=True, **kwargs)

def post_tick(self):
self._tile_cache_invalid = True
self._sprite_cache_invalid = True

self.level_score = bcd_to_dec(
int.from_bytes(self.pyboy.memory[ADDR_LEVEL_SCORE:ADDR_LEVEL_SCORE + LEVEL_SCORE_BYTE_WIDTH], "little"),
byte_width=LEVEL_SCORE_BYTE_WIDTH
)
self.time_left = (
bcd_to_dec(self.pyboy.memory[ADDR_TIME_LEFT_SECONDS]) +
bcd_to_dec(self.pyboy.memory[ADDR_TIME_LEFT_MINUTES]) * 60
)
self.lives_left = bcd_to_dec(self.pyboy.memory[ADDR_LIVES_LEFT])
self.whipe_level = self.pyboy.memory[ADDR_WHIPE_LEVEL]
self.invincible_timer = self.pyboy.memory[ADDR_INVINCIBLE_TIMER]
self.health = self.pyboy.memory[ADDR_HEALTH]

def start_game(self, timer_div=None):
"""
Call this function right after initializing PyBoy. This will navigate through menus to start the game at the
first playable state.

The state of the emulator is saved, and using `reset_game`, you can get back to this point of the game
instantly.

Kwargs:
* timer_div (int): Replace timer's DIV register with this value. Use `None` to randomize.
"""

# Boot screen
while True:
self.pyboy.tick(1, False)
if self.tilemap_background[1, 6:10] == [256, 327, 334, 256]: # 'KONAMI' logo on the first screen
break

self.pyboy.button("start") # Skip brand
self.pyboy.tick(7, False) # Wait for transition to finish (start screen)
self.pyboy.button("start") # Start level
self.pyboy.tick(300, False) # Skip level transition

PyBoyGameWrapper.start_game(self, timer_div=timer_div)

def game_over(self):
return self.health == 0

def __repr__(self):
# yapf: disable
return (
f"Castlevania The Adventure:\n\n" +
f"Score: {self.level_score}\n" +
f"Time left: {self.time_left}\n" +
f"Lives left: {self.lives_left}\n" +
f"Health: {self.health}\n" +
f"Whipe level: {self.whipe_level}\n"+
f"Invincible timer: {self.invincible_timer}\n" +
super().__repr__()
)
# yapf: enable
3 changes: 3 additions & 0 deletions pyboy/plugins/manager.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from pyboy.plugins.game_wrapper_tetris cimport GameWrapperTetris
from pyboy.plugins.game_wrapper_kirby_dream_land cimport GameWrapperKirbyDreamLand
from pyboy.plugins.game_wrapper_pokemon_gen1 cimport GameWrapperPokemonGen1
from pyboy.plugins.game_wrapper_pokemon_pinball cimport GameWrapperPokemonPinball
from pyboy.plugins.game_wrapper_castlevania_the_adventure cimport GameWrapperCastlevaniaTheAdventure
# imports end


Expand All @@ -48,6 +49,7 @@ cdef class PluginManager:
cdef public GameWrapperKirbyDreamLand game_wrapper_kirby_dream_land
cdef public GameWrapperPokemonGen1 game_wrapper_pokemon_gen1
cdef public GameWrapperPokemonPinball game_wrapper_pokemon_pinball
cdef public GameWrapperCastlevaniaTheAdventure game_wrapper_castlevania_the_adventure
cdef bint window_sdl2_enabled
cdef bint window_open_gl_enabled
cdef bint window_null_enabled
Expand All @@ -64,6 +66,7 @@ cdef class PluginManager:
cdef bint game_wrapper_kirby_dream_land_enabled
cdef bint game_wrapper_pokemon_gen1_enabled
cdef bint game_wrapper_pokemon_pinball_enabled
cdef bint game_wrapper_castlevania_the_adventure_enabled
# plugin_cdef end

cdef list handle_events(self, list) noexcept
Expand Down
13 changes: 13 additions & 0 deletions pyboy/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from pyboy.plugins.game_wrapper_kirby_dream_land import GameWrapperKirbyDreamLand # isort:skip
from pyboy.plugins.game_wrapper_pokemon_gen1 import GameWrapperPokemonGen1 # isort:skip
from pyboy.plugins.game_wrapper_pokemon_pinball import GameWrapperPokemonPinball # isort:skip
from pyboy.plugins.game_wrapper_castlevania_the_adventure import GameWrapperCastlevaniaTheAdventure # isort:skip
# imports end


Expand All @@ -43,6 +44,7 @@ def parser_arguments():
yield GameWrapperKirbyDreamLand.argv
yield GameWrapperPokemonGen1.argv
yield GameWrapperPokemonPinball.argv
yield GameWrapperCastlevaniaTheAdventure.argv
# yield_plugins end
pass

Expand Down Expand Up @@ -86,6 +88,8 @@ def __init__(self, pyboy, mb, pyboy_argv):
self.game_wrapper_pokemon_gen1_enabled = self.game_wrapper_pokemon_gen1.enabled()
self.game_wrapper_pokemon_pinball = GameWrapperPokemonPinball(pyboy, mb, pyboy_argv)
self.game_wrapper_pokemon_pinball_enabled = self.game_wrapper_pokemon_pinball.enabled()
self.game_wrapper_castlevania_the_adventure = GameWrapperCastlevaniaTheAdventure(pyboy, mb, pyboy_argv)
self.game_wrapper_castlevania_the_adventure_enabled = self.game_wrapper_castlevania_the_adventure.enabled()
# plugins_enabled end

def gamewrapper(self):
Expand All @@ -95,6 +99,7 @@ def gamewrapper(self):
if self.game_wrapper_kirby_dream_land_enabled: return self.game_wrapper_kirby_dream_land
if self.game_wrapper_pokemon_gen1_enabled: return self.game_wrapper_pokemon_gen1
if self.game_wrapper_pokemon_pinball_enabled: return self.game_wrapper_pokemon_pinball
if self.game_wrapper_castlevania_the_adventure_enabled: return self.game_wrapper_castlevania_the_adventure
# gamewrapper end
self.generic_game_wrapper_enabled = True
return self.generic_game_wrapper
Expand Down Expand Up @@ -135,6 +140,8 @@ def handle_events(self, events):
events = self.game_wrapper_pokemon_gen1.handle_events(events)
if self.game_wrapper_pokemon_pinball_enabled:
events = self.game_wrapper_pokemon_pinball.handle_events(events)
if self.game_wrapper_castlevania_the_adventure_enabled:
events = self.game_wrapper_castlevania_the_adventure.handle_events(events)
# foreach end
if self.generic_game_wrapper_enabled:
events = self.generic_game_wrapper.handle_events(events)
Expand Down Expand Up @@ -166,6 +173,8 @@ def post_tick(self):
self.game_wrapper_pokemon_gen1.post_tick()
if self.game_wrapper_pokemon_pinball_enabled:
self.game_wrapper_pokemon_pinball.post_tick()
if self.game_wrapper_castlevania_the_adventure_enabled:
self.game_wrapper_castlevania_the_adventure.post_tick()
# foreach end
if self.generic_game_wrapper_enabled:
self.generic_game_wrapper.post_tick()
Expand Down Expand Up @@ -253,6 +262,8 @@ def window_title(self):
title += self.game_wrapper_pokemon_gen1.window_title()
if self.game_wrapper_pokemon_pinball_enabled:
title += self.game_wrapper_pokemon_pinball.window_title()
if self.game_wrapper_castlevania_the_adventure_enabled:
title += self.game_wrapper_castlevania_the_adventure.window_title()
# foreach end
return title

Expand Down Expand Up @@ -292,6 +303,8 @@ def stop(self):
self.game_wrapper_pokemon_gen1.stop()
if self.game_wrapper_pokemon_pinball_enabled:
self.game_wrapper_pokemon_pinball.stop()
if self.game_wrapper_castlevania_the_adventure_enabled:
self.game_wrapper_castlevania_the_adventure.stop()
# foreach end
if self.generic_game_wrapper_enabled:
self.generic_game_wrapper.stop()
Expand Down
2 changes: 1 addition & 1 deletion pyboy/plugins/manager_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
windows = ["WindowSDL2", "WindowOpenGL", "WindowNull", "Debug"]
game_wrappers = [
"GameWrapperSuperMarioLand", "GameWrapperTetris", "GameWrapperKirbyDreamLand", "GameWrapperPokemonGen1",
"GameWrapperPokemonPinball"
"GameWrapperPokemonPinball", "GameWrapperCastlevaniaTheAdventure"
]
plugins = [
"DisableInput", "AutoPause", "RecordReplay", "Rewind", "ScreenRecorder", "ScreenshotRecorder", "DebugPrompt"
Expand Down
Loading