Files
PaintMyKI5/scanner/tests/test_ingame_painting.py

226 lines
9.3 KiB
Python

from __future__ import annotations
import json
import re
import tempfile
import unittest
from pathlib import Path
from scanner import scan_ki5_workshop as scanner
PROJECT_ROOT = Path(__file__).resolve().parents[2]
LUA_ROOT = PROJECT_ROOT / "42.20/media/lua"
class PaintUseAllocationTests(unittest.TestCase):
def test_allocates_one_full_bucket_proportionally(self) -> None:
paints = (
scanner.PaintPercentage(scanner.PaintCan("Base.PaintGrey", "Grey", (0.5,) * 3, 0.1), 60.0),
scanner.PaintPercentage(scanner.PaintCan("Base.PaintGreen", "Green", (0.4,) * 3, 0.1), 20.0),
scanner.PaintPercentage(scanner.PaintCan("Base.PaintWhite", "White", (0.9,) * 3, 0.1), 20.0),
)
allocated = scanner.allocate_paint_uses(paints, 10)
self.assertEqual(
[("Base.PaintGrey", 6.0), ("Base.PaintGreen", 2.0), ("Base.PaintWhite", 2.0)],
[(entry.paint_can.item_id, uses) for entry, uses in allocated],
)
self.assertAlmostEqual(10.0, sum(uses for _entry, uses in allocated))
def test_equal_remainders_are_deterministic(self) -> None:
paints = tuple(
scanner.PaintPercentage(
scanner.PaintCan(item_id, item_id, (0.5,) * 3, 0.1),
percentage,
)
for item_id, percentage in (
("Base.PaintRed", 33.3),
("Base.PaintBlue", 33.3),
("Base.PaintGreen", 33.4),
)
)
allocated = scanner.allocate_paint_uses(paints, 10)
self.assertEqual(
[("Base.PaintGreen", 3.34), ("Base.PaintBlue", 3.33), ("Base.PaintRed", 3.33)],
[(entry.paint_can.item_id, uses) for entry, uses in allocated],
)
self.assertAlmostEqual(10.0, sum(uses for _entry, uses in allocated))
def test_bucket_capacity_comes_from_game_use_delta(self) -> None:
paints = (
scanner.PaintCan("Base.PaintGreen", "Green", (0.4,) * 3, 0.1),
scanner.PaintCan("Base.PaintWhite", "White", (0.9,) * 3, 0.1),
)
self.assertEqual(10, scanner.paint_bucket_uses(paints))
class InGameLuaContractTests(unittest.TestCase):
EXPECTED_FILES = (
"shared/PaintMyKI5/PaintRequirements.lua",
"shared/PaintMyKI5/PaintInventory.lua",
"shared/PaintMyKI5/TimedActions/ISPaintMyKI5Vehicle.lua",
"client/PaintMyKI5/ISPaintMyKI5UI.lua",
"client/PaintMyKI5/PaintVehicleContextMenu.lua",
"shared/PaintMyKI5/TextureName.lua",
)
def test_runtime_files_exist(self) -> None:
self.assertEqual([], [name for name in self.EXPECTED_FILES if not (LUA_ROOT / name).is_file()])
def test_art_assets_are_packaged_for_mod_and_context_menus(self) -> None:
source_icon = (PROJECT_ROOT / "art/icon.png").read_bytes()
source_preview = (PROJECT_ROOT / "art/preview.png").read_bytes()
for directory in (PROJECT_ROOT, PROJECT_ROOT / "42.20"):
self.assertEqual(source_icon, (directory / "icon.png").read_bytes())
self.assertEqual(source_preview, (directory / "preview.png").read_bytes())
metadata = (directory / "mod.info").read_text(encoding="utf-8")
self.assertIn("icon=icon.png", metadata)
self.assertIn("poster=preview.png", metadata)
self.assertEqual(
source_icon,
(PROJECT_ROOT / "common/media/textures/PaintMyKI5_ContextMenu.png").read_bytes(),
)
context_source = (LUA_ROOT / self.EXPECTED_FILES[4]).read_text(encoding="utf-8")
self.assertIn(
'getTexture("media/textures/PaintMyKI5_ContextMenu.png")',
context_source,
)
def test_sandpaper_item_icon_translation_and_loot_loader_are_packaged(self) -> None:
item_script = (
PROJECT_ROOT / "42.20/media/scripts/PaintMyKI5_items.txt"
).read_text(encoding="utf-8")
sandpaper = re.search(r"item\s+Sandpaper\s*\{(.*?)\}", item_script, re.S)
self.assertIsNotNone(sandpaper)
self.assertIn("ItemType = base:normal", sandpaper.group(1))
self.assertIn("Icon = Sandpaper", sandpaper.group(1))
self.assertEqual(
(PROJECT_ROOT / "art/sandpaper.png").read_bytes(),
(PROJECT_ROOT / "common/media/textures/Item_Sandpaper.png").read_bytes(),
)
translations = json.loads(
(
PROJECT_ROOT
/ "42.20/media/lua/shared/Translate/EN/ItemName.json"
).read_text(encoding="utf-8")
)
self.assertEqual("Sandpaper", translations["ItemName_PaintMyKI5.Sandpaper"])
self.assertFalse(
(
PROJECT_ROOT
/ "42.20/media/lua/shared/Translate/EN/ItemName_EN.json"
).exists()
)
self.assertTrue(
(
PROJECT_ROOT
/ "42.20/media/lua/server/Items/PaintMyKI5_SandpaperDistribution.lua"
).is_file()
)
def test_server_authoritative_timed_action_contract(self) -> None:
source = (LUA_ROOT / self.EXPECTED_FILES[2]).read_text(encoding="utf-8")
self.assertIn("ISBaseTimedAction:derive", source)
self.assertIn("function ISPaintMyKI5Vehicle:complete()", source)
self.assertIn("PaintRequirements.getRequirements", source)
self.assertIn("PaintInventory.consumeRequirements", source)
self.assertGreaterEqual(source.count("checkPaintingRequirements"), 2)
self.assertIn("vehicle:setSkinIndex", source)
self.assertIn("vehicle:transmitSkinIndex", source)
self.assertNotRegex(source, r"sendClientCommand\([^\n]+setSkinIndex")
def test_context_menu_and_texture_preview_contract(self) -> None:
context_source = (LUA_ROOT / self.EXPECTED_FILES[4]).read_text(encoding="utf-8")
ui_source = (LUA_ROOT / self.EXPECTED_FILES[3]).read_text(encoding="utf-8")
self.assertIn("Events.OnFillWorldObjectContextMenu.Add", context_source)
self.assertIn("getFullName()", context_source)
self.assertIn("getTexture", ui_source)
self.assertIn("or getTexture(textureData.texture)", ui_source)
self.assertIn("drawTextureScaledAspect", ui_source)
self.assertIn("PaintMyKI5.TextureName.buildLabels", ui_source)
self.assertIn("paintButton:setTooltip", ui_source)
self.assertIn("checkPaintingRequirements", ui_source)
self.assertGreaterEqual(ui_source.count("PaintRequirements.isValidSelection"), 2)
self.assertNotIn("PaintRequirements.isValidTarget", ui_source)
self.assertIn("ISTimedActionQueue.add", ui_source)
self.assertLess(
ui_source.index("ISPathFindAction:pathToVehicleArea"),
ui_source.index("ISPaintMyKI5Vehicle:new"),
)
translations = json.loads(
(LUA_ROOT / "shared/Translate/EN/IG_UI.json").read_text(encoding="utf-8")
)
self.assertEqual(
"Missing requirements:",
translations["IGUI_PaintMyKI5_MissingRequirements"],
)
self.assertIn("%1", translations["IGUI_PaintMyKI5_MissingTool"])
self.assertIn("%1", translations["IGUI_PaintMyKI5_MissingPaint"])
def test_b42_translation_files_are_valid_json(self) -> None:
context = json.loads(
(LUA_ROOT / "shared/Translate/EN/ContextMenu.json").read_text(encoding="utf-8")
)
ui = json.loads((LUA_ROOT / "shared/Translate/EN/IG_UI.json").read_text(encoding="utf-8"))
self.assertEqual("Paint vehicle", context["ContextMenu_PaintMyKI5"])
self.assertIn("IGUI_PaintMyKI5_Title", ui)
self.assertIn("IGUI_PaintMyKI5_OneBucket", ui)
def test_generated_manifest_contains_exact_use_requirements(self) -> None:
manifest = (
LUA_ROOT / "shared/PaintMyKI5/PaintMyKI5VehiclePaintData.lua"
).read_text(encoding="utf-8")
self.assertIn("schemaVersion = 2", manifest)
self.assertIn("bucketUses = 10", manifest)
self.assertRegex(manifest, r'item = "Base\.Paint\w+", percent = \d+\.\d, uses = \d+\.\d\d')
use_groups = re.findall(r"paints = \{(.*?)\n \},", manifest, re.S)
self.assertTrue(use_groups)
self.assertTrue(
all(
abs(sum(map(float, re.findall(r"uses = (\d+\.\d+)", group))) - 10.0) < 0.001
for group in use_groups
)
)
def test_generated_mustang_green_skin_is_classified_as_green(self) -> None:
manifest = (
LUA_ROOT / "shared/PaintMyKI5/PaintMyKI5VehiclePaintData.lua"
).read_text(encoding="utf-8")
vehicle = re.search(
r'\["Base\.93mustangGT"\] = \{(.*?)\n \},\n'
r' \["Base\.93mustangSSP"\]',
manifest,
re.S,
)
self.assertIsNotNone(vehicle)
green_skin = re.search(
r'texture = "Vehicles/Vehicles_93mustangGT_Shell_Green",'
r"(.*?)\n \},",
vehicle.group(1),
re.S,
)
self.assertIsNotNone(green_skin)
green_percentage = re.search(
r'item = "Base\.PaintGreen", percent = ([\d.]+)',
green_skin.group(1),
)
self.assertIsNotNone(green_percentage)
self.assertGreaterEqual(float(green_percentage.group(1)), 90.0)
if __name__ == "__main__":
unittest.main()