M1d: Stilproben #12
+2
-1
@@ -7,7 +7,7 @@ LOAD_FAILED,Der Spielstand ließ sich nicht laden,The save could not be loaded
|
|||||||
INVENTORY_FULL,Die Tasche ist voll,Your bag is full
|
INVENTORY_FULL,Die Tasche ist voll,Your bag is full
|
||||||
PAUSED,Pause,Paused
|
PAUSED,Pause,Paused
|
||||||
SIMULATION_STOPPED,Die Simulation wurde angehalten,The simulation stopped
|
SIMULATION_STOPPED,Die Simulation wurde angehalten,The simulation stopped
|
||||||
HELP,WASD: laufen · Linke Maustaste: Stein schlagen · F5: speichern · F9: laden · Esc: Pause,WASD: walk · Left mouse button: break rock · F5: save · F9: load · Esc: pause
|
HELP,WASD: laufen · Linke Maustaste: Stein schlagen · F2: Stil wechseln · F5: speichern · F9: laden · Esc: Pause,WASD: walk · Left mouse button: break rock · F2: change the style · F5: save · F9: load · Esc: pause
|
||||||
DEFAULT_NAME,Spieler,Player
|
DEFAULT_NAME,Spieler,Player
|
||||||
MENU_NAME,Dein Name,Your name
|
MENU_NAME,Dein Name,Your name
|
||||||
MENU_SOLO,Allein spielen,Play alone
|
MENU_SOLO,Allein spielen,Play alone
|
||||||
@@ -38,6 +38,7 @@ SAVE_UNREADABLE,Der Spielstand lässt sich gerade nicht lesen,The save cannot be
|
|||||||
STYLE_WARM,Warm,Warm
|
STYLE_WARM,Warm,Warm
|
||||||
STYLE_PASTEL,Pastell,Pastel
|
STYLE_PASTEL,Pastell,Pastel
|
||||||
STYLE_GLOW,Glimmen,Glow
|
STYLE_GLOW,Glimmen,Glow
|
||||||
|
STYLE_SWITCHED,Stil: %s,Style: %s
|
||||||
MENU_SHOWCASE,Stilproben ansehen,See the style samples
|
MENU_SHOWCASE,Stilproben ansehen,See the style samples
|
||||||
SHOWCASE_TITLE,Stilprobe: %s,Style sample: %s
|
SHOWCASE_TITLE,Stilprobe: %s,Style sample: %s
|
||||||
SHOWCASE_BACK,Zurück,Back
|
SHOWCASE_BACK,Zurück,Back
|
||||||
|
|||||||
|
@@ -9,6 +9,8 @@ extends RefCounted
|
|||||||
## --host opens the world for guests at once
|
## --host opens the world for guests at once
|
||||||
## --port=PORT the port an open world listens on
|
## --port=PORT the port an open world listens on
|
||||||
## --join=ADDRESS joins a host at once; ADDRESS may end in ":PORT"
|
## --join=ADDRESS joins a host at once; ADDRESS may end in ":PORT"
|
||||||
|
## --style=NAME draws the playing field in the style NAME: warm,
|
||||||
|
## pastel or glow
|
||||||
|
|
||||||
## Longest profile name.
|
## Longest profile name.
|
||||||
const MAX_PROFILE_LENGTH := 32
|
const MAX_PROFILE_LENGTH := 32
|
||||||
@@ -21,6 +23,8 @@ var player_name := ""
|
|||||||
var start := ""
|
var start := ""
|
||||||
var port := NetLink.DEFAULT_PORT
|
var port := NetLink.DEFAULT_PORT
|
||||||
var address := ""
|
var address := ""
|
||||||
|
## The style to draw in, or empty for the one in the settings.
|
||||||
|
var style := &""
|
||||||
## Arguments that were not understood and are ignored.
|
## Arguments that were not understood and are ignored.
|
||||||
var ignored := PackedStringArray()
|
var ignored := PackedStringArray()
|
||||||
|
|
||||||
@@ -50,6 +54,11 @@ static func parse(arguments: PackedStringArray) -> LaunchOptions:
|
|||||||
"--join":
|
"--join":
|
||||||
options.start = "join"
|
options.start = "join"
|
||||||
options.address = value
|
options.address = value
|
||||||
|
"--style":
|
||||||
|
if Art.style_keys().has(StringName(value)):
|
||||||
|
options.style = StringName(value)
|
||||||
|
else:
|
||||||
|
options.ignored.append(argument)
|
||||||
_:
|
_:
|
||||||
options.ignored.append(argument)
|
options.ignored.append(argument)
|
||||||
return options
|
return options
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ func _ready() -> void:
|
|||||||
clock = TickClock.new(GwSession.TICKS_PER_SECOND)
|
clock = TickClock.new(GwSession.TICKS_PER_SECOND)
|
||||||
art = Art.new()
|
art = Art.new()
|
||||||
art.load_overrides(Art.OVERRIDE_DIR)
|
art.load_overrides(Art.OVERRIDE_DIR)
|
||||||
|
if not art.select(options.style):
|
||||||
|
art.select(StringName(store.style()))
|
||||||
map_view = MapView.new()
|
map_view = MapView.new()
|
||||||
map_view.art = art
|
map_view.art = art
|
||||||
add_child(map_view)
|
add_child(map_view)
|
||||||
@@ -253,11 +255,13 @@ func _hide_showcase() -> void:
|
|||||||
showcase.process_mode = Node.PROCESS_MODE_DISABLED
|
showcase.process_mode = Node.PROCESS_MODE_DISABLED
|
||||||
|
|
||||||
|
|
||||||
## Draws the playing field again after the style changed.
|
## Draws the playing field again after the style changed, and remembers the
|
||||||
|
## style for the next start.
|
||||||
func _redraw_art() -> void:
|
func _redraw_art() -> void:
|
||||||
map_view.redraw()
|
map_view.redraw()
|
||||||
for node: Node2D in [marker, figures, avatar]:
|
for node: Node2D in [marker, figures, avatar]:
|
||||||
node.queue_redraw()
|
node.queue_redraw()
|
||||||
|
store.set_style(String(art.style.key()))
|
||||||
|
|
||||||
|
|
||||||
## Says in the status line how the game is shared, and with how many.
|
## Says in the status line how the game is shared, and with how many.
|
||||||
@@ -390,6 +394,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
_set_paused(not clock.paused)
|
_set_paused(not clock.paused)
|
||||||
else:
|
else:
|
||||||
hud.flash("PAUSE_ONLY_ALONE")
|
hud.flash("PAUSE_ONLY_ALONE")
|
||||||
|
elif event.is_action_pressed("next_style"):
|
||||||
|
art.select_next()
|
||||||
|
hud.flash("STYLE_SWITCHED", [tr(art.style.title_key())])
|
||||||
|
|
||||||
|
|
||||||
## Goes back to the saved world. Only when playing alone: guests would no
|
## Goes back to the saved world. Only when playing alone: guests would no
|
||||||
|
|||||||
@@ -146,6 +146,15 @@ func set_last_address(address: String) -> void:
|
|||||||
_set_setting("network", "address", address)
|
_set_setting("network", "address", address)
|
||||||
|
|
||||||
|
|
||||||
|
## The key of the style the player chose last time, or an empty text.
|
||||||
|
func style() -> String:
|
||||||
|
return _setting("display", "style")
|
||||||
|
|
||||||
|
|
||||||
|
func set_style(key: String) -> void:
|
||||||
|
_set_setting("display", "style", key)
|
||||||
|
|
||||||
|
|
||||||
func _setting(section: String, key: String) -> String:
|
func _setting(section: String, key: String) -> String:
|
||||||
var settings := _read_settings(base_dir.path_join(SETTINGS_FILE))
|
var settings := _read_settings(base_dir.path_join(SETTINGS_FILE))
|
||||||
if settings == null:
|
if settings == null:
|
||||||
|
|||||||
@@ -28,3 +28,11 @@ func test_profile_names_cannot_leave_the_profile_folder() -> void:
|
|||||||
check_eq(options.storage_dir(), "user://", "a bad profile is ignored")
|
check_eq(options.storage_dir(), "user://", "a bad profile is ignored")
|
||||||
check_eq(options.port, NetLink.DEFAULT_PORT, "a bad port is ignored")
|
check_eq(options.port, NetLink.DEFAULT_PORT, "a bad port is ignored")
|
||||||
check_eq(options.ignored, PackedStringArray(["--profile=../oops", "--port=70000", "--fly"]), "everything ignored is listed")
|
check_eq(options.ignored, PackedStringArray(["--profile=../oops", "--port=70000", "--fly"]), "everything ignored is listed")
|
||||||
|
|
||||||
|
|
||||||
|
func test_a_style_can_be_chosen() -> void:
|
||||||
|
check_eq(LaunchOptions.parse(PackedStringArray(["--style=glow"])).style, GlowStyle.KEY, "a known style")
|
||||||
|
check_eq(LaunchOptions.parse(PackedStringArray()).style, &"", "none without the argument")
|
||||||
|
var unknown := LaunchOptions.parse(PackedStringArray(["--style=watercolour"]))
|
||||||
|
check_eq(unknown.style, &"", "an unknown style is not taken")
|
||||||
|
check_eq(unknown.ignored, PackedStringArray(["--style=watercolour"]), "but listed as ignored")
|
||||||
|
|||||||
@@ -452,3 +452,30 @@ func test_a_world_that_starts_takes_the_place_of_the_style_samples() -> void:
|
|||||||
check_eq(main.showcase.process_mode, Node.PROCESS_MODE_DISABLED, "and the samples take no input")
|
check_eq(main.showcase.process_mode, Node.PROCESS_MODE_DISABLED, "and the samples take no input")
|
||||||
check(not main.menu.visible, "the menu stays hidden")
|
check(not main.menu.visible, "the menu stays hidden")
|
||||||
_clean_up([directory])
|
_clean_up([directory])
|
||||||
|
|
||||||
|
|
||||||
|
func test_f2_switches_the_style_and_the_choice_is_kept() -> void:
|
||||||
|
var directory := TestFiles.temporary_dir("style")
|
||||||
|
var main := _start_main(directory)
|
||||||
|
main.play(false)
|
||||||
|
var first: StringName = main.art.style.key()
|
||||||
|
_press(main, "next_style")
|
||||||
|
var second: StringName = main.art.style.key()
|
||||||
|
check(second != first, "another style")
|
||||||
|
check_eq(main.hud.message(), tr("STYLE_SWITCHED") % tr(main.art.style.title_key()), "the player is told")
|
||||||
|
_stop_main(main)
|
||||||
|
|
||||||
|
var later := _start_main(directory)
|
||||||
|
check_eq(later.art.style.key(), second, "the next start draws in it again")
|
||||||
|
_clean_up([directory])
|
||||||
|
|
||||||
|
|
||||||
|
func test_the_command_line_chooses_the_style() -> void:
|
||||||
|
var directory := TestFiles.temporary_dir("style-option")
|
||||||
|
var main: Node = load("res://main.tscn").instantiate()
|
||||||
|
main.storage_dir = directory
|
||||||
|
main.command_line = PackedStringArray(["--style=pastel"])
|
||||||
|
tree.root.add_child(main)
|
||||||
|
_started.append(main)
|
||||||
|
check_eq(main.art.style.key(), PastelStyle.KEY, "the style of the command line")
|
||||||
|
_clean_up([directory])
|
||||||
|
|||||||
@@ -189,3 +189,12 @@ func test_settings_that_cannot_be_read_right_now_are_not_overwritten() -> void:
|
|||||||
check_eq(later.player_name(), "Ada", "the name on disk is the old one")
|
check_eq(later.player_name(), "Ada", "the name on disk is the old one")
|
||||||
check_eq(later.last_address(), "192.168.1.20", "the address is kept")
|
check_eq(later.last_address(), "192.168.1.20", "the address is kept")
|
||||||
TestFiles.remove_dir(directory)
|
TestFiles.remove_dir(directory)
|
||||||
|
|
||||||
|
|
||||||
|
func test_the_chosen_style_is_kept() -> void:
|
||||||
|
var directory := TestFiles.temporary_dir("style")
|
||||||
|
var store := SaveStore.new(directory)
|
||||||
|
check_eq(store.style(), "", "no style at first")
|
||||||
|
store.set_style("pastel")
|
||||||
|
check_eq(SaveStore.new(directory).style(), "pastel", "kept for the next start")
|
||||||
|
TestFiles.remove_dir(directory)
|
||||||
|
|||||||
+54
-25
@@ -1,14 +1,22 @@
|
|||||||
extends SceneTree
|
extends SceneTree
|
||||||
## Starts the game in a temporary storage folder, walks the figure toward the
|
## Starts the game in a temporary storage folder, walks the figure toward the
|
||||||
## rock beside the clearing, hits it once and saves a screenshot. Lets a
|
## rock beside the clearing, hits it once and saves a screenshot. A second
|
||||||
## change to the look be checked without playing by hand. The player's real
|
## figure with a name stands beside it. Lets a change to the look be checked
|
||||||
## key and save stay untouched.
|
## without playing by hand. The player's real key and save stay untouched.
|
||||||
##
|
##
|
||||||
## From the repository root (opens a window for a moment):
|
## From the repository root (opens a window for a moment):
|
||||||
## godot --path game -s res://tools/screenshot.gd -- <path of the PNG>
|
## godot --path game -s res://tools/screenshot.gd -- <path of the PNG>
|
||||||
|
## [--style=NAME] [--showcase]
|
||||||
|
## --style=NAME draws in that style; --showcase shows the style samples
|
||||||
|
## instead of the game.
|
||||||
|
|
||||||
const STORAGE := "user://screenshot-run"
|
const STORAGE := "user://screenshot-run"
|
||||||
const FRAMES_AFTER_HIT := 30
|
## Frames between the hit and the picture: the burst of chips is still in
|
||||||
|
## the air, and the crack is there.
|
||||||
|
const FRAMES_AFTER_HIT := 8
|
||||||
|
## Seconds into the showcase at which the picture is taken: the plant is
|
||||||
|
## nearly grown and the animal in the air.
|
||||||
|
const SHOWCASE_SECONDS := 5.2
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
@@ -16,37 +24,58 @@ func _initialize() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _run() -> void:
|
func _run() -> void:
|
||||||
var arguments := OS.get_cmdline_user_args()
|
var output := "user://screenshot.png"
|
||||||
var output := arguments[0] if arguments.size() > 0 else "user://screenshot.png"
|
var style := ""
|
||||||
|
var showcase := false
|
||||||
|
for argument in OS.get_cmdline_user_args():
|
||||||
|
if argument.begins_with("--style="):
|
||||||
|
style = argument
|
||||||
|
elif argument == "--showcase":
|
||||||
|
showcase = true
|
||||||
|
else:
|
||||||
|
output = argument
|
||||||
DirAccess.make_dir_recursive_absolute(STORAGE)
|
DirAccess.make_dir_recursive_absolute(STORAGE)
|
||||||
var main: Node = load("res://main.tscn").instantiate()
|
var main: Node = load("res://main.tscn").instantiate()
|
||||||
main.storage_dir = STORAGE
|
main.storage_dir = STORAGE
|
||||||
main.command_line = PackedStringArray()
|
main.command_line = PackedStringArray([style] if not style.is_empty() else [])
|
||||||
root.add_child(main)
|
root.add_child(main)
|
||||||
main.play(false)
|
if showcase:
|
||||||
await process_frame
|
main.open_showcase()
|
||||||
|
|
||||||
# Four tiles to the right, toward the rock outcrop beside the clearing.
|
|
||||||
for frame in 48:
|
|
||||||
main.avatar.walk(Vector2.RIGHT, 1.0 / 60.0)
|
|
||||||
await process_frame
|
await process_frame
|
||||||
var session: GwSession = main.session
|
main.showcase.time = SHOWCASE_SECONDS
|
||||||
var rock := session.spawn_point() + Vector2i(7, 0)
|
|
||||||
main.dig_at(rock)
|
|
||||||
for frame in FRAMES_AFTER_HIT:
|
|
||||||
await process_frame
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
else:
|
||||||
|
await _play(main)
|
||||||
|
|
||||||
var image := root.get_texture().get_image()
|
var image := root.get_texture().get_image()
|
||||||
var error := image.save_png(output)
|
var error := image.save_png(output)
|
||||||
print("screenshot %s: %s" % ["saved" if error == OK else "failed", ProjectSettings.globalize_path(output)])
|
print("screenshot %s: %s" % ["saved" if error == OK else "failed", ProjectSettings.globalize_path(output)])
|
||||||
root.remove_child(main)
|
root.remove_child(main)
|
||||||
main.free()
|
main.free()
|
||||||
for file in DirAccess.get_files_at(STORAGE):
|
_remove(STORAGE)
|
||||||
DirAccess.remove_absolute(STORAGE.path_join(file))
|
|
||||||
for directory in DirAccess.get_directories_at(STORAGE):
|
|
||||||
var path := STORAGE.path_join(directory)
|
|
||||||
for file in DirAccess.get_files_at(path):
|
|
||||||
DirAccess.remove_absolute(path.path_join(file))
|
|
||||||
DirAccess.remove_absolute(path)
|
|
||||||
DirAccess.remove_absolute(STORAGE)
|
|
||||||
quit(0 if error == OK else 1)
|
quit(0 if error == OK else 1)
|
||||||
|
|
||||||
|
|
||||||
|
## Plays alone: walks four tiles to the right, toward the rock outcrop
|
||||||
|
## beside the clearing, and hits it once.
|
||||||
|
func _play(main: Node) -> void:
|
||||||
|
main.play(false)
|
||||||
|
await process_frame
|
||||||
|
for frame in 48:
|
||||||
|
main.avatar.walk(Vector2.RIGHT, 1.0 / 60.0)
|
||||||
|
await process_frame
|
||||||
|
var session: GwSession = main.session
|
||||||
|
main.figures.place(session.local_player(), main.avatar.position + Vector2(-40, 8))
|
||||||
|
var rock := session.spawn_point() + Vector2i(7, 0)
|
||||||
|
main.dig_at(rock)
|
||||||
|
for frame in FRAMES_AFTER_HIT:
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
|
||||||
|
func _remove(path: String) -> void:
|
||||||
|
for file in DirAccess.get_files_at(path):
|
||||||
|
DirAccess.remove_absolute(path.path_join(file))
|
||||||
|
for directory in DirAccess.get_directories_at(path):
|
||||||
|
_remove(path.path_join(directory))
|
||||||
|
DirAccess.remove_absolute(path)
|
||||||
|
|||||||
Reference in New Issue
Block a user