Skip to content

Commit

Permalink
Player needs to meet job requirements before switching
Browse files Browse the repository at this point in the history
The player needs a certain amount of stat points in order to change. The player needs to be the correct race. The player needs to have mastered X number of skills from job Y in order to change. Added a Rogue class. Player starts as Soldier. Improve readability of SkillDirectoryProcessor. Added message feedback when trying to swtich jobs. Reduce duplicate code in WearableProcessor. Change structure of SkillDirectoryComponent, and updated codebase to reflect this.
  • Loading branch information
Shoes01 committed Oct 9, 2019
1 parent 76326d5 commit f485db2
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 79 deletions.
25 changes: 18 additions & 7 deletions _jobs.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
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}


JOBS = {
'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}
)
}
15 changes: 10 additions & 5 deletions components/actor/skill_directory.py
Original file line number Diff line number Diff line change
@@ -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': {
}
}
"""
3 changes: 2 additions & 1 deletion engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ def main():

if __name__ == '__main__':
# cProfile.run('main()') # This runs the profiler
main()
main()

2 changes: 1 addition & 1 deletion game.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
84 changes: 62 additions & 22 deletions processors/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
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
66 changes: 37 additions & 29 deletions processors/skill_directory.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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})
8 changes: 5 additions & 3 deletions processors/sub/character_sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions processors/sub/message_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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

Expand Down
24 changes: 13 additions & 11 deletions processors/wearable.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})

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})

0 comments on commit f485db2

Please sign in to comment.