def create_player_tank(dispatcher, atlas, x, y): # Creating the actual entity, which currently has only a name player = Entity(id='player') # Adding all necessary components, in our case input (which also spawns # bullets), two collision-related ones, position, health and a destructor # for orderly entity removal. player.add_component(InputComponent(dispatcher)) player.add_component(WalkerCollisionComponent(dispatcher)) player.add_component(PassingComponent(dispatcher)) player.add_component(PositionComponent(dispatcher, x, y)) player.add_component(DestructorHealthComponent(dispatcher, hitpoints=5)) player.add_component(DestructorComponent(dispatcher)) # Also a WidgetComponent, which requires a Widget images_dict = { 'player_r': atlas.get_element('player_r'), 'player_l': atlas.get_element('player_l'), 'player_d': atlas.get_element('player_d'), 'player_u': atlas.get_element('player_u') } player.add_component( SwitchWidgetComponent( dispatcher, SwitchingWidget(images_dict=images_dict, initial_image='player_r'))) dispatcher.add_event(BearEvent('ecs_create', player)) dispatcher.add_event( BearEvent('ecs_add', (player.id, player.position.x, player.position.y))) return player
def on_event(self, event): if event.event_type == 'tick': self.x_waited += event.event_value self.y_waited += event.event_value xpos, ypos = self.parent.widget_locations[self].pos self.vx += self.field[xpos][ypos].ax * event.event_value self.vy += self.field[xpos][ypos].ay * event.event_value if self.vx != 0: self.x_delay = abs(1 / self.vx) if self.vy != 0: self.y_delay = abs(1 / self.vy) if self.x_waited >= self.x_delay and self.vx != 0: new_x = xpos + round(self.vx/abs(self.vx)) self.x_waited = 0 else: new_x = xpos if self.y_waited >= self.y_delay and self.vy != 0: new_y = ypos + round(self.vy/abs(self.vy)) self.y_waited = 0 else: new_y = ypos if new_x != xpos or new_y != ypos: t = self.tetris.check_move((new_x, new_y), self.chars) if t == 0: self.parent.move_widget(self, (new_x, new_y)) elif t == 1: return [BearEvent(event_type='request_installation', event_value=self), BearEvent(event_type='play_sound', event_value='connect')] elif t == 2: return [BearEvent(event_type='request_destruction', event_value=self), BearEvent(event_type='play_sound', event_value='fly_away')]
def create_enemy_tank(dispatcher, atlas, entity_id, x, y): # ControllerComponent # DestructorHealthComponent # WalkerCollisionComponent # SwitchWidgetComponent enemy = Entity(id=entity_id) # Adding all necessary components, in our case input (which also spawns # bullets), two collision-related ones, position, health and a destructor # for orderly entity removal. enemy.add_component(WalkerCollisionComponent(dispatcher)) enemy.add_component(PassingComponent(dispatcher)) enemy.add_component(PositionComponent(dispatcher, x, y)) enemy.add_component(DestructorHealthComponent(dispatcher, hitpoints=1)) enemy.add_component(DestructorComponent(dispatcher)) enemy.add_component(ControllerComponent(dispatcher)) # Also a WidgetComponent, which requires a Widget images_dict = { 'enemy_r': atlas.get_element('enemy_r'), 'enemy_l': atlas.get_element('enemy_l'), 'enemy_d': atlas.get_element('enemy_d'), 'enemy_u': atlas.get_element('enemy_u') } enemy.add_component( SwitchWidgetComponent( dispatcher, SwitchingWidget(images_dict=images_dict, initial_image='enemy_r'))) dispatcher.add_event(BearEvent('ecs_create', enemy)) dispatcher.add_event( BearEvent('ecs_add', (enemy.id, enemy.position.x, enemy.position.y))) return enemy
def create_wall(dispatcher, atlas, entity_id, x, y): wall = Entity(entity_id) wall.add_component(PositionComponent(dispatcher, x, y)) wall.add_component(CollisionComponent(dispatcher)) wall.add_component(PassingComponent(dispatcher)) wall.add_component(DestructorComponent(dispatcher)) images_dict = { 'wall_3': atlas.get_element('wall_3'), 'wall_2': atlas.get_element('wall_2'), 'wall_1': atlas.get_element('wall_1') } wall.add_component( SwitchWidgetComponent( dispatcher, SwitchingWidget(images_dict=images_dict, initial_image='wall_3'))) wall.add_component( VisualDamageHealthComponent(dispatcher, hitpoints=3, widgets_dict={ 3: 'wall_3', 2: 'wall_2', 1: 'wall_1' })) dispatcher.add_event(BearEvent('ecs_create', wall)) dispatcher.add_event( BearEvent('ecs_add', (wall.id, wall.position.x, wall.position.y))) pass
def on_event(self, event): """ See class documentation :param event: BearEvent instance """ r = [] if event.event_type == 'ecs_move': entity_id, x, y = event.event_value # Checking if collision events need to be emitted # Check for collisions with border if x < 0 or x + self.entities[entity_id].widget.size[0]\ > len(self.chars[0]) or y < 0 or \ y + self.entities[entity_id].widget.size[1] > len(self.chars): r.append( BearEvent(event_type='ecs_collision', event_value=(entity_id, None))) else: # Apparently no collision with a border, can safely move self.move_child(self.widgets[entity_id], (x, y)) self.need_redraw = True collided = set() for y_offset in range(self.entities[entity_id].widget.size[1]): for x_offset in range( self.entities[entity_id].widget.size[0]): for other_widget in self._child_pointers[y+y_offset] \ [x+x_offset]: # Child_pointers is ECS-agnostic and stores pointers # to the actual widgets collided.add(other_widget) collided_ent_ids = set() for child in self.entities: if child != entity_id and \ self.entities[child].widget.widget in collided: collided_ent_ids.add(child) for child in collided_ent_ids: r.append(BearEvent('ecs_collision', (entity_id, child))) elif event.event_type == 'ecs_create': self.add_entity(event.event_value) self.need_redraw = True elif event.event_type == 'ecs_destroy': self.remove_entity(event.event_value) self.need_redraw = True elif event.event_type == 'ecs_remove': self.remove_child(self.entities[event.event_value].widget.widget) self.need_redraw = True elif event.event_type == 'ecs_add': entity_id, x, y = event.event_value self.add_child(self.widgets[entity_id], (x, y)) self.need_redraw = True elif event.event_type == 'ecs_update': # Some widget has decided it's time to redraw itself self.need_redraw = True elif event.event_type == 'service' and event.event_value == 'tick_over'\ and self.need_redraw: self._rebuild_self() self.terminal.update_widget(self) self.need_redraw = False if r: return r
def on_event(self, event): r = [] if event.event_type == 'key_down': moved = False if event.event_value in ('TK_D', 'TK_RIGHT'): self.last_move = (1, 0) moved = True elif event.event_value in ('TK_A', 'TK_LEFT'): self.last_move = (-1, 0) moved = True elif event.event_value in ('TK_S', 'TK_DOWN'): self.last_move = (0, 1) moved = True elif event.event_value in ('TK_W', 'TK_UP'): self.last_move = (0, -1) moved = True if moved: # events self.relative_move(*self.last_move, emit_event=False) r.append( BearEvent(event_type='ecs_move', event_value=(self.owner.id, self.x, self.y))) r.append(BearEvent(event_type='play_sound', event_value='step')) x = super().on_event(event) if x: if isinstance(x, BearEvent): r.append(x) else: #multiple return r += x return r
def create_spawner_house(dispatcher, atlas, x, y): house = Entity('house') house.add_component( WidgetComponent(dispatcher, Widget(*atlas.get_element('spawner')))) house.add_component(DestructorComponent(dispatcher)) house.add_component(PositionComponent(dispatcher, x, y)) dispatcher.add_event(BearEvent('ecs_create', house)) dispatcher.add_event( BearEvent('ecs_add', (house.id, house.position.x, house.position.y)))
def on_event(self, event): super().on_event(event) if event.event_type == 'tick': self.have_waited += event.event_value if self.have_waited >= self.delay: pos = self.terminal.widget_locations[self].pos if self.vx != 0: new_x = pos[0]+round(abs(self.vx)/self.vx) else: new_x = pos[0] if self.vy != 0: new_y = pos[1]+round(abs(self.vy)/self.vy) else: new_y = pos[1] self.terminal.move_widget(self, (new_x, new_y)) # The emitter always moves clockwise # So some stuff is hardcoded if new_x == 0 and self.vx < 0: #Lower left self.vx = 0 self.vy = -1 * self.abs_vy self.delay = 1/self.abs_vy elif new_y == 0 and self.vy < 0: # Upper left self.vy = 0 self.vx = self.abs_vx self.delay = 1/self.abs_vx elif new_x + self.width == 60 and self.vx > 0: #Upper right self.vx = 0 self.vy = self.abs_vy self.delay = 1/self.abs_vy elif new_y + self.height == 45 and self.vy > 0: # Lower right self.vx = -1 * self.abs_vx self.vy = 0 self.delay = 1/self.abs_vx self.have_waited = 0 for x_offset in range(5): for y_offset in range(5): if self.tetris[pos[0]+x_offset][pos[1]+y_offset] == 1: return [BearEvent(event_type='game_lost', event_value=None), BearEvent(event_type='play_sound', event_value='fail')] elif event.event_type == 'request_installation' or \ event.event_type == 'request_destruction': pos = self.terminal.widget_locations[self].pos self.fig = self.children[1] # The number 7 is empirical; maybe I'll change it later self.fig.vx = (30 - pos[0])/7 self.fig.vy = (23-pos[1])/7 self.remove_child(self.fig, remove_completely=True) self.dispatcher.register_listener(self.fig, 'tick') self.terminal.add_widget(self.fig, (pos[0]+1, pos[1]+1), layer=6) self.add_child(self.manager.create_figure(), (1, 1))
def test_tick_events(event_dispatcher, listener): # Check that event_dispatcher does indeed emit test events event_dispatcher.register_listener(listener, event_types=['tick', 'input']) event_dispatcher.start_queue() event_dispatcher.add_event(BearEvent(event_type='tick')) event_dispatcher.add_event(BearEvent(event_type='input', event_value='A')) event_dispatcher.dispatch_events() assert 'tick' in listener.accepted_types assert 'service' not in listener.accepted_types assert 'input' in listener.accepted_types
def _run_iteration(self, time_since_last_tick): # Get input events, if any for event in self.terminal.check_input(): self.queue.add_event(event) self.queue.add_event(BearEvent(event_type='tick', event_value=time_since_last_tick)) self.queue.dispatch_events() # Sending "Tick over" event, reminding widgets to update themselves self.queue.add_event(BearEvent(event_type='service', event_value='tick_over')) self.queue.dispatch_events() self.terminal.refresh()
def create_bullet(dispatcher, entity_id, x, y, vx, vy): bullet = Entity(entity_id) bullet.add_component( WidgetComponent(dispatcher, Widget([['*']], [['red']]))) bullet.add_component(PositionComponent(dispatcher, x, y, vx, vy)) bullet.add_component(ProjectileCollisionComponent(dispatcher, damage=1)) bullet.add_component(DestructorComponent(dispatcher)) dispatcher.add_event(BearEvent('ecs_create', bullet)) dispatcher.add_event( BearEvent('ecs_add', (bullet.id, bullet.position.x, bullet.position.y))) return bullet
def create_barrel(atlas, dispatcher, x, y): barrel_entity = Entity(id='Barrel') widget = SimpleAnimationWidget(Animation( (atlas.get_element('barrel_1'), atlas.get_element('barrel_2')), 2), emit_ecs=True) widget_component = WidgetComponent(dispatcher, widget, owner=barrel_entity) position_component = PositionComponent(dispatcher, x=x, y=y, owner=barrel_entity) collision = CollisionComponent(dispatcher, owner=barrel_entity) dispatcher.add_event( BearEvent(event_type='ecs_create', event_value=barrel_entity)) dispatcher.add_event( BearEvent(event_type='ecs_add', event_value=('Barrel', x, y)))
def on_event(self, event): if event.event_type == 'tick': self.rotated_this_tick = False self.move_cd -= event.event_value self.shoot_cd -= event.event_value if self.move_cd <= 0: try: player_x = EntityTracker().entities['player'].position.x player_y = EntityTracker().entities['player'].position.y except KeyError: # DO NOTHING AFTER THE PLAYER IS DEAD return dx = player_x - self.owner.position.x dy = player_y - self.owner.position.y # Turn towards player if has direct line of fire if abs(dx) < 3: self.direction = (0, 1 if dy > 0 else -1) self.owner.widget.switch_to_image( self.images[self.direction]) if abs(dy) < 3: self.direction = (1 if dx > 0 else -1, 0) self.owner.widget.switch_to_image( self.images[self.direction]) if self.direction is not None: self.owner.position.relative_move(*self.direction) # Shoot if necessary if self.shoot_cd <= 0 and (abs(dx) < 3 or abs(dy) < 3): offset = self.bullet_offsets[self.direction] create_bullet( self.dispatcher, f'{self.owner.id}_bullet{self.bullet_count}', self.owner.position.x + offset[0], self.owner.position.y + offset[1], self.direction[0] * 20, self.direction[1] * 20) self.bullet_count += 1 self.shoot_cd = self.shoot_delay self.dispatcher.add_event( BearEvent('play_sound', 'shot')) else: directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] if dx > 0: directions.extend([(1, 0)] * int(dx / 10)) elif dx < 0: directions.extend([(-1, 0)] * int(dx / -10)) if dy > 0: directions.extend([(0, 1)] * int(dy / 10)) elif dy < 0: directions.extend([(0, -1)] * int(dy / -10)) self.direction = choice(directions) self.owner.widget.switch_to_image( self.images[self.direction]) self.move_cd = self.move_delay elif event.event_type == 'ecs_collision'\ and event.event_value[0] == self.owner.id \ and not self.rotated_this_tick: if event.event_value[1] is None or hasattr( EntityTracker().entities[event.event_value[1]], 'collision'): self.direction = None self.rotated_this_tick = True
def collided_into(self, entity): if not entity: self.owner.destructor.destroy() elif hasattr(EntityTracker().entities[entity], 'collision'): self.dispatcher.add_event( BearEvent(event_type='ac_damage', event_value=(entity, self.damage))) self.owner.destructor.destroy()
def check_input(self): """ Check if terminal has input. If so, yield corresponding ``BearEvent``. This method returns an iterator because it's possible there would be more than one event in a single tick, eg when two buttons are pressed simultaneously. This method mostly just wraps bearlibterminal's input behaviour in `events<foo.wyrd.name/en:bearlibterminal:reference:input>`_, with a single exception: in bearlibterminal, when a key is pressed and held for more than a single tick, it first emits key_down, then waits for 0.5 seconds. Then, if the key is not released, it assumes the key is indeed held and starts spamming events every tick. This makes sense to avoid messing up the typing (where a slow typist would get char sequences like tthiisss). Bearlibterminal, on the other hand, is meant mostly for games that require more precise input timing. Therefore, it starts spamming ``key_down`` events immediately after the button is pressed and expects widgets and listeners to mind their input cooldowns themselves. :yields: BearEvent instances with ``event_type`` set to ``misc_input``, ``key_up`` or ``key_down``. """ while terminal.has_input(): # Process the input event in_event = terminal.read() if in_event in self.misc_input: yield BearEvent('misc_input', self.misc_input[in_event]) elif in_event in self._down_codes: self.currently_pressed.add(self._down_codes[in_event]) elif in_event in self._up_codes: try: self.currently_pressed.remove(self._up_codes[in_event]) except KeyError: # It's possible that the button was pressed before launching # the bear_hug app, and released now. Then it obviously # couldn't be in self.currently_pressed, causing exception pass yield BearEvent('key_up', self._up_codes[in_event]) else: raise BearException('Unknown input code {}'.format(in_event)) for key in self.currently_pressed: yield BearEvent('key_down', key)
def check_input(self): """ Check if terminal has input. If so, yield corresponding ``BearEvent``. This method returns an iterator because it's possible there would be more than one event in a single tick, eg when two buttons are pressed simultaneously. :yields: BearEvent instances with ``event_type`` set to ``misc_input``, ``key_up`` or ``key_down``. """ while terminal.has_input(): # Process the input event in_event = terminal.read() if in_event in self.misc_input: yield BearEvent('misc_input', self.misc_input[in_event]) elif in_event in self._down_codes: yield BearEvent('key_down', self._down_codes[in_event]) elif in_event in self._up_codes: yield BearEvent('key_up', self._up_codes[in_event]) else: raise BearException('Unknown input code {}'.format(in_event))
def create_cop(atlas, dispatcher, x, y): """ Create a cop entity :param dispatcher: :return: """ cop_entity = Entity(id='cop') t = Widget(*atlas.get_element('cop_r')) widget = deserialize_widget(repr(t)) widget_component = WidgetComponent(dispatcher, widget, owner=cop_entity) position_component = WalkerComponent(dispatcher, x=x, y=y, owner=cop_entity) collision = WalkerCollisionComponent(dispatcher, owner=cop_entity) passing = PassingComponent(dispatcher, shadow_pos=(0, 15), shadow_size=(13, 3), owner=cop_entity) dispatcher.add_event( BearEvent(event_type='ecs_create', event_value=cop_entity)) dispatcher.add_event( BearEvent(event_type='ecs_add', event_value=('cop', x, y)))
def check_for_removal(self): """ Check if something is to be removed :param pos: :return: """ # Return events, so this is expected to be called by FigureManager's # on_event. Later BuildingWidget will catch the event and update itself # accordingly. Maybe also some sound emission or animation or something r = [] for x in range(len(self.cells)-3): for y in range(len(self.cells[0])-3): # Check whether a given cell is a top-left corner of something if self[x][y] == 1: if x <= len(self.cells) - 7: #Check whether this cell is left side of horizontal 7 h7 = True for x_1 in range(1, 7): if self[x + x_1][y] != 1: h7 = False if h7: for x_1 in range(7): self[x+x_1][y] = 0 r += [BearEvent(event_type='h7', event_value=(x, y)), BearEvent(event_type='play_sound', event_value='explosion')] if y <= len(self.cells[0]) - 7: # Or a vertical 7 v7 = True for y_1 in range(1, 7): if self[x][y+y_1] != 1: v7 = False if v7: for y_1 in range(1, 7): self[x][y+y_1] = 0 r += [(BearEvent(event_type='v7', event_value=(x, y))), BearEvent(event_type='play_sound', event_value='explosion')] if x <= len(self.cells)-3 and y <= len(self.cells[0])-3: sq = True for x_1 in range(3): for y_1 in range(3): if self[x+x_1][y+y_1] != 1: sq = False if sq: for x_1 in range(3): for y_1 in range(3): self[x+x_1][y+y_1] = 0 r += [BearEvent(event_type='square', event_value=(x, y)), BearEvent(event_type='play_sound', event_value='explosion')] return r
def destroy(self): """ Destroy this component's owner. Unsubscribes owner and all its components from the queue and sends 'ecs_remove'. Then all components are deleted. Entity itself is left at the mercy of garbage collector. """ self.dispatcher.add_event(BearEvent('ecs_destroy', self.owner.id)) self.is_destroying = True # Destroys item on the 'tick_over', so that all # existing events involving owner (including 'ecs_remove)' are processed # normally, but unsubscribes it right now to prevent new ones from forming for component in self.owner.components: if component != self.name: self.dispatcher.unregister_listener( self.owner.__dict__[component])
def move(self, x, y, emit_event=True): """ Move the Entity to a specified position. :param x: x :param y: y :param emit_event: If True, emit an 'esc_move' event. There are a few cases (ie setting the coordinates after the component is created, but before the entity is added to the terminal) where this is undesirable. """ # This attr is set so that the turn could be undone (for example, that's # what WalkerCollision uses to avoid impossible steps). self.last_move = (x - self._x, y - self._y) self._x = x self._y = y if emit_event: self.dispatcher.add_event(BearEvent(event_type='ecs_move', event_value=(self.owner.id, self._x, self._y)))
def on_event(self, event): x = super().on_event(event) if isinstance(x, BearEvent): r = [x] elif isinstance(x, list): r = x else: r = [] if event.event_type == 'key_down': moved = False if event.event_value == 'TK_SPACE': bullet_offset = self.bullet_offsets[ self.owner.position.last_move] create_bullet(self.dispatcher, f'bullet_{self.bullet_count}', self.owner.position.x + bullet_offset[0], self.owner.position.y + bullet_offset[1], self.owner.position.last_move[0] * 20, self.owner.position.last_move[1] * 20) self.bullet_count += 1 self.dispatcher.add_event(BearEvent('play_sound', 'shot')) elif event.event_value in ('TK_D', 'TK_RIGHT'): move = (1, 0) self.owner.widget.switch_to_image('player_r') moved = True elif event.event_value in ('TK_A', 'TK_LEFT'): move = (-1, 0) self.owner.widget.switch_to_image('player_l') moved = True elif event.event_value in ('TK_S', 'TK_DOWN'): move = (0, 1) self.owner.widget.switch_to_image('player_d') moved = True elif event.event_value in ('TK_W', 'TK_UP'): move = (0, -1) self.owner.widget.switch_to_image('player_u') moved = True if moved: # Remembered for shots self.direction = move self.owner.position.relative_move(*move) return r
def on_event(self, event): """ See class documentation. :param event: BearEvent instance. """ # React to the events r = [] if event.event_type == 'ecs_move': entity_id, x, y = event.event_value if entity_id not in self.entities or entity_id not in self.widgets: # Silently ignore attempts to move nonexistent children # Some entities may not be shown right now, but still have a # PositionComponent that moves and emits events return # Checking if collision events need to be emitted # Check for collisions with border try: if x < 0 or x + self.widgets[entity_id].width \ > len(self._child_pointers[0]) or y < 0 or \ y + self.widgets[entity_id].height > len( self._child_pointers): r.append( BearEvent(event_type='ecs_collision', event_value=(entity_id, None))) else: # Apparently no collision with a border, can safely move try: self.move_child(self.widgets[entity_id], (x, y)) except: pass self.need_redraw = True except KeyError: # In some weird cases 'ecs_move' events can be emitted after the # entity got destroyed return elif event.event_type == 'ecs_create': self.add_entity(event.event_value) elif event.event_type == 'ecs_destroy': self.remove_entity(event.event_value) self.need_redraw = True elif event.event_type == 'ecs_remove': self.remove_child(self.entities[event.event_value].widget.widget) self.need_redraw = True elif event.event_type == 'ecs_add': entity_id, x, y = event.event_value self.add_child(self.widgets[entity_id], (x, y)) self.need_redraw = True elif event.event_type == 'ecs_scroll_to': try: self.scroll_to(event.event_value) self.need_redraw = True except BearLayoutException: pass elif event.event_type == 'ecs_scroll_by': try: self.scroll_by(event.event_value) self.need_redraw = True except BearLayoutException: pass elif event.event_type == 'ecs_update': # Some widget has decided it's time to redraw itself self.need_redraw = True elif event.event_type == 'service' and event.event_value == 'tick_over' \ and self.need_redraw: self._rebuild_self() self.terminal.update_widget(self) self.need_redraw = False if r: return r
def process_hitpoint_update(self): if self.hitpoints == 0 and hasattr(self.owner, 'destructor'): # self.dispatcher.add_event(BearEvent('play_sound', 'explosion')) self.dispatcher.add_event(BearEvent('play_sound', 'explosion')) self.owner.destructor.destroy()
def init_game(): """ Initalize all game variables and objects. :return: """ global atlas global field global building global tetris global figures global t global attractor global attractor2 global emitter global initial_figure global score global loop global dispatcher field = GravityField((60, 45)) building = BuildingWidget((60, 45)) tetris = TetrisSystem((60, 45)) figures = FigureManager(field=field, tetris=tetris, dispatcher=dispatcher, building=building, atlas=atlas) figures.register_terminal(t) dispatcher.register_listener( figures, ['request_destruction', 'request_installation']) dispatcher.register_listener(building, ['square', 'h7', 'v7']) sound = SoundListener({ 'fly_away': 'Fly.wav', 'explosion': 'Explosion.wav', 'connect': 'Connect.wav', 'fail': 'Fail.wav' }) dispatcher.register_listener(sound, 'play_sound') # The construction's start building.add_figure( Widget([[' ', '*', ' '], ['*', '*', '*'], [' ', '*', ' ']], [['blue', 'blue', 'blue'], ['blue', 'blue', 'blue'], ['blue', 'blue', 'blue']]), pos=(29, 20)) tetris[30][20] = 1 tetris[29][21] = 1 tetris[30][21] = 1 tetris[31][21] = 1 tetris[30][22] = 1 # Emitter and attractors attractor = Attractor(*atlas.get_element('attractor'), field=field, mass=150) field.add_attractor(attractor, (10, 25)) attractor2 = Attractor(*atlas.get_element('attractor'), field=field, mass=150) field.add_attractor(attractor2, (50, 25)) dispatcher.register_listener(attractor, ['misc_input', 'key_up', 'key_down']) dispatcher.register_listener(attractor2, ['misc_input', 'key_up', 'key_down']) emitter = EmitterWidget(*atlas.get_element('emitter'), manager=figures, dispatcher=dispatcher, tetris=tetris) dispatcher.register_listener( emitter, ['tick', 'service', 'request_installation', 'request_destruction']) initial_figure = figures.create_figure() dispatcher.register_listener(initial_figure, 'tick') score = ScoreCounter() dispatcher.register_listener(score, ['h7', 'v7', 'square']) # Adding stuff t.add_widget(score, pos=(39, 47), layer=1) t.add_widget(building, pos=(0, 0), layer=0) t.add_widget(attractor, pos=(10, 25), layer=1) t.add_widget(attractor2, pos=(50, 25), layer=3) t.add_widget(emitter, pos=(40, 40), layer=4) t.add_widget(initial_figure, pos=(25, 40), layer=6) dispatcher.add_event( BearEvent(event_type='request_destruction', event_value=initial_figure))
def on_event(self, event): if event.event_type == 'ecs_create': entity = event.event_value # if hasattr(entity, 'position') and hasattr(entity, 'collision'): self.entities[entity.id] = entity elif event.event_type == 'ecs_destroy': del (self.entities[event.event_value]) if event.event_value in self.currently_tracked: self.currently_tracked.remove(event.event_value) elif event.event_type == 'ecs_remove': if event.event_value in self.currently_tracked: self.currently_tracked.remove(event.event_value) elif event.event_type == 'ecs_add': # Anything added to the screen should have position and widget # But if it doesn't have CollisionComponent, it's not our problem if hasattr(self.entities[event.event_value[0]], 'collision'): self.currently_tracked.add(event.event_value[0]) elif event.event_type == 'ecs_move' \ and event.event_value[0] in self.currently_tracked: # Only process collisions between entities; if a collision into the # screen edge happens, it's the ECSLayout job to detect it moved_id, x, y = event.event_value moved_z = self.entities[moved_id].widget.z_level moved_depth = self.entities[moved_id].collision.depth moved_face = self.entities[moved_id].collision.face_position moved_face_size = self.entities[moved_id].collision.face_size moved_shift = self.entities[moved_id].collision.z_shift if moved_face_size == (0, 0): moved_face_size = self.entities[moved_id].widget.size r = [] for other_id in self.currently_tracked: other = self.entities[other_id] if other_id == moved_id or not hasattr(other, 'position') \ or not hasattr(other, 'collision'): continue other_z = other.widget.z_level other_depth = other.collision.depth other_shift = other.collision.z_shift if moved_z - moved_depth <= other_z and \ other_z - other_depth <= moved_z: # Only check if two entities are within collidable z-levels other_face = other.collision.face_position other_face_size = other.collision.face_size if other_face_size in ((0, 0), [0, 0]): # The interaction is unclean, but it prevents running # through this check every time something moves other.collision.face_size = other.widget.size z_range = (max(moved_z - moved_depth, other_z - other_depth), min(moved_z, other_z)) for z_level in range(z_range[0], z_range[1] + 1): moved_pos = (x + moved_face[0] + moved_shift[0] * (moved_z - z_level), y + moved_face[1] + moved_shift[1] * (moved_z - z_level)) other_pos = (other.position.x + other_face[0] + other_shift[0] * (other_z - z_level), other.position.y + other_face[1] + other_shift[1] * (other_z - z_level)) if rectangles_collide(moved_pos, moved_face_size, other_pos, other_face_size): r.append( BearEvent('ecs_collision', (moved_id, other_id))) continue return r
def on_event(self, event): """ See class documentation. :param event: BearEvent instance. """ # React to the events r = [] if event.event_type == 'ecs_move': entity_id, x, y = event.event_value # Checking if collision events need to be emitted # Check for collisions with border try: if x < 0 or x + self.entities[entity_id].widget.size[0] \ > len(self._child_pointers[0]) or y < 0 or \ y + self.entities[entity_id].widget.size[1] > len( self._child_pointers): r.append( BearEvent(event_type='ecs_collision', event_value=(entity_id, None))) else: # Apparently no collision with a border, can safely move self.move_child(self.widgets[entity_id], (x, y)) self.need_redraw = True collided = set() for y_offset in range( self.entities[entity_id].widget.size[1]): for x_offset in range( self.entities[entity_id].widget.size[0]): for other_widget in \ self._child_pointers[y + y_offset] \ [x + x_offset]: # Child_pointers is ECS-agnostic and stores pointers # to the actual widgets collided.add(other_widget) # TODO: optimize to avoid checking all entities in collision detector # Probably just storing all entity ids along with child pointers collided_ent_ids = set() for child in self.entities: if child != entity_id and \ self.entities[child].widget.widget in collided: collided_ent_ids.add(child) for child in collided_ent_ids: r.append(BearEvent('ecs_collision', (entity_id, child))) except KeyError: # In some weird cases 'ecs_move' events can be emitted after the # entity got destroyed return elif event.event_type == 'ecs_create': self.add_entity(event.event_value) self.need_redraw = True elif event.event_type == 'ecs_destroy': self.remove_entity(event.event_value) self.need_redraw = True elif event.event_type == 'ecs_remove': self.remove_child(self.entities[event.event_value].widget.widget) self.need_redraw = True elif event.event_type == 'ecs_add': entity_id, x, y = event.event_value self.add_child(self.widgets[entity_id], (x, y)) self.need_redraw = True elif event.event_type == 'ecs_scroll_to': try: self.scroll_to(event.event_value) self.need_redraw = True except BearLayoutException: pass elif event.event_type == 'ecs_scroll_by': try: self.scroll_by(event.event_value) self.need_redraw = True except BearLayoutException: pass elif event.event_type == 'ecs_update': # Some widget has decided it's time to redraw itself self.need_redraw = True elif event.event_type == 'service' and event.event_value == 'tick_over' \ and self.need_redraw: self._rebuild_self() self.terminal.update_widget(self) self.need_redraw = False if r: return r