def take_turn(self): """...""" # some kind of lock to prevent double calling AND queueing of # same objects on a single turn. monster = self.owner monster.path = None if self.num_turns > 0: # still confused... # move in a random direction, and decrease the number of turns # confused if monster.visible: monster.scene.msg_log.add( (monster.name + " looks confused"), get_color("pink")) if random.randint(1, 100) > 33: monster.move_rnd() else: monster.move() self.num_turns -= 1 # restore the previous AI (this one will be deleted because it's not # referenced anymore) else: self.effect = False monster.ai = monster.default_ai monster.color = monster.default_color monster.scene.msg_log.add( 'The ' + monster.name + ' is no longer confused!', get_color("yellow"))
def cast_fireball(who, target): """...""" if target is None: return 'cancelled' current_level = who.current_level msg_log = who.scene.msg_log msg_log.add( 'The fireball explodes, burning everything within ' + str(FIREBALL_RADIUS) + ' tiles!', get_color("yellow")) area = current_level.get_area(pos=target.pos, radius=FIREBALL_RADIUS) for pos in area: for creature in current_level[pos].creatures: if creature.combat: msg_log.add("The {} gets burned for {} hit points.".format( creature.name, FIREBALL_DAMAGE), get_color("orange")) creature.combat.receive_dmg(FIREBALL_DAMAGE, source=who) who.scene.tile_fx.add( coord=area, color=get_color("orange"), duration=1) return True
def __init__(self, *, new=True, character=None, mode='Pit', **kwargs): """...""" super().__init__() self.mode = mode self.create_layers(character=character, new=new, msg="Creating a world of treasures and dangers...") self.set_fov() self.on_update() self.msg_log.clear() if new: self.msg_log.add( ('Welcome stranger! ' 'Prepare to perish in the Tombs of the Ancient Kings.'), get_color("purple")) else: self.msg_log.add( ('Welcome back stranger! ' 'This time you WILL perish in the Tombs of the Ancient' ' Kings!'), get_color("purple")) self.msg_log.add(('You are at level {} of the dungeon.'.format( self.current_level.header.level)), get_color("orange")) # self.manager.disable_fps() # [layer.__setattr__('visible', False) for layer in self.layers] self.on_update()
def __init__(self, parent, name, alpha=90): """...""" self.max_width = 160 super().__init__(parent=parent, rect=Rect(16, 16, self.max_width, 26)) self.color_table = LBPercentTable( default_to_first=True, table=( (0, (223, 0, 0, 192)), # red (25, (*get_color("yellow")[:3], 176)), (50, (*get_color("lime")[:3], 160)), (75, (*get_color("green")[:3], 144)))) self.previous_value = None self.previous_max = None
def cast_lightning(who, target=None): # find closest enemy (inside a maximum range) and damage it monster = who.combat.closest_monster( who=who, max_range=LIGHTNING_RANGE) if monster is None: # no enemy found within maximum range who.scene.msg_log.add( 'No enemy is close enough to strike.', get_color("yellow")) return 'cancelled' # zap it! who.scene.msg_log.add( 'A lighting bolt strikes the ' + monster.name + ' with a loud thunder! The damage is ' + str(LIGHTNING_DAMAGE) + ' hit points.', get_color("light_blue")) monster.combat.receive_dmg(LIGHTNING_DAMAGE)
def create_items(self): items = self._items item_render = [] main_font_size = int(self.head_font_size // 1.1) self.main_font = self.fonts.load('caladea-regular.ttf', main_font_size) main_font_size = self.main_font.size("A") for i, item in enumerate(items): txt_sfc = self.main_font.render(item, True, self.unselected_color) txt_obj = txt_sfc.get_rect() x, y = (self.screen_size[0] // 2, self.head_title_obj.bottom + self.head_title_obj.height * 2 + main_font_size[1] * i) txt_obj.midtop = x, y txt_shd_sfc = self.main_font.render(item, True, get_color("black")) txt_shd_obj = txt_shd_sfc.get_rect() txt_shd_obj.midtop = x + 2, y + 2 item_render.append( ((txt_sfc, txt_obj), (txt_shd_sfc, txt_shd_obj))) self.item_render = item_render
def add(self, string, color=None): if color is None: color = get_color("desaturated_green") print(string) img = self.font.render(string, color) self._history.append(img) self._history = self._history[-5:]
def equip(self): """Equip object and show a message about it. If the slot is already being used, unequip whatever is there first. """ equipped_in_slot = self.possessor.combat.equipped_in_slot if self.type == 'two-handed melee weapons': for slot in ['right hand', 'left hand']: old_equipment = equipped_in_slot(slot) if old_equipment: old_equipment.unequip() self.slot = 'right hand' else: for slot in ['right hand', 'left hand']: old_equipment = equipped_in_slot(slot) if old_equipment and \ old_equipment.type == 'two-handed melee weapons': old_equipment.unequip() self.slot = 'right hand' break elif not old_equipment: self.slot = slot break if self.slot is None: self.slot = 'right hand' old_equipment = equipped_in_slot(self.slot) old_equipment.unequip() self.is_equipped = True self.scene.msg_log.add( 'Equipped ' + self.owner.name + ' on ' + self.slot + '.', get_color("light_green"))
def cast_heal(who, target=None): if target is None: target = who elif not hasattr(target, 'combat'): who.scene.msg_log.add( "You can't heal the " + target.name + " .", get_color("yellow")) return 'cancelled' # heal the target if target.combat.hit_points_current == target.combat.hit_points_total: who.scene.msg_log.add( 'You are already at full health.', get_color("yellow")) return 'cancelled' target.combat.heal(HEAL_AMOUNT) who.scene.msg_log.add( 'Your wounds start to feel better!', get_color("light_violet"))
def change_dng_level(who, direction): if direction == "down": who.scene.msg_log.add( 'There are stairs going {} here.'.format(direction) + "You descend deeper into the heart of the dungeon...", get_color("orange")) who.scene.new_level(who.scene.current_level + 1) return True
def increase_stat(self, choice): """...""" id, desc = choice self.combat._base_att[id] += 1 self.update_hp() self.scene.gfx.msg_log.add(desc.replace("current", "previous"), get_color("cyan"))
def player_death(victim): """...""" # the game ended! victim.scene.msg_log.add('You died!') victim.scene.state = 'dead' # for added effect, transform the player into a corpse! victim.id = ord('%') victim.color = get_color("dark_red")
def rnd_cast_confuse(who, target=None): # find closest enemy in-range and confuse it monster = who.combat.closest_monster( who=who, max_range=CONFUSE_RANGE) if monster is None: # no enemy found within maximum range who.scene.msg_log.add( 'No enemy is close enough to confuse.', get_color("yellow")) return 'cancelled' # replace the monster's AI with a "confused" one; after some turns it will # restore the old AI monster.ai = ai_comp.Confused() monster.ai.owner = monster # tell the new component who owns it monster.color = get_color("pink") who.scene.msg_log.add( 'The eyes of the ' + monster.name + ' look vacant, as he starts to stumble around!', get_color("light_green"))
def pick_up(self, getter): """...""" msg_log = self.scene.msg_log # add to the player's inventory and remove from the map if len(getter.inventory) >= 26: if getter == self.player: self.scene.msg_log.add( 'Your inventory is full, cannot pick up ' + self.owner.name + '.', get_color("yellow")) else: self.scene.rem_obj(self.owner, 'objects', self.owner.pos) getter.inventory.append(self.owner) self.possessor = getter msg_log.add( 'You picked up a ' + self.owner.name + '!', get_color("blue"))
def unequip(self): """Unequip object and show a message about it.""" if not self.is_equipped: return self.is_equipped = False self.scene.msg_log.add( 'Unequipped ' + self.owner.name + ' from ' + self.slot + '.', get_color("light_yellow")) self.slot = None
def __init__(self, **kwargs): """...""" combat = kwargs.pop("combat", None) if combat is None: race = kwargs.pop("race", None) _class = kwargs.pop('_class', None) combat = creatures.Character(race=race, _class=_class, owner=self) self.combat = combat self.color = get_color("yellow") super().__init__(name=combat.name, **kwargs)
def __init__(self, *, parent, rect=None): """...""" super().__init__(parent=parent, rect=rect) self.text = " " self.font = Font(name='caladea-bold.ttf', font_size=14, renderer=parent.manager.fonts, color=get_color("desaturated_green")) self.def_topright = self.parent.manager.width - 8, 8 self.previous_text = None self.surface = None
def gain_ability(): self.scene.gfx.msg_log.add( 'Your battle skills grow stronger! You reached level ' + str(self.combat.level) + '!', get_color("cyan")) self.scene.parent.choice( title='Level up! Choose a stat to raise:', items=[ 'Strength (current: {})'.format(self.combat._base_att[0]), 'Dexterity (current: {})'.format(self.combat._base_att[1]), 'Constitution (current: {})'.format( self.combat._base_att[2]) ], callback=self.increase_stat)
def monster_death(victim): """...""" import obj_components # transform it into a nasty corpse! it doesn't block, can't be # attacked and doesn't move victim.scene.msg_log.add('{} is dead! You gain {} xp'.format( victim.name.capitalize(), victim.combat.xp_award), get_color("cyan")) victim.scene.rem_obj(victim, 'creatures', victim.pos) if random.randint(1, 100) > 66: victim.id = ord('%') victim.color = get_color("dark_red") victim.item = obj_components.Item('remains') victim.item.owner = victim victim.name = 'remains of ' + victim.name victim.scene.add_obj(victim, 'objects', victim.pos) """TODO: standardize death as method.""" victim.blocks = False victim.combat = None victim.ai = None
def cast_confuse(who, target=None): status = 'ok' if target is None or not hasattr(target, 'combat'): status = 'cancelled' elif target.combat is None: status = 'cancelled' if status == 'cancelled': who.scene.msg_log.add("Thats not a valid target.", get_color("yellow")) return 'cancelled' else: # replace the monster's AI with a "confused" one; after some turns # it will restore the old AI target.ai = ai.Confused() target.ai.owner = target # tell the new component who owns it target.color = get_color("pink") who.scene.msg_log.add( 'The eyes of the ' + target.name + ' look vacant, as he starts to stumble around!', get_color("pink"))
def drop(self, dropper): """Add to the map and remove from the player's inventory. Also, place it at the player's coordinates. If it is an equipment, unequip it first. """ if self.owner.equipment: self.owner.equipment.unequip() self.scene.add_obj(self.owner, 'objects', self.owner.pos) dropper.inventory.remove(self.owner) self.possessor = None self.scene.msg_log.add( 'You dropped a ' + self.owner.name + '.', get_color("yellow")) return 'dropped'
def create_head(self): self.screen_size = screen_size = self.screen.get_size() self.head_font_size = head_font_size = screen_size[1] // 17 self.head_font = self.fonts.load('caladea-regular.ttf', head_font_size) x, y = (screen_size[0] // 2, screen_size[1] // 5) self.head_title_sfc = self.head_font.render(self._title, True, self.title_color) self.head_title_obj = self.head_title_sfc.get_rect() self.head_title_obj.center = (x, y) self.head_title_shadow_sfc = self.head_font.render( self._title, True, get_color("darker_gray")) self.head_title_shadow_obj = self.head_title_sfc.get_rect() self.head_title_shadow_obj.center = (x + 2, y + 2)
def equip(self): """Equip object and show a message about it. If the slot is already being used, unequip whatever is there first. """ equipped_in_slot = self.possessor.combat.equipped_in_slot self.slot = 'left hand' if self.type == 'shields' else 'body' old_equipment = equipped_in_slot(self.slot) if old_equipment: old_equipment.unequip() self.is_equipped = True self.scene.msg_log.add( 'Equipped ' + self.owner.name + ' on ' + self.slot + '.', get_color("light_green"))
def set_inventory(self, holder, target=None, mode="use"): """...""" self.holder = holder self.target = target self.mode = mode self.inventory = self.holder.inventory self.inv_render = [] offset = self.offset main_font = self.main_font limit = self.rows * self.cols for i, item in enumerate(self.inventory[offset:offset + limit]): row = i % self.rows col = i // self.rows if item.equipment: if item.equipment.is_equipped: text = "*{} ({})".format(item.name, item.equipment.slot) else: text = "{} ({})".format(item.name, item.equipment.slot) color = item.color else: text = item.name + str(i) color = None txt_sfc = main_font.render(text, color) txt_obj = txt_sfc.get_rect() txt_obj.left = (self.main_rect.left + self.tab_x + self.main_item_w * col) txt_obj.top = self.main_rect.top + (self.main_item_h * row) + 12 txt_sfc.set_rect(txt_obj) txt_shd_sfc = main_font.render(text, color=get_color("black")) txt_shd_sfc.set_rect(txt_obj.mode(2, 2)) self.inv_render.append(txt_shd_sfc, txt_sfc)
def create_head(self): """...""" factory = self.manager.factory head_font = self.head_font h = self.head_font.height * 2 w = int(self.w * 0.9) x, y = int(self.w * 0.05), int(self.h * 0.05) head_rect = head_rect = Rect(x, y, w, h) self.head_surface = factory.from_gradient(rect=head_rect) head_title_sfc = head_font.render("< Inventory >") head_title_obj = head_title_sfc.get_rect() head_title_obj.center = head_rect.center head_title_sfc.set_rect(head_title_obj) head_title_shadow_sfc = head_font.render("< Inventory >", color=get_color("black")) head_title_shadow_sfc.set_rect(head_title_obj.move(2, 2)) self.head_rect = head_rect self.head_title_sfc = head_title_sfc self.head_title_shadow_sfc = head_title_shadow_sfc
def on_update(self): """...""" text = self.text if text is None: return if self.text_rendered != text or not self.sprites: render = self.font.render msg_sfc = render(text) msg_obj = msg_sfc.get_rect() msg_obj.center = self.center msg_sfc.set_rect(msg_obj) msg_shadow_sfc = render(text, color=get_color("gray")) msg_shadow_sfc.set_rect(msg_obj.move(2, 2)) self.sprites = [msg_shadow_sfc, msg_sfc] self.text_rendered = text if self.sprites: self.parent.manager.spriterenderer.render(sprites=self.sprites)
def create_fonts(self): """...""" fonts = self.fonts color = get_color("chartreuse") self.head_font = Font(font_size=26, color=color, renderer=fonts) self.main_font = Font(font_size=18, color=color, renderer=fonts)
class GuiTextbox(GuiButton): """Textbox GUI element.""" states = ["regular", "disabled"] radius = 0 regular_color = get_color("darkest_grey") regular_color_mod = None regular_alpha_mod = None regular_font_color = get_color("green") outline_color = get_color("black") outline_width = 2 clear_on_execute = False # remove text upon enter disable_on_execute = True blink_speed = 500 # prompt blink time in milliseconds delete_speed = 60 # backspace held clear speed in milliseconds def __init__(self, rect, command, **kwargs): """Initialization. Args.: command (method): callback to execute when enter key is pressed. Callback will receive 2 arguments: id and final (the string in the textbox). """ self.buffer = array("B") self.blink = True self.blink_timer = 0.0 self.delete_timer = 0.0 super().__init__(rect=rect, command=command, **kwargs) self.create_cursor() self.manager.set_text_input(True, self) def on_text_input(self, event): """...""" if event.type == sdl2.SDL_TEXTINPUT: self.buffer.append(ord(event.text.text)) self.create_text() def on_key_press(self, event, mod): """super__doc__.""" sym = event.key.keysym.sym if sym == sdl2.SDLK_BACKSPACE: self.handle_backspace() def on_key_release(self, event, mod): """super__doc__.""" sym = event.key.keysym.sym if sym == sdl2.SDLK_RETURN: self.execute() def on_mouse_motion(self, x, y, dx, dy): """super__doc__.""" if self.collidepoint(x=x, y=y): self.disabled = False else: self.disabled = True def on_mouse_press(self, event, x, y, button, double): """super__doc__.""" if self.collidepoint(x=x, y=y): self.disabled = False else: self.disabled = True def execute(self): """...""" if self.command: self.command(self, self.regular_text) self.active = not self.disable_on_execute if self.clear_on_execute: del self.buffer[:] def switch_blink(self): """...""" ticks = sdl2.SDL_GetTicks() if ticks - self.blink_timer > self.blink_speed: self.blink = not self.blink self.blink_timer = ticks def handle_backspace(self): """...""" ticks = sdl2.SDL_GetTicks() if ticks - self.delete_timer > self.delete_speed: self.delete_timer = ticks try: self.buffer.pop() except IndexError: pass else: self.create_text() @property def regular_text(self): """...""" return self.buffer.tobytes().decode("utf-8") or " " def create_cursor(self): """...""" self.cursor = self.fonts.render(text="|", name=self.regular_font, size=self.regular_font_size, color=self.regular_font_color) self.cursor.set_color_mod(self.regular_font_color_mod) def on_update(self): """super__doc__.""" self.switch_blink() if self.disabled: state = "disabled" elif self.clicked: state = "clicked" elif self.hovered: state = "hovered" else: state = "regular" sprites = self.sprites[state] text_sprite = sprites[1] rect = text_sprite.get_rect() if self.blink: sprites = (*sprites, self.cursor) self.cursor.position = rect.topright self.manager.spriterenderer.render(sprites=sprites) def create_text(self): """Create the text sprites.""" fonts = self.manager.fonts text_size = fonts.text_size text = self.regular_text font = self.regular_font color = self.regular_font_color size = self.regular_font_size # e.g. regular_text, regular_font, regular_font_size, # regular_font_color, regular_font_color_mod for state in self.states: start = 0 sprite_text = getattr(self, "%s_text" % state) or text sprite_font = getattr(self, "%s_font" % state) or font sprite_size = getattr(self, "%s_font_size" % state) or size sprite_color = getattr(self, "%s_font_color" % state) or color sprite_color_mod = getattr(self, "%s_font_color_mod" % state) while (text_size(sprite_text[start:], sprite_font, sprite_size)[0] > (self.width - 6)): start += 1 sprite_text = sprite_text[start:] sprite = fonts.render(text=sprite_text, name=sprite_font, size=sprite_size, color=sprite_color) sprite.set_color_mod(sprite_color_mod) button_sprite = self.sprites[state][0] sprite.center_on(button_sprite) self.sprites[state] = (button_sprite, sprite)
def __init__(self, name, **kwargs): """...""" self.combat = creatures.Beast(model=name, owner=self) self.color = get_color('blood_red') self.ai = ai.Basic(owner=self) super().__init__(name=self.combat.name, **kwargs)
def take_turn(self): """A basic monster takes its turn.""" # some kind of lock to prevent double calling AND queueing of # same objects on a single turn. monster = self.owner target = monster.scene.player distance = monster.distance_to(monster.scene.player) if not monster.visible and not monster.path: monster.move_rnd() print("AI1 (not visible not pathing): monster.move_rnd()") elif distance >= 2: # implement reach here if monster.path: # continue following the path try: old_path = list(monster.path) moved = monster.move(monster.path.pop(2)) except: print("-AI2: failed to follow path") moved = False else: print("AI2: following path, dist {}".format(distance)) # show the path remaining for i, pos in enumerate(old_path[2:-1]): if i == 0: color = get_color("orange") elif i == 1: color = get_color("yellow") else: color = get_color("green") """ monster.scene.tile_fx.add( coord=[pos], color=color, duration=1) """ else: moved = False if not moved: # find a new path monster.path = monster.move_towards(target=target) try: """ monster.scene.tile_fx.add( coord=monster.path[2:-1], color=get_color("green"), duration=1) """ moved = monster.move(monster.path.pop(2)) print("AI3: new path {}, FROM {}, TO {}, DIST {}".format( monster.path, tuple(monster.pos), tuple(monster.scene.player.pos), distance)) except: monster.scene.pathing = [] monster.move_rnd() print("AI4: path failed: monster.move_rnd()") # close enough, attack! (if the player is still alive.) elif target.combat.hit_points_current > 0: try: monster.combat.attack(target) except Exception as e: print(monster.combat.name) raise e