From 27c37c357a428560ed916749c233904bf2ba69c1 Mon Sep 17 00:00:00 2001 From: jarcane Date: Tue, 15 Jul 2014 01:02:33 +0300 Subject: [PATCH] Ranged combat system There is now a ranged combat system. When equipped with a gun, you can now shoot at a target with s, which brings up target_tile and lets you shoot at something so long as you have ammo left. you can check ammo with a. You can also attack at point blank range through the usual numpad/arrows method. --- handhrl.py | 178 +++++++++++++++++++++++++++++++++++++++------------- handhrl.pyc | Bin 46940 -> 49051 bytes hhtable.py | 43 ++++++++++++- 3 files changed, 174 insertions(+), 47 deletions(-) diff --git a/handhrl.py b/handhrl.py index 0e6fa07..fd7189f 100644 --- a/handhrl.py +++ b/handhrl.py @@ -18,7 +18,6 @@ along with this program. If not, see . ''' - import libtcodpy as libtcod import math import textwrap @@ -30,7 +29,6 @@ import hhtable - SCREEN_WIDTH = 80 SCREEN_HEIGHT = 50 MAP_WIDTH = 80 @@ -59,7 +57,6 @@ LEVEL_UP_BASE = 300 LEVEL_UP_FACTOR = 200 - color_dark_wall = libtcod.Color(128, 128, 128) color_light_wall = libtcod.Color(130, 110, 50) color_dark_ground = libtcod.Color(192, 192, 192) @@ -230,7 +227,8 @@ def drop(self): class Equipment: # an object that can be equipped, yielding bonuses. automatically adds the item component. - def __init__(self, slot, to_hit_bonus=0, damage_bonus=0, damage_roll=None, armor_bonus=0, max_hp_bonus=0): + def __init__(self, slot, to_hit_bonus=0, damage_bonus=0, damage_roll=None, armor_bonus=0, max_hp_bonus=0, + ranged=False, ammo=None): self.to_hit_bonus = to_hit_bonus self.damage_bonus = damage_bonus self.damage_roll = damage_roll @@ -238,6 +236,8 @@ def __init__(self, slot, to_hit_bonus=0, damage_bonus=0, damage_roll=None, armor self.max_hp_bonus = max_hp_bonus self.slot = slot self.is_equipped = False + self.ranged = ranged + self.ammo = ammo def toggle_equip(self): # toggle equip/dequip state if self.is_equipped: @@ -286,12 +286,12 @@ def armor_class(self): # return actual defense, by summing up the bonuses from return self.base_armor_class + bonus @property - def damage(self): # return actual damage bonus, plus any special bonuses + def damage(self): # return actual damage bonus, plus any special bonuses bonus = sum(equipment.damage_bonus for equipment in get_all_equipped(self.owner)) return self.base_damage + bonus @property - def damage_roll(self): # return current damage roll or roll from equipment + def damage_roll(self): # return current damage roll or roll from equipment for equipment in get_all_equipped(self.owner): if equipment.damage_roll: return equipment.damage_roll @@ -324,14 +324,31 @@ def heal(self, amount): self.hp = self.max_hp def attack(self, target): - # first check for successful attack + # first check for to hit target to_hit_target = self.to_hit + target.fighter.armor_class + 5 + # check of the target is attacking with a gun + has_gun = False + for i in get_all_equipped(self.owner): + if i.is_equipped and i.ranged: + has_gun = True + gun = i + + # check if gun has ammo + if has_gun: + if gun.ammo > 0: + gun.ammo -= 1 + else: + message("You don't have any ammo!") + return + + # use the right pronoun if target.ai is not None: pronoun = 'the ' else: pronoun = '' + # roll to hit if rolldice(1, 20) >= to_hit_target: message(self.owner.name.title() + ' misses ' + pronoun + target.name + '.') return @@ -348,6 +365,52 @@ def attack(self, target): message(self.owner.name.title() + ' hits ' + pronoun + target.name + ' but it has no effect!', libtcod.grey) + def shoot(self): + # first check if the character is equipped with a ranged weapon + has_gun = False + for i in get_all_equipped(self.owner): + if i.is_equipped and i.ranged: + has_gun = True + gun = i + + if not has_gun: + message("You're not carrying a gun!", libtcod.red) + return + + # check if the gun has ammo + if gun.ammo is None or gun.ammo < 1: + message("You're out of ammo in that gun!", libtcod.red) + return + + # target a monster + message('Left-click on a target monster, or right-click to cancel.') + target = target_monster() + if not target: + return + + # calculate to-hit + to_hit_target = self.to_hit + target.fighter.armor_class + 5 + + # deduct ammo + gun.ammo -= 1 + + # roll to hit + if rolldice(1, 20) >= to_hit_target: + message(self.owner.name.title() + ' misses the ' + target.name + '.') + return + + # now roll for damage (curr. using OD&D style) + damage = rolldice(*self.damage_roll) + gun.damage_bonus + + if damage > 0: + # make the target take some damage + message(self.owner.name.title() + ' hits the ' + target.name + ' for ' + str(damage) + ' hit points.', + libtcod.yellow) + target.fighter.take_damage(damage, self.owner.name) + else: + message(self.owner.name.title() + ' hits the ' + target.name + ' but it has no effect!', + libtcod.grey) + class BasicMonster: # AI for a basic monster @@ -447,7 +510,7 @@ def show_text_log(text, img=None, delay=True, center_first_line=False): delay = False libtcod.console_set_default_foreground(0, libtcod.white) - libtcod.console_print_ex(0, SCREEN_WIDTH/2, SCREEN_HEIGHT - 2, libtcod.BKGND_NONE, libtcod.CENTER, + libtcod.console_print_ex(0, SCREEN_WIDTH / 2, SCREEN_HEIGHT - 2, libtcod.BKGND_NONE, libtcod.CENTER, 'Press any key to continue') libtcod.console_flush() libtcod.console_wait_for_keypress(True) @@ -482,15 +545,17 @@ def help_screen(): '', 'ESC - Exit to menu, saving game', 'Alt+Enter - toggle fullscreen', - 'NumPad or Arrows - move character', + 'NumPad or Arrows - move character or attack adjacent', '5 or Space - wait one turn', 'h or ? - display this help screen', + 's - shoot with ranged weapon if equipped', + 'a - check ammo of equipped ranged weapon', 'g - get item beneath character', 'i - inventory/use item', 'd - drop item', 'c - character status', '< - descend stairs' - ] + ] show_text_log(help_text, generate_screen(), delay=False, center_first_line=True) @@ -522,7 +587,7 @@ def main_menu(firstrun=False): if choice == 0: new_game(firstrun) - firstrun=False + firstrun = False play_game() if choice == 1: @@ -536,7 +601,7 @@ def main_menu(firstrun=False): try: show_scores() except: - msgbox('\n No high scores yet!\n',24) + msgbox('\n No high scores yet!\n', 24) continue elif choice == 3: break @@ -552,7 +617,8 @@ def new_game(firstrun=False): # create Player object # Assume Soldier class with 10 STR, 10 DEX, 10 CON - fighter_component = Fighter(hp=rolldice(3,6)+rolldice(1,10), armor_class=10, to_hit=1, damage=1, damage_roll=[1, 3], + fighter_component = Fighter(hp=rolldice(3, 6) + rolldice(1, 10) + 100, armor_class=10, to_hit=1, damage=1, + damage_roll=[1, 3], xp=0, death_function=player_death) player = Object(0, 0, chr(1), get_text_entry('What is your name, Ensign?', generate_screen()), libtcod.white, blocks=True, fighter=fighter_component) @@ -675,14 +741,14 @@ def new_score(player): scores['scores'] = new_list scores.close() - choice = menu('Game Over\n', ['See your score','Return to main menu'], 22) + choice = menu('Game Over\n', ['See your score', 'Return to main menu'], 22) if choice == 0: show_scores() def show_scores(): # load the score file, sort the list by score, then display - score_file = shelve.open('scorefile','r') + score_file = shelve.open('scorefile', 'r') scores = score_file['scores'] scores.sort(key=operator.itemgetter(0), reverse=True) score_list = ['High Scores'] @@ -699,6 +765,7 @@ def show_scores(): show_text_log(score_list, generate_starpic(), delay=False, center_first_line=True) + def end_game(): ending = [ '*INITIATE COMM SEQUENCE EMERGENCY ALPHA-0x1*', @@ -749,6 +816,21 @@ def handle_keys(key, mouse): # test for other keys key_char = chr(key.c) + if key_char == 'a': + has_gun = False + for i in get_all_equipped(player): + if i.is_equipped and i.ranged: + has_gun = True + gun = i + if has_gun: + message(gun.owner.name.capitalize() + ' has ' + str(gun.ammo) + ' shots remaining.') + if key_char == 's': + # shoot at someone + player.fighter.shoot() + # remove the target from the map until the next redraw + for object in objects: + object.clear() + return if key_char == 'g': # pick up an item for object in objects: @@ -777,17 +859,18 @@ def handle_keys(key, mouse): except: highest = '' show_text_log([ - 'Character Information', - 'Name: ' + player.name, - 'Level: ' + str(player.level), - 'Experience: ' + str(player.fighter.xp), - 'Experience to level up: ' + str(level_up_xp), - 'Maximum HP: ' + str(player.fighter.max_hp), - 'To-hit: +' + str(player.fighter.to_hit), - 'Damage: ' + str(player.fighter.damage_roll[0]) + 'd' + str(player.fighter.damage_roll[1]) + highest, - 'Damage Bonus: +' + str(player.fighter.damage), - 'AC: ' + str(player.fighter.armor_class), - ], generate_screen(), delay=False) + 'Character Information', + 'Name: ' + player.name, + 'Level: ' + str(player.level), + 'Experience: ' + str(player.fighter.xp), + 'Experience to level up: ' + str(level_up_xp), + 'Maximum HP: ' + str(player.fighter.max_hp), + 'AC: ' + str(player.fighter.armor_class), + 'To-hit: +' + str(player.fighter.to_hit), + 'Damage Bonus: +' + str(player.fighter.damage), + 'Damage Roll: ' + str(player.fighter.damage_roll[0]) + 'd' + str( + player.fighter.damage_roll[1]) + highest, + ], generate_screen(), delay=False) if key_char == 'h' or key_char == '?': help_screen() return 'didnt-take-turn' @@ -799,7 +882,7 @@ def target_tile(max_range=None): key = libtcod.Key() mouse = libtcod.Mouse() while True: - # render the screen. this reases the inventory and shows the names of objects under the mouse + # render the screen. this raises the inventory and shows the names of objects under the mouse libtcod.console_flush() libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse) render_all() @@ -875,15 +958,15 @@ def get_text_entry(header, img): if timer % (LIMIT_FPS // 4) == 0: if timer % (LIMIT_FPS // 2) == 0: timer = 0 - libtcod.console_set_char(0, cursor + x, y, "_") - libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) + libtcod.console_set_char(0, cursor + x, y, "_") + libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) else: - libtcod.console_set_char(0, cursor + x, y, " ") - libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) + libtcod.console_set_char(0, cursor + x, y, " ") + libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) if key.vk == libtcod.KEY_BACKSPACE and cursor > 0: - libtcod.console_set_char(0, cursor + x, y, " ") - libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) + libtcod.console_set_char(0, cursor + x, y, " ") + libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) command = command[:-1] cursor -= 1 elif key.vk == libtcod.KEY_ENTER: @@ -893,15 +976,16 @@ def get_text_entry(header, img): break elif key.c > 0: letter = chr(key.c) - libtcod.console_set_char(0, cursor + x, y, letter) #print new character at appropriate position on screen - libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) #make it white or something - command += letter #add to the string + libtcod.console_set_char(0, cursor + x, y, letter) # print new character at appropriate position on screen + libtcod.console_set_char_foreground(0, cursor + x, y, libtcod.white) # make it white or something + command += letter # add to the string cursor += 1 libtcod.console_flush() return command + def player_move_or_attack(dx, dy): global fov_recompute global objects @@ -1020,8 +1104,8 @@ def get_monster_from_hitdice(x, y, name, hitdice, color): else: roll = (num / 2, sides) - fighter_component = Fighter(hp=rolldice(*hitdice), armor_class=10-num, to_hit=to_hit, - damage=0, damage_roll=roll, xp=num*sides, death_function=monster_death) + fighter_component = Fighter(hp=rolldice(*hitdice), armor_class=10 - num, to_hit=to_hit, + damage=0, damage_roll=roll, xp=num * sides * 5, death_function=monster_death) ai_component = BasicMonster() monster = Object(x, y, letter, name, color, blocks=True, fighter=fighter_component, ai=ai_component) @@ -1029,7 +1113,6 @@ def get_monster_from_hitdice(x, y, name, hitdice, color): def get_item(x, y): - choice = libtcod.random_get_int(0, 1, 4) if choice == 1: @@ -1051,15 +1134,17 @@ def get_item(x, y): return item + def get_weapon(x, y): weapon = hhtable.make_weapon() equipment_component = Equipment(slot='right hand', damage_roll=weapon['damage'], to_hit_bonus=weapon['bonus'], - damage_bonus=weapon['bonus']) + damage_bonus=weapon['bonus'], ranged=weapon['gun'], ammo=weapon['ammo']) item = Object(x, y, weapon['char'], weapon['name'], libtcod.brass, equipment=equipment_component) return item + def get_armor(x, y): choice = libtcod.random_get_int(0, 1, 2) @@ -1070,7 +1155,7 @@ def get_armor(x, y): shield_name = str(shield_bonus) + ' plexsteel shield' else: shield_name = 'plexsteel shield' - equipment_component = Equipment(slot='left hand', armor_bonus=-1+shield_bonus) + equipment_component = Equipment(slot='left hand', armor_bonus=-1 + shield_bonus) item = Object(x, y, '[', shield_name, libtcod.darker_orange, equipment=equipment_component) elif choice == 2: # create vacc suit armor @@ -1079,11 +1164,12 @@ def get_armor(x, y): armor_name = str(armor_bonus) + ' vacc suit' else: armor_name = 'vacc suit' - equipment_component = Equipment(slot='armor', armor_bonus=-2+armor_bonus) + equipment_component = Equipment(slot='armor', armor_bonus=-2 + armor_bonus) item = Object(x, y, ']', armor_name, libtcod.silver, equipment=equipment_component) return item + def get_placeable(x, y): return Object(x, y, chr(127), 'terminal', libtcod.silver) @@ -1151,6 +1237,7 @@ def place_objects(room): objects.append(item) item.send_to_back() # items appear below other objects + def make_map(): global map, objects, stairs @@ -1388,7 +1475,7 @@ def render_all(): libtcod.console_print_ex(panel, 1, 0, libtcod.BKGND_NONE, libtcod.LEFT, get_names_under_mouse()) # display names of objects under player on right side of panel - libtcod.console_print_ex(panel, SCREEN_WIDTH-2, 0, libtcod.BKGND_NONE, libtcod.RIGHT, get_names_under_player()) + libtcod.console_print_ex(panel, SCREEN_WIDTH - 2, 0, libtcod.BKGND_NONE, libtcod.RIGHT, get_names_under_player()) # blit the contents of "panel" to root console libtcod.console_blit(panel, 0, 0, SCREEN_WIDTH, PANEL_HEIGHT, 0, 0, PANEL_Y) @@ -1528,7 +1615,7 @@ def check_level_up(): libtcod.yellow) render_all() # re-render console so that message plays before menu - #check player level, roll 1d10 for new HP if 6 or less, or just +3 (see H&H rulebook) + # check player level, roll 1d10 for new HP if 6 or less, or just +3 (see H&H rulebook) if player.level <= 6: hit_die = rolldice(1, 10) else: @@ -1596,7 +1683,8 @@ def cast_fireball(): # ############################################ # Initialization & Main Loop # ############################################ -libtcod.console_set_custom_font('terminal16x16_gs_ro.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_ASCII_INROW) +libtcod.console_set_custom_font('terminal16x16_gs_ro.png', + libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_ASCII_INROW) libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'Hulks and Horrors', False) libtcod.sys_set_fps(LIMIT_FPS) panel = libtcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT) diff --git a/handhrl.pyc b/handhrl.pyc index 9505104f7c6c3b2a19871d04303bf974d7db6b96..d98ff118db15d06c5a43954332a4262911121b4c 100644 GIT binary patch delta 10849 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 AQUCw| delta 8960 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 0: name = name + ' +' + str(bonus) + # determine if it's a gun + if char in [')', '}', '=', '&']: + gun = True + else: + gun = False + + # give it ammo if it is + if gun: + if char in [')', '}', '=']: + ammo = rolldice(3, 10) + else: + ammo = rolldice(1, 10) + else: + ammo = None + # put it together weapon = {'char': char, 'name': name, 'damage': damage, - 'bonus': bonus} + 'bonus': bonus, + 'gun': gun, + 'ammo': ammo} return weapon \ No newline at end of file