class PrTracks: """ PrTracks ======== provides object which manage track instruction groups 1. Container instruction group for canvas 2. List of PrTracks 3. show/hide tracks 4. z-order handling """ def __init__(self): self.container = InstructionGroup() self.tracks = {} def get(self, idx): if idx in self.tracks: return self.tracks[idx] def add(self, idx, track): self.tracks[idx] = track self.container.add(track.canvas) def remove(self, idx): self.container.remove(self.tracks[idx].canvas) self.tracks[idx] = None def show(self, idx): track = self.tracks[idx] if track: if self.tracks[idx].visible == False: # self.container.add(track.canvas) # track.visible = True track.show() def hide(self, idx): track = self.tracks[idx] if track: if self.tracks[idx].visible: # self.container.remove(track.canvas) # track.visible = False track.hide() def draw(self): self.container.clear() for key in self.tracks: self.container.add(self.tracks[key].canvas) return self.container
class LetterGrid(Widget): """An instance is a grid of letter boxes. While letter boxes are arranged in a grid, not all grids must have a letter box. This allows us to represent a seven-cross. Instance Attributes: cols: number of columns in the grid [int > 0] rows: number of rows in the grid [int > 0] foreground: the foreground color [4 element list of floats] background: the background color [4 element list of floats] textcolor: the text color [4 element list of floats] font_size: the size of the font [int > 0] bold: whether the font is bolded [boolean] italic: whether the font is in italics [boolean] border: the border size in pixels [odd, positive integer] _labels: the collection of all letter boxes [map of tuples to LetterBox] _back: drawing layer for unpressed boxes _front: drawing layer for pressed boxes """ # Computed propery @property def cellsize(self): """A 2-element tuple with the cellsize in pixels.""" if self._resized: width = self.size[0]/float(self.cols) height = self.size[1]/float(self.rows) self._cellsize = (width, height) self._resized = False return self._cellsize # Kivy style properties for KV sheets cols = NumericProperty(1) rows = NumericProperty(1) border = NumericProperty(3) font_size = NumericProperty(36) bold = BooleanProperty(True) italic = BooleanProperty(False) foreground = ListProperty([0,0,0,1]) background = ListProperty([1,1,1,1]) textcolor = ListProperty([1,0,0,1]) def __init__(self, **kw): """**Constructor**: Create a new letter box :param keywords: dictionary of keyword arguments **Precondition**: See below. To use the constructor for this class, you should provide it with a list of keyword arguments that initialize various attributes. For example, to initialize a 2x3 grid, use the constructor call GObject(rows=2,cols=3) You do not need to provide the keywords as a dictionary. The ** in the parameter `keywords` does that automatically. Any attribute of this class may be used as a keyword. The argument must satisfy the invariants of that attribute. See the list of attributes of this class for more information.""" Widget.__init__(self,**kw) self._resized = True self._labels = dict() self._set_properties(kw) self.bind(pos=self._reposition) # Create a layer for proper state control. self._back = InstructionGroup() self._front = InstructionGroup() self.canvas.add(self._back) self.canvas.add(self._front) # Bind kivy attributes to methods self.bind(size=self._resize) self.bind(cols=self._resize) self.bind(rows=self._resize) self.bind(border=self._set_border) self.bind(font_size=self._set_font_size) self.bind(bold=self._set_bold) self.bind(italic=self._set_italic) self.bind(foreground=self._set_foreground) self.bind(background=self._set_background) self.bind(textcolor=self._set_textcolor) def clear(self): """Reset the entire letter grid, eliminating all letter boxes.""" self._labels = dict() self.canvas.clear() self.canvas.add(Color(0,0,0,1)) self.canvas.add(Rectangle(pos=self.pos,size=self.size)) self._back = InstructionGroup() self._front = InstructionGroup() self.canvas.add(self._back) self.canvas.add(self._front) def _set_properties(self,kw): """Sets the letter box attributes according to kw If an attribute is not in kw, the attribute is set to a default. :param keywords: dictionary of keyword arguments **Precondition**: Same as __init__""" if 'cols' in kw: self.cols = kw['cols'] if 'rows' in kw: self.rows = kw['rows'] if 'background' in kw: self.background = kw['background'] if 'foreground' in kw: self.foreground = kw['foreground'] if 'textcolor' in kw: self.textcolor = kw['textcolor'] if 'border' in kw: self.border =kw['border'] if 'font_size' in kw: self.fontsize =kw['font_size'] if 'bold' in kw: self.bold =kw['bold'] if 'italic' in kw: self.italic =kw['italic'] # Methods to update the letter grid def add_cell(self, s, col, row): """Adds a new cell to the letter grid. The letter grid has s as its initial text. If the cell already exists, it replaces the text with s. Precondition: row and col are valid indices in the grid. s is a string.""" assert row >= 0 and row < self.rows, 'Row '+`row`+' is out of range [0,'+`self.rows`+']' assert col >= 0 and col < self.cols, 'Row '+`col`+' is out of range [0,'+`self.cols`+']' if (col,row) in self._labels: self._labels[(col,row)].text = s return label = LetterBox(text=s, fontsize=self.font_size, color=self.textcolor) label.bold = self.bold label.italic = self.italic label.size = self.cellsize x = self.pos[0] + col*self.cellsize[0] y = self.pos[1] + row*self.cellsize[1] label.pos = [x,y] self._labels[(col,row)] = label self._back.add(label.canvas) def delete_cell(self, col, row): """Deletes the LetterBox at col and row. If there is no LetterBox at that position, this method does nothing. Precondition: row and col are valid indices in the grid.""" if not (col, row) in self._labels: return label = self._labels[(col,row)] self._back.remove(label.canvas) del self._labels[(col,row)] def get_cell(self, col, row): """Returns the LetterBox at col and row. If there is no LetterBox at that position, it returns None. Precondition: row and col are valid indices in the grid.""" assert row >= 0 and row < self.rows, 'Row '+`row`+' is out of range [0,'+`self.rows`+']' assert col >= 0 and col < self.cols, 'Row '+`col`+' is out of range [0,'+`self.cols`+']' if not (col, row) in self._labels: return None return self._labels[(col,row)] def toggle_cell(self, col, row): """Toggles the state of the LetterBox at col and row. If there is no LetterBox at that position, it does nothing. Precondition: row and col are valid indices in the grid.""" if not (col, row) in self._labels: return label = self._labels[(col,row)] label.state = not label.state tmp = label.foreground label.foreground = label.background label.background = tmp tmp = label.textcolor tmp = map(lambda x: 1-x, tmp[:-1])+tmp[-1:] label.textcolor = tmp if label.state: self._front.add(label.canvas) return True self._front.remove(label.canvas) return False # Call Back Methods def _reposition(self,obj,value): """Repositions the graphics object. This function is called by Kivy services, so it passes the object and new position as an argument.""" for pos in self._labels: self._labels[pos].pos[0] = self.pos[0]+self.cellsize[0]*pos[0] self._labels[pos].pos[1] = self.pos[1]+self.cellsize[1]*pos[1] def _resize(self,obj,value): """Resizes the graphics object. This function is called by Kivy services, so it passes the object and new size as an argument.""" self._resized = True for pos in self._labels: self._labels[pos].size = self.cellsize self._labels[pos].pos[0] = self.pos[0]+self.cellsize[0]*pos[0] self._labels[pos].pos[1] = self.pos[1]+self.cellsize[1]*pos[1] def _set_border(self,obj,value): """Updates the border attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].border = value def _set_font_size(self,obj,value): """Updates the font size attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].fontsize = value def _set_bold(self,obj,value): """Updates the bold attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].bold = value def _set_italic(self,obj,value): """Updates the italic attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].italic = value def _set_foreground(self,obj,value): """Updates the foreground attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].foreground = list(value) def _set_background(self,obj,value): """Updates the background attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].background = list(value) def _set_textcolor(self,obj,value): """Updates the text color attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].textcolor = list(value)
class LetterGrid(Widget): """An instance is a grid of letter boxes. While letter boxes are arranged in a grid, not all grids must have a letter box. This allows us to represent a seven-cross. Instance Attributes: cols: number of columns in the grid [int > 0] rows: number of rows in the grid [int > 0] foreground: the foreground color [4 element list of floats] background: the background color [4 element list of floats] textcolor: the text color [4 element list of floats] font_size: the size of the font [int > 0] bold: whether the font is bolded [boolean] italic: whether the font is in italics [boolean] border: the border size in pixels [odd, positive integer] _labels: the collection of all letter boxes [map of tuples to LetterBox] _back: drawing layer for unpressed boxes _front: drawing layer for pressed boxes """ # Computed propery @property def cellsize(self): """A 2-element tuple with the cellsize in pixels.""" if self._resized: width = self.size[0] / float(self.cols) height = self.size[1] / float(self.rows) self._cellsize = (width, height) self._resized = False return self._cellsize # Kivy style properties for KV sheets cols = NumericProperty(1) rows = NumericProperty(1) border = NumericProperty(3) font_size = NumericProperty(36) bold = BooleanProperty(True) italic = BooleanProperty(False) foreground = ListProperty([0, 0, 0, 1]) background = ListProperty([1, 1, 1, 1]) textcolor = ListProperty([1, 0, 0, 1]) def __init__(self, **kw): """**Constructor**: Create a new letter box :param keywords: dictionary of keyword arguments **Precondition**: See below. To use the constructor for this class, you should provide it with a list of keyword arguments that initialize various attributes. For example, to initialize a 2x3 grid, use the constructor call GObject(rows=2,cols=3) You do not need to provide the keywords as a dictionary. The ** in the parameter `keywords` does that automatically. Any attribute of this class may be used as a keyword. The argument must satisfy the invariants of that attribute. See the list of attributes of this class for more information.""" Widget.__init__(self, **kw) self._resized = True self._labels = dict() self._set_properties(kw) self.bind(pos=self._reposition) # Create a layer for proper state control. self._back = InstructionGroup() self._front = InstructionGroup() self.canvas.add(self._back) self.canvas.add(self._front) # Bind kivy attributes to methods self.bind(size=self._resize) self.bind(cols=self._resize) self.bind(rows=self._resize) self.bind(border=self._set_border) self.bind(font_size=self._set_font_size) self.bind(bold=self._set_bold) self.bind(italic=self._set_italic) self.bind(foreground=self._set_foreground) self.bind(background=self._set_background) self.bind(textcolor=self._set_textcolor) def clear(self): """Reset the entire letter grid, eliminating all letter boxes.""" self._labels = dict() self.canvas.clear() self.canvas.add(Color(0, 0, 0, 1)) self.canvas.add(Rectangle(pos=self.pos, size=self.size)) self._back = InstructionGroup() self._front = InstructionGroup() self.canvas.add(self._back) self.canvas.add(self._front) def _set_properties(self, kw): """Sets the letter box attributes according to kw If an attribute is not in kw, the attribute is set to a default. :param keywords: dictionary of keyword arguments **Precondition**: Same as __init__""" if 'cols' in kw: self.cols = kw['cols'] if 'rows' in kw: self.rows = kw['rows'] if 'background' in kw: self.background = kw['background'] if 'foreground' in kw: self.foreground = kw['foreground'] if 'textcolor' in kw: self.textcolor = kw['textcolor'] if 'border' in kw: self.border = kw['border'] if 'font_size' in kw: self.fontsize = kw['font_size'] if 'bold' in kw: self.bold = kw['bold'] if 'italic' in kw: self.italic = kw['italic'] # Methods to update the letter grid def add_cell(self, s, col, row): """Adds a new cell to the letter grid. The letter grid has s as its initial text. If the cell already exists, it replaces the text with s. Precondition: row and col are valid indices in the grid. s is a string.""" assert row >= 0 and row < self.rows, 'Row ' + ` row ` + ' is out of range [0,' + ` self.rows ` + ']' assert col >= 0 and col < self.cols, 'Row ' + ` col ` + ' is out of range [0,' + ` self.cols ` + ']' if (col, row) in self._labels: self._labels[(col, row)].text = s return label = LetterBox(text=s, fontsize=self.font_size, color=self.textcolor) label.bold = self.bold label.italic = self.italic label.size = self.cellsize x = self.pos[0] + col * self.cellsize[0] y = self.pos[1] + row * self.cellsize[1] label.pos = [x, y] self._labels[(col, row)] = label self._back.add(label.canvas) def delete_cell(self, col, row): """Deletes the LetterBox at col and row. If there is no LetterBox at that position, this method does nothing. Precondition: row and col are valid indices in the grid.""" if not (col, row) in self._labels: return label = self._labels[(col, row)] self._back.remove(label.canvas) del self._labels[(col, row)] def get_cell(self, col, row): """Returns the LetterBox at col and row. If there is no LetterBox at that position, it returns None. Precondition: row and col are valid indices in the grid.""" assert row >= 0 and row < self.rows, 'Row ' + ` row ` + ' is out of range [0,' + ` self.rows ` + ']' assert col >= 0 and col < self.cols, 'Row ' + ` col ` + ' is out of range [0,' + ` self.cols ` + ']' if not (col, row) in self._labels: return None return self._labels[(col, row)] def toggle_cell(self, col, row): """Toggles the state of the LetterBox at col and row. If there is no LetterBox at that position, it does nothing. Precondition: row and col are valid indices in the grid.""" if not (col, row) in self._labels: return label = self._labels[(col, row)] label.state = not label.state tmp = label.foreground label.foreground = label.background label.background = tmp tmp = label.textcolor tmp = map(lambda x: 1 - x, tmp[:-1]) + tmp[-1:] label.textcolor = tmp if label.state: self._front.add(label.canvas) return True self._front.remove(label.canvas) return False # Call Back Methods def _reposition(self, obj, value): """Repositions the graphics object. This function is called by Kivy services, so it passes the object and new position as an argument.""" for pos in self._labels: self._labels[pos].pos[0] = self.pos[0] + self.cellsize[0] * pos[0] self._labels[pos].pos[1] = self.pos[1] + self.cellsize[1] * pos[1] def _resize(self, obj, value): """Resizes the graphics object. This function is called by Kivy services, so it passes the object and new size as an argument.""" self._resized = True for pos in self._labels: self._labels[pos].size = self.cellsize self._labels[pos].pos[0] = self.pos[0] + self.cellsize[0] * pos[0] self._labels[pos].pos[1] = self.pos[1] + self.cellsize[1] * pos[1] def _set_border(self, obj, value): """Updates the border attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].border = value def _set_font_size(self, obj, value): """Updates the font size attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].fontsize = value def _set_bold(self, obj, value): """Updates the bold attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].bold = value def _set_italic(self, obj, value): """Updates the italic attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].italic = value def _set_foreground(self, obj, value): """Updates the foreground attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].foreground = list(value) def _set_background(self, obj, value): """Updates the background attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].background = list(value) def _set_textcolor(self, obj, value): """Updates the text color attribute. This method propagates its value across all LetterBoxes in the grid. This function is called by Kivy services, so it passes the object and new attribute value as an argument.""" for pos in self._labels: self._labels[pos].textcolor = list(value)
class World: RADIUS_WIDTH = 2 RADIUS_HEIGHT = 2 LOAD_RADIUS = 1 def __init__(self, pos: Tuple[float, float]): self.world_group = InstructionGroup() self.top_group = InstructionGroup() chunk_pos = World.get_chunk_coords_from_pos(pos) self.loaded_center = chunk_pos self.seed = random.randint(0, 2**32 - 1) self.chunks = dict() self.loaded_chunks = [[ None for _ in World.get_loaded_range(chunk_pos[0], World.RADIUS_WIDTH) ] for _ in World.get_loaded_range(chunk_pos[1], World.RADIUS_HEIGHT)] size = Chunk.SIZE, Chunk.SIZE self.terrain_instructions = [[ Sprite(None, (0, 0), size) for _ in World.get_loaded_range(chunk_pos[0], World.RADIUS_WIDTH) ] for _ in World.get_loaded_range(chunk_pos[1], World.RADIUS_HEIGHT)] self.features_chunk_instructions = [[ InstructionGroup() for _ in World.get_loaded_range(chunk_pos[0], World.RADIUS_WIDTH) ] for _ in World.get_loaded_range(chunk_pos[1], World.RADIUS_HEIGHT)] self.top_features_chunk_instructions = [[ InstructionGroup() for _ in World.get_loaded_range(chunk_pos[0], World.RADIUS_WIDTH) ] for _ in World.get_loaded_range(chunk_pos[1], World.RADIUS_HEIGHT)] for row in self.features_chunk_instructions: for instruction in row: self.world_group.add(instruction) self.load_area(self.loaded_center) def get_chunk_from_coords(self, pos): coords = self.get_chunk_coords_from_pos(pos) return self.chunks[coords[1]][coords[0]] def get_chunk_in_range(self, rng: int): for y in World.get_loaded_range(World.RADIUS_HEIGHT, rng): for x in World.get_loaded_range(World.RADIUS_WIDTH, rng): yield self.loaded_chunks[y][x] def update(self, pos: Tuple[float, float]): x, y = pos x, y = World.get_chunk_coords_from_pos((x, y)) lx, ly = self.loaded_center if x + World.LOAD_RADIUS == lx or x - World.LOAD_RADIUS == lx or x == lx: if y + World.LOAD_RADIUS == ly or y - World.LOAD_RADIUS == ly or y == ly: return self.loaded_center = x, y self.load_area(self.loaded_center) def draw(self, canvas: RenderContext): for row in self.terrain_instructions: for terrain in row: terrain.draw(canvas) canvas.add(self.world_group) def draw_top(self, canvas: RenderContext): canvas.add(self.top_group) def render_chunk_at(self, x: int, y: int): instruction_group = self.features_chunk_instructions[y][x] self.world_group.remove(instruction_group) top_instruction_group = self.top_features_chunk_instructions[y][x] self.top_group.remove(top_instruction_group) instruction_group = InstructionGroup() self.features_chunk_instructions[y][x] = instruction_group self.world_group.add(instruction_group) top_instruction_group = InstructionGroup() self.top_features_chunk_instructions[y][x] = top_instruction_group self.top_group.add(top_instruction_group) self.loaded_chunks[y][x].draw_features(instruction_group, top_instruction_group) def render_chunk(self, chunk): for y in range(len(self.loaded_chunks)): for x in range(len(self.loaded_chunks[y])): if chunk == self.loaded_chunks[y][x]: self.render_chunk_at(x, y) return def load_area(self, pos: Tuple[int, int]): for index_y, y in enumerate( World.get_loaded_range(pos[1], World.RADIUS_HEIGHT)): if y not in self.chunks: self.chunks[y] = dict() row_chunks = self.chunks[y] for index_x, x in enumerate( World.get_loaded_range(pos[0], World.RADIUS_WIDTH)): if x not in row_chunks: row_chunks[x] = Chunk((x * Chunk.SIZE, y * Chunk.SIZE), self.seed) terrain_instruction = self.terrain_instructions[index_y][ index_x] self.loaded_chunks[index_y][index_x] = row_chunks[x] self.loaded_chunks[index_y][index_x].draw(terrain_instruction) self.render_chunk_at(index_x, index_y) @staticmethod def get_loaded_range(x: int, rng: int): return range(x - rng, x + rng + 1) @staticmethod def get_chunk_coords_from_pos(pos: Tuple[float, float]) -> Tuple[int, int]: x, y = pos if x < 0: x -= Chunk.SIZE if y < 0: y -= Chunk.SIZE return int(x / Chunk.SIZE), int(y / Chunk.SIZE)
class SelectAttackState(TurnAction): def __init__(self, target, **kwargs): super(SelectAttackState, self).__init__(target, **kwargs) self.amount = self.target.map.tile_width self.moving = False self.velocity = [0, 0] self.layer = self.target.map.layers.by_name['below'] self.foreshadowed = self.layer.get_at(*target.center) self.move_keys = [Keyboard.keycodes['left'], Keyboard.keycodes['right'], Keyboard.keycodes['up'], Keyboard.keycodes['down'], Keyboard.keycodes['enter']] self.travelled = set() self.checked = set() self.index = {} self.instructions = InstructionGroup() self.layer = self.target.map.layers.by_name['below'] self.confirmed = False self.effect = MeleeDamage tile = self.layer.get_at(*self.target.pos) self.cursor = Sprite(pos=[tile.px, tile.py], size=self.target.size, texture=images['cursor'], opacity=0.5) self.current_tile = self.target.get_current_cell() self.target.game.layer_widgets['sprite_layer'].add_widget(self.cursor) self.get_tiles_in_range(tile, 0) self.selected = None self.last_touched = None self.highlight_tiles() def highlight_tiles(self): for tile in self.travelled: self.instructions.add(Color(rgba=[1, .4, .3, .3])) self.instructions.add(Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height))) self.target.game.layer_widgets['below'].canvas.add(self.instructions) # print(self.travelled, self.foreshadowed) @memoized def get_tiles_in_range(self, tile, moved): self.travelled.add(tile) if moved < 2: for neighbor in self.layer.get_neighbor_cells(tile): # if not self.target.map.layers['objects'].collide(Rect(*neighbor.center + (8, 8)), 'wall'): self.get_tiles_in_range(neighbor, moved + 1) def touch(self, touch, *args): print('touch!', touch) pos = self.target.map.pixel_from_screen(*touch.pos) cell = self.target.map.layers.by_name['below'].get_at(*pos) print('at {}. Found? {}'.format(pos, cell)) if cell is not None and cell in self.travelled: print('cell not none, found in travels') self.cursor.pos = (cell.px, cell.py) if cell is self.last_touched: if self.get_selected(): self.confirm() else: self.last_touched = cell self.highlight_selected(cell) def highlight_selected(self, tile): if self.selected: for s in self.selected: self.instructions.remove(s) self.selected = [Color(rgba=[6, .3, .2, .6]), Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height))] for a in self.selected: self.instructions.add(a) def get_selected(self): self.selected_targets = [] for battler in self.target.game.entities: # TODO - figure out why this code is selecting both characters... print('checks say:', battler is not self.target, not battler.incapacitated, self.cursor.collide_point(*battler.center)) if battler is not self.target and not battler.incapacitated and self.cursor.collide_point(*battler.center): self.selected_targets.append(battler) print('selected targets:', self.selected_targets) return self.selected_targets def update(self, dt): if not self.confirmed: if not self.moving: self.velocity = [0, 0] if keys.get(Keyboard.keycodes['left']): self.velocity = self.velocity_dict['left'] elif keys.get(Keyboard.keycodes['right']): self.velocity = self.velocity_dict['right'] elif keys.get(Keyboard.keycodes['up']): self.velocity = self.velocity_dict['up'] elif keys.get(Keyboard.keycodes['down']): self.velocity = self.velocity_dict['down'] elif keys.get(Keyboard.keycodes['enter']): print('battle_entities currently:', self.target.game.entities) if self.get_selected(): self.confirm() else: pass elif keys.get(Keyboard.keycodes['backspace']): print('pressed backspace') self.end() else: return new_x = self.current_tile.x + self.velocity[0] new_y = self.current_tile.y + self.velocity[1] # print('new not none') new_target = self.layer.get_tile(new_x, new_y) if new_target and new_target.tile.is_passable() and not new_target.occupied: # print('starting to move!') self.foreshadowed = new_target if self.foreshadowed in self.travelled: self.start_moving() else: self.move(dt) else: if not self.current_effect.finished: self.current_effect.update(dt) else: self.ready_next_effect() def move(self, dt): # because we are moving a cursor Sprite and there's a dependency mess, we are pasting here for now x, y = self.foreshadowed.px, self.foreshadowed.py delta_x = self.cursor.x - x delta_y = self.cursor.y - y distance = Vector(*self.cursor.pos).distance((x, y)) if distance >= 0.5: delta_x = (delta_x / distance) * (dt * 50) delta_y = (delta_y / distance) * (dt * 50) new_x, new_y = self.cursor.pos new_x += -delta_x new_y += -delta_y self.cursor.pos = [new_x, new_y] distance = Vector(*self.cursor.pos).distance((x, y)) if distance <= 0.5: self.done_moving() else: return False def done_moving(self, *args): self.cursor.pos = [self.foreshadowed.px, self.foreshadowed.py] self.current_tile = self.foreshadowed self.moving = False def start_moving(self, *args): self.moving = True def end(self): super(SelectAttackState, self).end() self.target.state = BattleMenuState(self.target) self.target.game.layer_widgets['below'].canvas.remove(self.instructions) self.target.anim_delay = -1 self.target.reload() # reset animation self.cursor.parent.remove_widget(self.cursor)
class SelectMoveState(State): def __init__(self, target, **kwargs): super(SelectMoveState, self).__init__(target, **kwargs) self.amount = self.target.map.tile_width self.moving = False self.velocity = [0, 0] self.layer = self.target.map.layers.by_name['below'] self.foreshadowed = self.layer.get_at(*target.center) self.current_tile = self.target.get_current_cell() self.move_keys = [Keyboard.keycodes['left'], Keyboard.keycodes['right'], Keyboard.keycodes['up'], Keyboard.keycodes['down'], Keyboard.keycodes['enter']] self.travelled = set() self.checked = set() self.index = {} self.instructions = InstructionGroup() self.layer = self.target.map.layers.by_name['below'] tile = self.layer.get_at(*self.target.pos) self.get_tiles_in_range(tile, 0) self.last_touched = None self.selected = [] self.highlight_tiles() def touch(self, touch, *args): print('touch!', touch) pos = self.target.map.pixel_from_screen(*touch.pos) cell = self.target.map.layers.by_name['below'].get_at(*pos) print('at {}. Found? {}'.format(pos, cell)) if cell is not None and cell in self.travelled: print('cell not none, found in travels') if cell is self.last_touched: self.target.set_position(cell.px, cell.py) self.end() else: self.last_touched = cell self.highlight_selected(cell) def highlight_selected(self, tile): if self.selected: for s in self.selected: self.instructions.remove(s) self.selected = [Color(rgba=[6, .3, .2, .6]), Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height))] for a in self.selected: self.instructions.add(a) def highlight_tiles(self): for tile in self.travelled: self.instructions.add(Color(rgba=[.3, .5, .8, .5])) self.instructions.add(Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height))) self.target.game.layer_widgets['below'].canvas.add(self.instructions) @memoized def get_tiles_in_range(self, tile, moved): self.travelled.add(tile) # did this to keep smallest range possible to reach selected tile, for calculating cost at end of move state self.index[tile] = min(self.index.get(tile, 1000), moved) if moved < self.target.move_range(): for neighbor in self.layer.get_neighbor_cells(tile): self.get_tiles_in_range(neighbor, moved + 1) def update(self, dt): if not self.moving: pressed = [key for key in self.move_keys if keys.get(key)] if pressed: self.velocity = [0, 0] if Keyboard.keycodes['left'] in pressed: self.target.set_face('left') self.velocity = self.velocity_dict['left'] elif Keyboard.keycodes['right'] in pressed: self.target.set_face('right') self.velocity = self.velocity_dict['right'] elif Keyboard.keycodes['up'] in pressed: self.target.set_face('up') self.velocity = self.velocity_dict['up'] elif Keyboard.keycodes['down'] in pressed: self.target.set_face('down') self.velocity = self.velocity_dict['down'] elif keys.get(Keyboard.keycodes['enter']): self.end() return self.current_tile = self.target.get_current_cell() new_x = self.current_tile.x + self.velocity[0] new_y = self.current_tile.y + self.velocity[1] # print('new not none') new_target = self.layer.get_tile(new_x, new_y) if new_target and new_target.tile.is_passable() and not new_target.occupied: # print('starting to move!') self.foreshadowed = new_target if self.foreshadowed in self.travelled: self.start_moving() else: if self.target.anim_delay > 0: self.target.reload() # reset animation self.target.anim_delay = -1 else: self.move(dt) def move(self, dt): done = move(dt, self.target, self.foreshadowed.px, self.foreshadowed.py) if done: self.done_moving() def done_moving(self, *args): self.target.set_position(self.foreshadowed.px, self.foreshadowed.py) self.moving = False def start_moving(self, *args): self.moving = True def end(self): current = self.layer.get_at(*self.target.center) self.target.spend_moves(self.index[self.layer.get_at(*self.target.center)]) self.target.game.layer_widgets['below'].canvas.remove(self.instructions) self.target.anim_delay = -1 self.target.reload() # reset animation self.target.set_position(current.px, current.py) self.target.state = BattleMenuState(self.target)
class SelectAttackState(TurnAction): def __init__(self, target, **kwargs): super(SelectAttackState, self).__init__(target, **kwargs) self.amount = self.target.map.tile_width self.moving = False self.velocity = [0, 0] self.layer = self.target.map.layers.by_name['below'] self.foreshadowed = self.layer.get_at(*target.center) self.move_keys = [ Keyboard.keycodes['left'], Keyboard.keycodes['right'], Keyboard.keycodes['up'], Keyboard.keycodes['down'], Keyboard.keycodes['enter'] ] self.travelled = set() self.checked = set() self.index = {} self.instructions = InstructionGroup() self.layer = self.target.map.layers.by_name['below'] self.confirmed = False self.effect = MeleeDamage tile = self.layer.get_at(*self.target.pos) self.cursor = Sprite(pos=[tile.px, tile.py], size=self.target.size, texture=images['cursor'], opacity=0.5) self.current_tile = self.target.get_current_cell() self.target.game.layer_widgets['sprite_layer'].add_widget(self.cursor) self.get_tiles_in_range(tile, 0) self.selected = None self.last_touched = None self.highlight_tiles() def highlight_tiles(self): for tile in self.travelled: self.instructions.add(Color(rgba=[1, .4, .3, .3])) self.instructions.add( Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height))) self.target.game.layer_widgets['below'].canvas.add(self.instructions) # print(self.travelled, self.foreshadowed) @memoized def get_tiles_in_range(self, tile, moved): self.travelled.add(tile) if moved < 2: for neighbor in self.layer.get_neighbor_cells(tile): # if not self.target.map.layers['objects'].collide(Rect(*neighbor.center + (8, 8)), 'wall'): self.get_tiles_in_range(neighbor, moved + 1) def touch(self, touch, *args): print('touch!', touch) pos = self.target.map.pixel_from_screen(*touch.pos) cell = self.target.map.layers.by_name['below'].get_at(*pos) print('at {}. Found? {}'.format(pos, cell)) if cell is not None and cell in self.travelled: print('cell not none, found in travels') self.cursor.pos = (cell.px, cell.py) if cell is self.last_touched: if self.get_selected(): self.confirm() else: self.last_touched = cell self.highlight_selected(cell) def highlight_selected(self, tile): if self.selected: for s in self.selected: self.instructions.remove(s) self.selected = [ Color(rgba=[6, .3, .2, .6]), Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height)) ] for a in self.selected: self.instructions.add(a) def get_selected(self): self.selected_targets = [] for battler in self.target.game.entities: # TODO - figure out why this code is selecting both characters... print('checks say:', battler is not self.target, not battler.incapacitated, self.cursor.collide_point(*battler.center)) if battler is not self.target and not battler.incapacitated and self.cursor.collide_point( *battler.center): self.selected_targets.append(battler) print('selected targets:', self.selected_targets) return self.selected_targets def update(self, dt): if not self.confirmed: if not self.moving: self.velocity = [0, 0] if keys.get(Keyboard.keycodes['left']): self.velocity = self.velocity_dict['left'] elif keys.get(Keyboard.keycodes['right']): self.velocity = self.velocity_dict['right'] elif keys.get(Keyboard.keycodes['up']): self.velocity = self.velocity_dict['up'] elif keys.get(Keyboard.keycodes['down']): self.velocity = self.velocity_dict['down'] elif keys.get(Keyboard.keycodes['enter']): print('battle_entities currently:', self.target.game.entities) if self.get_selected(): self.confirm() else: pass elif keys.get(Keyboard.keycodes['backspace']): print('pressed backspace') self.end() else: return new_x = self.current_tile.x + self.velocity[0] new_y = self.current_tile.y + self.velocity[1] # print('new not none') new_target = self.layer.get_tile(new_x, new_y) if new_target and new_target.tile.is_passable( ) and not new_target.occupied: # print('starting to move!') self.foreshadowed = new_target if self.foreshadowed in self.travelled: self.start_moving() else: self.move(dt) else: if not self.current_effect.finished: self.current_effect.update(dt) else: self.ready_next_effect() def move(self, dt): # because we are moving a cursor Sprite and there's a dependency mess, we are pasting here for now x, y = self.foreshadowed.px, self.foreshadowed.py delta_x = self.cursor.x - x delta_y = self.cursor.y - y distance = Vector(*self.cursor.pos).distance((x, y)) if distance >= 0.5: delta_x = (delta_x / distance) * (dt * 50) delta_y = (delta_y / distance) * (dt * 50) new_x, new_y = self.cursor.pos new_x += -delta_x new_y += -delta_y self.cursor.pos = [new_x, new_y] distance = Vector(*self.cursor.pos).distance((x, y)) if distance <= 0.5: self.done_moving() else: return False def done_moving(self, *args): self.cursor.pos = [self.foreshadowed.px, self.foreshadowed.py] self.current_tile = self.foreshadowed self.moving = False def start_moving(self, *args): self.moving = True def end(self): super(SelectAttackState, self).end() self.target.state = BattleMenuState(self.target) self.target.game.layer_widgets['below'].canvas.remove( self.instructions) self.target.anim_delay = -1 self.target.reload() # reset animation self.cursor.parent.remove_widget(self.cursor)
class SelectMoveState(State): def __init__(self, target, **kwargs): super(SelectMoveState, self).__init__(target, **kwargs) self.amount = self.target.map.tile_width self.moving = False self.velocity = [0, 0] self.layer = self.target.map.layers.by_name['below'] self.foreshadowed = self.layer.get_at(*target.center) self.current_tile = self.target.get_current_cell() self.move_keys = [ Keyboard.keycodes['left'], Keyboard.keycodes['right'], Keyboard.keycodes['up'], Keyboard.keycodes['down'], Keyboard.keycodes['enter'] ] self.travelled = set() self.checked = set() self.index = {} self.instructions = InstructionGroup() self.layer = self.target.map.layers.by_name['below'] tile = self.layer.get_at(*self.target.pos) self.get_tiles_in_range(tile, 0) self.last_touched = None self.selected = [] self.highlight_tiles() def touch(self, touch, *args): print('touch!', touch) pos = self.target.map.pixel_from_screen(*touch.pos) cell = self.target.map.layers.by_name['below'].get_at(*pos) print('at {}. Found? {}'.format(pos, cell)) if cell is not None and cell in self.travelled: print('cell not none, found in travels') if cell is self.last_touched: self.target.set_position(cell.px, cell.py) self.end() else: self.last_touched = cell self.highlight_selected(cell) def highlight_selected(self, tile): if self.selected: for s in self.selected: self.instructions.remove(s) self.selected = [ Color(rgba=[6, .3, .2, .6]), Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height)) ] for a in self.selected: self.instructions.add(a) def highlight_tiles(self): for tile in self.travelled: self.instructions.add(Color(rgba=[.3, .5, .8, .5])) self.instructions.add( Rectangle(pos=(tile.px, tile.py), size=(tile.px_width, tile.px_height))) self.target.game.layer_widgets['below'].canvas.add(self.instructions) @memoized def get_tiles_in_range(self, tile, moved): self.travelled.add(tile) # did this to keep smallest range possible to reach selected tile, for calculating cost at end of move state self.index[tile] = min(self.index.get(tile, 1000), moved) if moved < self.target.move_range(): for neighbor in self.layer.get_neighbor_cells(tile): self.get_tiles_in_range(neighbor, moved + 1) def update(self, dt): if not self.moving: pressed = [key for key in self.move_keys if keys.get(key)] if pressed: self.velocity = [0, 0] if Keyboard.keycodes['left'] in pressed: self.target.set_face('left') self.velocity = self.velocity_dict['left'] elif Keyboard.keycodes['right'] in pressed: self.target.set_face('right') self.velocity = self.velocity_dict['right'] elif Keyboard.keycodes['up'] in pressed: self.target.set_face('up') self.velocity = self.velocity_dict['up'] elif Keyboard.keycodes['down'] in pressed: self.target.set_face('down') self.velocity = self.velocity_dict['down'] elif keys.get(Keyboard.keycodes['enter']): self.end() return self.current_tile = self.target.get_current_cell() new_x = self.current_tile.x + self.velocity[0] new_y = self.current_tile.y + self.velocity[1] # print('new not none') new_target = self.layer.get_tile(new_x, new_y) if new_target and new_target.tile.is_passable( ) and not new_target.occupied: # print('starting to move!') self.foreshadowed = new_target if self.foreshadowed in self.travelled: self.start_moving() else: if self.target.anim_delay > 0: self.target.reload() # reset animation self.target.anim_delay = -1 else: self.move(dt) def move(self, dt): done = move(dt, self.target, self.foreshadowed.px, self.foreshadowed.py) if done: self.done_moving() def done_moving(self, *args): self.target.set_position(self.foreshadowed.px, self.foreshadowed.py) self.moving = False def start_moving(self, *args): self.moving = True def end(self): current = self.layer.get_at(*self.target.center) self.target.spend_moves( self.index[self.layer.get_at(*self.target.center)]) self.target.game.layer_widgets['below'].canvas.remove( self.instructions) self.target.anim_delay = -1 self.target.reload() # reset animation self.target.set_position(current.px, current.py) self.target.state = BattleMenuState(self.target)