Add generated terrain feature overlays
@@ -36,7 +36,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
|||||||
- Pre-battle briefing shows the campaign chapter, chapter battle number, location, victory/defeat summary, and first-clear reward preview.
|
- Pre-battle briefing shows the campaign chapter, chapter battle number, location, victory/defeat summary, and first-clear reward preview.
|
||||||
- Pre-battle prep commands use compact icon buttons with active selection state and Korean hover labels for chapters, shop, talks, armory, roster, formation, and save records.
|
- Pre-battle prep commands use compact icon buttons with active selection state and Korean hover labels for chapters, shop, talks, armory, roster, formation, and save records.
|
||||||
- Battle, top-toolbar, and pre-battle command buttons now prefer generated high-resolution bronze/parchment icon art under `art/ui/icons` and generated lacquer/jade/cinnabar button surfaces under `art/ui/buttons`, with procedural icon drawing and flat button styles kept only as fallbacks.
|
- Battle, top-toolbar, and pre-battle command buttons now prefer generated high-resolution bronze/parchment icon art under `art/ui/icons` and generated lacquer/jade/cinnabar button surfaces under `art/ui/buttons`, with procedural icon drawing and flat button styles kept only as fallbacks.
|
||||||
- Battle maps blend generated terrain texture tiles for plains, forests, hills, wasteland, roads, water, villages, and castles over the high-resolution battlefield backdrop.
|
- Battle maps blend generated terrain texture tiles for plains, forests, hills, wasteland, roads, water, villages, and castles over the high-resolution battlefield backdrop, with generated transparent feature overlays for road connections, water, villages, castles, and wasteland accents.
|
||||||
- Core UI panels now prefer generated high-resolution lacquer, scroll, jade, and command-seal frame textures under `art/ui/panels`, with the calmer ink/wood/jade flat styles kept as fallbacks instead of the earlier yellow, hard-edged frame treatment.
|
- Core UI panels now prefer generated high-resolution lacquer, scroll, jade, and command-seal frame textures under `art/ui/panels`, with the calmer ink/wood/jade flat styles kept as fallbacks instead of the earlier yellow, hard-edged frame treatment.
|
||||||
- Pre-battle Chapters overview shows story-arc progress with map thumbnail rows, compact status badges, hover details, and can open completed or current battles.
|
- Pre-battle Chapters overview shows story-arc progress with map thumbnail rows, compact status badges, hover details, and can open completed or current battles.
|
||||||
- Pre-battle Save menu can write and load one manual campaign checkpoint separate from the automatic save, using compact seal-style slot rows and hover checkpoint details.
|
- Pre-battle Save menu can write and load one manual campaign checkpoint separate from the automatic save, using compact seal-style slot rows and hover checkpoint details.
|
||||||
@@ -130,7 +130,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
|||||||
- Victory and defeat result overlay.
|
- Victory and defeat result overlay.
|
||||||
- Dialogue portrait slot stays fixed across speaker and narration lines, with officer default image paths, optional per-line overrides, and speaker-initial or record fallback.
|
- Dialogue portrait slot stays fixed across speaker and narration lines, with officer default image paths, optional per-line overrides, and speaker-initial or record 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 photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He.
|
||||||
- AI-generated scenario battlefield backgrounds, opening story panels, terrain textures, transparent class units, transparent generic-enemy units, item icons, toolbar UI icons, generated UI panel frames, generated button surfaces, tactical tile markers, class crest icons, and battlefield map badges live under `art/backgrounds`, `art/story`, `art/terrain`, `art/units`, `art/items`, `art/ui/icons`, `art/ui/panels`, `art/ui/buttons`, `art/ui/tiles`, `art/ui/class_icons`, and `art/ui/map_badges`.
|
- AI-generated scenario battlefield backgrounds, opening story panels, terrain textures and terrain feature overlays, transparent class units, transparent generic-enemy units, item icons, toolbar UI icons, generated UI panel frames, generated button surfaces, tactical tile markers, class crest icons, and battlefield map badges live under `art/backgrounds`, `art/story`, `art/terrain`, `art/terrain/features`, `art/units`, `art/items`, `art/ui/icons`, `art/ui/panels`, `art/ui/buttons`, `art/ui/tiles`, `art/ui/class_icons`, and `art/ui/map_badges`.
|
||||||
- Battle HUD portrait thumbnails show the selected unit, or a hovered unit when nothing is selected.
|
- 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.
|
- 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 mark speaking side for text alignment inside a fixed visual-novel-style dialogue panel with stable portrait, progress, and previous-line controls.
|
- Dialogue lines can mark speaking side for text alignment inside a fixed visual-novel-style dialogue panel with stable portrait, progress, and previous-line controls.
|
||||||
|
|||||||
BIN
art/terrain/features/terrain_feature_castle.png
Normal file
|
After Width: | Height: | Size: 499 KiB |
BIN
art/terrain/features/terrain_feature_road_e.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
art/terrain/features/terrain_feature_road_es.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
art/terrain/features/terrain_feature_road_esw.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
art/terrain/features/terrain_feature_road_ew.png
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
art/terrain/features/terrain_feature_road_n.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
art/terrain/features/terrain_feature_road_ne.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
art/terrain/features/terrain_feature_road_nesw.png
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
art/terrain/features/terrain_feature_road_new.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
art/terrain/features/terrain_feature_road_node.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
art/terrain/features/terrain_feature_road_ns.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
art/terrain/features/terrain_feature_road_nse.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
art/terrain/features/terrain_feature_road_nsw.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
art/terrain/features/terrain_feature_road_s.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
art/terrain/features/terrain_feature_road_sw.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
art/terrain/features/terrain_feature_road_w.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
art/terrain/features/terrain_feature_road_wn.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
art/terrain/features/terrain_feature_town.png
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
art/terrain/features/terrain_feature_wasteland.png
Normal file
|
After Width: | Height: | Size: 524 KiB |
BIN
art/terrain/features/terrain_feature_water.png
Normal file
|
After Width: | Height: | Size: 459 KiB |
@@ -35,6 +35,7 @@ const TILE_MARKER_TEXTURE_PATH_TEMPLATE := "res://art/ui/tiles/tile_marker_%s.pn
|
|||||||
const CLASS_ICON_TEXTURE_PATH_TEMPLATE := "res://art/ui/class_icons/class_%s.png"
|
const CLASS_ICON_TEXTURE_PATH_TEMPLATE := "res://art/ui/class_icons/class_%s.png"
|
||||||
const MAP_BADGE_TEXTURE_PATH_TEMPLATE := "res://art/ui/map_badges/map_badge_%s.png"
|
const MAP_BADGE_TEXTURE_PATH_TEMPLATE := "res://art/ui/map_badges/map_badge_%s.png"
|
||||||
const TERRAIN_TEXTURE_PATH_TEMPLATE := "res://art/terrain/terrain_%s.png"
|
const TERRAIN_TEXTURE_PATH_TEMPLATE := "res://art/terrain/terrain_%s.png"
|
||||||
|
const TERRAIN_FEATURE_TEXTURE_PATH_TEMPLATE := "res://art/terrain/features/terrain_feature_%s.png"
|
||||||
const OPENING_PROLOGUE_REBELLION_PATH := "res://art/story/opening/yellow_turban_rebellion.png"
|
const OPENING_PROLOGUE_REBELLION_PATH := "res://art/story/opening/yellow_turban_rebellion.png"
|
||||||
const OPENING_PROLOGUE_RESOLVE_PATH := "res://art/story/opening/cao_cao_resolves.png"
|
const OPENING_PROLOGUE_RESOLVE_PATH := "res://art/story/opening/cao_cao_resolves.png"
|
||||||
const OPENING_PROLOGUE_XIAHOU_DUN_PATH := "res://art/story/opening/xiahou_dun_joins.png"
|
const OPENING_PROLOGUE_XIAHOU_DUN_PATH := "res://art/story/opening/xiahou_dun_joins.png"
|
||||||
@@ -4890,6 +4891,13 @@ func _load_terrain_texture(terrain_key: String) -> Texture2D:
|
|||||||
return _load_art_texture(TERRAIN_TEXTURE_PATH_TEMPLATE % normalized_key)
|
return _load_art_texture(TERRAIN_TEXTURE_PATH_TEMPLATE % normalized_key)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_terrain_feature_texture(feature_key: String) -> Texture2D:
|
||||||
|
var normalized_key := feature_key.strip_edges().to_lower()
|
||||||
|
if normalized_key.is_empty():
|
||||||
|
return null
|
||||||
|
return _load_art_texture(TERRAIN_FEATURE_TEXTURE_PATH_TEMPLATE % normalized_key)
|
||||||
|
|
||||||
|
|
||||||
func _load_tile_marker_texture(marker_key: String) -> Texture2D:
|
func _load_tile_marker_texture(marker_key: String) -> Texture2D:
|
||||||
var normalized_key := marker_key.strip_edges().to_lower()
|
var normalized_key := marker_key.strip_edges().to_lower()
|
||||||
if normalized_key.is_empty():
|
if normalized_key.is_empty():
|
||||||
@@ -4948,6 +4956,17 @@ func _draw_map_badge(badge_key: String, rect: Rect2, modulate: Color = Color.WHI
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_terrain_feature(feature_key: String, rect: Rect2, modulate: Color = Color.WHITE, inset: float = 0.0) -> bool:
|
||||||
|
var texture := _load_terrain_feature_texture(feature_key)
|
||||||
|
if texture == null:
|
||||||
|
return false
|
||||||
|
var target_rect := rect.grow(-inset)
|
||||||
|
if target_rect.size.x <= 0.0 or target_rect.size.y <= 0.0:
|
||||||
|
return false
|
||||||
|
draw_texture_rect(texture, target_rect, false, modulate)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _terrain_texture_modulate(cell: Vector2i, terrain_key: String, has_background: bool) -> Color:
|
func _terrain_texture_modulate(cell: Vector2i, terrain_key: String, has_background: bool) -> Color:
|
||||||
var alpha := 0.17 if has_background else 0.58
|
var alpha := 0.17 if has_background else 0.58
|
||||||
if terrain_key == "W":
|
if terrain_key == "W":
|
||||||
@@ -4982,18 +5001,22 @@ func _draw_terrain_detail(cell: Vector2i, rect: Rect2, terrain_key: String) -> v
|
|||||||
draw_line(peak, point + Vector2(12, 9), Color(0.20, 0.18, 0.14, 0.42), 2.2)
|
draw_line(peak, point + Vector2(12, 9), Color(0.20, 0.18, 0.14, 0.42), 2.2)
|
||||||
draw_line(point + Vector2(-5, 5), point + Vector2(5, 6), Color(0.10, 0.09, 0.07, 0.20), 1.1)
|
draw_line(point + Vector2(-5, 5), point + Vector2(5, 6), Color(0.10, 0.09, 0.07, 0.20), 1.1)
|
||||||
elif terrain_key == "D":
|
elif terrain_key == "D":
|
||||||
_draw_wasteland_detail(cell, rect)
|
if not _draw_terrain_feature("wasteland", rect, Color(1.0, 1.0, 1.0, 0.68)):
|
||||||
|
_draw_wasteland_detail(cell, rect)
|
||||||
elif terrain_key == "R":
|
elif terrain_key == "R":
|
||||||
_draw_road_detail(cell, rect)
|
_draw_road_detail(cell, rect)
|
||||||
elif terrain_key == "W":
|
elif terrain_key == "W":
|
||||||
for index in range(3):
|
if not _draw_terrain_feature("water", rect, Color(1.0, 1.0, 1.0, 0.78)):
|
||||||
var y := rect.position.y + 15.0 + float(index) * 14.0
|
for index in range(3):
|
||||||
draw_line(rect.position + Vector2(10, y - rect.position.y), rect.position + Vector2(rect.size.x - 10, y - rect.position.y + 4), Color(0.70, 0.88, 1.0, 0.32), 1.6)
|
var y := rect.position.y + 15.0 + float(index) * 14.0
|
||||||
_draw_water_shoreline(cell, rect)
|
draw_line(rect.position + Vector2(10, y - rect.position.y), rect.position + Vector2(rect.size.x - 10, y - rect.position.y + 4), Color(0.70, 0.88, 1.0, 0.32), 1.6)
|
||||||
|
_draw_water_shoreline(cell, rect)
|
||||||
elif terrain_key == "C":
|
elif terrain_key == "C":
|
||||||
_draw_castle_detail(rect)
|
if not _draw_terrain_feature("castle", rect, Color(1.0, 1.0, 1.0, 0.74)):
|
||||||
|
_draw_castle_detail(rect)
|
||||||
elif terrain_key == "T":
|
elif terrain_key == "T":
|
||||||
_draw_town_detail(rect)
|
if not _draw_terrain_feature("town", rect, Color(1.0, 1.0, 1.0, 0.74)):
|
||||||
|
_draw_town_detail(rect)
|
||||||
else:
|
else:
|
||||||
for index in range(2):
|
for index in range(2):
|
||||||
var point := _terrain_detail_point(cell, rect, index + 15)
|
var point := _terrain_detail_point(cell, rect, index + 15)
|
||||||
@@ -5001,6 +5024,8 @@ func _draw_terrain_detail(cell: Vector2i, rect: Rect2, terrain_key: String) -> v
|
|||||||
|
|
||||||
|
|
||||||
func _draw_road_detail(cell: Vector2i, rect: Rect2) -> void:
|
func _draw_road_detail(cell: Vector2i, rect: Rect2) -> void:
|
||||||
|
if _draw_terrain_feature(_road_feature_key(cell), rect, Color(1.0, 1.0, 1.0, 0.82)):
|
||||||
|
return
|
||||||
var road_color := Color(0.82, 0.69, 0.48, 0.34)
|
var road_color := Color(0.82, 0.69, 0.48, 0.34)
|
||||||
var rut_color := Color(0.36, 0.28, 0.18, 0.24)
|
var rut_color := Color(0.36, 0.28, 0.18, 0.24)
|
||||||
var center := rect.position + rect.size * 0.5
|
var center := rect.position + rect.size * 0.5
|
||||||
@@ -5024,6 +5049,45 @@ func _draw_road_detail(cell: Vector2i, rect: Rect2) -> void:
|
|||||||
draw_circle(center, 5.0, Color(0.84, 0.70, 0.48, 0.24))
|
draw_circle(center, 5.0, Color(0.84, 0.70, 0.48, 0.24))
|
||||||
|
|
||||||
|
|
||||||
|
func _road_feature_key(cell: Vector2i) -> String:
|
||||||
|
var flags := _terrain_connection_flags(cell, "R")
|
||||||
|
var north := bool(flags.get("north", false))
|
||||||
|
var east := bool(flags.get("east", false))
|
||||||
|
var south := bool(flags.get("south", false))
|
||||||
|
var west := bool(flags.get("west", false))
|
||||||
|
if north and east and south and west:
|
||||||
|
return "road_nesw"
|
||||||
|
if north and south and east:
|
||||||
|
return "road_nse"
|
||||||
|
if east and south and west:
|
||||||
|
return "road_esw"
|
||||||
|
if north and south and west:
|
||||||
|
return "road_nsw"
|
||||||
|
if north and east and west:
|
||||||
|
return "road_new"
|
||||||
|
if north and south:
|
||||||
|
return "road_ns"
|
||||||
|
if east and west:
|
||||||
|
return "road_ew"
|
||||||
|
if north and east:
|
||||||
|
return "road_ne"
|
||||||
|
if east and south:
|
||||||
|
return "road_es"
|
||||||
|
if south and west:
|
||||||
|
return "road_sw"
|
||||||
|
if west and north:
|
||||||
|
return "road_wn"
|
||||||
|
if north:
|
||||||
|
return "road_n"
|
||||||
|
if east:
|
||||||
|
return "road_e"
|
||||||
|
if south:
|
||||||
|
return "road_s"
|
||||||
|
if west:
|
||||||
|
return "road_w"
|
||||||
|
return "road_node"
|
||||||
|
|
||||||
|
|
||||||
func _draw_water_shoreline(cell: Vector2i, rect: Rect2) -> void:
|
func _draw_water_shoreline(cell: Vector2i, rect: Rect2) -> void:
|
||||||
var shore := Color(0.82, 0.88, 0.72, 0.24)
|
var shore := Color(0.82, 0.88, 0.72, 0.24)
|
||||||
if _terrain_key_at(cell + Vector2i(0, -1)) != "W":
|
if _terrain_key_at(cell + Vector2i(0, -1)) != "W":
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ func _init() -> void:
|
|||||||
_check_generated_class_icon_textures(failures)
|
_check_generated_class_icon_textures(failures)
|
||||||
_check_generated_map_badge_textures(failures)
|
_check_generated_map_badge_textures(failures)
|
||||||
_check_generated_terrain_textures(failures)
|
_check_generated_terrain_textures(failures)
|
||||||
|
_check_generated_terrain_feature_textures(failures)
|
||||||
_check_scene_texture_loading(failures)
|
_check_scene_texture_loading(failures)
|
||||||
_check_hud_focus_text(failures)
|
_check_hud_focus_text(failures)
|
||||||
_check_ancient_ui_theme(failures)
|
_check_ancient_ui_theme(failures)
|
||||||
@@ -3018,6 +3019,19 @@ func _check_generated_terrain_textures(failures: Array[String]) -> void:
|
|||||||
_check_terrain_texture_path(failures, "res://art/terrain/terrain_%s.png" % terrain_key, "terrain texture %s" % terrain_key)
|
_check_terrain_texture_path(failures, "res://art/terrain/terrain_%s.png" % terrain_key, "terrain texture %s" % terrain_key)
|
||||||
|
|
||||||
|
|
||||||
|
func _check_generated_terrain_feature_textures(failures: Array[String]) -> void:
|
||||||
|
var feature_keys := [
|
||||||
|
"road_node",
|
||||||
|
"road_n", "road_s", "road_e", "road_w",
|
||||||
|
"road_ns", "road_ew",
|
||||||
|
"road_ne", "road_es", "road_sw", "road_wn",
|
||||||
|
"road_nse", "road_esw", "road_nsw", "road_new", "road_nesw",
|
||||||
|
"town", "castle", "water", "wasteland"
|
||||||
|
]
|
||||||
|
for feature_key in feature_keys:
|
||||||
|
_check_terrain_feature_texture_path(failures, "res://art/terrain/features/terrain_feature_%s.png" % feature_key, "terrain feature %s" % feature_key)
|
||||||
|
|
||||||
|
|
||||||
func _check_scene_texture_loading(failures: Array[String]) -> void:
|
func _check_scene_texture_loading(failures: Array[String]) -> void:
|
||||||
var scene = BattleSceneScript.new()
|
var scene = BattleSceneScript.new()
|
||||||
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||||
@@ -3038,6 +3052,8 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
|
|||||||
failures.append("battle scene should load generated class icon textures")
|
failures.append("battle scene should load generated class icon textures")
|
||||||
if scene._load_map_badge_texture("objective") == null or scene._load_map_badge_texture("recover") == null:
|
if scene._load_map_badge_texture("objective") == null or scene._load_map_badge_texture("recover") == null:
|
||||||
failures.append("battle scene should load generated map badge textures")
|
failures.append("battle scene should load generated map badge textures")
|
||||||
|
if scene._load_terrain_feature_texture("road_ew") == null or scene._load_terrain_feature_texture("town") == null or scene._load_terrain_feature_texture("castle") == null:
|
||||||
|
failures.append("battle scene should load generated terrain feature overlays")
|
||||||
var conversations: Array = scene.state.get_briefing().get("camp_conversations", [])
|
var conversations: Array = scene.state.get_briefing().get("camp_conversations", [])
|
||||||
if not conversations.is_empty():
|
if not conversations.is_empty():
|
||||||
var first_lines: Array = (conversations[0] as Dictionary).get("lines", [])
|
var first_lines: Array = (conversations[0] as Dictionary).get("lines", [])
|
||||||
@@ -4451,6 +4467,8 @@ func _check_terrain_and_unit_presentation(failures: Array[String]) -> void:
|
|||||||
failures.append("Sishui Gate road should connect west/east at 3,5: %s" % str(road_flags))
|
failures.append("Sishui Gate road should connect west/east at 3,5: %s" % str(road_flags))
|
||||||
if bool(road_flags.get("north", false)) or bool(road_flags.get("south", false)):
|
if bool(road_flags.get("north", false)) or bool(road_flags.get("south", false)):
|
||||||
failures.append("Sishui Gate road should not connect north/south at 3,5: %s" % str(road_flags))
|
failures.append("Sishui Gate road should not connect north/south at 3,5: %s" % str(road_flags))
|
||||||
|
if scene._road_feature_key(Vector2i(2, 4)) != "road_ew":
|
||||||
|
failures.append("Sishui Gate road should use generated east-west road feature: %s" % scene._road_feature_key(Vector2i(2, 4)))
|
||||||
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||||
failures.append("could not reload opening battle for unit presentation helpers")
|
failures.append("could not reload opening battle for unit presentation helpers")
|
||||||
scene.free()
|
scene.free()
|
||||||
@@ -5269,6 +5287,57 @@ func _check_terrain_texture_path(failures: Array[String], path: String, context:
|
|||||||
failures.append("%s should contain visible generated terrain texture variation: %s" % [context, path])
|
failures.append("%s should contain visible generated terrain texture variation: %s" % [context, path])
|
||||||
|
|
||||||
|
|
||||||
|
func _check_terrain_feature_texture_path(failures: Array[String], path: String, context: String) -> void:
|
||||||
|
if path.is_empty():
|
||||||
|
failures.append("%s has an empty feature path" % context)
|
||||||
|
return
|
||||||
|
if not path.begins_with("res://"):
|
||||||
|
failures.append("%s must use a res:// path: %s" % [context, path])
|
||||||
|
return
|
||||||
|
if not FileAccess.file_exists(path):
|
||||||
|
failures.append("%s references missing terrain feature texture: %s" % [context, path])
|
||||||
|
return
|
||||||
|
var image := Image.new()
|
||||||
|
var err := image.load(path)
|
||||||
|
if err != OK:
|
||||||
|
failures.append("%s references unreadable terrain feature texture: %s" % [context, path])
|
||||||
|
return
|
||||||
|
if image.get_width() < 512 or image.get_height() < 512:
|
||||||
|
failures.append("%s should keep high-resolution overlay pixels: %s (%dx%d)" % [context, path, image.get_width(), image.get_height()])
|
||||||
|
return
|
||||||
|
if image.get_width() != image.get_height():
|
||||||
|
failures.append("%s should be square: %s (%dx%d)" % [context, path, image.get_width(), image.get_height()])
|
||||||
|
return
|
||||||
|
var transparent_samples := 0
|
||||||
|
var opaque_samples := 0
|
||||||
|
var green_fringe_samples := 0
|
||||||
|
var min_luma := 999.0
|
||||||
|
var max_luma := -999.0
|
||||||
|
var total_samples := 0
|
||||||
|
for y in range(0, image.get_height(), 8):
|
||||||
|
for x in range(0, image.get_width(), 8):
|
||||||
|
total_samples += 1
|
||||||
|
var pixel := image.get_pixel(x, y)
|
||||||
|
if pixel.a <= 0.01:
|
||||||
|
transparent_samples += 1
|
||||||
|
continue
|
||||||
|
if pixel.a >= 0.32:
|
||||||
|
opaque_samples += 1
|
||||||
|
if pixel.a > 0.05 and pixel.g > 0.92 and pixel.r < 0.08 and pixel.b < 0.08:
|
||||||
|
green_fringe_samples += 1
|
||||||
|
var luma := pixel.r * 0.299 + pixel.g * 0.587 + pixel.b * 0.114
|
||||||
|
min_luma = minf(min_luma, luma)
|
||||||
|
max_luma = maxf(max_luma, luma)
|
||||||
|
if transparent_samples < int(total_samples * 0.18):
|
||||||
|
failures.append("%s should keep transparent padding so the map art shows through: %s (%d/%d sampled pixels)" % [context, path, transparent_samples, total_samples])
|
||||||
|
if opaque_samples < int(total_samples * 0.04):
|
||||||
|
failures.append("%s should contain readable generated feature artwork: %s (%d/%d sampled pixels)" % [context, path, opaque_samples, total_samples])
|
||||||
|
if green_fringe_samples > 0:
|
||||||
|
failures.append("%s should not keep chroma-key green pixels: %s (%d sampled)" % [context, path, green_fringe_samples])
|
||||||
|
if max_luma - min_luma < 0.06:
|
||||||
|
failures.append("%s should contain visible generated feature variation: %s" % [context, path])
|
||||||
|
|
||||||
|
|
||||||
func _check_image_path(failures: Array[String], path: String, context: String) -> void:
|
func _check_image_path(failures: Array[String], path: String, context: String) -> void:
|
||||||
if path.is_empty():
|
if path.is_empty():
|
||||||
failures.append("%s has an empty image path" % context)
|
failures.append("%s has an empty image path" % context)
|
||||||
|
|||||||