Guest User

rpg game file

a guest
Feb 16th, 2024
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 42.02 KB | Gaming | 0 0
  1. from __future__ import annotations
  2.  
  3. from collections.abc import Sequence
  4. from typing import Optional
  5.  
  6. import pygame
  7.  
  8. from src.constants import TILE_SIZE
  9. from src.game_entities.alteration import Alteration
  10. from src.game_entities.breakable import Breakable
  11. from src.game_entities.building import Building
  12. from src.game_entities.character import Character
  13. from src.game_entities.chest import Chest
  14. from src.game_entities.consumable import Consumable
  15. from src.game_entities.door import Door
  16. from src.game_entities.effect import Effect
  17. from src.game_entities.entity import Entity
  18. from src.game_entities.equipment import Equipment
  19. from src.game_entities.foe import Foe, Keyword
  20. from src.game_entities.fountain import Fountain
  21. from src.game_entities.gold import Gold
  22. from src.game_entities.item import Item
  23. from src.game_entities.key import Key
  24. from src.game_entities.player import Player
  25. from src.game_entities.portal import Portal
  26. from src.game_entities.potion import Potion
  27. from src.game_entities.shield import Shield
  28. from src.game_entities.shop import Shop
  29. from src.game_entities.skill import Skill
  30. from src.game_entities.spellbook import Spellbook
  31. from src.game_entities.weapon import Weapon
  32. from src.gui.position import Position
  33. from src.services.language import *
  34.  
  35. foes_data = {}
  36. fountains_data = {}
  37. skills_data = {}
  38.  
  39. RACES_DATA_PATH = "data/races.xml"
  40. CLASSES_DATA_PATH = "data/classes.xml"
  41.  
  42. from src.services.global_foes import foes_by_mission, link_foe_to_mission
  43.  
  44.  
  45. def load_races() -> dict[str, dict[str, any]]:
  46.     """
  47.  
  48.    :return:
  49.    """
  50.     races = {}
  51.     races_file = etree.parse(RACES_DATA_PATH).getroot()
  52.     for race_element in races_file.findall("*"):
  53.         race = {}
  54.         constitution = race_element.find("constitution")
  55.         race["constitution"] = (
  56.             int(constitution.text.strip()) if constitution is not None else 0
  57.         )
  58.         move = race_element.find("move")
  59.         race["move"] = (
  60.             int(race_element.find("move").text.strip()) if move is not None else 0
  61.         )
  62.         race["skills"] = [
  63.             get_skill_data(skill.text.strip())
  64.             for skill in race_element.findall("skills/skill/name")
  65.         ]
  66.         races[race_element.tag] = race
  67.     return races
  68.  
  69.  
  70. def load_classes() -> dict[str, dict[str, any]]:
  71.     """
  72.  
  73.    :return:
  74.    """
  75.     classes = {}
  76.     classes_file = etree.parse(CLASSES_DATA_PATH).getroot()
  77.     for class_element in classes_file.findall("*"):
  78.         class_data = {}
  79.         constitution = class_element.find("constitution")
  80.         class_data["constitution"] = (
  81.             int(constitution.text.strip()) if constitution is not None else 0
  82.         )
  83.         move = class_element.find("move")
  84.         class_data["move"] = int(move.text.strip()) if move is not None else 0
  85.         class_data["stats_up"] = load_stats_up(class_element)
  86.         class_data["skills"] = [
  87.             get_skill_data(skill.text.strip())
  88.             for skill in class_element.findall("skills/skill/name")
  89.         ]
  90.         classes[class_element.tag] = class_data
  91.     return classes
  92.  
  93.  
  94. def load_stat_up(element, stat_name) -> Sequence[int]:
  95.     """
  96.  
  97.    :param element:
  98.    :param stat_name:
  99.    :return:
  100.    """
  101.     return [
  102.         int(value)
  103.         for value in element.find("stats_up/" + stat_name).text.strip().split(",")
  104.     ]
  105.  
  106.  
  107. def load_stats_up(element) -> dict[str, Sequence[int]]:
  108.     """
  109.  
  110.    :param element:
  111.    :return:
  112.    """
  113.     return {
  114.         "hp": load_stat_up(element, "hp"),
  115.         "def": load_stat_up(element, "defense"),
  116.         "res": load_stat_up(element, "resistance"),
  117.         "str": load_stat_up(element, "strength"),
  118.     }
  119.  
  120.  
  121. def get_skill_data(name) -> Skill:
  122.     """
  123.  
  124.    :param name:
  125.    :return:
  126.    """
  127.     if name not in skills_data:
  128.         # Required data
  129.         skill_element = etree.parse("data/skills.xml").find(name)
  130.         formatted_name = skill_element.find("name/" + language)
  131.         if formatted_name is not None:
  132.             formatted_name = formatted_name.text.strip()
  133.         else:
  134.             formatted_name = skill_element.find("name/en").text.strip()
  135.         nature = skill_element.find("type").text.strip()
  136.         description = get_localized_string(skill_element.find("info")).strip()
  137.  
  138.         # Not required elements
  139.         power = 0
  140.         power_element = skill_element.find("power")
  141.         if power_element is not None:
  142.             power = int(power_element.text.strip())
  143.         stats = []
  144.         stats_element = skill_element.find("stats")
  145.         if stats_element is not None:
  146.             stats = list(stats_element.text.replace(" ", "").split(","))
  147.         alterations = []
  148.         alterations_element = skill_element.find("alteration")
  149.         if alterations_element is not None:
  150.             alterations = list(alterations_element.text.replace(" ", "").split(","))
  151.  
  152.         skills_data[name] = Skill(
  153.             name, formatted_name, nature, description, power, stats, alterations
  154.         )
  155.     return skills_data[name]
  156.  
  157.  
  158. def load_alteration(alteration_element) -> Alteration:
  159.     """
  160.  
  161.    :param alteration_element:
  162.    :return:
  163.    """
  164.     name = alteration_element.find("name").text.strip()
  165.     abbreviation = alteration_element.find("abbr").text.strip()
  166.     power = int(alteration_element.find("power").text.strip())
  167.     duration = int(alteration_element.find("duration").text.strip())
  168.     description = alteration_element.find("desc").text.strip()
  169.     specificities = [
  170.         spec.text.strip() for spec in alteration_element.findall("specs/spec")
  171.     ]
  172.     return Alteration(name, abbreviation, power, duration, description, specificities)
  173.  
  174.  
  175. def load_placements(positions, gap_x, gap_y) -> Sequence[Position]:
  176.     """
  177.  
  178.    :param positions:
  179.    :param gap_x:
  180.    :param gap_y:
  181.    :return:
  182.    """
  183.     placements = []
  184.     for coordinates in positions:
  185.         x_coordinate = int(coordinates.find("x").text) * TILE_SIZE + gap_x
  186.         y_coordinate = int(coordinates.find("y").text) * TILE_SIZE + gap_y
  187.         placements.append(pygame.Vector2(x_coordinate, y_coordinate))
  188.     return placements
  189.  
  190.  
  191. def load_all_entities_from_save(data, gap_x, gap_y) -> dict[str, list[Entity]]:
  192.     """
  193.  
  194.    :param data:
  195.    :param gap_x:
  196.    :param gap_y:
  197.    :return:
  198.    """
  199.     return {
  200.         "allies": load_entities_from_save(
  201.             "character", data.findall("allies/ally"), gap_x, gap_y
  202.         ),
  203.         "foes": load_entities_from_save("foe", data.findall("foes/foe"), gap_x, gap_y),
  204.         "breakables": load_entities_from_save(
  205.             "breakable", data.findall("breakables/breakable"), gap_x, gap_y
  206.         ),
  207.         "chests": load_entities_from_save(
  208.             "chest", data.findall("chests/chest"), gap_x, gap_y
  209.         ),
  210.         "doors": load_entities_from_save(
  211.             "door", data.findall("doors/door"), gap_x, gap_y
  212.         ),
  213.         "buildings": load_entities_from_save(
  214.             "building", data.findall("buildings/building"), gap_x, gap_y
  215.         ),
  216.         "fountains": load_entities_from_save(
  217.             "fountain", data.findall("fountains/fountain"), gap_x, gap_y
  218.         ),
  219.         "portals": load_entities_from_save(
  220.             "portal", data.findall("portals/couple"), gap_x, gap_y
  221.         ),
  222.     }
  223.  
  224.  
  225. def load_entities_from_save(entity_nature, data, gap_x, gap_y) -> list[Entity]:
  226.     """
  227.  
  228.    :param entity_nature:
  229.    :param data:
  230.    :param gap_x:
  231.    :param gap_y:
  232.    :return:
  233.    """
  234.     collection = []
  235.  
  236.     for element in data:
  237.         # TODO: Too many branches for a similar behaviour, need refactoring (mapping?)
  238.         if entity_nature == "character":
  239.             entity = load_ally_from_save(element, gap_x, gap_y)
  240.         elif entity_nature == "foe":
  241.             entity = load_foe_from_save(element, gap_x, gap_y)
  242.         elif entity_nature == "chest":
  243.             entity = load_chest_from_save(element, gap_x, gap_y)
  244.         elif entity_nature == "door":
  245.             entity = load_door_from_save(element, gap_x, gap_y)
  246.         elif entity_nature == "building":
  247.             entity = load_building_from_save(element, gap_x, gap_y)
  248.         elif entity_nature == "portal":
  249.             entity, other_entity = load_portal_from_save(element, gap_x, gap_y)
  250.             collection.append(other_entity)
  251.         elif entity_nature == "fountain":
  252.             entity = load_fountain_from_save(element, gap_x, gap_y)
  253.         elif entity_nature == "breakable":
  254.             entity = load_breakable_from_save(element, gap_x, gap_y)
  255.         else:
  256.             print(f"Unrecognized nature : {entity_nature}")
  257.             entity = None
  258.         collection.append(entity)
  259.     return collection
  260.  
  261.  
  262. def load_artificial_entity_from_save(entity, data, gap_x, gap_y, extension_path=""):
  263.     """
  264.  
  265.    :param entity:
  266.    :param data:
  267.    :param gap_x:
  268.    :param gap_y:
  269.    :param extension_path:
  270.    :return:
  271.    """
  272.     name = entity.find("name").text.strip()
  273.  
  274.     # Static data
  275.     sprite = "imgs/" + extension_path + data.find("sprite").text.strip()
  276.     strategy = data.find("strategy").text.strip()
  277.  
  278.     # Dynamic data
  279.     x_coordinate = int(entity.find("position/x").text) * TILE_SIZE + gap_x
  280.     y_coordinate = int(entity.find("position/y").text) * TILE_SIZE + gap_y
  281.     position = pygame.Vector2(x_coordinate, y_coordinate)
  282.  
  283.     level_element = (
  284.         entity.find("level") if entity.find("level") is not None else data.find("level")
  285.     )
  286.     lvl = int(level_element.text.strip())
  287.     specific_strategy = entity.find("strategy")
  288.     if specific_strategy is not None:
  289.         strategy = specific_strategy.text.strip()
  290.  
  291.     dynamic_data = entity
  292.     hit_points = int(dynamic_data.find("hp").text.strip())
  293.     strength = int(dynamic_data.find("strength").text.strip())
  294.     defense = int(dynamic_data.find("defense").text.strip())
  295.     resistance = int(dynamic_data.find("resistance").text.strip())
  296.     alterations = []
  297.     for alteration in dynamic_data.findall("alterations/alteration"):
  298.         alterations.append(load_alteration(alteration))
  299.  
  300.     return {
  301.         "name": name,
  302.         "sprite": sprite,
  303.         "strategy": strategy,
  304.         "position": position,
  305.         "level": lvl,
  306.         "hp": hit_points,
  307.         "strength": strength,
  308.         "defense": defense,
  309.         "resistance": resistance,
  310.         "alterations": alterations,
  311.     }
  312.  
  313.  
  314. def load_artificial_entity(
  315.     name: str,
  316.     data: etree.Element,
  317.     position: Position,
  318.     level: Optional[int] = None,
  319.     strategy: Optional[str] = None,
  320.     extension_path: str = "",
  321. ):
  322.     # Static data
  323.     sprite = "imgs/" + extension_path + data.find("sprite").text.strip()
  324.     if strategy is None:
  325.         strategy = data.find("strategy").text.strip()
  326.  
  327.     # Dynamic data
  328.     if level is None:
  329.         level = int(data.find("level").text.strip())
  330.     hit_points = int(data.find("hp").text.strip())
  331.     strength = int(data.find("strength").text.strip())
  332.     defense = int(data.find("defense").text.strip())
  333.     resistance = int(data.find("resistance").text.strip())
  334.     alterations = []
  335.     for alteration in data.findall("alterations/alteration"):
  336.         alterations.append(load_alteration(alteration))
  337.  
  338.     return {
  339.         "name": name,
  340.         "sprite": sprite,
  341.         "strategy": strategy,
  342.         "position": position,
  343.         "level": level,
  344.         "hp": hit_points,
  345.         "strength": strength,
  346.         "defense": defense,
  347.         "resistance": resistance,
  348.         "alterations": alterations,
  349.     }
  350.  
  351.  
  352. def load_ally_from_save(ally_element, gap_x, gap_y):
  353.     """
  354.  
  355.    :param ally_element:
  356.    :param gap_x:
  357.    :param gap_y:
  358.    :return:
  359.    """
  360.     name = ally_element.find("name").text.strip()
  361.     generic_data = etree.parse("data/characters.xml").find(name)
  362.  
  363.     attributes = load_artificial_entity_from_save(
  364.         ally_element, generic_data, gap_x, gap_y
  365.     )
  366.  
  367.     # Static data character
  368.     race = generic_data.find("race").text.strip()
  369.     classes = [generic_data.find("class").text.strip()]
  370.     interaction_element = generic_data.find("interaction")
  371.     dialog = []
  372.     for talk in interaction_element.findall("talk"):
  373.         dialog.append(get_localized_string(talk).strip())
  374.     interaction = {
  375.         "dialog": dialog,
  376.         "join_team": interaction_element.find("join_team") is not None,
  377.     }
  378.  
  379.     # Dynamic data character
  380.     dynamic_data = ally_element
  381.     gold = int(dynamic_data.find("gold").text.strip())
  382.  
  383.     equipments = []
  384.     for equipment in dynamic_data.findall("equipment/*"):
  385.         equipments.append(load_item(equipment))
  386.  
  387.     skills = [
  388.         (
  389.             get_skill_data(skill.text.strip())
  390.             if not skill.text.strip() in skills_data
  391.             else skills_data[skill.text.strip()]
  392.         )
  393.         for skill in dynamic_data.findall("skills/skill/name")
  394.     ]
  395.  
  396.     loaded_ally = Character(
  397.         attributes["name"],
  398.         attributes["position"],
  399.         attributes["sprite"],
  400.         attributes["hp"],
  401.         attributes["defense"],
  402.         attributes["resistance"],
  403.         attributes["strength"],
  404.         classes,
  405.         equipments,
  406.         attributes["strategy"],
  407.         attributes["level"],
  408.         skills,
  409.         attributes["alterations"],
  410.         race,
  411.         gold,
  412.         interaction,
  413.     )
  414.  
  415.     for item in dynamic_data.findall("inventory/item"):
  416.         item_loaded = load_item(item)
  417.         loaded_ally.set_item(item_loaded)
  418.  
  419.     current_hit_points = int(ally_element.find("current_hp").text.strip())
  420.     loaded_ally.hit_points = current_hit_points
  421.  
  422.     experience = int(ally_element.find("exp").text.strip())
  423.     loaded_ally.earn_xp(experience)
  424.  
  425.     return loaded_ally
  426.  
  427.  
  428. def load_ally(name: str, position: Position) -> Character:
  429.     generic_data = etree.parse("data/characters.xml").find(name)
  430.  
  431.     attributes = load_artificial_entity(name, generic_data, position)
  432.  
  433.     # Static data character
  434.     race = generic_data.find("race").text.strip()
  435.     classes = [generic_data.find("class").text.strip()]
  436.     interaction_element = generic_data.find("interaction")
  437.     dialog = []
  438.     for talk in interaction_element.findall("talk"):
  439.         dialog.append(get_localized_string(talk).strip())
  440.     interaction = {
  441.         "dialog": dialog,
  442.         "join_team": interaction_element.find("join_team") is not None,
  443.     }
  444.  
  445.     # Dynamic data character
  446.     gold = int(generic_data.find("gold").text.strip())
  447.  
  448.     equipments = []
  449.     for equipment in generic_data.findall("equipment/*"):
  450.         equipment_loaded = parse_item_file(equipment.text.strip())
  451.         equipments.append(equipment_loaded)
  452.  
  453.     skills = (
  454.         Character.classes_data[classes[0]]["skills"]
  455.         + Character.races_data[race]["skills"]
  456.     )
  457.  
  458.     loaded_ally = Character(
  459.         attributes["name"],
  460.         attributes["position"],
  461.         attributes["sprite"],
  462.         attributes["hp"],
  463.         attributes["defense"],
  464.         attributes["resistance"],
  465.         attributes["strength"],
  466.         classes,
  467.         equipments,
  468.         attributes["strategy"],
  469.         attributes["level"],
  470.         skills,
  471.         attributes["alterations"],
  472.         race,
  473.         gold,
  474.         interaction,
  475.     )
  476.  
  477.     for item in generic_data.findall("inventory/item"):
  478.         item_loaded = parse_item_file(item.text.strip())
  479.  
  480.         loaded_ally.set_item(item_loaded)
  481.  
  482.     # Up stats according to current lvl
  483.     loaded_ally.stats_up(attributes["level"] - 1)
  484.     # Restore hp due to lvl up
  485.     loaded_ally.healed()
  486.  
  487.     return loaded_ally
  488.  
  489. def load_foe_from_save(foe_element, gap_x, gap_y):
  490.     """
  491.  
  492.    :param foe_element:
  493.    :param gap_x:
  494.    :param gap_y:
  495.    :return:
  496.    """
  497.     name = foe_element.find("name").text.strip()
  498.     if name not in foes_data:
  499.         foes_data[name] = etree.parse("data/foes.xml").find(name)
  500.         # Load grow rates of this kind of foe in the class
  501.         Foe.grow_rates[name] = load_stats_up(foes_data[name])
  502.  
  503.     attributes = load_artificial_entity_from_save(
  504.         foe_element, foes_data[name], gap_x, gap_y, "dungeon_crawl/monster/"
  505.     )
  506.  
  507.     # Static data foe
  508.     xp_gain = int(foes_data[name].find("xp_gain").text.strip())
  509.     foe_range = foes_data[name].find("reach")
  510.     reach = (
  511.         [int(reach) for reach in foe_range.text.strip().split(",")]
  512.         if foe_range is not None
  513.         else [1]
  514.     )
  515.     attack_kind = foes_data[name].find("attack_kind").text.strip()
  516.     loot = [
  517.         (
  518.             parse_item_file(item.find("name").text.strip()),
  519.             float(item.find("probability").text),
  520.         )
  521.         for item in foes_data[name].findall("loot/item")
  522.     ]
  523.     gold_looted = foes_data[name].find("loot/gold")
  524.     if gold_looted is not None:
  525.         loot.append(
  526.             (
  527.                 Gold(int(gold_looted.find("amount").text)),
  528.                 float(gold_looted.find("probability").text),
  529.             )
  530.         )
  531.     keywords_element = foes_data[name].find("keywords")
  532.     keywords = (
  533.         [
  534.             Keyword[keyword.upper()]
  535.             for keyword in keywords_element.text.strip().split(",")
  536.         ]
  537.         if keywords_element is not None
  538.         else []
  539.     )
  540.     move = int(foes_data[name].find("move").text.strip())
  541.  
  542.     # Dynamic data foe
  543.     # Overwrite static loaded loot
  544.     loot = [
  545.         (
  546.             parse_item_file(item.find("name").text.strip()),
  547.             float(item.find("probability").text),
  548.         )
  549.         for item in foe_element.findall("loot/item")
  550.     ]
  551.     gold_looted = foe_element.find("loot/gold")
  552.     if gold_looted is not None:
  553.         loot.append(
  554.             (
  555.                 Gold(int(gold_looted.find("amount").text)),
  556.                 float(gold_looted.find("probability").text),
  557.             )
  558.         )
  559.  
  560.     mission_target_element = foe_element.find("mission_target")
  561.     mission_target = (
  562.         mission_target_element.text.strip()
  563.         if mission_target_element is not None
  564.         else None
  565.     )
  566.  
  567.     loaded_foe = Foe(
  568.         attributes["name"],
  569.         attributes["position"],
  570.         attributes["sprite"],
  571.         attributes["hp"],
  572.         attributes["defense"],
  573.         attributes["resistance"],
  574.         move,
  575.         attributes["strength"],
  576.         attack_kind,
  577.         attributes["strategy"],
  578.         reach,
  579.         xp_gain,
  580.         loot,
  581.         keywords,
  582.         attributes["level"],
  583.         attributes["alterations"],
  584.         mission_target,
  585.     )
  586.  
  587.     current_hp = int(foe_element.find("current_hp").text.strip())
  588.     loaded_foe.hit_points = current_hp
  589.  
  590.     experience = int(foe_element.find("exp").text.strip())
  591.     loaded_foe.earn_xp(experience)
  592.  
  593.     if mission_target is not None:
  594.         link_foe_to_mission(loaded_foe, mission_target)
  595.  
  596.     return loaded_foe
  597.  
  598.  
  599. def load_foe(
  600.     name: str,
  601.     position: Position,
  602.     level: int,
  603.     strategy: Optional[str],
  604.     specific_loot: Sequence[Item],
  605.     mission_target: str,
  606. ) -> Foe:
  607.     if name not in foes_data:
  608.         foes_data[name] = etree.parse("data/foes.xml").find(name)
  609.         # Load grow rates of this kind of foe in the class
  610.         Foe.grow_rates[name] = load_stats_up(foes_data[name])
  611.  
  612.     attributes = load_artificial_entity(
  613.         name, foes_data[name], position, level, strategy, "dungeon_crawl/monster/"
  614.     )
  615.  
  616.     # Static data foe
  617.     xp_gain = int(foes_data[name].find("xp_gain").text.strip())
  618.     foe_range = foes_data[name].find("reach")
  619.     reach = (
  620.         [int(reach) for reach in foe_range.text.strip().split(",")]
  621.         if foe_range is not None
  622.         else [1]
  623.     )
  624.     attack_kind = foes_data[name].find("attack_kind").text.strip()
  625.     loot = [
  626.         (
  627.             parse_item_file(item.find("name").text.strip()),
  628.             float(item.find("probability").text),
  629.         )
  630.         for item in foes_data[name].findall("loot/item")
  631.     ] + [(item, 1.0) for item in specific_loot]
  632.     gold_looted = foes_data[name].find("loot/gold")
  633.     if gold_looted is not None:
  634.         loot.append(
  635.             (
  636.                 Gold(int(gold_looted.find("amount").text)),
  637.                 float(gold_looted.find("probability").text),
  638.             )
  639.         )
  640.     keywords_element = foes_data[name].find("keywords")
  641.     keywords = (
  642.         [
  643.             Keyword[keyword.upper()]
  644.             for keyword in keywords_element.text.strip().split(",")
  645.         ]
  646.         if keywords_element is not None
  647.         else []
  648.     )
  649.     move = int(foes_data[name].find("move").text.strip())
  650.  
  651.     loaded_foe = Foe(
  652.         attributes["name"],
  653.         attributes["position"],
  654.         attributes["sprite"],
  655.         attributes["hp"],
  656.         attributes["defense"],
  657.         attributes["resistance"],
  658.         move,
  659.         attributes["strength"],
  660.         attack_kind,
  661.         attributes["strategy"],
  662.         reach,
  663.         xp_gain,
  664.         loot,
  665.         keywords,
  666.         attributes["level"],
  667.         attributes["alterations"],
  668.         mission_target,
  669.     )
  670.  
  671.     # Up stats according to current lvl
  672.     loaded_foe.stats_up(attributes["level"] - 1)
  673.     # Restore hp due to lvl up
  674.     loaded_foe.healed()
  675.  
  676.     return loaded_foe
  677.  
  678.  
  679. def load_chest_from_save(chest, gap_x, gap_y):
  680.     """
  681.  
  682.    :param chest:
  683.    :param gap_x:
  684.    :param gap_y:
  685.    :return:
  686.    """
  687.     # Static data
  688.     x_coordinate = int(chest.find("position/x").text) * TILE_SIZE + gap_x
  689.     y_coordinate = int(chest.find("position/y").text) * TILE_SIZE + gap_y
  690.     position = pygame.Vector2(x_coordinate, y_coordinate)
  691.     sprite_closed = chest.find("closed/sprite").text.strip()
  692.     sprite_opened = chest.find("opened/sprite").text.strip()
  693.  
  694.     # Dynamic data
  695.     potential_items = []
  696.     opened = chest.find("state").text.strip() == "True"
  697.     it_name = chest.find("contains/item").text.strip()
  698.     item = parse_item_file(it_name)
  699.  
  700.     potential_items.append((item, 1.0))
  701.  
  702.     loaded_chest = Chest(position, sprite_closed, sprite_opened, potential_items)
  703.  
  704.     if opened:
  705.         loaded_chest.open()
  706.  
  707.     return loaded_chest
  708.  
  709.  
  710. def load_door_from_save(door, gap_x, gap_y):
  711.     """
  712.  
  713.    :param door:
  714.    :param gap_x:
  715.    :param gap_y:
  716.    :return:
  717.    """
  718.     # Static data
  719.     x_coordinate = int(door.find("position/x").text) * TILE_SIZE + gap_x
  720.     y_coordinate = int(door.find("position/y").text) * TILE_SIZE + gap_y
  721.     position = pygame.Vector2(x_coordinate, y_coordinate)
  722.     sprite = door.find("sprite").text.strip()
  723.  
  724.     # Dynamic data
  725.     pick_lock_initiated = door.find("pick_lock_initiated") is not None
  726.  
  727.     loaded_door = Door(position, sprite, pick_lock_initiated)
  728.     return loaded_door
  729.  
  730.  
  731. def load_building_from_save(building, gap_x, gap_y):
  732.     """
  733.  
  734.    :param building:
  735.    :param gap_x:
  736.    :param gap_y:
  737.    :return:
  738.    """
  739.     # Static data
  740.     name = building.find("name").text.strip()
  741.     x_coordinate = int(building.find("position/x").text) * TILE_SIZE + gap_x
  742.     y_coordinate = int(building.find("position/y").text) * TILE_SIZE + gap_y
  743.     position = pygame.Vector2(x_coordinate, y_coordinate)
  744.     sprite = building.find("sprite").text.strip()
  745.     interaction = building.find("interaction")
  746.     interaction_element = {}
  747.     if interaction is not None:
  748.         talks = interaction.find("talks")
  749.         if talks is not None:
  750.             interaction_element["talks"] = []
  751.             for talk in talks.findall("talk"):
  752.                 interaction_element["talks"].append(get_localized_string(talk).strip())
  753.         else:
  754.             interaction_element["talks"] = []
  755.         interaction_element["gold"] = (
  756.             int(interaction.find("gold").text.strip())
  757.             if interaction.find("gold") is not None
  758.             else 0
  759.         )
  760.         interaction_element["item"] = (
  761.             parse_item_file(interaction.find("item").text.strip())
  762.             if interaction.find("item") is not None
  763.             else None
  764.         )
  765.  
  766.     nature = building.find("type")
  767.     if nature is not None:
  768.         nature = nature.text.strip()
  769.         if nature == "shop":
  770.             stock = []
  771.             for item in building.findall("items/item"):
  772.                 entry = {
  773.                     "item": parse_item_file(item.find("name").text.strip()),
  774.                     "quantity": int(item.find("quantity").text.strip()),
  775.                 }
  776.                 stock.append(entry)
  777.             loaded_building = Shop(name, position, sprite, stock, interaction_element)
  778.         else:
  779.             print("Error : building type isn't recognized : ", type)
  780.             raise SystemError
  781.     else:
  782.         loaded_building = Building(name, position, sprite, interaction_element)
  783.  
  784.     # Dynamic data
  785.     locked = building.find("state").text.strip()
  786.     if locked == "True":
  787.         loaded_building.remove_interaction()
  788.  
  789.     return loaded_building
  790.  
  791.  
  792. def load_obstacles(tree, gap_x, gap_y):
  793.     """
  794.  
  795.    :param tree:
  796.    :param gap_x:
  797.    :param gap_y:
  798.    :return:
  799.    """
  800.     loaded_obstacles = []
  801.     for positions in tree.findall("positions"):
  802.         fixed_y = positions.find("y")
  803.         if fixed_y is not None:
  804.             fixed_y = int(fixed_y.text) * TILE_SIZE + gap_y
  805.             from_x = int(positions.find("from_x").text) * TILE_SIZE + gap_x
  806.             to_x = int(positions.find("to_x").text) * TILE_SIZE + gap_x
  807.             for i in range(from_x, to_x + TILE_SIZE, TILE_SIZE):
  808.                 pos = (i, fixed_y)
  809.                 loaded_obstacles.append(pos)
  810.         else:
  811.             fixed_x = int(positions.find("x").text) * TILE_SIZE + gap_x
  812.             from_y = int(positions.find("from_y").text) * TILE_SIZE + gap_y
  813.             to_y = int(positions.find("to_y").text) * TILE_SIZE + gap_y
  814.             for i in range(from_y, to_y + TILE_SIZE, TILE_SIZE):
  815.                 pos = (fixed_x, i)
  816.                 loaded_obstacles.append(pos)
  817.  
  818.     for obstacle in tree.findall("position"):
  819.         x_coordinate = int(obstacle.find("x").text) * TILE_SIZE + gap_x
  820.         y_coordinate = int(obstacle.find("y").text) * TILE_SIZE + gap_y
  821.         pos = (x_coordinate, y_coordinate)
  822.         loaded_obstacles.append(pos)
  823.     return loaded_obstacles
  824.  
  825.  
  826. def load_portal_from_save(portal_couple, gap_x, gap_y):
  827.     """
  828.  
  829.    :param portal_couple:
  830.    :param gap_x:
  831.    :param gap_y:
  832.    :return:
  833.    """
  834.     first_x = int(portal_couple.find("first/position/x").text) * TILE_SIZE + gap_x
  835.     first_y = int(portal_couple.find("first/position/y").text) * TILE_SIZE + gap_y
  836.     first_position = pygame.Vector2(first_x, first_y)
  837.     second_x = int(portal_couple.find("second/position/x").text) * TILE_SIZE + gap_x
  838.     second_y = int(portal_couple.find("second/position/y").text) * TILE_SIZE + gap_y
  839.     second_position = pygame.Vector2(second_x, second_y)
  840.     sprite = "imgs/dungeon_crawl/" + portal_couple.find("sprite").text.strip()
  841.     first_portal = Portal(first_position, sprite)
  842.     second_portal = Portal(second_position, sprite)
  843.     Portal.link_portals(first_portal, second_portal)
  844.     return first_portal, second_portal
  845.  
  846.  
  847. def load_fountain_from_save(fountain, gap_x, gap_y):
  848.     """
  849.  
  850.    :param fountain:
  851.    :param gap_x:
  852.    :param gap_y:
  853.    :return:
  854.    """
  855.     name = fountain.find("type").text.strip()
  856.     x_coordinate = int(fountain.find("position/x").text) * TILE_SIZE + gap_x
  857.     y_coordinate = int(fountain.find("position/y").text) * TILE_SIZE + gap_y
  858.     position = pygame.Vector2(x_coordinate, y_coordinate)
  859.     if name not in fountains_data:
  860.         fountains_data[name] = etree.parse("data/fountains.xml").find(name)
  861.     sprite = "imgs/dungeon_crawl/" + fountains_data[name].find("sprite").text.strip()
  862.     sprite_empty = (
  863.         "imgs/dungeon_crawl/" + fountains_data[name].find("sprite_empty").text.strip()
  864.     )
  865.     effect_name = fountains_data[name].find("effect").text.strip()
  866.     power = int(fountains_data[name].find("power").text.strip())
  867.     duration = int(fountains_data[name].find("duration").text.strip())
  868.     effect = Effect(effect_name, power, duration)
  869.     times = int(fountains_data[name].find("times").text.strip())
  870.  
  871.     loaded_fountain = Fountain(name, position, sprite, sprite_empty, effect, times)
  872.  
  873.     # Load remaining uses from saved data
  874.     times = int(fountain.find("times").text.strip())
  875.     loaded_fountain.set_times(times)
  876.  
  877.     return loaded_fountain
  878.  
  879.  
  880. def load_fountain(name: str, position: Position) -> Fountain:
  881.     if name not in fountains_data:
  882.         fountains_data[name] = etree.parse("data/fountains.xml").find(name)
  883.  
  884.     sprite = "imgs/dungeon_crawl/" + fountains_data[name].find("sprite").text.strip()
  885.     sprite_empty = (
  886.         "imgs/dungeon_crawl/" + fountains_data[name].find("sprite_empty").text.strip()
  887.     )
  888.  
  889.     effect_name = fountains_data[name].find("effect").text.strip()
  890.     power = int(fountains_data[name].find("power").text.strip())
  891.     duration = int(fountains_data[name].find("duration").text.strip())
  892.     effect = Effect(effect_name, power, duration)
  893.     times = int(fountains_data[name].find("times").text.strip())
  894.  
  895.     return Fountain(name, position, sprite, sprite_empty, effect, times)
  896.  
  897.  
  898. def load_breakable_from_save(breakable, gap_x, gap_y):
  899.     """
  900.  
  901.    :param breakable:
  902.    :param gap_x:
  903.    :param gap_y:
  904.    :return:
  905.    """
  906.     # Static data
  907.     x_coordinate = int(breakable.find("position/x").text) * TILE_SIZE + gap_x
  908.     y_coordinate = int(breakable.find("position/y").text) * TILE_SIZE + gap_y
  909.     pos = (x_coordinate, y_coordinate)
  910.     sprite = "imgs/dungeon_crawl/dungeon/" + breakable.find("sprite").text.strip()
  911.     hit_points = int(breakable.find("current_hp").text.strip())
  912.  
  913.     return Breakable(pos, sprite, hit_points, 0, 0)
  914.  
  915.  
  916. def load_restrictions(restrictions_element):
  917.     """
  918.  
  919.    :param restrictions_element:
  920.    :return:
  921.    """
  922.     restrictions = {}
  923.     if restrictions_element is None:
  924.         return restrictions
  925.  
  926.     classes = restrictions_element.find("classes")
  927.     if classes is not None:
  928.         restrictions["classes"] = classes.text.strip().split(",")
  929.     races = restrictions_element.find("races")
  930.     if races is not None:
  931.         restrictions["races"] = races.text.strip().split(",")
  932.  
  933.     return restrictions
  934.  
  935.  
  936. def load_events(events_el, gap_x, gap_y):
  937.     """
  938.  
  939.    :param events_el:
  940.    :param gap_x:
  941.    :param gap_y:
  942.    :return:
  943.    """
  944.     events = {}
  945.     for event in events_el:
  946.         events[event.tag] = {}
  947.         dialog_els = event.findall("dialog")
  948.         if dialog_els:
  949.             events[event.tag]["dialogs"] = []
  950.             for dialog_element in dialog_els:
  951.                 title_element = dialog_element.find("title")
  952.                 events[event.tag]["dialogs"].append(
  953.                     {
  954.                         "title": title_element.text.strip()
  955.                         if title_element is not None
  956.                         else "",
  957.                         "talks": [
  958.                             talk.text.strip()
  959.                             for talk in dialog_element.find("talks").findall("talk")
  960.                         ],
  961.                     }
  962.                 )
  963.         new_players_elements = event.findall("new_player")
  964.         if new_players_elements:
  965.             events[event.tag]["new_players"] = [
  966.                 {
  967.                     "name": player_element.find("name").text.strip(),
  968.                     "position": (
  969.                         int(player_element.find("position/x").text.strip()) * TILE_SIZE
  970.                         + gap_x,
  971.                         int(player_element.find("position/y").text.strip()) * TILE_SIZE
  972.                         + gap_y,
  973.                     ),
  974.                 }
  975.                 for player_element in new_players_elements
  976.             ]
  977.  
  978.     return events
  979.  
  980.  
  981. def load_player(player_element, from_save):
  982.     """
  983.  
  984.    :param player_element:
  985.    :param from_save:
  986.    :return:
  987.    """
  988.     name = player_element.find("name").text.strip()
  989.     level = player_element.find("level")
  990.     if level is None:
  991.         # If level is not specified, default value is 1
  992.         level = 1
  993.     else:
  994.         level = int(level.text.strip())
  995.     player_class = player_element.find("class").text.strip()
  996.     race = player_element.find("race").text.strip()
  997.     gold = int(player_element.find("gold").text.strip())
  998.     experience = int(player_element.find("exp").text.strip()) if from_save else 0
  999.     hit_points = int(player_element.find("hp").text.strip())
  1000.     strength = int(player_element.find("strength").text.strip())
  1001.     defense = int(player_element.find("defense").text.strip())
  1002.     res = int(player_element.find("resistance").text.strip())
  1003.     current_hp = (
  1004.         int(player_element.find("current_hp").text.strip()) if from_save else hit_points
  1005.     )
  1006.     inventory = []
  1007.     for item in player_element.findall("inventory/item"):
  1008.         item_loaded = (
  1009.             load_item(item) if from_save else parse_item_file(item.text.strip())
  1010.         )
  1011.         inventory.append(item_loaded)
  1012.  
  1013.     equipments = []
  1014.     for equipment in player_element.findall("equipment/*"):
  1015.         eq_loaded = (
  1016.             load_item(equipment)
  1017.             if from_save
  1018.             else parse_item_file(equipment.text.strip())
  1019.         )
  1020.         equipments.append(eq_loaded)
  1021.  
  1022.     alterations = []
  1023.     if from_save:
  1024.         skills = [
  1025.             (
  1026.                 get_skill_data(skill.text.strip())
  1027.                 if skill.text.strip() not in skills_data
  1028.                 else skills_data[skill.text.strip()]
  1029.             )
  1030.             for skill in player_element.findall("skills/skill/name")
  1031.         ]
  1032.         for alteration in player_element.findall("alterations/alteration"):
  1033.             alterations.append(load_alteration(alteration))
  1034.         tree = etree.parse("data/characters.xml").getroot()
  1035.         player_t = tree.xpath(name)[0]
  1036.     else:
  1037.         skills = (
  1038.             Character.classes_data[player_class]["skills"]
  1039.             + Character.races_data[race]["skills"]
  1040.         )
  1041.         player_t = player_element
  1042.  
  1043.     # -- Reading of the XML file for default character's values (i.e. sprites)
  1044.     sprite = "imgs/" + player_t.find("sprite").text.strip()
  1045.     compl_sprite = player_t.find("complement_sprite")
  1046.     if compl_sprite is not None:
  1047.         compl_sprite = "imgs/" + compl_sprite.text.strip()
  1048.  
  1049.     player = Player(
  1050.         name,
  1051.         sprite,
  1052.         hit_points,
  1053.         defense,
  1054.         res,
  1055.         strength,
  1056.         [player_class],
  1057.         equipments,
  1058.         race,
  1059.         gold,
  1060.         level,
  1061.         skills,
  1062.         alterations,
  1063.         complementary_sprite_link=compl_sprite,
  1064.     )
  1065.     player.earn_xp(experience)
  1066.     player.items = inventory
  1067.     player.hit_points = current_hp
  1068.     if from_save:
  1069.         position = (
  1070.             int(player_element.find("position/x").text.strip()) * TILE_SIZE,
  1071.             int(player_element.find("position/y").text.strip()) * TILE_SIZE,
  1072.         )
  1073.         player.position = position
  1074.         state = player_element.find("turnFinished").text.strip()
  1075.         if state == "True":
  1076.             player.end_turn()
  1077.     else:
  1078.         # Up stats according to current lvl
  1079.         player.stats_up(level - 1)
  1080.         # Restore hp due to lvl up
  1081.         player.healed()
  1082.  
  1083.     return player
  1084.  
  1085.  
  1086. def load_players(data):
  1087.     """
  1088.  
  1089.    :param data:
  1090.    :return:
  1091.    """
  1092.     players = []
  1093.     for player_element in data.findall("players/player"):
  1094.         players.append(load_player(player_element, True))
  1095.     return players
  1096.  
  1097.  
  1098. def load_escaped_players(data):
  1099.     """
  1100.  
  1101.    :param data:
  1102.    :return:
  1103.    """
  1104.     players = []
  1105.     for player_element in data.findall("escaped_players/player"):
  1106.         players.append(load_player(player_element, True))
  1107.     return players
  1108.  
  1109.  
  1110. def init_player(name):
  1111.     """
  1112.  
  1113.    :param name:
  1114.    :return:
  1115.    """
  1116.     # -- Reading of the XML file
  1117.     tree = etree.parse("data/characters.xml").getroot()
  1118.     player_t = tree.xpath(name)[0]
  1119.     return load_player(player_t, False)
  1120.  
  1121.  
  1122. def load_weapon_effect(eff):
  1123.     """
  1124.  
  1125.    :param eff:
  1126.    :return:
  1127.    """
  1128.     loaded_effect = {}
  1129.  
  1130.     # Load effect
  1131.     name = eff.find("name").text.strip()
  1132.     power_element = eff.find("power")
  1133.     power = int(power_element.text.strip()) if power_element is not None else 0
  1134.     duration_element = eff.find("duration")
  1135.     duration = int(duration_element.text.strip()) if duration_element is not None else 0
  1136.     loaded_effect["effect"] = Effect(name, power, duration)
  1137.  
  1138.     # Load probability
  1139.     loaded_effect["probability"] = int(
  1140.         float(eff.find("probability").text.strip()) * 100
  1141.     )
  1142.  
  1143.     return loaded_effect
  1144.  
  1145.  
  1146. def load_item(data):
  1147.     """
  1148.  
  1149.    :param data:
  1150.    :return:
  1151.    """
  1152.     name = data.find("name").text.strip()
  1153.  
  1154.     # Retrieve static data
  1155.     item = parse_item_file(name)
  1156.     item.resell_price = int(data.find("value").text.strip())
  1157.     if isinstance(item, (Shield, Weapon)):
  1158.         item.durability = int(data.find("durability").text.strip())
  1159.  
  1160.     return item
  1161.  
  1162.  
  1163. def parse_item_file(name):
  1164.     """
  1165.  
  1166.    :param name:
  1167.    :return:
  1168.    """
  1169.     # Retrieve data root for item
  1170.     item_tree_root = etree.parse("data/items.xml").getroot().find(".//" + name)
  1171.  
  1172.     sprite = "imgs/dungeon_crawl/item/" + item_tree_root.find("sprite").text.strip()
  1173.     info = get_localized_string(item_tree_root.find("info")).strip()
  1174.     price = item_tree_root.find("price")
  1175.     if price is not None:
  1176.         price = int(price.text.strip())
  1177.     else:
  1178.         price = 0
  1179.     category = item_tree_root.find("category").text.strip()
  1180.  
  1181.     if category in ("potion", "consumable"):
  1182.         effects = []
  1183.         for effect in item_tree_root.findall(".//effect"):
  1184.             effect_name = effect.find("type").text.strip()
  1185.             power_element = effect.find("power")
  1186.             power = int(power_element.text.strip()) if power_element is not None else 0
  1187.             duration_element = effect.find("duration")
  1188.             duration = (
  1189.                 int(duration_element.text.strip())
  1190.                 if duration_element is not None
  1191.                 else 0
  1192.             )
  1193.             effects.append(Effect(effect_name, power, duration))
  1194.         item = (
  1195.             Potion(name, sprite, info, price, effects)
  1196.             if category == "potion"
  1197.             else Consumable(name, sprite, info, price, effects)
  1198.         )
  1199.     elif category == "armor":
  1200.         body_part = item_tree_root.find("bodypart").text.strip()
  1201.         defense_element = item_tree_root.find("def")
  1202.         defense = (
  1203.             int(defense_element.text.strip()) if defense_element is not None else 0
  1204.         )
  1205.         weight = int(item_tree_root.find("weight").text.strip())
  1206.         equipment_sprites = item_tree_root.find("equipped_sprites")
  1207.         if equipment_sprites is not None:
  1208.             equipped_sprites = []
  1209.             for eq_sprite in equipment_sprites.findall("sprite"):
  1210.                 equipped_sprites.append(
  1211.                     "imgs/dungeon_crawl/player/" + eq_sprite.text.strip()
  1212.                 )
  1213.         else:
  1214.             equipped_sprites = [
  1215.                 "imgs/dungeon_crawl/player/"
  1216.                 + item_tree_root.find("equipped_sprite").text.strip()
  1217.             ]
  1218.         restrictions = load_restrictions(item_tree_root.find("restrictions"))
  1219.         item = Equipment(
  1220.             name,
  1221.             sprite,
  1222.             info,
  1223.             price,
  1224.             equipped_sprites,
  1225.             body_part,
  1226.             defense,
  1227.             0,
  1228.             0,
  1229.             weight,
  1230.             restrictions,
  1231.         )
  1232.     elif category == "shield":
  1233.         parry = int(float(item_tree_root.find("parry_rate").text.strip()) * 100)
  1234.         defense_element = item_tree_root.find("def")
  1235.         defense = (
  1236.             int(defense_element.text.strip()) if defense_element is not None else 0
  1237.         )
  1238.         fragility = int(item_tree_root.find("fragility").text.strip())
  1239.         weight = int(item_tree_root.find("weight").text.strip())
  1240.         equipped_sprite = [
  1241.             "imgs/dungeon_crawl/player/hand_left/"
  1242.             + item_tree_root.find("equipped_sprite").text.strip()
  1243.         ]
  1244.         restrictions = load_restrictions(item_tree_root.find("restrictions"))
  1245.         item = Shield(
  1246.             name,
  1247.             sprite,
  1248.             info,
  1249.             price,
  1250.             equipped_sprite,
  1251.             defense,
  1252.             weight,
  1253.             parry,
  1254.             fragility,
  1255.             restrictions,
  1256.         )
  1257.     elif category == "weapon":
  1258.         power = int(item_tree_root.find("power").text.strip())
  1259.         attack_kind = item_tree_root.find("kind").text.strip()
  1260.         weight = int(item_tree_root.find("weight").text.strip())
  1261.         fragility = int(item_tree_root.find("fragility").text.strip())
  1262.         weapon_range = [
  1263.             int(reach) for reach in item_tree_root.find("range").text.strip().split(",")
  1264.         ]
  1265.         equipped_sprite = [
  1266.             "imgs/dungeon_crawl/player/hand_right/"
  1267.             + item_tree_root.find("equipped_sprite").text.strip()
  1268.         ]
  1269.         restrictions = load_restrictions(item_tree_root.find("restrictions"))
  1270.         effects = item_tree_root.find("effects")
  1271.         possible_effects = []
  1272.         if effects is not None:
  1273.             possible_effects = [
  1274.                 load_weapon_effect(eff) for eff in effects.findall("effect")
  1275.             ]
  1276.  
  1277.         keywords_element = item_tree_root.find("strong_against/keywords")
  1278.         strong_against = (
  1279.             [
  1280.                 Keyword[keyword.upper()]
  1281.                 for keyword in keywords_element.text.strip().split(",")
  1282.             ]
  1283.             if keywords_element is not None
  1284.             else []
  1285.         )
  1286.  
  1287.         item = Weapon(
  1288.             name,
  1289.             sprite,
  1290.             info,
  1291.             price,
  1292.             equipped_sprite,
  1293.             power,
  1294.             attack_kind,
  1295.             weight,
  1296.             fragility,
  1297.             weapon_range,
  1298.             restrictions,
  1299.             possible_effects,
  1300.             strong_against,
  1301.         )
  1302.     elif category == "key":
  1303.         for_chest = item_tree_root.find("open_chest") is not None
  1304.         for_door = item_tree_root.find("open_door") is not None
  1305.         item = Key(name, sprite, info, price, for_chest, for_door)
  1306.     elif category == "spellbook":
  1307.         spell = item_tree_root.find("effect").text.strip()
  1308.         item = Spellbook(name, sprite, info, price, spell)
  1309.     else:
  1310.         # No special category
  1311.         item = Item(name, sprite, info, price)
  1312.  
  1313.     return item
  1314.  
Advertisement
Add Comment
Please, Sign In to add comment