Return to repo list

heart-of-gold

Tactical RPG written in python, using pygame.
Return to HMagellan.com

commit 54093995d1cf6ba9cb2dca2027b5542154a2415a
parent 5760aecfb53d23e86bd73b85db4dcb2ae934d21b
Author: Erik Letson <hmagellan@hmagellan.com>
Date:   Sun, 22 Nov 2020 15:39:07 -0600

big improvement to piece loading, levelup, active stat calc

Diffstat:
Mdata/board/testmap1/testmap1.json | 243++++++++++++++-----------------------------------------------------------------
Mdata/img/stat_screen_1.png | 0
Adata/img/weapon_icons_1.png | 0
Adata/json/pieces.json | 40++++++++++++++++++++++++++++++++++++++++
Msrc/constants.py | 4++++
Msrc/piece.py | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
6 files changed, 196 insertions(+), 235 deletions(-)

diff --git a/data/board/testmap1/testmap1.json b/data/board/testmap1/testmap1.json @@ -1,251 +1,94 @@ { - "testent1" : { - "name" : "Jisella", - "type" : "Piece", - "sheet" : "jisella_1", - "sprite" : [0, 0], - "pictures" : "jisella_pictures_1", + "testent1" : { + "name" : "Jisella", + "template" : "Jisella", + "level" : 1, + "rank" : 0, + "exp" : 0, "visible" : true, "animation" : "stand_L", "animated" : true, "passable" : false, "tile" : [5, 4], "team" : "Player", - "normal_stats" : { - "LVL" : 1, - "EXP" : 0, - "HP" : 100, - "ATK" : 10, - "DEF" : 6, - "SPD" : 9, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 2, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Fire", - "EXPERTISE" : ["Sword"] - }, - "active_stats" : { - "LVL" : 1, - "EXP" : 0, - "HP" : 80, - "ATK" : 10, - "DEF" : 6, - "SPD" : 9, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 2, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Fire", - "EXPERTISE" : ["Sword"] - }, "equipment" : { "weapon" : "Basic_Sword_1", "armor" : "Basic_Armor_1", "acc1" : null, "acc2" : null, "acc3" : null - } - }, + }, + "stat_mod" : { }, + "stat_dist" : { }, + "replace" : { } + }, "testent2" : { "name" : "Fakella", - "type" : "Piece", - "sheet" : "jisella_2", - "sprite" : [0, 0], - "pictures" : "jisella_pictures_2", + "template" : "Jisella", + "level" : 2, + "rank" : 0, + "exp" : 100, "visible" : true, - "animation" : "stand_R", + "animation" : "stand_L", "animated" : true, "passable" : false, - "tile" : [0, 2], - "team" : "Enemy", - "normal_stats" : { - "LVL" : 1, - "EXP" : 100, - "HP" : 100, - "ATK" : 10, - "DEF" : 6, - "SPD" : 3, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 2, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Water", - "EXPERTISE" : ["Sword"] - }, - "active_stats" : { - "LVL" : 1, - "EXP" : 100, - "HP" : 100, - "ATK" : 10, - "DEF" : 6, - "SPD" : 3, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 2, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Water", - "EXPERTISE" : ["Sword"] - }, + "tile" : [0, 3], + "team" : "Player", "equipment" : { "weapon" : "Basic_Sword_1", "armor" : "Basic_Armor_1", "acc1" : null, "acc2" : null, "acc3" : null - } + }, + "stat_mod" : { }, + "stat_dist" : { }, + "replace" : { "sheet" : "jisella_2", "pictures" : "jisella_pictures_2" } }, "testent3" : { - "name" : "OtherName", - "type" : "Piece", - "sheet" : "jisella_4", - "sprite" : [0, 0], - "pictures" : "jisella_pictures_4", + "name" : "Faker2", + "template" : "Jisella", + "level" : 1, + "rank" : 0, + "exp" : 0, "visible" : true, - "animation" : "stand_R", + "animation" : "stand_L", "animated" : true, "passable" : false, - "tile" : [7, 2], + "tile" : [6, 1], "team" : "Enemy", - "normal_stats" : { - "LVL" : 1, - "EXP" : 0, - "HP" : 100, - "ATK" : 10, - "DEF" : 6, - "SPD" : 1, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 6, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Water", - "EXPERTISE" : ["Sword"] - }, - "active_stats" : { - "LVL" : 1, - "EXP" : 0, - "HP" : 100, - "ATK" : 10, - "DEF" : 6, - "SPD" : 1, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 6, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Water", - "EXPERTISE" : ["Sword"] - }, "equipment" : { "weapon" : "Basic_Sword_1", "armor" : "Basic_Armor_1", "acc1" : null, "acc2" : null, "acc3" : null - } + }, + "stat_mod" : { }, + "stat_dist" : { }, + "replace" : { "sheet" : "jisella_3", "pictures" : "jisella_pictures_3" } }, "testent4" : { "name" : "SomeName", - "type" : "Piece", - "sheet" : "jisella_3", - "sprite" : [0, 0], - "pictures" : "jisella_pictures_3", + "template" : "Jisella", + "level" : 8, + "rank" : 0, + "exp" : 100, "visible" : true, - "animation" : "stand_R", + "animation" : "stand_L", "animated" : true, "passable" : false, - "tile" : [0, 9], + "tile" : [2, 2], "team" : "Enemy", - "normal_stats" : { - "LVL" : 1, - "EXP" : 0, - "HP" : 100, - "ATK" : 10, - "DEF" : 6, - "SPD" : 11, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 9, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Water", - "EXPERTISE" : ["Sword"] - }, - "active_stats" : { - "LVL" : 1, - "EXP" : 0, - "HP" : 10, - "ATK" : 10, - "DEF" : 6, - "SPD" : 11, - "ACC" : 7, - "INT" : 5, - "WIS" : 6, - "LUK" : 8, - "INIT" : 9, - "CNTR" : 2, - "GARD" : 1, - "PUSH" : 2, - "MOVE" : 6, - "SKIL" : 3, - "PASS" : 3, - "AFFINITY" : "Water", - "EXPERTISE" : ["Sword"] - }, "equipment" : { "weapon" : "Basic_Sword_1", "armor" : "Basic_Armor_1", "acc1" : null, "acc2" : null, "acc3" : null - } + }, + "stat_mod" : { }, + "stat_dist" : { }, + "replace" : { "sheet" : "jisella_4", "pictures" : "jisella_pictures_4" } } } - diff --git a/data/img/stat_screen_1.png b/data/img/stat_screen_1.png Binary files differ. diff --git a/data/img/weapon_icons_1.png b/data/img/weapon_icons_1.png Binary files differ. diff --git a/data/json/pieces.json b/data/json/pieces.json @@ -0,0 +1,40 @@ +{ + "Jisella" : { + "type" : "Piece", + "sheet" : "jisella_1", + "pictures" : "jisella_pictures_1", + "class" : "Adventurer", + "affinity" : "Fire", + "expertise" : ["Sword"], + "stats" : { + "HP" : 20, + "ATK" : 6, + "DEF" : 3, + "SPD" : 5, + "ACC" : 4, + "INT" : 2, + "WIS" : 2, + "LUK" : 4, + "INIT" : 3, + "CNTR" : 2, + "GARD" : 1, + "PUSH" : 2, + "MOVE" : 6, + "SKIL" : 3, + "PASS" : 4 + }, + "growth" : { + "2" : { "ATK" : 1 }, + "3" : { "SPD" : 1, "DEF" : 1 }, + "4" : { "ACC" : 1, "WIS" : 1, "INT" : 1 }, + "5" : { "LUK" : 1, "SPD" : 1 }, + "7" : { "ATK" : 1, "ACC" : 1 }, + "10": { "DEF" : 1, "ATK" : 1 }, + "11": { "ATK" : 1, "WIS" : 1, "INT" : 1 }, + "13": { "LUK" : 2, "ACC" : 1 }, + "17": { "SPD" : 1, "INT" : 1 }, + "19": { "ATK" : 2, "SPD" : 2 }, + "20": { "SPD" : 1, "WIS" : 1, "ACC" : 1 } + } + } +} diff --git a/src/constants.py b/src/constants.py @@ -40,6 +40,10 @@ JSON_PATH = os.path.join(DATA_PATH, "json") MENU_JSON_PATH = os.path.join(JSON_PATH, "menus") SCENE_JSON_PATH = os.path.join(JSON_PATH, "scenes") +# Piece constants +CHARBASE = json.load(open(os.path.join(JSON_PATH, "pieces.json"))) +AFFINITIES = ("Fire", "Miasma", "Water", "Wind", "Life", "Lightning") + # Item constants ITEMBASE = json.load(open(os.path.join(JSON_PATH, "items.json"))) WEAPON_TYPE_SOURCES = { diff --git a/src/piece.py b/src/piece.py @@ -1,4 +1,4 @@ -import pygame, os, json, random +import pygame, os, json, random, copy from . import manager, entity from .constants import * @@ -52,37 +52,77 @@ class PieceManager(manager.Manager): # here. This should probably be so for all methods that load # from definition. for p in definition: - n_sheet = self.bus.fetch("sheet_manager", "sheets")[definition[p]["sheet"]] - n_sprite = tuple(definition[p]["sprite"]) - n_anim = self.bus.fetch("sheet_manager", "animations")[definition[p]["sheet"]][definition[p]["animation"]] - n_animated = definition[p]["animated"] - n_name = definition[p]["name"] - n_pictures = definition[p]["pictures"] - n_passable = definition[p]["passable"] - n_nstats = definition[p]["normal_stats"] - n_astats = definition[p]["active_stats"] - n_team = definition[p]["team"] - n_equip = definition[p]["equipment"] - np = Piece(n_sheet, n_sprite, n_anim, n_animated, self, n_name, n_pictures, n_passable, n_nstats, n_astats, n_team, n_equip) - np.assign_tile(tuple(definition[p]["tile"]), self.bus.fetch("board_manager", "board_tile_layer")[tuple(definition[p]["tile"])]) + # NOTE: This is one of those screwy things you don't often run into, but the copy module is + # needed here because of the pointer nature of python vals + work = copy.deepcopy(definition[p]) + n_sheet = self.bus.fetch("sheet_manager", "sheets")[work["sheet"]] + n_anim = self.bus.fetch("sheet_manager", "animations")[work["sheet"]][work["animation"]] + n_name = work["name"] + n_pic = work["pictures"] + n_lvl = work["level"] + n_exp = work["exp"] + n_rank = work["rank"] + n_nstats = work["normal_stats"] + n_mod = work["stat_mod"] + n_dist = work["stat_dist"] + n_aff = work["affinity"] + n_tise = work["expertise"] + n_team = work["team"] + n_equip = work["equipment"] + n_growth = work["growth"] + np = Piece(n_sheet, (0, 0), n_anim, True, self, n_name, n_pic, n_lvl, n_exp, n_rank, n_aff, n_tise, False, n_nstats, n_mod, n_dist, + n_team, n_equip, n_growth) + np.assign_tile(tuple(work["tile"]), self.bus.fetch("board_manager", "board_tile_layer")[tuple(work["tile"])]) np.snap_to_tile() self.add_piece(np) def load_pieces_from_file(self, filename): """ - Load pieces from a given file. Checks to see if - the provided filename is a JSON (or rather, has a - .json extension) and tries to correct it if not. - This method is called to load the pre-defined pieces - that spawn in pre-made boards. + Load pieces from a given JSON file, + cross-referenced against the piece + database. """ + # Setup + foldname = "" + full_def = {} + + # Ensure the dir and file names match our structure if filename[::-4] != ".json": foldname = filename filename = filename + ".json" else: foldname = filename[::-4] + + # Next, load up the JSON file jsondef = json.load(open(os.path.join(BOARD_PATH, foldname, filename))) - self.load_pieces_from_def(jsondef) + + # Now, create the full definition given the JSON and the character database + for p in jsondef: + temp = CHARBASE[jsondef[p]["template"]] + full_def[p] = { + "name" : jsondef[p]["name"], + "sheet" : jsondef[p]["replace"]["sheet"] if "sheet" in jsondef[p]["replace"].keys() else temp["sheet"], + "sprite" : (0, 0), + "animation" : jsondef[p]["replace"]["animation"] if "animation" in jsondef[p]["replace"].keys() else "stand_L", + "animated" : True, + "passable" : False, + "pictures" : jsondef[p]["replace"]["pictures"] if "pictures" in jsondef[p]["replace"].keys() else temp["pictures"], + "level" : jsondef[p]["level"], + "exp" : jsondef[p]["exp"], + "rank" : jsondef[p]["rank"], + "normal_stats" : temp["stats"], + "stat_mod" : jsondef[p]["stat_mod"], + "stat_dist" : jsondef[p]["stat_dist"], + "affinity" : jsondef[p]["replace"]["affinity"] if "affinity" in jsondef[p]["replace"] else temp["affinity"], + "expertise" : temp["expertise"], + "team" : jsondef[p]["team"], + "tile" : jsondef[p]["tile"], + "equipment" : jsondef[p]["equipment"], + "growth" : temp["growth"] + } + + # Lastly, load the def as real pieces + self.load_pieces_from_def(full_def) def load_tile_cursor(self, sheet): """ @@ -205,8 +245,8 @@ class Piece(entity.Entity): """ def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False, - manager = None, name = None, pictures = None, passable = False, normal_stats = None, active_stats = None, - team = None, equipment = None): + manager = None, name = None, pictures = None, level = None, exp = None, rank = None, affinity = None, expertise = None, + passable = False, normal_stats = None, stat_mod = None, stat_dist = None, team = None, equipment = None, growth = None): # Parent initialization super().__init__(sheet, sprite, animation, animated) @@ -217,6 +257,11 @@ class Piece(entity.Entity): # Others self.manager = manager self.name = name + self.level = 1 + self.rank = rank + self.exp = exp + self.affinity = affinity + self.expertise = expertise self.pictures = { "icons_small" : None, "icons_large" : None, @@ -225,7 +270,11 @@ class Piece(entity.Entity): self.load_pictures(pictures) self.passable = passable self.normal_stats = normal_stats - self.active_stats = active_stats + self.active_stats = { i : 0 for i in self.normal_stats } + self.equip_mod = { i : 0 for i in self.normal_stats } # derived later + self.effect_mod = { i : 0 for i in self.normal_stats } # this mod can only be applied in battle, defaults empty always + self.dist_mod = { i : stat_dist[i] if i in stat_dist else 0 for i in self.normal_stats } + self.other_mod = { i : stat_mod[i] if i in stat_mod else 0 for i in self.normal_stats } self.team = team self.equipment = { "weapon" : None, @@ -234,8 +283,12 @@ class Piece(entity.Entity): "acc2" : None, "acc3" : None } + self.growth = growth self.attack_range = None self.equip([equipment[e] for e in equipment]) + if level != 1: + self.level_up(level) + self.modulate_stats() self.back_to_stand = False # TODO: This may not be the best way self.current_tile_path = [] self.current_tile_path_index = 0 @@ -269,7 +322,7 @@ class Piece(entity.Entity): self.haste_mod = 3 # 3 - normal, 1 - minimum (slow 2), 5 - maximum (haste 2) self.set_animation(self.manager.bus.fetch("sheet_manager", "animations")[self.sheet.name]["stand_" + self.facing.name], True) - + def load_pictures(self, pictures): """ Load universal pictures from a given pictures @@ -284,19 +337,18 @@ class Piece(entity.Entity): that match entries in the item database. """ if type(equipment) == str: - if (ITEMBASE[equipment]["slot"] != "weapon" or ITEMBASE[equipment]["type"] in self.active_stats["EXPERTISE"]): + if (ITEMBASE[equipment]["slot"] != "weapon" or ITEMBASE[equipment]["type"] in self.expertise): self.equipment[ITEMBASE[equipment]["slot"]] = equipment else: print("Failed to equip " + equipment + " to " + self.name) elif type(equipment) in (list, tuple): for e in equipment: if e != None: - if ITEMBASE[e]["slot"] != "weapon" or ITEMBASE[e]["type"] in self.active_stats["EXPERTISE"]: + if ITEMBASE[e]["slot"] != "weapon" or ITEMBASE[e]["type"] in self.expertise: self.equipment[ITEMBASE[e]["slot"]] = e else: print("Failed to equip " + e + " to " + self.name) self.derive_range() - self.derive_equipment_bonus() def unequip(self, slot): """ @@ -320,15 +372,17 @@ class Piece(entity.Entity): else: self.attack_range = 0 - def derive_equipment_bonus(self): + def level_up(self, level_num): """ - Figure out what are stats are after taking the - equipment into account. + Increase the level of this piece by 'level_num' + and take growth into account. """ - for i in self.equipment: - if self.equipment[i] != None: - for s in ITEMBASE[self.equipment[i]]["stats"]: - self.active_stats[s] += ITEMBASE[self.equipment[i]]["stats"][s] + for l in range(self.level, min(self.level + level_num - 1, 21)): + self.level += 1 + for g in self.growth: + if self.level % int(g) == 0: + for s in self.growth[g]: + self.normal_stats[s] += self.growth[g][s] def render_damage_number(self): """ @@ -517,7 +571,9 @@ class Piece(entity.Entity): if self.damage_timer == 72: self.set_animation(self.manager.bus.fetch("sheet_manager", "animations")[self.sheet.name]["hurt_" + self.facing.name], True) elif self.damage_timer == 30: - self.active_stats["HP"] -= self.damage_to_receive + # TODO: This is possibly not the best way + self.effect_mod["HP"] -= self.damage_to_receive + self.modulate_stats() if self.active_stats["HP"] > 0: self.create_health_bar() else: @@ -535,6 +591,23 @@ class Piece(entity.Entity): else: self.set_animation(self.sheet.manager.animations[self.sheet.name]["stand_" + self.facing.name], True) self.manager.bus.perform_turn_manager_refresh_state() + + def modulate_stats(self): + """ + Calculate each stat mod dict, and apply all + those mods to the normal_stats to produce the + current active_stats. This calculates only + the equip_mod; all other mods are determined + elsewhere and taken for granted here. + """ + self.equip_mod = { i : 0 for i in self.normal_stats } + for e in self.equipment: + if self.equipment[e] != None: + for n in self.normal_stats: + self.equip_mod[n] += ITEMBASE[self.equipment[e]]["stats"][n] + + for s in self.normal_stats: + self.active_stats[s] = self.normal_stats[s] + self.equip_mod[s] + self.other_mod[s] + self.dist_mod[s] + self.effect_mod[s] def act(self): """ @@ -543,6 +616,7 @@ class Piece(entity.Entity): self.execute_tile_path_move() self.execute_attack_action() self.execute_be_damaged() + self.modulate_stats() # TODO: Something else should be done so that this doesn't overwrite other # legit non-walking, non-standing anims. THIS MAY NOT BE THE BEST. if self.back_to_stand: