def distribute_exp(): """ Open a menu in which the player can distribute the EXP he acquired.""" from render import menu, message options = [ 'Strength (500)', 'Dexterity (500)', 'Stamina (500)', '1 -0 Health Level (500)', '1 -1 Health Level (400)', '1 -2 Health Level (300)', '1 -4 Health Level (200)' ] choice = menu('Choose a skill point to distribute points to:', options, 50, alpha=1) cost = {0: 500, 1: 500, 2: 500, 3: 500, 4: 500, 5: 400, 6: 300, 7: 200} if choice is not None: if choice == 'exit': return if gvar.game.player.exp >= cost[choice]: skill = { 0: gain_strength, 1: gain_dexterity, 2: gain_stamina, 3: gain_perception, 4: gain_0hl, 5: gain_1hl, 6: gain_2hl, 7: gain_4hl } amount = {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1} skill[choice](amount[choice]) gvar.game.player.exp -= cost[choice] message('You feel stronger!') else: message('You don\'t have enough experience.')
def dequip(self): """ Dequips the Equipment""" from render import message if not self.is_equipped: return self.is_equipped = False message('Put off the ' + self.owner.name, libtcod.yellow)
def ranged_attack(): """ | Perform a ranged attack. | Checks for a ranged weapon in hand and then calls *input.target_tile* to determine a target. """ from input import target_tile from render import message from utils import is_player mobToHit = None if self.owner.get_equipped_in_slot('hand').ranged is not None: if is_player(self.owner): target = target_tile() if target == 'cancelled': return 'cancelled' else: target = 'closest' if target == 'closest': mobToHit = closest_mob(self.perception + self.skills['Awareness']*2) else: for hit in self.currentmap().objects: if target.x == hit.x and target.y == hit.y and hit.fighter is not None: mobToHit = hit if mobToHit is None: # @TODO No enemy found, drop arrow if is_player(self.owner): message('There is no enemy here!', libtcod.orange) else: self.attack(mobToHit.fighter, ranged=True) return self.owner.get_equipped_in_slot('hand').ranged.speed else: if is_player(self.owner): message('You do not have a ranged weapon equipped!', libtcod.orange) return 'cancelled'
def player_death(player): """ | Death function for the player. | Set game state to 'dead' and turn the player into a corpse. """ from render import message message('You died!', libtcod.red) gvar.game.game_state = 'dead' gvar.game.player.char = '%' gvar.game.player.color = libtcod.dark_red
def take_turn(self): """ | Normal Turn Routine | Mobs take turns if the player can hear it #TODO Change to Mob's FOH | If they're close enough, they attack """ from render import message from utils import a_star_search, applyBonus, revertBonus mob = self.owner status = self.status delta = self.statusDelta speed = 1 # Base speed of 1 tick, to prevent infinite mob action loops if libtcod.map_is_in_fov(gvar.foh_map, mob.x, mob.y): # Normal Behaviour if not status: if mob.distance_to(gvar.game.player) >= 2: # Move towards the player, if she's too far away destination = a_star_search(gvar.game.player.currentmap(), (mob.x, mob.y),(gvar.game.player.x, gvar.game.player.y)) # Perform A*-Search if destination == [(mob.x, mob.y), (mob.x, mob.y)]: # idle for the next 3 turns, if no path to player is found self.status = 'idle' #@TODO Should have prevented A*-Overload, doesn't seem to work self.statusDelta = 3 mob.move_to(destination[-2]) speed = self.owner.fighter.movement_speed() elif sum(gvar.game.player.fighter.hl) > 0: # Player is close enough, perform an attack speed = mob.fighter.attack(gvar.game.player.fighter) # Confusion - Moves to random directions, also -4 dodgeDV penalty elif status == 'confusion': applyBonus('dodgeDV', -4, self.owner.fighter) if delta > 0: self.owner.move(libtcod.random_get_int(0, -1, 1), libtcod.random_get_int(0, -1, 1)) self.statusDelta -= 1 else: revertBonus('dodgeDV', -4, self.owner.fighter) self.status = None message('The smoke around the ' + self.owner.name + ' disappears.', libtcod.violet) if self.owner.fighter is not None: speed = self.owner.fighter.movement_speed() # Idle - Don't do anything, Speed 3 elif status == 'idle': if delta > 0: self.statusDelta -= 1 else: self.status = None speed = 3 return speed
def equip(self): """ | Equips the Equipment | Dequipping other items in this slot """ from render import message currentEquipped = gvar.game.player.get_equipped_in_slot(self.slot) if currentEquipped is not None: currentEquipped.dequip() self.is_equipped = True message('Equipped the ' + self.owner.name, libtcod.yellow)
def cast_heal(min, max, mutableBySkill=True): """ | **Spells and Effects: Heal player** | Heal the player by a random value between *min* and *max* boundaries. | If *mutableBySkill* is true, the player's Medicine skill adds to the boundaries. """ from render import message message('You start to feel better!', libtcod.light_violet) modifier = 0 if mutableBySkill: modifier = libtcod.random_get_int( 0, 0, gvar.game.player.fighter.skills['Medicine']) gvar.game.player.fighter.heal( libtcod.random_get_int(0, min, max) + modifier)
def drop(self): """ | Drop the Item | Add it to the Global Objects List | and set it's coordinates to the dropper's """ from render import message if self.owner.equipment: self.owner.equipment.dequip() gvar.game.player.currentmap().objects.append(self.owner) gvar.game.player.inventory.remove(self.owner) self.owner.currentDungeon = gvar.game.player.currentmap().owner self.owner.currentLevel = gvar.game.player.currentmap().owner.maps.index(gvar.game.player.currentmap())-1 self.owner.x = gvar.game.player.x self.owner.y = gvar.game.player.y message('You dropped a ' + self.owner.name + '.', libtcod.yellow)
def mob_death(mob): """ | Death function for mobs. | Turns the mob into a corpse. It doesn't block, has no AI and Fighter components. | Also, add the mob's EXP value to the player's. """ from render import message message(mob.name.capitalize() + ' is dead!', libtcod.orange) mob.char = '%' mob.color = libtcod.dark_red mob.blocks = False mob.fighter = None mob.ai = None mob.name = 'remains of ' + mob.name mob.send_to_back() gvar.game.player.exp += mob.exp message('Gained ' + str(mob.exp) + ' EXP!')
def cast_lightning(min, max): """ | **Spells and Effects: Lightning** | Cast a lightning and deal a random amount of Lethal damage inside the given *min* and *max* boundaries. | The target is the closest mob inside a radius of 5 fields. """ from utils import closest_mob from render import message mob = closest_mob(5) if mob is None: message('No enemy is close enough to strike.', libtcod.red) return 'cancelled' damage = libtcod.random_get_int(0, min, max) message( 'A lighting bolt strikes the ' + mob.name + '! The damage is ' + str(damage) + ' Lethal HL.', libtcod.light_blue) mob.fighter.take_damage(damage)
def cast_confusion(min, max): """ | **Spells and Effects: Confusion** | Apply a 'confusion' status to the closes mob within 5 fields. | A confused enemy moves into a random direction every time it takes a turn. | Also, it doesn't react to ai_blocked tiles, so it can fall down a hole. """ from utils import closest_mob from render import message mob = closest_mob(5) if mob is None: message('No enemy is close enough to target.', libtcod.red) return 'cancelled' message( 'A violet smoke appears around the ' + mob.name + '! It\'s confused.', libtcod.violet) mob.ai.applyStatus('confusion', libtcod.random_get_int(0, min, max))
def new_game(): """ | Start a new game | Create a map, FOV Map and print Welcome Mesage """ from charactercreation import create_character from spawn import spawn_player gvar.game = gvar.Game() gvar.game.init_world() spawn_player([gvar.game.world.worldspace, 0]) initialize_fov() color = libtcod.color_lerp(libtcod.white, libtcod.red, 0.9) render.message('Welcome, Child of the Unconquered Sun.', color) render.animate_background('main_menu', 0.5, reverse=True) ch = create_character() if ch == 'exit': main_menu() play_game()
def cast_fireball(min, max, explosion_radius=2): """ | **Spells and Effects: Fireball.** | Target a tile within the FOV with and cast a fireball with given explosion *radius* onto the targeted tile. | Deal damage equal to a random value inside the *min* and *max* boundaries. """ from input import target_tile from render import message target = target_tile(radius=explosion_radius) if target == 'cancelled': return 'cancelled' if target is not None: for hit in gvar.game.objects: for p in target.splash.circle: if hit.fighter is not None: if hit.fighter.hl[3] > 0: if hit.x == p[0] and hit.y == p[1]: dmg = libtcod.random_get_int(0, min, max) hit.fighter.take_damage(dmg) message('The fireball burns ' + hit.name + ' for ' + dmg + ' damage')
def use(self): """ | call the use_function, if its defined | if there are args, add them to the call """ from render import message if self.use_function is None and self.owner.equipment is None: # No valid action found message('The ' + self.owner.name + ' cannot be used.') elif self.use_function is None and self.owner.equipment is not None:# It's an Equipment, equip it self.owner.equipment.toggle_equip() else: if len(self.args) == 0: if self.use_function() != 'cancelled': # It can be used, call use_function if self.count>=2: # Decrease Stack count, or remove the item self.count -= 1 else: gvar.game.player.inventory.remove(self.owner) # @TODO Inventory for Mobs else: if self.use_function(self.args[0], self.args[1]) != 'cancelled': if self.count>=2: self.count -= 1 else: gvar.game.player.inventory.remove(self.owner)
def attack(self, target, ranged=False): """ | Performs an attack against the target. | Melee or ranged indicated by ranged boolean """ from utils import d10, is_player from render import message activeWeapon = self.owner.get_equipped_in_slot('hand') if (activeWeapon is None): # get equipped weapon from slot 'hand' activeWeapon = Equipment(slot='hand', weapon=Weapon()) # Insert dummy weapon if no weapon is equipped # @TODO Two-Handed Weapons weapon = activeWeapon.ranged if ranged else activeWeapon.weapon # determine weapon/ranged component to use hitroll = d10(self.dexterity + self.skills[weapon.skill], 'Hit', player=is_player(self.owner)) if hitroll[0] != 'botch': hitroll[0] -= self.health_penalty() hitroll[0] = max(hitroll[0], 0) else: pass # @TODO Botch Behaviour # Check for hit, if successes > enemy's dodge DV ( -onslaughtPenalty for player) threshold = target.dodgeDV() threshold -= target.onslaughtPenalty() if is_player(target.owner) else 0 if hitroll[0] != 'botch' and hitroll[0] > threshold: # target hit, roll damage if ranged: dmgroll = d10(weapon.damage, 'Damage', botchable=False, player=is_player(self.owner)) else: dmgroll = d10(self.strength + weapon.damage, 'Damage', botchable=False, player=is_player(self.owner)) damage = dmgroll[0] + (hitroll[0] - threshold) # add remaining successes from hitroll if damage > target.hardness(): # check if damage is bigger than hardness if weapon.damage_type == 'bashing': # apply soak damage -= target.bashing_soak() elif weapon.damage_type == 'lethal': damage -= target.lethal_soak() if damage > 0: # apply damage message(self.owner.name.capitalize() + ' hits ' + target.owner.name + ' for ' + str(damage) + ' hit points.') target.take_damage(damage) else: message(self.owner.name.capitalize() + ' hits ' + target.owner.name + ' but the strike bounces off his armor!') else: message(self.owner.name.capitalize() + ' attacks ' + target.owner.name + ' but misses.') return weapon.speed
def pick_up(self): """ | Add the item to the player's inventory | @TODO Inventory System for Enemies and other NPCs """ from render import message if len(gvar.game.player.inventory) >= 26: # Inventory Full, Alphabetical Limit # @TODO Multi-Page Menus & Encumbrance System message('Your inventory is full, cannot pick up ' + self.owner.name + '.', libtcod.red) else: for item in gvar.game.player.inventory: # Loop through items already in inventory if item.name == self.owner.name: # and stack them item.item.count += 1 gvar.game.player.currentmap().objects.remove(self.owner) # Remove the item from the map message('You picked up a ' + self.owner.name + '!', libtcod.green) return gvar.game.player.inventory.append(self.owner) gvar.game.player.currentmap().objects.remove(self.owner) message('You picked up a ' + self.owner.name + '!', libtcod.green)
def fall_into(entity): """ | Step function: | Fall into a hole. Takes an entity (player, mob) as input. | Rolls Wits + Awareness to determine, if they see the hole. | If they do and there is a ledge near (not diagonally), do a reflexive Wits+Awareness roll, to determine if they can hang down the ledge. | Otherwise, kill them. """ from utils import d10, is_player from render import message map = entity.currentmap() result = d10(entity.fighter.wits + entity.fighter.skills["Awareness"], 'Fall:', botchable=True, player=is_player(entity)) if result[0] <= 3: ledge = False for spot in map.adjacent(entity.x, entity.y, diagonal=False): if map.map[spot[0]][spot[1]].tile_type == 'floor': ledge = True if ledge: # ledge near, do a reflex roll to catch it reflex = d10(entity.fighter.wits + entity.fighter.skills["Awareness"], 'Reflex:', botchable=True, player=is_player(entity)) if reflex[0] >= 2: if not is_player: message(entity.name + " is hanging down the ledge", libtcod.white) else: message("You are hanging down the ledge", libtcod.white) else: if not is_player: message(entity.name + " has fallen down the hole", libtcod.red) else: message("You have fallen down the hole", libtcod.red) entity.fighter.death_function(entity) else: # no ledge near if not is_player: message(entity.name + " has fallen down the hole", libtcod.red) else: message("You have fallen down the hole", libtcod.red) entity.fighter.death_function(entity) else: if not is_player: message(entity.name + " is hanging down the ledge", libtcod.white) else: message("You are hanging down the ledge", libtcod.white)