183 lines
7.3 KiB
Python
183 lines
7.3 KiB
Python
import sqlite3
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from engine.character import Ailment, BodyPart, Character, ClothingPiece, HeldItem, Organ
|
|
from engine.character_library import CharacterLibrary
|
|
from engine.defs import DefRegistry
|
|
from engine.inventory import Inventory
|
|
from engine.item import ItemInstance
|
|
|
|
DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs"
|
|
|
|
|
|
@pytest.fixture()
|
|
def registry():
|
|
return DefRegistry.load(DEFS_DIR)
|
|
|
|
|
|
@pytest.fixture()
|
|
def library():
|
|
return CharacterLibrary(sqlite3.connect(":memory:"))
|
|
|
|
|
|
def build_character(registry) -> Character:
|
|
character = Character(
|
|
entity_id="ignored-on-save", # library keys on (account_id, character_id), not entity_id
|
|
name="Vex",
|
|
species_id="reptilian_quad",
|
|
gender="none",
|
|
body_type="stocky",
|
|
blood_percentage=88.0,
|
|
strength=14,
|
|
body_parts=[
|
|
BodyPart(
|
|
"torso", "reptilian_torso", integrity=70.0,
|
|
ailments=[Ailment("poisoned", "Poisoned", severity=0.3, progress=12.0)],
|
|
organs=[Organ("reptilian_heart", integrity=60.0)],
|
|
),
|
|
],
|
|
default_body_parts=registry.new_default_body_parts("reptilian_quad"),
|
|
clothing=[ClothingPiece("torso", "leather_jacket")],
|
|
default_clothing=[],
|
|
held_items=[HeldItem("right_hand", "chronowand", attachments=["scope_basic"])],
|
|
implants=["chronoimplant"],
|
|
mental_stats={"insanity": 4.0},
|
|
)
|
|
character.bio["stealth"] = 5
|
|
backpack = Inventory(4, 4)
|
|
backpack.place(ItemInstance("rope-1", "sandwich"), registry.get_item_def("sandwich"), 0, 0)
|
|
character.clothing[0] = ClothingPiece("torso", "backpack_basic")
|
|
character.clothing[0].inventory = backpack
|
|
character.inventory = Inventory(6, 5)
|
|
character.inventory.place(ItemInstance("staff-1", "staff_oak"), registry.get_item_def("staff_oak"), 0, 0)
|
|
return character
|
|
|
|
|
|
def test_save_and_load_roundtrip_preserves_identity_and_species(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
assert loaded is not None
|
|
assert loaded.name == "Vex"
|
|
assert loaded.species_id == "reptilian_quad"
|
|
assert loaded.gender == "none"
|
|
assert loaded.body_type == "stocky"
|
|
assert loaded.hair_color is None
|
|
assert loaded.blood_percentage == pytest.approx(88.0)
|
|
assert loaded.strength == 14
|
|
assert loaded.position is None # portable template, not tied to any world
|
|
|
|
|
|
def test_roundtrip_preserves_body_parts_ailments_and_organs(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
|
|
torso = loaded.get_body_part("torso")
|
|
assert torso.part_def_id == "reptilian_torso"
|
|
assert torso.integrity == pytest.approx(70.0)
|
|
assert [a.id for a in torso.ailments] == ["poisoned"]
|
|
assert torso.ailments[0].progress == pytest.approx(12.0)
|
|
heart = next(o for o in torso.organs if o.organ_def_id == "reptilian_heart")
|
|
assert heart.integrity == pytest.approx(60.0)
|
|
|
|
# default (non-overridden) parts and their organs came back too
|
|
head = loaded.get_body_part("head")
|
|
assert head.part_def_id == "reptilian_head"
|
|
assert {o.organ_def_id for o in head.organs} == {"reptilian_brain", "reptilian_eyes", "reptilian_ears"}
|
|
|
|
|
|
def test_roundtrip_preserves_clothing_and_its_granted_inventory(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
|
|
backpack = loaded.get_clothing("torso")
|
|
assert backpack.item_def_id == "backpack_basic"
|
|
assert backpack.inventory is not None
|
|
assert {p.item.item_id for p in backpack.inventory.items()} == {"rope-1"}
|
|
|
|
|
|
def test_roundtrip_preserves_held_items_and_attachments(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
|
|
held = loaded.get_held_item("right_hand")
|
|
assert held.item_def_id == "chronowand"
|
|
assert held.attachments == ["scope_basic"]
|
|
|
|
|
|
def test_roundtrip_preserves_implants(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
assert loaded.implants == ["chronoimplant"]
|
|
|
|
|
|
def test_roundtrip_preserves_bio_and_mental_stats(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
assert loaded.bio["stealth"] == 5
|
|
assert loaded.mental_stats == {"insanity": 4.0}
|
|
|
|
|
|
def test_roundtrip_preserves_top_level_inventory(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
placed = {p.item.item_id: (p.x, p.y) for p in loaded.inventory.items()}
|
|
assert placed == {"staff-1": (0, 0)}
|
|
|
|
|
|
def test_load_character_returns_none_when_not_found(registry, library):
|
|
assert library.load_character("account-1", "no-such-char", registry) is None
|
|
|
|
|
|
def test_list_characters_returns_id_name_species_for_an_account(registry, library):
|
|
library.save_character("account-1", "char-1", build_character(registry))
|
|
other = build_character(registry)
|
|
other.name = "Kess"
|
|
library.save_character("account-1", "char-2", other)
|
|
library.save_character("account-2", "char-3", build_character(registry)) # different account
|
|
|
|
listed = library.list_characters("account-1")
|
|
assert {(cid, name) for cid, name, species in listed if species} == {("char-1", "Vex"), ("char-2", "Kess")}
|
|
|
|
|
|
def test_delete_character_removes_it_and_leaves_others(registry, library):
|
|
library.save_character("account-1", "char-1", build_character(registry))
|
|
library.save_character("account-1", "char-2", build_character(registry))
|
|
|
|
library.delete_character("account-1", "char-1")
|
|
|
|
assert library.load_character("account-1", "char-1", registry) is None
|
|
assert library.load_character("account-1", "char-2", registry) is not None
|
|
|
|
|
|
def test_saving_twice_under_the_same_id_overwrites_not_duplicates(registry, library):
|
|
character = build_character(registry)
|
|
library.save_character("account-1", "char-1", character)
|
|
character.blood_percentage = 50.0
|
|
library.save_character("account-1", "char-1", character)
|
|
|
|
loaded = library.load_character("account-1", "char-1", registry)
|
|
assert loaded.blood_percentage == pytest.approx(50.0)
|
|
assert len(library.list_characters("account-1")) == 1
|
|
|
|
|
|
def test_same_character_id_isolated_between_accounts(registry, library):
|
|
a = build_character(registry)
|
|
a.name = "Account A's Vex"
|
|
b = build_character(registry)
|
|
b.name = "Account B's Vex"
|
|
library.save_character("account-a", "char-1", a)
|
|
library.save_character("account-b", "char-1", b)
|
|
|
|
assert library.load_character("account-a", "char-1", registry).name == "Account A's Vex"
|
|
assert library.load_character("account-b", "char-1", registry).name == "Account B's Vex"
|