diff --git a/README.md b/README.md index e0d5849..7e6674d 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr - Victory and defeat result overlay. - Dialogue portrait slot with officer default image paths, optional per-line overrides, and speaker-initial fallback. - Initial AI-generated photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He. -- Initial AI-generated battlefield, transparent class unit, transparent generic-enemy unit, and transparent item icon assets live under `art/backgrounds`, `art/units`, and `art/items`. +- AI-generated scenario battlefield backgrounds, transparent class units, transparent generic-enemy units, and transparent item icon assets live under `art/backgrounds`, `art/units`, and `art/items`. - Battle HUD portrait thumbnails show the selected unit, or a hovered unit when nothing is selected. - Selected and hovered unit focus text shows combat role, movement type, current terrain move cost, terrain bonuses, core stats, and status effects. - Dialogue lines can place the portrait on the left or right side of an expanded visual-novel-style dialogue panel with progress and previous-line controls. diff --git a/art/backgrounds/guandu_defensive_camps.png b/art/backgrounds/guandu_defensive_camps.png new file mode 100644 index 0000000..da232e3 Binary files /dev/null and b/art/backgrounds/guandu_defensive_camps.png differ diff --git a/art/backgrounds/sishui_gate_pass.png b/art/backgrounds/sishui_gate_pass.png new file mode 100644 index 0000000..e6613cc Binary files /dev/null and b/art/backgrounds/sishui_gate_pass.png differ diff --git a/art/backgrounds/wan_castle_escape.png b/art/backgrounds/wan_castle_escape.png new file mode 100644 index 0000000..be584b0 Binary files /dev/null and b/art/backgrounds/wan_castle_escape.png differ diff --git a/art/backgrounds/xiapi_flooded_siege.png b/art/backgrounds/xiapi_flooded_siege.png new file mode 100644 index 0000000..9cb72f0 Binary files /dev/null and b/art/backgrounds/xiapi_flooded_siege.png differ diff --git a/data/scenarios/002_sishui_gate.json b/data/scenarios/002_sishui_gate.json index 859ee9e..9fa189a 100644 --- a/data/scenarios/002_sishui_gate.json +++ b/data/scenarios/002_sishui_gate.json @@ -46,6 +46,7 @@ "map": { "width": 12, "height": 9, + "background": "res://art/backgrounds/sishui_gate_pass.png", "terrain": [ "GGGGGRRGGGGG", "GGGFFRRGHHGG", diff --git a/data/scenarios/008_wan_castle_escape.json b/data/scenarios/008_wan_castle_escape.json index 484f3e5..a5d1eb3 100644 --- a/data/scenarios/008_wan_castle_escape.json +++ b/data/scenarios/008_wan_castle_escape.json @@ -74,6 +74,7 @@ "map": { "width": 15, "height": 10, + "background": "res://art/backgrounds/wan_castle_escape.png", "terrain": [ "GGGGGHHGGGFFFFG", "GGFFGHHGGGFFFFG", diff --git a/data/scenarios/009_xiapi_siege.json b/data/scenarios/009_xiapi_siege.json index 56e526e..0748253 100644 --- a/data/scenarios/009_xiapi_siege.json +++ b/data/scenarios/009_xiapi_siege.json @@ -67,6 +67,7 @@ "map": { "width": 15, "height": 10, + "background": "res://art/backgrounds/xiapi_flooded_siege.png", "terrain": [ "GGGGGHHGGGWWGGG", "GGGFFHHGGGWWGGG", diff --git a/data/scenarios/012_guandu_showdown.json b/data/scenarios/012_guandu_showdown.json index 5c25524..9dbd803 100644 --- a/data/scenarios/012_guandu_showdown.json +++ b/data/scenarios/012_guandu_showdown.json @@ -67,6 +67,7 @@ "map": { "width": 17, "height": 11, + "background": "res://art/backgrounds/guandu_defensive_camps.png", "terrain": [ "GGGGGHHGGGGHHGGGG", "GGFFGHHGGFFHHGFFG", diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 7bfe23f..504c762 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -134,7 +134,7 @@ ## Milestone 5: Presentation -- Portrait and art pipeline. Officer definitions can provide default portrait paths, classes can provide board `sprite` paths, items can provide transparent `icon` cutout paths, scenarios can provide map `background` paths, and the first generated officer portraits, unit sprites, item icons, and battlefield background are in `art/`. +- Portrait and art pipeline. Officer definitions can provide default portrait paths, classes can provide board `sprite` paths, items can provide transparent `icon` cutout paths, scenarios can provide map `background` paths, and generated officer portraits, unit sprites, item icons, plus scenario battlefield backgrounds are in `art/`. - Unit sprites and animations. Board movement now has a short slide animation, attacks request class-specific motion profiles for infantry, archers, cavalry, command units, and heavy warrior/bandit units, and board units show class sprite tokens, team rings, class badges, low-HP warning rings, and small status pips for active support, debuff, poison, seal, snare, and disarm effects, with HUD summaries showing remaining phases. - Battlefield presentation. Maps can draw a high-resolution background under translucent terrain overlays, with terrain-specific detail patterns, connected road segments, shoreline/castle/forest/hill edge blends, and generic-enemy token marks. - Battle HUD presentation. Selected and hovered units now summarize battlefield role, movement type, current terrain move cost, defense/avoid bonuses, core stats, status effects, and gear so class identity reads alongside the visual tokens. diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 2d6e61b..3c91dec 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3,7 +3,39 @@ extends SceneTree const BattleStateScript := preload("res://scripts/core/battle_state.gd") const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd") -const WUCHAO_BACKGROUND_PATH := "res://art/backgrounds/wuchao_night_granary.png" +const SCENARIO_BACKGROUNDS := [ + { + "scenario_path": "res://data/scenarios/001_yellow_turbans.json", + "background_path": "res://art/backgrounds/battlefield_frontier.png", + "label": "001 opening field background", + "allow_default": true + }, + { + "scenario_path": "res://data/scenarios/002_sishui_gate.json", + "background_path": "res://art/backgrounds/sishui_gate_pass.png", + "label": "002 Sishui Gate background" + }, + { + "scenario_path": "res://data/scenarios/008_wan_castle_escape.json", + "background_path": "res://art/backgrounds/wan_castle_escape.png", + "label": "008 Wan Castle background" + }, + { + "scenario_path": "res://data/scenarios/009_xiapi_siege.json", + "background_path": "res://art/backgrounds/xiapi_flooded_siege.png", + "label": "009 Xiapi flooded siege background" + }, + { + "scenario_path": "res://data/scenarios/012_guandu_showdown.json", + "background_path": "res://art/backgrounds/guandu_defensive_camps.png", + "label": "012 Guandu defensive camps background" + }, + { + "scenario_path": "res://data/scenarios/013_wuchao_raid.json", + "background_path": "res://art/backgrounds/wuchao_night_granary.png", + "label": "013 Wuchao night granary background" + } +] func _init() -> void: @@ -47,7 +79,7 @@ func _check_battle_visual_data(failures: Array[String]) -> void: failures.append("001 shop should expose merchant lines") _check_image_path(failures, state.get_map_background_path(), "001 map background") - _check_wuchao_background_data(failures) + _check_scenario_backgrounds_data(failures) for unit_id in ["cao_cao", "xiahou_dun", "yellow_turban_1", "yellow_turban_2", "yellow_turban_3"]: var unit := state.get_unit(unit_id) if unit.is_empty(): @@ -88,16 +120,21 @@ func _check_battle_visual_data(failures: Array[String]) -> void: _check_alpha_cutout_path(failures, icon_path, "item %s icon cutout" % item_id) -func _check_wuchao_background_data(failures: Array[String]) -> void: - var state = BattleStateScript.new() - if not state.load_battle("res://data/scenarios/013_wuchao_raid.json"): - failures.append("could not load Wuchao raid for background") - return - var background_path := state.get_map_background_path() - if background_path != WUCHAO_BACKGROUND_PATH: - failures.append("013 Wuchao should use its night granary background: %s" % background_path) - return - _check_image_path(failures, background_path, "013 Wuchao map background") +func _check_scenario_backgrounds_data(failures: Array[String]) -> void: + for expectation in SCENARIO_BACKGROUNDS: + var entry: Dictionary = expectation + var scenario_path := str(entry.get("scenario_path", "")) + var expected_path := str(entry.get("background_path", "")) + var label := str(entry.get("label", scenario_path)) + var state = BattleStateScript.new() + if not state.load_battle(scenario_path): + failures.append("could not load scenario for background: %s" % label) + continue + var background_path: String = state.get_map_background_path() + if background_path != expected_path: + failures.append("%s should use %s, got %s" % [label, expected_path, background_path]) + continue + _check_image_path(failures, background_path, label) func _check_scene_texture_loading(failures: Array[String]) -> void: @@ -136,16 +173,29 @@ func _check_scene_texture_loading(failures: Array[String]) -> void: failures.append("camp conversation button text should preview supply effect") scene.free() - var wuchao_scene = BattleSceneScript.new() - if not wuchao_scene.state.load_battle("res://data/scenarios/013_wuchao_raid.json"): - failures.append("could not load Wuchao scene state") - wuchao_scene.free() - return - if wuchao_scene.state.get_map_background_path() == BattleSceneScript.DEFAULT_BATTLE_BACKGROUND_PATH: - failures.append("Wuchao scene should not rely on the default battlefield background") - if wuchao_scene._current_battle_background_texture() == null: - failures.append("battle scene should load Wuchao night granary background texture") - wuchao_scene.free() + _check_scenario_background_texture_loading(failures) + + +func _check_scenario_background_texture_loading(failures: Array[String]) -> void: + for expectation in SCENARIO_BACKGROUNDS: + var entry: Dictionary = expectation + var scenario_path := str(entry.get("scenario_path", "")) + var expected_path := str(entry.get("background_path", "")) + var label := str(entry.get("label", scenario_path)) + var scene = BattleSceneScript.new() + if not scene.state.load_battle(scenario_path): + failures.append("could not load scene state for background: %s" % label) + scene.free() + continue + var actual_path: String = scene.state.get_map_background_path() + if actual_path != expected_path: + failures.append("%s scene loaded %s instead of %s" % [label, actual_path, expected_path]) + var allow_default := bool(entry.get("allow_default", false)) + if not allow_default and actual_path == BattleSceneScript.DEFAULT_BATTLE_BACKGROUND_PATH: + failures.append("%s should not rely on the default battlefield background" % label) + if scene._current_battle_background_texture() == null: + failures.append("battle scene should load texture for %s" % label) + scene.free() func _check_hud_focus_text(failures: Array[String]) -> void: