diff --git a/_jobs.py b/_jobs.py index 19156da..fbdd857 100644 --- a/_jobs.py +++ b/_jobs.py @@ -1,8 +1,9 @@ class Job: - def __init__(self, description, name, races, upkeep): + def __init__(self, description, name, races, skills, upkeep): self.description = description # Type: str self.name = name # Type: str self.races = races # Type: list + self.skills = skills # Type: dict {'job': number} self.upkeep = upkeep # Type: dict {'stat': penalty value} @@ -10,19 +11,29 @@ def __init__(self, description, name, races, upkeep): 'soldier': Job( description="Baby's first job.", name='soldier', - upkeep={}, - races=('human',) + races=('human',), + skills={}, + upkeep={} ), 'warrior': Job( description='Has access to more devastating skills.', # Not rly. name='warrior', - upkeep={'magic': 1, 'speed': 2}, - races=('human',) + races=('human',), + skills={}, + upkeep={'magic': 1, 'speed': 2} ), 'beserker': Job( description='Classic orc.', name='beserker', - upkeep={'speed': 1, 'hp': 1}, - races=('orc',) + races=('orc',), + skills={}, + upkeep={'speed': 1, 'hp': 1} + ), + 'rogue': Job( + description='A job for seasoned fighters.', + name='rogue', + races=('human', 'goblin'), + skills={'soldier': 1}, + upkeep={'speed': 1, 'hp': 15} ) } \ No newline at end of file diff --git a/components/actor/skill_directory.py b/components/actor/skill_directory.py index 5d55107..7d9abd7 100644 --- a/components/actor/skill_directory.py +++ b/components/actor/skill_directory.py @@ -1,14 +1,19 @@ class SkillDirectoryComponent: - def __init__(self): - self.skill_directory = {} + def __init__(self, job='unemployed'): + self.skill_directory = {job: {}} """ Usage Example: self.skill_directory = { - 'bash': (85, 100), - 'slash': (100, 100), - 'skill_name': (current_ap, max_ap) + 'soldier': { + 'bash': (85, 100), + 'slash': (100, 100), + 'skill_name': (current_ap, max_ap) + }, + 'job_name': { + + } } """ \ No newline at end of file diff --git a/engine.py b/engine.py index 8aa7b80..7933454 100644 --- a/engine.py +++ b/engine.py @@ -14,4 +14,5 @@ def main(): if __name__ == '__main__': # cProfile.run('main()') # This runs the profiler - main() \ No newline at end of file + main() + \ No newline at end of file diff --git a/game.py b/game.py index b05ba64..6f0c376 100644 --- a/game.py +++ b/game.py @@ -245,7 +245,7 @@ def create_entity(self, entity): RaceComponent(race='human'), PositionComponent(), RenderComponent(color_bg=None, char='@', codepoint=SPRITES['player'], color_fg=ENTITY_COLORS['player'], color_explored=None), - SkillDirectoryComponent(), + SkillDirectoryComponent(job='soldier'), SoulComponent(eccentricity=5, max_rarity=10), StatsComponent(hp=500, attack=10) ) diff --git a/processors/job.py b/processors/job.py index 8dfb41b..d3cfaf7 100644 --- a/processors/job.py +++ b/processors/job.py @@ -4,6 +4,8 @@ from _jobs import JOBS, Job from components.actor.job import JobComponent from components.actor.race import RaceComponent +from components.actor.skill_directory import SkillDirectoryComponent +from processors.skill_directory import SkillDirectoryProcessor from processors.state import StateProcessor from menu import PopupMenu, PopupChoice from queue import Queue @@ -30,30 +32,68 @@ def process(self): _result = {'ent': ent, 'job': job} _processor = JobProcessor _upkeep = job.upkeep - - # /// Check the validity of the job change. /// - _validity = True - - # Race validity. - if self.world.component_for_entity(ent, RaceComponent).race not in job.races: - _validity = False - - # Upkeep validity. - ### The player may not switch to a job if it can't pay the upkeep. - ### However, other stats unrelated to the job may be negative. - ### Note: _upkeep does not include the * -10. - bare_stats = generate_stats(ent, self.world, include_upkeep=False) - for key, value in _upkeep.items(): - if bare_stats[key] - value * 10 < 0: - _validity = False - - # ///////////////////////////////////////////// + _validity, _ = check_validity(ent, job, self.world) - menu.contents.append(PopupChoice(name=_name, key=_key, result=_result, processor=_processor, valid=_validity, description=_description, upkeep=_upkeep)) + menu.contents.append( + PopupChoice( + name=_name, + key=_key, + result=_result, + processor=_processor, + valid=_validity, + description=_description, + upkeep=_upkeep + ) + ) self.world.get_processor(StateProcessor).queue.put({'popup': menu}) else: - ent_job = self.world.component_for_entity(ent, JobComponent) - ent_job.update_upkeep(job.upkeep) - ent_job.job = job.name \ No newline at end of file + valid, message_data = check_validity(ent, job, self.world) + + if valid: + # Switch jobs! + ent_job = self.world.component_for_entity(ent, JobComponent) + ent_job.update_upkeep(job.upkeep) + ent_job.job = job.name + self.world.get_processor(SkillDirectoryProcessor).queue.put({'new_job': job.name, 'ent': ent}) + + self.world.messages.append({'job_switch': message_data}) + +def check_validity(ent, job, world): + validity = True + message_data = {} + + # Race validity. + if world.component_for_entity(ent, RaceComponent).race not in job.races: + validity = False + message_data['wrong_race'] = True + + # Upkeep validity. + ### The player may not switch to a job if it can't pay the upkeep. + ### However, other stats unrelated to the job may be negative. + ### Note: _upkeep does not include the * -10. + bare_stats = generate_stats(ent, world, include_upkeep=False) + for key, value in job.upkeep.items(): + if bare_stats[key] - value * 10 < 0: + validity = False + message_data['not_enough_stats'] = True + + # Skill validity. + sd_comp = world.component_for_entity(ent, SkillDirectoryComponent) + for required_job, required_number in job.skills.items(): + # Determine how many skills from the given job have been mastered. + mastery_number = 0 + if required_job in sd_comp.skill_directory: + for name, ap in sd_comp.skill_directory[required_job].items(): + if ap[0] == ap[1]: + mastery_number += 1 + + if mastery_number < required_number: + validity = False + message_data['not_enough_skills'] = True + + if validity: + message_data['switch_class'] = job.name + + return validity, message_data \ No newline at end of file diff --git a/processors/skill_directory.py b/processors/skill_directory.py index 19ba189..81bbe2a 100644 --- a/processors/skill_directory.py +++ b/processors/skill_directory.py @@ -1,6 +1,7 @@ import esper from components.actor.equipment import EquipmentComponent +from components.actor.job import JobComponent from components.actor.skill_directory import SkillDirectoryComponent from components.item.skill import ItemSkillComponent from queue import Queue @@ -15,42 +16,49 @@ def process(self): event = self.queue.get() ap_gain = event.get('ap_gain') + ent = event['ent'] item = event.get('item') + new_job = event.get('new_job') new_skill = event.get('new_skill') - ent = event['ent'] - skill = event.get('skill') - sd_comp = self.world.component_for_entity(ent, SkillDirectoryComponent) + job = self.world.component_for_entity(ent, JobComponent).job + sd_comp = self.world.component_for_entity(ent, SkillDirectoryComponent) - if new_skill: - skill = None if not self.world.has_component(item, ItemSkillComponent) else self.world.component_for_entity(item, ItemSkillComponent) + # Add an entry to the ent's skill directory. + if new_job: + if new_job not in sd_comp.skill_directory.keys(): + sd_comp.skill_directory[job] = {} + + elif item and new_skill: + # Add this item's skill to the directory, if it is not already present. + skill = self.world.component_for_entity(item, ItemSkillComponent) - if skill and skill.name not in sd_comp.skill_directory.keys(): - sd_comp.skill_directory[skill.name] = (0, skill.ap_max) + if job in sd_comp.skill_directory.keys(): + if skill not in sd_comp.skill_directory[job].keys(): + sd_comp.skill_directory[job][skill.name] = (0, skill.ap_max) + else: + print("ERROR: This item is trying to add a skill to a job that doesn't exist!") elif ap_gain: # Go through each item that is equipped, and add AP to its skill. eqp_comp = self.world.component_for_entity(ent, EquipmentComponent) - for item in eqp_comp.equipment: - skill = None if not self.world.has_component(item, ItemSkillComponent) else self.world.component_for_entity(item, ItemSkillComponent) + for other_item in eqp_comp.equipment: + newly_maxed = False + + other_skill = None if not self.world.component_for_entity(other_item, ItemSkillComponent) else self.world.component_for_entity(other_item, ItemSkillComponent).name + + ap_current, ap_max = sd_comp.skill_directory[job][other_skill] + + already_maxed = True if ap_current == ap_max else False - if skill is not None: - newly_maxed = False - already_maxed = False - - ap_current, ap_max = sd_comp.skill_directory[skill.name] - - if ap_current == ap_max: - already_maxed = True - - ap_current += ap_gain - - if ap_current >= ap_max: - ap_current = ap_max - newly_maxed = True - - sd_comp.skill_directory[skill.name] = (ap_current, ap_max) - - if newly_maxed and not already_maxed: - message_data = {'name': skill.name} - self.world.messages.append({'skill_mastered': message_data}) + ap_current += ap_gain + + if ap_current >= ap_max: + ap_current = ap_max + newly_maxed = True if not already_maxed else False + + sd_comp.skill_directory[job][other_skill] = (ap_current, ap_max) + + if newly_maxed: + message_data = {'name': other_skill} + self.world.messages.append({'skill_mastered': message_data}) diff --git a/processors/sub/character_sheet.py b/processors/sub/character_sheet.py index 8599c84..ed6eb59 100644 --- a/processors/sub/character_sheet.py +++ b/processors/sub/character_sheet.py @@ -105,9 +105,11 @@ def render_character_sheet(console_object, world): x, y = 77, 5 console.print(x - 1, y - 1, 'Skill Directory:', color_fg) i = 0 - for skill, ap in skill_directory.items(): - console.print(x, y + i, f"{skill.capitalize()}: {ap[0]}/{ap[1]}") - i += 1 + for job, skill_info in skill_directory.items(): + if skill_info: + for skill_name, ap in skill_info.items(): + console.print(x, y + i, f"{skill_name.capitalize()}: {ap[0]}/{ap[1]}") + i += 1 def generate_equipped_items(titles, world): equipment = world.component_for_entity(1, EquipmentComponent).equipment diff --git a/processors/sub/message_log.py b/processors/sub/message_log.py index 1f805ce..df10ae5 100644 --- a/processors/sub/message_log.py +++ b/processors/sub/message_log.py @@ -44,6 +44,7 @@ def render_message_log(console_object, new_turn, world): _game_loaded = message.get('game_loaded') _game_saved = message.get('game_saved') _heal = message.get('heal') + _job_switch = message.get('job_switch') _max_hp = message.get('max_hp') _move_items = message.get('move_items') _pickup = message.get('pickup') @@ -113,6 +114,22 @@ def render_message_log(console_object, new_turn, world): console.print(0, 0 + dy, '(Turn %s) You heal for %s point(s).' % (turn, value), LOG_COLORS['success']) + elif _job_switch: + wrong_race = _job_switch.get('wrong_race') + not_enough_stats = _job_switch.get('not_enough_stats') + not_enough_skills = _job_switch.get('not_enough_skills') + switch_class = _job_switch.get('switch_class') + + if wrong_race: + console.print(0, 0 + dy, f'You fail to switch classes, due to race.', LOG_COLORS['failure']) + elif not_enough_stats: + console.print(0, 0 + dy, f'You fail to switch classes, due to stats.', LOG_COLORS['failure']) + elif not_enough_skills: + console.print(0, 0 + dy, f'You fail to switch classes, due to skills.', LOG_COLORS['failure']) + elif switch_class: + console.print(0, 0 + dy, f'You are now a {switch_class}. Way to go.', LOG_COLORS['success']) + + elif _max_hp: value, turn = _max_hp diff --git a/processors/wearable.py b/processors/wearable.py index 0f60c79..07f9a31 100644 --- a/processors/wearable.py +++ b/processors/wearable.py @@ -67,29 +67,31 @@ def process(self): if item in eqp.equipment: # Already worn, so remove it. + message_data = {} # Clear the message data, as the RemovableProcessor will do its own thing. self.world.get_processor(RemovableProcessor).queue.put({'ent': ent, 'item': item}) elif slot_filled: # An item is already in the slot we want; swap the two items. + # Wear the item! + wear_item(ent, eqp, item, name_component, self.world) message_data['success'] = 'slot_filled' - self.world.messages.append({'wear': message_data}) - eqp.equipment.append(item) - name_component.name += ' (worn)' - message_data['job'] = self.world.component_for_entity(item, JobReqComponent).job_req.capitalize() + # Remove the other item. self.world.get_processor(RemovableProcessor).queue.put({'ent': ent, 'item': slot_filled_item}) elif self.world.component_for_entity(item, JobReqComponent).job_req != self.world.component_for_entity(ent, JobComponent).job: # Not the correct job to wear the item. message_data['success'] = 'wrong_job' message_data['job'] = self.world.component_for_entity(item, JobReqComponent).job_req.capitalize() - self.world.messages.append({'wear': message_data}) elif self.world.has_component(item, WearableComponent): # Wear the item! + wear_item(ent, eqp, item, name_component, self.world) message_data['success'] = True - self.world.messages.append({'wear': message_data}) - eqp.equipment.append(item) - name_component.name += ' (worn)' - self.world.get_processor(EnergyProcessor).queue.put({'ent': ent, 'item': True}) - self.world.get_processor(SkillDirectoryProcessor).queue.put({'ent': ent, 'item': item, 'new_skill': True}) else: # This is not a wearable item. message_data['success'] = False - self.world.messages.append({'wear': message_data}) \ No newline at end of file + + self.world.messages.append({'wear': message_data}) + +def wear_item(ent, eqp, item, name_component, world): + name_component.name += ' (worn)' + eqp.equipment.append(item) + world.get_processor(EnergyProcessor).queue.put({'ent': ent, 'item': True}) + world.get_processor(SkillDirectoryProcessor).queue.put({'ent': ent, 'item': item, 'new_skill': True})