Example #1
0
    def _put_loot_on_ground(self, enemy_death_position: Tuple[int, int],
                            loot: List[LootEntry]):
        for loot_entry in loot:
            if len(loot) > 1:
                position_offset = (random.randint(-20,
                                                  20), random.randint(-20, 20))
            else:
                position_offset = (0, 0)
            loot_position = sum_of_vectors(enemy_death_position,
                                           position_offset)

            if loot_entry.money_amount:
                money_pile_on_ground = create_money_pile_on_ground(
                    loot_entry.money_amount, loot_position)
                self.game_state.money_piles_on_ground.append(
                    money_pile_on_ground)
            elif loot_entry.item_type:
                item_on_ground = create_item_on_ground(loot_entry.item_type,
                                                       loot_position)
                self.game_state.items_on_ground.append(item_on_ground)
            elif loot_entry.consumable_type:
                consumable_on_ground = create_consumable_on_ground(
                    loot_entry.consumable_type, loot_position)
                self.game_state.consumables_on_ground.append(
                    consumable_on_ground)
Example #2
0
    def _put_loot_on_ground(self, enemy_death_position: Tuple[int, int],
                            loot: List[LootEntry]):
        for loot_entry in loot:
            if len(loot) > 1:
                position_offset = (random.randint(-20,
                                                  20), random.randint(-20, 20))
            else:
                position_offset = (0, 0)
            loot_position = sum_of_vectors(enemy_death_position,
                                           position_offset)

            if isinstance(loot_entry, MoneyLootEntry):
                money_pile_on_ground = create_money_pile_on_ground(
                    loot_entry.amount, loot_position)
                self.game_state.money_piles_on_ground.append(
                    money_pile_on_ground)
            elif isinstance(loot_entry, ItemLootEntry):
                item_id = randomized_item_id(loot_entry.item_type)
                item_on_ground = create_item_on_ground(item_id, loot_position)
                self.game_state.items_on_ground.append(item_on_ground)
            elif isinstance(loot_entry, SuffixedItemLootEntry):
                item_id = randomized_suffixed_item_id(loot_entry.item_type,
                                                      loot_entry.suffix_id)
                item_on_ground = create_item_on_ground(item_id, loot_position)
                self.game_state.items_on_ground.append(item_on_ground)
            elif isinstance(loot_entry, ConsumableLootEntry):
                consumable_on_ground = create_consumable_on_ground(
                    loot_entry.consumable_type, loot_position)
                self.game_state.consumables_on_ground.append(
                    consumable_on_ground)
Example #3
0
    def _add_smart_floor_tiles(self, tiles: List[Tuple[int, int, int, int]]):
        floor_cells = [
            ((r[0] - self.game_state.game_world.entire_world_area.x) //
             GRID_CELL_SIZE,
             (r[1] - self.game_state.game_world.entire_world_area.y) //
             GRID_CELL_SIZE) for r in tiles
        ]
        self.grid.add_floor_cells(floor_cells)
        xmin = min([cell[0] for cell in floor_cells]) - 1
        xmax = max([cell[0] for cell in floor_cells]) + 2
        ymin = min([cell[1] for cell in floor_cells]) - 1
        ymax = max([cell[1] for cell in floor_cells]) + 2

        for y in range(ymin, ymax):
            for x in range(xmin, xmax):
                pos = sum_of_vectors(
                    self.game_state.game_world.entire_world_area.topleft,
                    (x * GRID_CELL_SIZE, y * GRID_CELL_SIZE))
                is_even_cell = x % 2 == 0 and y % 2 == 0  # ground sprite covers 4 cells, so we only need them on even cells
                if is_even_cell and any([
                        self.grid.is_floor(c)
                        for c in [(x, y), (x + 1, y), (x, y + 1), (x + 1,
                                                                   y + 1)]
                ]):
                    _add_decoration(Sprite.DECORATION_GROUND_STONE,
                                    self.game_state, pos)
                if self.grid.is_wall((x, y)):
                    wall_type = DungeonGenerator.determine_wall_type(
                        self.grid, (x, y))
                    self._set_wall(pos, wall_type)
                if self.grid.is_floor((x, y)):
                    # print("deleting wall from cell (%i,%i)" % (pos))
                    self._delete_wall(pos)
    def handle_mouse_movement(
            self, mouse_screen_pos: Tuple[int,
                                          int]) -> Optional[MapEditorAction]:

        self._set_currently_hovered_component_not_hovered()

        self._mouse_screen_pos = mouse_screen_pos
        self._entity_icon_hovered_by_mouse = None

        self._snapped_mouse_screen_pos = (
            (mouse_screen_pos[0] // self.grid_cell_size) * self.grid_cell_size,
            (mouse_screen_pos[1] // self.grid_cell_size) * self.grid_cell_size)
        self._snapped_mouse_world_pos = sum_of_vectors(
            self._snapped_mouse_screen_pos, self.camera_world_area.topleft)
        self._is_snapped_mouse_within_world = self.world_area.collidepoint(
            self._snapped_mouse_world_pos[0], self._snapped_mouse_world_pos[1])
        self._is_snapped_mouse_over_ui = self._is_screen_position_within_ui(
            self._snapped_mouse_screen_pos)

        mouse_ui_pos = self._translate_screen_position_to_ui(mouse_screen_pos)

        for icon in self.entity_icons_by_type[self._shown_tab]:
            if icon.contains(mouse_ui_pos):
                self._on_hover_component(icon)
                entity = self._get_map_editor_entity_by_id(
                    icon.map_editor_entity_id)
                self._entity_icon_hovered_by_mouse = entity
                return

        for component in self._checkboxes + self._buttons + self._tab_buttons:
            if component.contains(mouse_ui_pos):
                self._on_hover_component(component)
                return

        if self._minimap.contains(mouse_ui_pos):
            self._on_hover_component(self._minimap)
            if self._is_mouse_button_down:
                position_ratio = self._minimap.get_position_ratio(mouse_ui_pos)
                return SetCameraPosition(position_ratio)
            return

        if self._is_mouse_button_down and self._is_snapped_mouse_within_world and not self._is_snapped_mouse_over_ui:
            if self._user_state.placing_entity:
                if self._user_state.placing_entity.wall_type or self._user_state.placing_entity.decoration_sprite:
                    return AddEntity(self._snapped_mouse_world_pos,
                                     self._user_state.placing_entity)
                elif self._user_state.placing_entity.is_smart_floor_tile:
                    return self._add_smart_floor_tiles()
            elif self._user_state.deleting_entities:
                return DeleteEntities(self._snapped_mouse_world_pos)
            elif self._user_state.deleting_decorations:
                return DeleteDecorations(self._snapped_mouse_world_pos)
            elif self._user_state.deleting_smart_floor_tiles:
                return self._delete_smart_floor_tiles()
            else:
                raise Exception("Unhandled user state: " +
                                str(self._user_state))
 def render_map_editor_world_entity_at_position(self, sprite: Sprite,
                                                entity_size: Tuple[int,
                                                                   int],
                                                position: Tuple[int, int]):
     image_with_relative_position = self._get_image_for_sprite(
         sprite, Direction.DOWN, 0)
     sprite_position = sum_of_vectors(
         position, image_with_relative_position.position_relative_to_entity)
     self.screen_render.image(image_with_relative_position.image,
                              sprite_position)
     self.screen_render.rect((50, 250, 0),
                             Rect(position[0], position[1], entity_size[0],
                                  entity_size[1]), 3)
Example #6
0
 def update(self, npc: NonPlayerCharacter, game_state: GameState,
            time_passed: Millis):
     self._time_since_summoning += time_passed
     if self._time_since_summoning > self._summoning_cooldown:
         necro_center_pos = npc.world_entity.get_center_position()
         self._time_since_summoning = 0
         self._alive_summons = [
             summon for summon in self._alive_summons
             if summon in game_state.game_world.non_player_characters
         ]
         if len(self._alive_summons) < self._max_summons:
             relative_pos_from_summoner = (random.randint(-150, 150),
                                           random.randint(-150, 150))
             summon_center_pos = sum_of_vectors(necro_center_pos,
                                                relative_pos_from_summoner)
             summon_type = random.choice(self._summon_npc_types)
             summon_size = NON_PLAYER_CHARACTERS[summon_type].size
             summon_pos = game_state.game_world.get_within_world(
                 get_position_from_center_position(summon_center_pos,
                                                   summon_size),
                 summon_size)
             summon_enemy = self._create_npc(summon_type, summon_pos)
             is_wall_blocking = game_state.game_world.walls_state.does_rect_intersect_with_wall(
                 rect_from_corners(necro_center_pos, summon_center_pos))
             is_position_blocked = game_state.game_world.would_entity_collide_if_new_pos(
                 summon_enemy.world_entity, summon_pos)
             if not is_wall_blocking and not is_position_blocked:
                 self._summoning_cooldown = self._random_summoning_cooldown(
                 )
                 game_state.game_world.add_non_player_character(
                     summon_enemy)
                 self._alive_summons.append(summon_enemy)
                 game_state.game_world.visual_effects.append(
                     VisualCircle((80, 150, 100), necro_center_pos, 40, 70,
                                  Millis(120), 3))
                 game_state.game_world.visual_effects.append(
                     VisualCircle((80, 150, 100), summon_center_pos, 40, 70,
                                  Millis(120), 3))
                 play_sound(SoundId.ENEMY_NECROMANCER_SUMMON)
             else:
                 # Failed to summon, so try again without waiting full duration
                 self._summoning_cooldown = 500
         else:
             self._summoning_cooldown = self._random_summoning_cooldown()
    def _put_loot_on_ground(self, enemy_death_position: Tuple[int, int],
                            loot: List[LootEntry]):
        for loot_entry in loot:
            if len(loot) > 1:
                position_offset = (random.randint(-20,
                                                  20), random.randint(-20, 20))
            else:
                position_offset = (0, 0)
            loot_position = sum_of_vectors(enemy_death_position,
                                           position_offset)

            if isinstance(loot_entry, MoneyLootEntry):
                money_pile_on_ground = create_money_pile_on_ground(
                    loot_entry.amount, loot_position)
                self.game_state.game_world.money_piles_on_ground.append(
                    money_pile_on_ground)
            elif isinstance(loot_entry, ItemLootEntry):
                item_id = randomized_item_id(loot_entry.item_type)
                item_on_ground = create_item_on_ground(item_id, loot_position)
                self.game_state.game_world.items_on_ground.append(
                    item_on_ground)
            elif isinstance(loot_entry, AffixedItemLootEntry):
                item_id = loot_entry.item_id
                item_on_ground = create_item_on_ground(item_id, loot_position)
                self.game_state.game_world.items_on_ground.append(
                    item_on_ground)
                loot_center_pos = (loot_position[0] + ITEM_ENTITY_SIZE[0] // 2,
                                   loot_position[1] + ITEM_ENTITY_SIZE[1] // 2)
                self.game_state.game_world.visual_effects.append(
                    VisualCircle((170, 200, 170), loot_center_pos, 30, 40,
                                 Millis(500), 2))
                self.game_state.game_world.visual_effects.append(
                    VisualCircle((70, 100, 70), loot_center_pos, 25, 35,
                                 Millis(500), 2))
            elif isinstance(loot_entry, ConsumableLootEntry):
                consumable_on_ground = create_consumable_on_ground(
                    loot_entry.consumable_type, loot_position)
                self.game_state.game_world.consumables_on_ground.append(
                    consumable_on_ground)
Example #8
0
 def _render_map_editor_world_entity_at_position(self, sprite: Sprite, position: Tuple[int, int]):
     image_with_relative_position = self._get_image_for_sprite(sprite, Direction.DOWN, 0)
     sprite_position = sum_of_vectors(position, image_with_relative_position.position_relative_to_entity)
     self._screen_render.image(image_with_relative_position.image, sprite_position)
    def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity,
                    _is_player_invisible: bool, time_passed: Millis):
        self._time_since_decision += time_passed
        self._time_since_summoning += time_passed
        self._time_since_healing += time_passed
        self._time_since_shoot += time_passed
        if self._time_since_summoning > self._summoning_cooldown:
            necro_center_pos = npc.world_entity.get_center_position()
            self._time_since_summoning = 0
            self._alive_summons = [summon for summon in self._alive_summons
                                   if summon in game_state.non_player_characters]
            if len(self._alive_summons) < 3:
                relative_pos_from_summoner = (random.randint(-150, 150), random.randint(-150, 150))
                summon_center_pos = sum_of_vectors(necro_center_pos, relative_pos_from_summoner)
                summon_type = random.choice([NpcType.ZOMBIE, NpcType.MUMMY])
                summon_size = NON_PLAYER_CHARACTERS[summon_type].size
                summon_pos = game_state.get_within_world(
                    get_position_from_center_position(summon_center_pos, summon_size), summon_size)
                summon_enemy = create_npc(summon_type, summon_pos)
                is_wall_blocking = game_state.walls_state.does_rect_intersect_with_wall(
                    rect_from_corners(necro_center_pos, summon_center_pos))
                is_position_blocked = game_state.would_entity_collide_if_new_pos(summon_enemy.world_entity, summon_pos)
                if not is_wall_blocking and not is_position_blocked:
                    self._summoning_cooldown = self._random_summoning_cooldown()
                    game_state.add_non_player_character(summon_enemy)
                    self._alive_summons.append(summon_enemy)
                    game_state.visual_effects.append(
                        VisualCircle((80, 150, 100), necro_center_pos, 40, 70, Millis(120), 3))
                    game_state.visual_effects.append(
                        VisualCircle((80, 150, 100), summon_center_pos, 40, 70, Millis(120), 3))
                    play_sound(SoundId.ENEMY_NECROMANCER_SUMMON)
                else:
                    # Failed to summon, so try again without waiting full duration
                    self._summoning_cooldown = 500
            else:
                self._summoning_cooldown = self._random_summoning_cooldown()

        if self._time_since_healing > self._healing_cooldown:
            self._time_since_healing = 0
            self._healing_cooldown = self._random_healing_cooldown()
            necro_center_pos = npc.world_entity.get_center_position()
            nearby_hurt_enemies = [
                e for e in game_state.non_player_characters
                if e.is_enemy
                   and is_x_and_y_within_distance(necro_center_pos, e.world_entity.get_center_position(), 200)
                   and e != npc and not e.health_resource.is_at_max()
            ]
            if nearby_hurt_enemies:
                healing_target = nearby_hurt_enemies[0]
                healing_target.health_resource.gain(5)
                healing_target_pos = healing_target.world_entity.get_center_position()
                visual_line = VisualLine((80, 200, 150), necro_center_pos, healing_target_pos, Millis(350), 3)
                game_state.visual_effects.append(visual_line)
                play_sound(SoundId.ENEMY_NECROMANCER_HEAL)

        if self._time_since_shoot > self._shoot_cooldown:
            self._time_since_shoot = 0
            self._shoot_cooldown = self._random_shoot_cooldown()
            npc.world_entity.direction = get_directions_to_position(npc.world_entity, player_entity.get_position())[0]
            npc.world_entity.set_not_moving()
            center_position = npc.world_entity.get_center_position()
            distance_from_enemy = 35
            projectile_pos = translate_in_direction(
                get_position_from_center_position(center_position, PROJECTILE_SIZE),
                npc.world_entity.direction, distance_from_enemy)
            projectile_speed = 0.2
            projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.NONE, npc.world_entity.direction,
                                            projectile_speed)
            projectile = Projectile(projectile_entity, create_projectile_controller(PROJECTILE_TYPE))
            game_state.projectile_entities.append(projectile)
            play_sound(SoundId.ENEMY_ATTACK_NECRO)

        if self._time_since_decision > self._decision_interval:
            self._time_since_decision = 0
            if random.random() < 0.2:
                direction = random_direction()
                npc.world_entity.set_moving_in_dir(direction)
            else:
                npc.world_entity.set_not_moving()
Example #10
0
 def image_with_relative_pos(
         self, image_with_relative_position: ImageWithRelativePosition,
         pos: Tuple[int, int]):
     translated_pos = sum_of_vectors(
         pos, image_with_relative_position.position_relative_to_entity)
     self.image(image_with_relative_position.image, translated_pos)
Example #11
0
def main(map_file_name: Optional[str]):
    map_file_path = MAP_DIR + (map_file_name or "map1.json")

    if Path(map_file_path).exists():
        game_state = create_game_state_from_json_file(CAMERA_SIZE,
                                                      map_file_path, HERO_ID)
    else:
        player_entity = create_hero_world_entity(HERO_ID, (0, 0))
        player_state = create_player_state(HERO_ID)
        game_state = GameState(player_entity, [], [], [], [], [], CAMERA_SIZE,
                               Rect(-250, -250, 500, 500), player_state, [],
                               [], [])

    pygame.init()

    pygame_screen = pygame.display.set_mode(SCREEN_SIZE)
    images_by_sprite = load_images_by_sprite(ENTITY_SPRITE_INITIALIZERS)
    images_by_ui_sprite = load_images_by_ui_sprite(UI_ICON_SPRITE_PATHS,
                                                   MAP_EDITOR_UI_ICON_SIZE)
    images_by_portrait_sprite = load_images_by_portrait_sprite(
        PORTRAIT_ICON_SPRITE_PATHS, PORTRAIT_ICON_SIZE)
    world_view = GameWorldView(pygame_screen, CAMERA_SIZE, SCREEN_SIZE,
                               images_by_sprite)
    ui_view = MapEditorView(pygame_screen, CAMERA_SIZE, SCREEN_SIZE,
                            images_by_sprite, images_by_ui_sprite,
                            images_by_portrait_sprite)

    user_state = UserState.deleting_entities()

    is_mouse_button_down = False

    possible_grid_cell_sizes = [25, 50]
    grid_cell_size_index = 0
    grid_cell_size = possible_grid_cell_sizes[grid_cell_size_index]

    camera_move_distance = 75  # must be a multiple of the grid size
    snapped_mouse_screen_position = (0, 0)
    snapped_mouse_world_position = (0, 0)
    exact_mouse_screen_position = (0, 0)
    is_snapped_mouse_within_world = True
    is_snapped_mouse_over_ui = False

    game_state.center_camera_on_player()
    game_state.camera_world_area.topleft = (
        (game_state.camera_world_area.x // grid_cell_size) * grid_cell_size,
        (game_state.camera_world_area.y // grid_cell_size) * grid_cell_size)

    held_down_arrow_keys = set([])
    clock = pygame.time.Clock()
    camera_pan_timer = PeriodicTimer(Millis(50))

    shown_tab = EntityTab.ITEMS

    while True:

        for event in pygame.event.get():
            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN
                                             and event.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

            if event.type == pygame.MOUSEMOTION:
                exact_mouse_screen_position: Tuple[int, int] = event.pos
                ui_view.handle_mouse_movement(exact_mouse_screen_position)
                snapped_mouse_screen_position = (
                    (exact_mouse_screen_position[0] // grid_cell_size) *
                    grid_cell_size,
                    (exact_mouse_screen_position[1] // grid_cell_size) *
                    grid_cell_size)
                snapped_mouse_world_position = sum_of_vectors(
                    snapped_mouse_screen_position,
                    game_state.camera_world_area.topleft)
                is_snapped_mouse_within_world = game_state.is_position_within_game_world(
                    snapped_mouse_world_position)
                is_snapped_mouse_over_ui = ui_view.is_screen_position_within_ui(
                    snapped_mouse_screen_position)
                if is_mouse_button_down and is_snapped_mouse_within_world and not is_snapped_mouse_over_ui:
                    if user_state.placing_entity:
                        if user_state.placing_entity.wall_type:
                            _add_wall(game_state, snapped_mouse_world_position,
                                      user_state.placing_entity.wall_type)
                        elif user_state.placing_entity.decoration_sprite:
                            _add_decoration(
                                user_state.placing_entity.decoration_sprite,
                                game_state, snapped_mouse_world_position)
                    elif user_state.deleting_entities:
                        _delete_map_entities_from_position(
                            game_state, snapped_mouse_world_position)
                    else:
                        _delete_map_decorations_from_position(
                            game_state, snapped_mouse_world_position)

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_s:
                    save_file = map_file_path
                    save_game_state_to_json_file(game_state, save_file)
                    print("Saved state to " + save_file)
                elif event.key in [
                        pygame.K_RIGHT, pygame.K_DOWN, pygame.K_LEFT,
                        pygame.K_UP
                ]:
                    held_down_arrow_keys.add(event.key)
                elif event.key == pygame.K_q:
                    user_state = UserState.deleting_entities()
                elif event.key == pygame.K_z:
                    user_state = UserState.deleting_decorations()
                elif event.key == pygame.K_PLUS:
                    grid_cell_size_index = (grid_cell_size_index +
                                            1) % len(possible_grid_cell_sizes)
                    grid_cell_size = possible_grid_cell_sizes[
                        grid_cell_size_index]
                elif event.key == pygame.K_v:
                    shown_tab = EntityTab.ITEMS
                elif event.key == pygame.K_b:
                    shown_tab = EntityTab.NPCS
                elif event.key == pygame.K_n:
                    shown_tab = EntityTab.WALLS
                elif event.key == pygame.K_m:
                    shown_tab = EntityTab.MISC

            if event.type == pygame.KEYUP:
                if event.key in held_down_arrow_keys:
                    held_down_arrow_keys.remove(event.key)

            if event.type == pygame.MOUSEBUTTONDOWN:
                ui_view.handle_mouse_click()
                is_mouse_button_down = True
                if user_state.placing_entity:
                    entity_being_placed = user_state.placing_entity
                    if is_snapped_mouse_within_world and not is_snapped_mouse_over_ui:
                        if entity_being_placed.is_player:
                            game_state.player_entity.set_position(
                                snapped_mouse_world_position)
                        elif entity_being_placed.npc_type:
                            _add_npc(entity_being_placed.npc_type, game_state,
                                     snapped_mouse_world_position)
                        elif entity_being_placed.wall_type:
                            _add_wall(game_state, snapped_mouse_world_position,
                                      entity_being_placed.wall_type)
                        elif entity_being_placed.consumable_type:
                            _add_consumable(
                                entity_being_placed.consumable_type,
                                game_state, snapped_mouse_world_position)
                        elif entity_being_placed.item_type:
                            _add_item(entity_being_placed.item_type,
                                      game_state, snapped_mouse_world_position)
                        elif entity_being_placed.decoration_sprite:
                            _add_decoration(
                                entity_being_placed.decoration_sprite,
                                game_state, snapped_mouse_world_position)
                        elif entity_being_placed.money_amount:
                            _add_money(entity_being_placed.money_amount,
                                       game_state,
                                       snapped_mouse_world_position)
                        elif entity_being_placed.portal_id:
                            _add_portal(entity_being_placed.portal_id,
                                        game_state,
                                        snapped_mouse_world_position)
                        elif entity_being_placed.is_chest:
                            _add_chest(game_state,
                                       snapped_mouse_world_position)
                        else:
                            raise Exception("Unknown entity: " +
                                            str(entity_being_placed))
                elif user_state.deleting_entities:
                    _delete_map_entities_from_position(
                        game_state, snapped_mouse_world_position)
                else:
                    _delete_map_decorations_from_position(
                        game_state, snapped_mouse_world_position)

            if event.type == pygame.MOUSEBUTTONUP:
                is_mouse_button_down = False

        clock.tick()
        time_passed = clock.get_time()

        if camera_pan_timer.update_and_check_if_ready(time_passed):
            if pygame.K_RIGHT in held_down_arrow_keys:
                game_state.translate_camera_position((camera_move_distance, 0))
            if pygame.K_LEFT in held_down_arrow_keys:
                game_state.translate_camera_position(
                    (-camera_move_distance, 0))
            if pygame.K_DOWN in held_down_arrow_keys:
                game_state.translate_camera_position((0, camera_move_distance))
            if pygame.K_UP in held_down_arrow_keys:
                game_state.translate_camera_position(
                    (0, -camera_move_distance))

        entities_to_render = game_state.get_all_entities_to_render()
        decorations_to_render = game_state.get_decorations_to_render()
        world_view.render_world(
            all_entities_to_render=entities_to_render,
            decorations_to_render=decorations_to_render,
            player_entity=game_state.player_entity,
            is_player_invisible=game_state.player_state.is_invisible,
            player_active_buffs=game_state.player_state.active_buffs,
            camera_world_area=game_state.camera_world_area,
            non_player_characters=game_state.non_player_characters,
            visual_effects=game_state.visual_effects,
            render_hit_and_collision_boxes=ui_view.
            checkbox_show_entity_outlines.checked,
            player_health=game_state.player_state.health_resource.value,
            player_max_health=game_state.player_state.health_resource.
            max_value,
            entire_world_area=game_state.entire_world_area,
            entity_action_text=None)

        camera_world_area = game_state.camera_world_area
        world_area = game_state.entire_world_area
        camera_rect_ratio = ((camera_world_area.x - world_area.x) /
                             world_area.w,
                             (camera_world_area.y - world_area.y) /
                             world_area.h, camera_world_area.w / world_area.w,
                             camera_world_area.h / world_area.h)

        npc_positions_ratio = [
            ((npc.world_entity.x - world_area.x) / world_area.w,
             (npc.world_entity.y - world_area.y) / world_area.h)
            for npc in game_state.non_player_characters
        ]
        wall_positions_ratio = [
            ((wall.world_entity.x - world_area.x) / world_area.w,
             (wall.world_entity.y - world_area.y) / world_area.h)
            for wall in game_state.walls_state.walls
        ]

        if shown_tab == EntityTab.ITEMS:
            shown_entities = ITEM_ENTITIES
        elif shown_tab == EntityTab.NPCS:
            shown_entities = NPC_ENTITIES
        elif shown_tab == EntityTab.WALLS:
            shown_entities = WALL_ENTITIES
        elif shown_tab == EntityTab.MISC:
            shown_entities = MISC_ENTITIES
        else:
            raise Exception("Unknown entity tab: " + str(shown_tab))

        ui_view.set_shown_tab(shown_tab)

        entity_icon_hovered_by_mouse = ui_view.render(
            entities=shown_entities,
            placing_entity=user_state.placing_entity,
            deleting_entities=user_state.deleting_entities,
            deleting_decorations=user_state.deleting_decorations,
            num_enemies=len(game_state.non_player_characters),
            num_walls=len(game_state.walls_state.walls),
            num_decorations=len(
                game_state.decorations_state.decoration_entities),
            grid_cell_size=grid_cell_size,
            mouse_screen_position=exact_mouse_screen_position,
            camera_rect_ratio=camera_rect_ratio,
            npc_positions_ratio=npc_positions_ratio,
            wall_positions_ratio=wall_positions_ratio)

        if is_mouse_button_down and entity_icon_hovered_by_mouse:
            user_state = UserState.placing_entity(entity_icon_hovered_by_mouse)

        if is_snapped_mouse_over_ui:
            pass
            # render nothing over UI
        elif not is_snapped_mouse_within_world:
            snapped_mouse_rect = Rect(snapped_mouse_screen_position[0],
                                      snapped_mouse_screen_position[1],
                                      grid_cell_size, grid_cell_size)
            ui_view.render_map_editor_mouse_rect((250, 50, 0),
                                                 snapped_mouse_rect)
        elif user_state.placing_entity:
            entity_being_placed = user_state.placing_entity
            ui_view.render_map_editor_world_entity_at_position(
                entity_being_placed.sprite, entity_being_placed.entity_size,
                snapped_mouse_screen_position)
        elif user_state.deleting_entities:
            snapped_mouse_rect = Rect(snapped_mouse_screen_position[0],
                                      snapped_mouse_screen_position[1],
                                      grid_cell_size, grid_cell_size)
            ui_view.render_map_editor_mouse_rect((250, 250, 0),
                                                 snapped_mouse_rect)
        elif user_state.deleting_decorations:
            snapped_mouse_rect = Rect(snapped_mouse_screen_position[0],
                                      snapped_mouse_screen_position[1],
                                      grid_cell_size, grid_cell_size)
            ui_view.render_map_editor_mouse_rect((0, 250, 250),
                                                 snapped_mouse_rect)
        else:
            raise Exception("Unhandled user_state: " + str(user_state))

        pygame.display.update()