def __init__(self, item, **kwargs): super().__init__(**kwargs) self.camera = self.parent.parent.camera self.world = self.parent.parent.world self.item = item self.dir = (0, 0) self.targettile = None self.droptile = None self.help_pos = DynamicPos( (self.game.width // 2, self.game.height + constants.MENU_SCALE * 2.5), speed=10) self.help_pos.move((self.help_pos.x, self.game.height / 2 + constants.TILE_SIZE * constants.MENU_SCALE)) text_args = { "renderer": self.game.renderer, "size": 5 * constants.MENU_SCALE, "centered": True } self.help_text = (wgt.Text(**text_args, text="Pick a direction", offset=(0, 0), color=constants.LIGHT_GRAY), wgt.TextLines(**text_args, text=["Z to throw", "X to cancel"], offset=(0, 7 * constants.MENU_SCALE)))
def __init__(self, speed, rect_size): self._ppt = round(constants.MENU_SCALE * 1.5) * 20 self._shake = 0 self._shake_x = 0 self._shake_y = 0 self._pos = DynamicPos((0, 0), speed=speed) self._rect_size = rect_size self._t_lastshake = 0
def __init__(self, item, **kwargs): super().__init__(**kwargs) self.world = self.parent.parent.world self.item = item self.options = [] self.visible = True if self.world.has_component(item, c.UseEffect): self.options.append("use") if self.world.has_component(item, c.Explosive): self.options.append("prime") self.options.append("throw") self.options.append("drop") self.size = len(self.options) inv_slot_size = constants.TILE_SIZE*constants.MENU_SCALE self.pos = (40 + inv_slot_size*2 + 12*constants.MENU_SCALE, self.game.height/2 - inv_slot_size*3) image_bottom = self.pos[1]+inv_slot_size*1.5 self.options_pos = [DynamicPos((self.pos[0], image_bottom + (10 + i*12)*constants.MENU_SCALE), speed=20) for i in range(self.size)] self.cursorpos = 0 audio.play("snap2", replace=True) self.widgets = [] if self.world.has_component(self.item, c.Describable): text_x = constants.TILE_SIZE * constants.MENU_SCALE * 1.6 describe = self.world.entity_component(self.item, c.Describable) self.widgets.extend(( wgt.Text(renderer=self.game.renderer, size=10*constants.MENU_SCALE, text=describe.name, offset=(text_x, 0)), wgt.Text(renderer=self.game.renderer, size=5*constants.MENU_SCALE, text=describe.desc, offset=(text_x, 15*constants.MENU_SCALE)), ))
def __init__(self, **kwargs): super().__init__(**kwargs) self.visible = False self.cursorpos = [0, 0] self.size = [2, 5] self.slot_size = constants.TILE_SIZE * constants.MENU_SCALE self.pos = DynamicPos((-self.slot_size * 2 - 21, self.game.height / 2 - self.slot_size * 3), speed=10) self.image_grid_pos = [0, 0] self.widgets = [ wgt.TextLines(renderer=self.game.renderer, size=5 * constants.MENU_SCALE, offset=(0, -19 * constants.MENU_SCALE), text=["Z to select", "X to return"]) ] self.add_child_scene(ImageGrid, grid_size=self.size, image=self.game.renderer.get_image( name="inventory-slot", scale=constants.MENU_SCALE), pos=self.image_grid_pos)
class Inventory(Scene): """Main inventory menu.""" def __init__(self, **kwargs): super().__init__(**kwargs) self.visible = False self.cursorpos = [0, 0] self.size = [2, 5] self.slot_size = constants.TILE_SIZE * constants.MENU_SCALE self.pos = DynamicPos((-self.slot_size * 2 - 21, self.game.height / 2 - self.slot_size * 3), speed=10) self.image_grid_pos = [0, 0] self.widgets = [ wgt.TextLines(renderer=self.game.renderer, size=5 * constants.MENU_SCALE, offset=(0, -19 * constants.MENU_SCALE), text=["Z to select", "X to return"]) ] self.add_child_scene(ImageGrid, grid_size=self.size, image=self.game.renderer.get_image( name="inventory-slot", scale=constants.MENU_SCALE), pos=self.image_grid_pos) def handle_input(self, keypress): handled = False if keypress.has_action(key_input.Action.BACK) or keypress.has_action( key_input.Action.INVENTORY_CLOSE): handled = True self.hide() if keypress.has_action(key_input.Action.DIRECTION): handled = True audio.play("click3", replace=True) if keypress.has_action(key_input.Action.UP): self.cursorpos[1] = max(self.cursorpos[1] - 1, 0) if keypress.has_action(key_input.Action.DOWN): self.cursorpos[1] = min(self.cursorpos[1] + 1, self.size[1] - 1) if keypress.has_action(key_input.Action.LEFT): self.cursorpos[0] = max(self.cursorpos[0] - 1, 0) if keypress.has_action(key_input.Action.RIGHT): self.cursorpos[0] = min(self.cursorpos[0] + 1, self.size[0] - 1) if keypress.has_action(key_input.Action.ACCEPT): handled = True itempos = self.cursorpos[0] * self.size[1] + self.cursorpos[1] items = self.parent.world.entity_component( self.parent.world.tags.player, c.Inventory).contents if itempos < len(items): self.game.set_focus( self.add_child_scene(InventoryOptions, items[itempos])) return handled def update(self, delta): if self.visible or self.game.focus_scene is self: self.pos.update(delta) if self.pos.x < -self.slot_size * 2 - 20: self.visible = False else: self.visible = True for i in range(2): self.image_grid_pos[i] = self.pos[i] def draw(self, screen): if not self.visible: return drawposx = round(self.pos.x) drawposy = round(self.pos.y) inventory = self.parent.world.entity_component( self.parent.world.tags.player, c.Inventory) for widget in self.widgets: widget.draw(screen, (drawposx, drawposy)) for i, entity in enumerate(inventory.contents): pos = (drawposx + self.slot_size * (i // self.size[1] + 0.5), drawposy + self.slot_size * (i % self.size[1] + 0.5)) self.parent.draw_centered_entity(screen, entity, constants.MENU_SCALE, pos) inv_cursor_screenpos = (drawposx + self.slot_size * self.cursorpos[0], drawposy + self.slot_size * self.cursorpos[1]) screen.blit( self.game.renderer.get_image(name="inventory-cursor-box", scale=constants.MENU_SCALE), inv_cursor_screenpos) def show(self): """Tell inventory to move onscreen.""" self.pos.move((40, self.pos.y)) audio.play("snap1", replace=True) audio.dim_music() self.game.set_focus(self) def hide(self): """Tell inventory to move offscreen.""" self.pos.move((-self.slot_size * 2 - 21, self.pos.y)) audio.play("drop", replace=True) audio.undim_music() self.game.remove_focus(self)
class ThrowOptions(Scene): """Throw direction selector once an item has been chosen to throw.""" scene_properties = {**Scene.scene_properties, "draw_above_parent": False} def __init__(self, item, **kwargs): super().__init__(**kwargs) self.camera = self.parent.parent.camera self.world = self.parent.parent.world self.item = item self.dir = (0, 0) self.targettile = None self.droptile = None self.help_pos = DynamicPos( (self.game.width // 2, self.game.height + constants.MENU_SCALE * 2.5), speed=10) self.help_pos.move((self.help_pos.x, self.game.height / 2 + constants.TILE_SIZE * constants.MENU_SCALE)) text_args = { "renderer": self.game.renderer, "size": 5 * constants.MENU_SCALE, "centered": True } self.help_text = (wgt.Text(**text_args, text="Pick a direction", offset=(0, 0), color=constants.LIGHT_GRAY), wgt.TextLines(**text_args, text=["Z to throw", "X to cancel"], offset=(0, 7 * constants.MENU_SCALE))) def handle_input(self, keypress): handled = False if keypress.has_action(key_input.Action.BACK): self.game.set_focus( self.parent.add_child_scene(inventory_options.InventoryOptions, self.item)) self.remove_scene() handled = True if keypress.has_action(key_input.Action.DIRECTION): self.dir = keypress.get_direction() playerpos = self.world.entity_component(self.world.tags.player, c.TilePosition) self.targettile = [playerpos.x, playerpos.y] stopped = False distance = 0 while not stopped: self.targettile[0] += self.dir[0] self.targettile[1] += self.dir[1] distance += 1 if not self.world.get_system(s.GridSystem).on_grid( self.targettile): self.targettile[0] -= self.dir[0] self.targettile[1] -= self.dir[1] distance -= 1 stopped = True if self.world.get_system(s.GridSystem).get_blocker_at( self.targettile) != 0: distance -= 1 stopped = True if distance == 5: stopped = True self.droptile = [ playerpos.x + self.dir[0] * distance, playerpos.y + self.dir[1] * distance ] handled = True if keypress.has_action(key_input.Action.ACCEPT): if self.droptile is not None: self.world.entity_component(self.world.tags.player, c.Inventory).contents.remove( self.item) self.world.remove_component(self.item, c.Stored) self.world.add_component(self.item, c.TilePosition(*self.droptile)) if self.targettile is not None: target = self.world.get_system(s.GridSystem).get_blocker_at( self.targettile) if target: if self.world.has_component(self.item, c.UseEffect): use = self.world.entity_component( self.item, c.UseEffect) for effect in use.effects: getattr(self.parent.parent, effect[0])(target, *effect[1:]) if self.world.entity_component(self.item, c.Item).consumable: self.world.add_component(self.item, c.Dead()) self.remove_scene() handled = True if keypress.has_action(key_input.Action.INVENTORY_CLOSE): self.remove_scene() return handled def update(self, delta): self.help_pos.update(delta) def draw(self, screen): item_x = self.game.width // 2 + self.dir[0] * self.camera.get_zoom() / 2 item_y = self.game.height // 2 + self.dir[1] * self.camera.get_zoom( ) / 2 self.parent.parent.draw_centered_entity(screen, self.item, self.camera.get_scale(), (item_x, item_y)) if self.targettile is not None: crosshair = self.game.renderer.get_image( name="crosshair", scale=self.camera.get_scale()) self.game.renderer.draw_centered_image( screen, crosshair, self.camera.tile_to_screen_pos(*self.targettile)) for text in self.help_text: text.draw(screen, (self.help_pos.x, self.help_pos.y))
class Camera: """The renderer camera. Can follow a point and shake. """ def __init__(self, speed, rect_size): self._ppt = round(constants.MENU_SCALE * 1.5) * 20 self._shake = 0 self._shake_x = 0 self._shake_y = 0 self._pos = DynamicPos((0, 0), speed=speed) self._rect_size = rect_size self._t_lastshake = 0 def get_rect(self): """Return the rect in which the camera can see. Rect position and size is in pixels. """ x = (self._pos.x + random.uniform( -self._shake_x, self._shake_x)) * self._ppt / constants.TILE_SIZE y = (self._pos.y + random.uniform( -self._shake_y, self._shake_y)) * self._ppt / constants.TILE_SIZE rect = pygame.Rect((0, 0), self._rect_size) rect.center = (x, y) return rect def get_scale(self): """Return scale of camera. Larger number means more zoomed in.""" return self._ppt / constants.TILE_SIZE def get_zoom(self): """Get pixels per tile of camera. Larger number means larger tiles.""" return self._ppt def zoom(self, zoom): """Change the pixels per tile of the camera. Positive zoom means zooming in.""" self._ppt += zoom def shake(self, amount): """Shake the camera.""" self._shake += amount def set_target(self, pos, direct=False): """Set target position of the camera.""" self._pos.move(pos, direct) def tile_to_pixel_pos(self, x, y): """Including zoom, return the position of the center of a tile relative to the top-left of the map.""" return ((x + 0.5) * self._ppt, (y + 0.5) * self._ppt) def tile_to_camera_pos(self, x, y): """Excluding zoom, return the position of the center of a tile relative to the top-left of the map.""" return ((x + 0.5) * constants.TILE_SIZE, (y + 0.5) * constants.TILE_SIZE) def tile_to_screen_pos(self, x, y): """Return the position of the center of a tile relative to the top-left of the screen.""" pixelpos = self.tile_to_pixel_pos(x, y) rect = self.get_rect() return (pixelpos[0] - rect.x, pixelpos[1] - rect.y) def update(self, t_frame, pos=None): """Update shake amount and move towards target position.""" if pos is not None: self.set_target(pos) self._pos.update(t_frame) self._t_lastshake += t_frame while self._t_lastshake >= 1000 / 30: self._t_lastshake -= 1000 / 30 self._shake_x = random.uniform(-self._shake, self._shake) self._shake_y = random.uniform(-self._shake, self._shake) self._shake *= 0.75 if self._shake < 0.1: self._shake = 0