commit 2ae288a3d61945c7a4d7d9629847ed23ffac8ee6 Author: Robin Hübner Date: Fri Sep 21 03:43:57 2018 +0200 initial commit. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8f9b22e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +*.jpg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.bmp filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.ase filter=lfs diff=lfs merge=lfs -text +*.atex filter=lfs diff=lfs merge=lfs -text +*.tex filter=lfs diff=lfs merge=lfs -text +*.vs filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.pdn filter=lfs diff=lfs merge=lfs -text +*.aseprite filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.scn filter=lfs diff=lfs merge=lfs -text +*.res filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5dd296 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.import +.cache +*.*.s?? diff --git a/Game.gd b/Game.gd new file mode 100644 index 0000000..7793ca0 --- /dev/null +++ b/Game.gd @@ -0,0 +1,55 @@ +extends Node + +# globals, for the game +var reading_speed = 1.0 + +var game_started = false setget _on_game_started_set + +var current_registry + +# music related +onready var music_player = get_node("music_player") +onready var sfx_player = get_node("sfx_player") + +signal on_music_volume_changed(new_value) +signal on_sfx_volume_changed(new_value) +signal on_reading_speed_changed(new_value) + +signal on_settings_enter() +signal on_settings_exit() + +const Themes = {} + +# voices for characters +const brain_voice = null +const arborator_voice = null + +const Characters = { + BRAIN = { + name = "~brain~", + voice = brain_voice, + talking_speed = 1.0, + }, + ARBORATOR = { + name = "arborator", + voice = arborator_voice, + talking_speed = 1.0, + } +} + +func _register_functions(): + pass + +func _on_game_started_set(v): + if v and not game_started: + _on_game_start() + game_started = v + +func _on_game_start(): + + # all game state here + current_registry = {} + + +func _ready(): + pass \ No newline at end of file diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..ddfb759 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,23 @@ +[gd_resource type="AudioBusLayout" format=2] + +[resource] + +bus/0/name = "Master" +bus/0/solo = false +bus/0/mute = false +bus/0/bypass_fx = false +bus/0/volume_db = 0.0 +bus/0/send = "" +bus/1/name = "Music" +bus/1/solo = false +bus/1/mute = false +bus/1/bypass_fx = false +bus/1/volume_db = 0.0 +bus/1/send = "Master" +bus/2/name = "Sfx" +bus/2/solo = false +bus/2/mute = false +bus/2/bypass_fx = false +bus/2/volume_db = 0.0 +bus/2/send = "Master" + diff --git a/default_env.tres b/default_env.tres new file mode 100644 index 0000000..ad86b72 --- /dev/null +++ b/default_env.tres @@ -0,0 +1,101 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +radiance_size = 4 +sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 ) +sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 ) +sky_curve = 0.25 +sky_energy = 1.0 +ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 ) +ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 ) +ground_curve = 0.01 +ground_energy = 1.0 +sun_color = Color( 1, 1, 1, 1 ) +sun_latitude = 35.0 +sun_longitude = 0.0 +sun_angle_min = 1.0 +sun_angle_max = 100.0 +sun_curve = 0.05 +sun_energy = 16.0 +texture_size = 2 + +[resource] + +background_mode = 2 +background_sky = SubResource( 1 ) +background_sky_custom_fov = 0.0 +background_color = Color( 0, 0, 0, 1 ) +background_energy = 1.0 +background_canvas_max_layer = 0 +ambient_light_color = Color( 0, 0, 0, 1 ) +ambient_light_energy = 1.0 +ambient_light_sky_contribution = 1.0 +fog_enabled = false +fog_color = Color( 0.5, 0.6, 0.7, 1 ) +fog_sun_color = Color( 1, 0.9, 0.7, 1 ) +fog_sun_amount = 0.0 +fog_depth_enabled = true +fog_depth_begin = 10.0 +fog_depth_curve = 1.0 +fog_transmit_enabled = false +fog_transmit_curve = 1.0 +fog_height_enabled = false +fog_height_min = 0.0 +fog_height_max = 100.0 +fog_height_curve = 1.0 +tonemap_mode = 0 +tonemap_exposure = 1.0 +tonemap_white = 1.0 +auto_exposure_enabled = false +auto_exposure_scale = 0.4 +auto_exposure_min_luma = 0.05 +auto_exposure_max_luma = 8.0 +auto_exposure_speed = 0.5 +ss_reflections_enabled = false +ss_reflections_max_steps = 64 +ss_reflections_fade_in = 0.15 +ss_reflections_fade_out = 2.0 +ss_reflections_depth_tolerance = 0.2 +ss_reflections_roughness = true +ssao_enabled = false +ssao_radius = 1.0 +ssao_intensity = 1.0 +ssao_radius2 = 0.0 +ssao_intensity2 = 1.0 +ssao_bias = 0.01 +ssao_light_affect = 0.0 +ssao_color = Color( 0, 0, 0, 1 ) +ssao_quality = 0 +ssao_blur = 3 +ssao_edge_sharpness = 4.0 +dof_blur_far_enabled = false +dof_blur_far_distance = 10.0 +dof_blur_far_transition = 5.0 +dof_blur_far_amount = 0.1 +dof_blur_far_quality = 1 +dof_blur_near_enabled = false +dof_blur_near_distance = 2.0 +dof_blur_near_transition = 1.0 +dof_blur_near_amount = 0.1 +dof_blur_near_quality = 1 +glow_enabled = false +glow_levels/1 = false +glow_levels/2 = false +glow_levels/3 = true +glow_levels/4 = false +glow_levels/5 = true +glow_levels/6 = false +glow_levels/7 = false +glow_intensity = 0.8 +glow_strength = 1.0 +glow_bloom = 0.0 +glow_blend_mode = 2 +glow_hdr_threshold = 1.0 +glow_hdr_scale = 2.0 +glow_bicubic_upscale = false +adjustment_enabled = false +adjustment_brightness = 1.0 +adjustment_contrast = 1.0 +adjustment_saturation = 1.0 + diff --git a/dialog.gd b/dialog.gd new file mode 100644 index 0000000..943dc11 --- /dev/null +++ b/dialog.gd @@ -0,0 +1,450 @@ +extends Control + +# load from dialog vm +const Dialog = preload("res://dialog_vm.gd") +const DialogController = preload("res://dialog_controller.gd") + +# classes from there +const DialogSegment = Dialog.DialogSegment +const DialogWithSubjectSegment = Dialog.DialogWithSubjectSegment +const DialogWithSubjectExpressionSegment = Dialog.DialogWithSubjectExpressionSegment +const QuestionSegment = Dialog.QuestionSegment +const ChoiceSegment = Dialog.ChoiceSegment +const ChoiceWithJump = Dialog.ChoiceWithJump +const Choice = Dialog.Choice + +const WaitSegment = Dialog.YieldCallSegment + +# resources specific to this dialogy thingy +const DialogChoices = preload("res://ui/dialog_choices.tscn") +const DialogChoice = preload("res://ui/dialog_choice.tscn") + +var IS_DEBUG = false + +var current_dialog +var current_dialog_controller + +# ui elements +onready var advance_button = get_node("top_panel/auto_btn") +onready var fastforward_button = get_node("top_panel/ffwd_btn") + +onready var title_label = get_node("body_container/bottom_panel/subject_margin/subject_panel/subject_label") +onready var body_container = get_node("body_container") +onready var body_label = get_node("body_container/bottom_panel/body_margin/body_label") + +# clickable regions, for accessibility +onready var top_panel = get_node("top_panel") + +onready var audio_player = get_node("audio_player") + +# params to set from outside +var dialog_path + +# activeness +var active + +# tween to fade in/out +var tween + +# timer +var timer + +# anim player, if any +var anim_player + +# linkin in ur get_node relative to where you want +var get_node_fnc + +# various main dialog related constants + +# signals +signal entered_dialog +signal exited_frame + +# character related signals +signal on_speaker_expression_changed(spkr, expr) + +signal on_dialog_init(d) +signal on_dialog_completed(d) + +# setters +func set_auto_advance(v): + current_dialog_controller.set_advance_state(v) + advance_button.pressed = v + +# signals from dialog controller +func _on_printing_changed(v): + pass + # next_arrow.visible = v + +func _on_reading_speed_changed(new_value): + current_dialog_controller.set_reading_speed(new_value) + +func set_advance_state(): + current_dialog_controller.set_advance_state(advance_button.is_pressed()) + +func set_fastforward_state(): + var ffwd_pressed = fastforward_button.is_pressed() + current_dialog_controller.set_fastforward_state(fastforward_button.is_pressed()) + advance_button.set_disabled(ffwd_pressed) + +# init functions, must be called before use +func set_dialog_path(path): + dialog_path = path + +func set_dialog_vm(vm): + current_dialog = vm + +func set_dialog_controller(c): + current_dialog_controller = c + +func set_dialog_anim_player(p): + anim_player = p + +func set_get_character_function(fnc): + get_node_fnc = fnc + +func _ready(): + + advance_button.connect("pressed", self, "set_advance_state") + fastforward_button.connect("pressed", self, "set_fastforward_state") + + # clicking to advance + body_container.connect("gui_input", self, "_on_body_click") + top_panel.connect("gui_input", self, "_on_body_click") + + Game.connect("on_reading_speed_changed", self, "_on_reading_speed_changed") + Game.connect("on_settings_enter", self, "_on_settings_enter") + Game.connect("on_settings_exit", self, "_on_settings_exit") + + tween = Tween.new() + add_child(tween) + + timer = Timer.new() + add_child(timer) + + +func _on_body_click(ev): + current_dialog_controller._on_body_click(is_active(), ev) + +func _register_func(r, reg_name, func_name): + if r.has(reg_name): + r[reg_name].set_instance(self) + else: + r[reg_name] = funcref(self, func_name) + +func _register_functions(): + var r = current_dialog.get_local_registry() + + # normal functions + _register_func(r, "SET_EXPR", "_set_expr_fnc") + _register_func(r, "FLIP", "_flip_char") + _register_func(r, "HIDE", "_hide_char") + _register_func(r, "SHOW", "_show_char") + + # yieldy function + _register_func(r, "WAIT", "_wait_dialog_fnc") + + # anim shite, also yieldy + _register_func(r, "PLAY_ANIM", "_play_anim_fnc") + + # PLAY AND WAIT; HOW ABOUT DEM APPLES + _register_func(r, "PLAY_ANIM_WITH_WAIT", "_play_wait_with_anim_fnc") + +func _play_wait_with_anim_fnc(args): + if args.size() == 2: + var anim_name = args[0] + var wait_time = float(args[1]) + + if anim_player: + anim_player.play(anim_name) + + timer.one_shot = true + timer.wait_time = wait_time + timer.start() + return [timer, "timeout"] + +func _play_anim_fnc(args): + if args.size() == 1: + var anim_name = args[0] + if anim_player: + anim_player.play(anim_name) + return [anim_player, "animation_finished"] + +func _wait_dialog_fnc(args): + if args.size() == 1: + var time = float(args[0]) + print("Dialog: waiting for: %f seconds" % time) + timer.wait_time = time + timer.one_shot = true + timer.start() + return [timer, "timeout"] + +func _set_expr_fnc(args): + if args.size() == 2: + var speaker = args[0] + var expr = args[1] + _set_speaker_expression(speaker, expr) + +func _show_char(args): + var char_name = args[0] + var character = get_node_fnc.call(char_name) + _do_show_char(character) + +func _do_show_char(c): + tween.interpolate_property( + c, "modulate:a", c.modulate.a, 1.0, + 1.0, Tween.TRANS_QUAD, + Tween.EASE_IN_OUT + ) + tween.start() + +func _hide_char(args): + var char_name = args[0] + var character = get_node_fnc.call(char_name) + _do_hide_char(character) + +func _do_hide_char(c): + tween.interpolate_property( + c, "modulate:a", c.modulate.a, 0.0, + 1.0, Tween.TRANS_QUAD, + Tween.EASE_IN_OUT + ) + tween.start() + +func _flip_char(args): + var char_name = args[0] + var character = get_node_fnc.call(char_name) + _do_flip_char(character) + +func _do_flip_char(c): + c.flip_h = !c.flip_h + +func _on_settings_enter(s): + if is_active(): + visible = false + +func _on_settings_exit(s): + if is_active(): + visible = true + +func reload_dialog(): + initialize_dialog() + +func get_local_registry(): + return current_dialog.get_local_registry() + +func initialize_dialog(): + + assert(dialog_path != null) + + var dialog_file = File.new() + dialog_file.open(dialog_path, File.READ) + var text = dialog_file.get_as_text() + dialog_file.close() + + var dialog = Dialog.dialog_from_text(text) + current_dialog = dialog + + # also setup kv store for dialog variables + current_dialog.set_registry(Game.current_registry) + + # set up controller too yes + current_dialog_controller = DialogController.new(dialog, audio_player) + + # register functions + _register_functions() + + # signals + current_dialog_controller.connect("on_update_dialog", self, "_on_update_dialog") + current_dialog_controller.connect("on_dialog_completed", self, "_on_dialog_completed") + current_dialog_controller.connect("on_printing_changed", self, "_on_printing_changed") + + emit_signal("on_dialog_init", self) + +func _on_dialog_completed(d): + emit_signal("on_dialog_completed", self) + exit_frame() + +func _on_update_dialog(s): + _update_dialog(s) + +func _fade_in(): + var FADE_TIME = 1.0 + tween.interpolate_property( + self, + "modulate:a", 0.0, 1.0, FADE_TIME, + Tween.TRANS_QUAD, Tween.EASE_IN + ) + tween.start() + +func _fade_out(): + var FADE_TIME = 1.0 + tween.interpolate_property( + self, + "modulate:a", 1.0, 0.0, FADE_TIME, + Tween.TRANS_QUAD, Tween.EASE_OUT + ) + tween.start() + + yield(tween, "tween_completed") + visible = false + +func enter_frame(): + + _fade_in() + + var r = Game.current_registry + r["IS_IN_DIALOG"] = true + + # reload if is debug + if IS_DEBUG: reload_dialog() + + active = true + visible = true + current_dialog.reset() + # next_arrow.visible = false + emit_signal("entered_dialog") + + # dialogs + var first_segment = current_dialog.advance() + _update_dialog(first_segment) + + +func is_active(): + return active + +func exit_frame(): + + _fade_out() + + # HACK: cludge to reset state + advance_button.set_pressed(false) + fastforward_button.set_pressed(false) + advance_button.emit_signal("pressed") + fastforward_button.emit_signal("pressed") + + current_dialog_controller.reset() + active = false + + var r = Game.current_registry + r["IS_IN_DIALOG"] = false + emit_signal("exited_frame") + + +func _set_speaker_subject(subject): + if Game.Characters.has(subject): + var data = Game.Characters[subject] + var subject_name = data.name + var subject_colour = data.name_colour + var subject_voice = data.voice + current_dialog_controller.chars_per_second = Game.DEFAULT_CHARS_PER_SECOND * data.talking_speed + _set_speaker_name(subject_name, subject_colour) + if subject_voice != null: + _set_speaker_voice(subject_voice) + else: + var subject_name = subject + current_dialog_controller.chars_per_second = Game.DEFAULT_CHARS_PER_SECOND + _set_speaker_name(subject_name, Color(1, 1, 1)) + +func _get_speaker_name(): + return title_label.text + +func _set_speaker_name(speaker_name, colour = Color(1, 1, 1)): + title_label.set_text(speaker_name) + title_label.modulate = colour + +func _set_speaker_voice(voice_sample): + audio_player.stream = voice_sample + +func _set_speaker_expression(speaker, expression): + emit_signal("on_speaker_expression_changed", speaker, expression) + +# temporary maybe + +func _setup_question(new_question): + body_container.add_child(new_question) + current_dialog_controller.waiting_for_answer = true + +func _question_answered_yes(qs): + body_container.remove_child(qs) + current_dialog_controller.waiting_for_answer = false + current_dialog_controller._advance_dialog() + +func _question_answered_no(qs): + body_container.remove_child(qs) + current_dialog_controller.waiting_for_answer = false + exit_frame() + +func _choice_made(choice_modal, choice): + if IS_DEBUG: print("chose: ", choice.get_choice()) + # Game.add_history_entry("you", choice.get_choice()) + remove_child(choice_modal) + if choice is ChoiceWithJump: + # HACK: fix this later or make this unnecessary + var current_segment = current_dialog.jump(choice.target) + call_deferred("_update_dialog", current_segment) + else: + _update_dialog(choice) + current_dialog_controller.waiting_for_answer = false + +# loop de loop +func _update_dialog(segment): + + if segment is ChoiceSegment: + + var choices = segment.get_choices() + + # create new dialog choices and shit + var new_choices = DialogChoices.instance() + var vbox = new_choices.get_node("vbox") + + for choice in choices: + var new_choice = DialogChoice.instance() + new_choice.text = choice.get_choice() + new_choice.connect("pressed", self, "_choice_made", [new_choices, choice]) + vbox.add_child(new_choice) + + add_child(new_choices) + + elif segment is Choice: + var new_content = segment.get_response() + if typeof(new_content) == TYPE_OBJECT and (new_content is DialogWithSubjectSegment or new_content is DialogWithSubjectExpressionSegment): + _update_dialog(new_content) + else: + body_label.text = new_content + # Game.add_history_entry(_get_speaker_name(), new_content) + + elif segment is DialogSegment or segment is DialogWithSubjectSegment or segment is DialogWithSubjectExpressionSegment: + var new_content = segment.get_content() + body_label.visible_characters = 0 + body_label.text = new_content + + if segment is DialogWithSubjectSegment: + _set_speaker_subject(segment.subject) + # Game.add_history_entry(_get_speaker_name(), new_content) + elif segment is DialogWithSubjectExpressionSegment: + _set_speaker_subject(segment.subject) + _set_speaker_expression(segment.subject, segment.expression) + # Game.add_history_entry("%s (%s)" % [_get_speaker_name(), segment.expression], new_content) + else: + pass + # Game.add_history_entry(_get_speaker_name(), new_content) + + elif segment is WaitSegment: + var new_content = segment.get_content() + body_label.visible_characters = 0 + body_label.text = new_content + + # whatever subject you want during functions to wait on, animations and such + _set_speaker_subject("") + + else: + print("encountered unknown segment: " + segment.get_content()) + + current_dialog_controller.update_logic_dialog(segment) + +func _process(delta): + if is_active(): + current_dialog_controller.tick(delta, body_label) + diff --git a/dialog/introduction.txt b/dialog/introduction.txt new file mode 100644 index 0000000..85ba903 --- /dev/null +++ b/dialog/introduction.txt @@ -0,0 +1,2 @@ + ... You wake up to the sound of sirens blaring. + diff --git a/dialog_controller.gd b/dialog_controller.gd new file mode 100644 index 0000000..cc4d64b --- /dev/null +++ b/dialog_controller.gd @@ -0,0 +1,173 @@ +extends Reference + +const Dialog = preload("res://dialog_vm.gd") + +# classes from vm +const DialogSegment = Dialog.DialogSegment +const DialogWithSubjectSegment = Dialog.DialogWithSubjectSegment +const DialogWithSubjectExpressionSegment = Dialog.DialogWithSubjectExpressionSegment +const QuestionSegment = Dialog.QuestionSegment +const ChoiceSegment = Dialog.ChoiceSegment +const ChoiceWithJump = Dialog.ChoiceWithJump +const Choice = Dialog.Choice + +const WaitSegment = Dialog.YieldCallSegment + +# current dialog vm +var current_dialog +var audio_player + +# dialog controller related state, internal + +# speed at which dialog prints characters +var chars_per_second = 16 # initialized from which character is speaking +var reading_speed_mul = 1 +var special_speed_mul = 1 + +# intermediate state for dialog +var waiting_for_answer = false +var print_done = false setget _on_print_set +var print_target = 0 +var print_delta = 0 + +# ui button controlled state +var is_auto_advancing = false +var auto_advance_delay = 1 +var auto_advance_timer = 0 + +var is_new_word = false +var is_fastforwarding = false +var ff_speed_mul = 1 + +# character printing sound related state +var sound_playing = false +var sound_play_time = 2.08 # TODO: maybe adjust this too depending on voice sample/chars per second +var sound_played_time = 0 + +# signals +signal on_printing_done() +signal on_printing_changed(status) +signal on_update_dialog(segment) +signal on_dialog_completed(d) + +func _init(d, a): + current_dialog = d + audio_player = a + +func reset(): + self.print_done = true + print_target = 0 + print_delta = 0 + +# sfx related + +func sound_play(): + sound_playing = true + audio_player.play() + +func sound_stop(): + audio_player.stop() + sound_playing = false + +func sound_done_playing(delta): + sound_played_time += delta * reading_speed_mul * special_speed_mul + if sound_played_time >= sound_play_time / chars_per_second: + sound_played_time = 0 + sound_playing = false + return not sound_playing + +# temporary hopefully +func _advance_dialog(): + var current_segment = current_dialog.advance() + if not current_segment == null: + emit_signal("on_update_dialog", current_segment) + else: + emit_signal("on_dialog_completed", self) + +func _on_body_click(is_visible, event): + if is_visible and not waiting_for_answer: + if event.is_action_pressed("dialog_click"): + if print_done == false: # if still printing, jump forwards now + print_delta = print_target - 1 + self.print_done = true + else: + _advance_dialog() + +# interior callbacks +func _on_print_set(v): + print_done = v + if print_done: + emit_signal("on_printing_done") + emit_signal("on_printing_changed", v) + +func set_reading_speed(spd): + reading_speed_mul = spd + +func set_advance_state(s): + is_auto_advancing = s + +func set_fastforward_state(s): + is_fastforwarding = s + if is_fastforwarding: + is_auto_advancing = true + ff_speed_mul = 10 + auto_advance_delay = 0.1 + else: + ff_speed_mul = 1 + auto_advance_delay = 1 + +func update_logic_dialog(segment): + + if segment is QuestionSegment: + var new_content = segment.get_content() + print_target = new_content.length() + self.print_done = false + print_delta = 0 + + elif segment is ChoiceSegment: + waiting_for_answer = true + + elif segment is Choice: + var new_content = segment.get_response() + if typeof(new_content) == TYPE_OBJECT and (new_content is DialogWithSubjectSegment or new_content is DialogWithSubjectExpressionSegment): + _update_logic_dialog(new_content) + else: + print_target = new_content.length() + self.print_done = false + print_delta = 0 + + elif segment is DialogSegment or segment is DialogWithSubjectSegment or segment is DialogWithSubjectExpressionSegment: + var new_content = segment.get_content() + print_target = new_content.length() + self.print_done = false + print_delta = 0 + + elif segment is WaitSegment: + var new_content = segment.get_content() + print_target = new_content.length() + self.print_done = false + print_delta = 0 + + else: + print("encountered unknown segment: " + segment.get_content()) + +func tick(delta, body_label): + + if not current_dialog.is_eof(): + var cur_segment = current_dialog.segments[current_dialog.segment_index] + if cur_segment is WaitSegment and cur_segment.finished: _advance_dialog() + + if print_target != 0: + if print_delta >= print_target: + self.print_done = true + auto_advance_timer = auto_advance_delay + print_target = 0 + print_delta = 0 + else: + if sound_done_playing(delta) and not is_fastforwarding: sound_play() + body_label.visible_characters = print_delta + print_delta += delta * (chars_per_second * reading_speed_mul) * ff_speed_mul + elif print_target == 0: + auto_advance_timer -= delta + if auto_advance_timer <= 0 and is_auto_advancing and not waiting_for_answer: + call_deferred("_advance_dialog") \ No newline at end of file diff --git a/dialog_layer.gd b/dialog_layer.gd new file mode 100644 index 0000000..e594650 --- /dev/null +++ b/dialog_layer.gd @@ -0,0 +1,29 @@ +extends CanvasLayer + +export (String, FILE, "*.txt") var dialog_path; + +onready var dialog_box = get_node("dialog_box") + +func _ready(): + + # start signal + get_parent().connect("on_scene_ready", self, "_on_scene_ready") + + dialog_box.connect("on_dialog_init", self, "_on_dialog_init") + dialog_box.connect("on_dialog_completed", self, "_on_dialog_completed") + + # set up params + dialog_box.set_dialog_path(dialog_path) + + # init after yes + dialog_box.initialize_dialog() + + +func _on_scene_ready(): + dialog_box.enter_frame() + +func _on_dialog_init(d): + pass + +func _on_dialog_completed(d): + dialog_box.exit_frame() \ No newline at end of file diff --git a/dialog_vm.gd b/dialog_vm.gd new file mode 100644 index 0000000..de81675 --- /dev/null +++ b/dialog_vm.gd @@ -0,0 +1,570 @@ +extends Reference + +static func dialog_from_text(text): + + var label_regex = RegEx.new() + label_regex.compile("[A-Z0-9_]+:") + + # validation + var has_question = false + var lines = text.split("\n") + + var new_dialog = Dialog.new() + + # intermediate state + var current_choice_segment + var choices = 0 + + if current_choice_segment == null: + current_choice_segment = ChoiceSegment.new() + + var line_number = 0 + for line in lines: + + line_number += 1 + + # comment lines + if line.begins_with("#"): + pass + + # is flag unset + elif line.begins_with("!@"): + var l = line.right(3).strip_edges() + var flag = l.left(l.length() - 1) + # if OS.is_debug_build(): print("flag to false: ", flag) + new_dialog.add_segment(FlagSetSegment.new(flag, false)) + + # is goto, or conditional goto + elif line.begins_with("!"): + + var label_end = line.find(",") + + # unconditional goto + if label_end == -1: + var label = line.substr(2, line.length() - 3).strip_edges() + + # if OS.is_debug_build(): print("goto: ", label) + new_dialog.add_segment(GotoSegment.new(label)) + else: # conditional goto + var label = line.substr(2, label_end - 2).strip_edges() + var target_end = line.find("]") + var target = line.substr(label_end + 1, (target_end - 1) - label_end).strip_edges() + + # if OS.is_debug_build(): print("goto: ", target, " - ", label) + new_dialog.add_segment(ConditionalGotoSegment.new(target, label, true)) + + # is either random goto or question + elif line.begins_with("?"): + + var str_line = line.strip_edges().right(1) + + # question, initiates dialog, treat first encountered question as question + if str_line.begins_with("<"): + + var q_line = str_line.strip_edges() + + has_question = true + var subject_right_bracket_index = q_line.find(">") + var subject = q_line.substr(1, subject_right_bracket_index - 1).strip_edges() + var question_text = q_line.right(subject_right_bracket_index + 1).strip_edges() + + var question_segment = QuestionSegment.new(question_text, subject) + new_dialog.add_segment(question_segment) + + else: # random goto: picks one of the choices to goto, used like ?[P1, P2, P3] + + var left_bracket = str_line.find("[") + var right_bracket = str_line.find("]") + + if left_bracket == -1: + printerr("couldn't find left [ on line: %d" % line_number) + assert(false) + + if right_bracket == -1: + printerr("couldn't find right ] on line: %d" % line_number) + assert(false) + + # if there's actually even a single thing inside + if right_bracket - left_bracket > 1: + + var arg_string = str_line.substr(left_bracket + 1, (right_bracket - left_bracket) - 1) + var args = arg_string.split(",", false) + + var stripped_args = [] + for arg in args: + stripped_args.push_back(arg.strip_edges()) + + # if OS.is_debug_build(): print("random goto, arg_string: %s" % arg_string) + + new_dialog.add_segment(RandomGotoSegment.new(stripped_args)) + + # is flag set, either a normal set to true with @[SOME_FLAG] or a set to string with @[FLAG, STR] + elif line.begins_with("@"): + var l = line.right(2) + var comma_index = l.find(",") + if comma_index == -1: + var flag = l.left(l.length() - 1) + # if OS.is_debug_build(): print("flag to true: ", flag) + new_dialog.add_segment(FlagSetSegment.new(flag, true)) + else: + var flag = l.left(comma_index).strip_edges() + var arg_end = l.find("]") + var arg_int = l.right(comma_index + 1) + var arg = arg_int.left(arg_int.length() - 1).strip_edges() + # if OS.is_debug_build(): print("flag: ", flag, " with arg: ", arg) + new_dialog.add_segment(FlagSetSegment.new(flag, arg)) + + # is label, can goto + elif line.ends_with(":") and label_regex.search(line): + var label = line.left(line.length() - 1).strip_edges() + + # if OS.is_debug_build(): print("label: ", label) + new_dialog.add_segment(LabelSegment.new(label)) + + # is choice in conversation by player + elif line.begins_with("["): + + var right_end_index = line.find("]") + var choice = line.substr(1, right_end_index - 1).strip_edges() + var response = line.right(right_end_index + 1).strip_edges() + + # is it a jump choice? + var left_brace_index = response.find("[") + var right_brace_index = response.find("]") + if left_brace_index != -1 and right_brace_index != -1: + var jump_target = response.substr(left_brace_index + 1, right_brace_index - 1) + # if OS.is_debug_build(): print("choice: ", choice, " target: ", jump_target) + current_choice_segment.add_choice_with_jump(choice, jump_target) + else: + + var subject_right_comma_index = response.find(",") + var subject_right_bracket_index = response.find(">") + + # is response with + if response.begins_with("<") and subject_right_bracket_index > 0 and subject_right_comma_index < subject_right_bracket_index: + var subject = response.substr(1, subject_right_comma_index - 1).strip_edges() + var expression = response.substr(subject_right_comma_index + 1, (subject_right_bracket_index - 1) - subject_right_comma_index).strip_edges() + var line_text = response.right(subject_right_bracket_index + 1).strip_edges() + # if OS.is_debug_build(): print("choice: ", choice, " response: ", line_text, " with: <", subject, ", ", expression, ">") + current_choice_segment.add_choice(choice, DialogWithSubjectExpressionSegment.new(line_text, subject, expression)) + elif response.begins_with("<"): # is response with + var subject = response.substr(1, subject_right_bracket_index - 1).strip_edges() + var line_text = response.right(subject_right_bracket_index + 1).strip_edges() + # if OS.is_debug_build(): print("choice: ", choice, " response: ", line_text, " with: <", subject, ">") + current_choice_segment.add_choice(choice, DialogWithSubjectSegment.new(line_text, subject)) + else: + # if OS.is_debug_build(): print("choice: ", choice, " response: ", response) + current_choice_segment.add_choice(choice, response) + + choices += 1 + + # we're full + if choices == 3: + # if OS.is_debug_build(): print("choice segment created.") + new_dialog.add_segment(current_choice_segment) + + # setup new one for next round + current_choice_segment = ChoiceSegment.new() + choices = 0 + + elif line.begins_with("<"): + + # test for right comma and > + var subject_right_comma_index = line.find(",") + var subject_right_bracket_index = line.find(">") + + # is dialog + if subject_right_comma_index > 0 and subject_right_comma_index < subject_right_bracket_index: + var subject = line.substr(1, subject_right_comma_index - 1).strip_edges() + var expression = line.substr(subject_right_comma_index + 1, (subject_right_bracket_index - 1) - subject_right_comma_index).strip_edges() + var line_text = line.right(subject_right_bracket_index + 1).strip_edges() + var dialog_subject_segment = DialogWithSubjectExpressionSegment.new(line_text, subject, expression) + new_dialog.add_segment(dialog_subject_segment) + + else: # is dialog + var subject = line.substr(1, subject_right_bracket_index - 1).strip_edges() + var line_text = line.right(subject_right_bracket_index + 1).strip_edges() + var dialog_subject_segment = DialogWithSubjectSegment.new(line_text, subject) + new_dialog.add_segment(dialog_subject_segment) + + # simple function calls, can look like: > FUNC, but also like > FUNC [A, B, C, D] + # yieldable function calls can look like: >> FUNC, but also like >> FUNC [A, B, C, D] + elif line.begins_with(">"): + + var str_line = line.right(1).strip_edges() + var is_yielding_function = false + + if str_line.begins_with(">"): + str_line = str_line.right(1).strip_edges() + is_yielding_function = true + + var left_bracket = str_line.find("[") + var right_bracket = str_line.find("]") + + # has left bracket, should have right bracket, has params + if (left_bracket != -1 and right_bracket != -1) or \ + (left_bracket != -1 and right_bracket == -1) or \ + (left_bracket == -1 and right_bracket != -1): + + var command_end = str_line.find("[") + var command = str_line.left(command_end).strip_edges() + # if OS.is_debug_build(): print("parsing command with args: ", command) + + if left_bracket == -1: + printerr("couldn't find left [ on line: %d" % line_number) + assert(false) + + if right_bracket == -1: + printerr("couldn't find right ] on line: %d" % line_number) + assert(false) + + var args_str = str_line.substr(left_bracket + 1, (right_bracket - left_bracket) - 1).strip_edges() + var args = args_str.split(",", false) + + var stripped_args = [] + for a in args: + stripped_args.push_back(a.strip_edges()) + + if is_yielding_function: + var call_segment = YieldCallSegment.new(command, stripped_args) + new_dialog.add_segment(call_segment) + else: + var call_segment = CallSegment.new(command, stripped_args) + new_dialog.add_segment(call_segment) + + else: # no params, argless call + + var command = str_line.strip_edges() + # if OS.is_debug_build(): print("parsing command: ", command) + + if is_yielding_function: + var call_segment = YieldCallSegment.new(command) + new_dialog.add_segment(call_segment) + else: + var call_segment = CallSegment.new(command) + new_dialog.add_segment(call_segment) + + + else: # is normal text + var line_text = line.strip_edges() + + # if line is not empty, process + if not line_text.empty(): + var dialog_segment = DialogSegment.new(line_text) + new_dialog.add_segment(dialog_segment) + + return new_dialog + +static func parse_dialog_line(text): + pass + +# types of segments of text, held in Dialogs array + +class QuestionSegment: + + var subject + var question + + func _init(q, s): + question = q + subject = s + + func get_content(): + return question + +class DialogSegment: + + var text + + func _init(t): + text = t + + func get_content(): + return text + +class DialogWithSubjectSegment: + + var text + var subject + + func _init(t, s): + text = t + subject = s + + func get_content(): + return text + +class DialogWithSubjectExpressionSegment: + + var text + var subject + var expression + + func _init(t, s, e): + text = t + subject = s + expression = e + + func get_content(): + return text + +class Choice: + + var choice + var response + + func _init(c, r): + choice = c + response = r + + func get_choice(): + return choice + + func get_response(): + return response + +class ChoiceWithJump: + + var choice + var target + + func _init(c, t): + choice = c + target = t + + func get_choice(): + return choice + + func get_target(): + return target + +class ChoiceSegment: + + var choices + + func _init(): + choices = [] + + func add_choice(choice, response): + var new_choice = Choice.new(choice, response) + choices.push_back(new_choice) + + func add_choice_with_jump(choice, target): + var new_choice = ChoiceWithJump.new(choice, target) + choices.push_back(new_choice) + + func get_choices(): + return choices + +class LabelSegment: + + var label + + func _init(l): + label = l + +class GotoSegment: + + var target + + func _init(t): + target = t + +class RandomGotoSegment: + + var targets + + func _init(t): + targets = t + +class ConditionalGotoSegment extends GotoSegment: + + var variable + var expected_value + + func _init(t, v, e).(t): + variable = v + expected_value = e + + func test(registry): + if variable in registry: + var v = registry[variable] + return v + else: + return false + +class CallSegment: + + var fname + var args + + func _init(f, a = null): + fname = f + args = a + + func call(l, r): + if l.has(fname): + if args != null: return l[fname].call_func(args) + else: return l[fname].call_func([]) + elif r.has(fname): + if args != null: return r[fname].call_func(args) + else: return r[fname].call_func([]) + +class YieldCallSegment extends CallSegment: + + var started + var finished + + func _init(f, a = null).(f, a): + started = false + + func call(l, r): + started = true + finished = false + return .call(l, r) + + func get_content(): + return "..." + +class FlagSetSegment: + + var flag + var value + + func _init(f, v): + flag = f + value = v + + func set(registry): + registry[flag] = value + +class Dialog: + + var segment_index = -1 + var segments + + # goto label dictionary + var labels + + # local registry + var locals + + # dependency, registry of stuff + var registry + + # yield state + var is_yielding + + func _init(): + is_yielding = false + segments = [] + labels = {} + locals = {} + + func get_local_registry(): + return locals + + func set_registry(r): + registry = r + + func add_segment(s): + + if s is GotoSegment: + if not s.target in labels: + labels[s.target] = -1 + elif s is RandomGotoSegment: + for t in s.targets: + if not t in labels: + labels[t] = -1 + elif s is LabelSegment: + var sz = segments.size() + if not labels.has(s.label) or labels[s.label] == -1: + # if OS.is_debug_build(): print("added label: ", s.label, " to: ", sz) + labels[s.label] = sz + else: + printerr("ERROR: label with name: %s already registered at %d!" % [s.label, labels[s.label]]) + assert(labels[s.label] != -1) + + segments.push_back(s) + + func push_instruction(): + if is_eof(): return null + var new_segment = segments[segment_index] + while (new_segment is GotoSegment \ + or new_segment is RandomGotoSegment \ + or new_segment is LabelSegment \ + or new_segment is CallSegment \ + or new_segment is FlagSetSegment) and not is_yielding and not is_eof(): + new_segment = segments[segment_index] + if new_segment is ConditionalGotoSegment: + if new_segment.test(registry): # if true, jump to offset + var offset = labels[new_segment.target] + if offset == -1: + printerr("could not find label: %s" % new_segment.target) + assert(offset != -1) + # if OS.is_debug_build(): print("conditionally jumped to offset: ", offset) + segment_index = offset + else: # else skip + segment_index += 1 + elif new_segment is RandomGotoSegment: + var rand_idx = floor(rand_range(0, new_segment.targets.size())) + var rand_target = new_segment.targets[rand_idx] + var offset = labels[rand_target] + if offset == -1: + printerr("could not find label: %s" % rand_target) + assert(offset != -1) + # if OS.is_debug_build(): print("random jumped to: ", offset, " with: ", rand_target) + segment_index = offset + elif new_segment is GotoSegment: + var offset = labels[new_segment.target] + if offset == -1: + printerr("could not find label: %s" % new_segment.target) + assert(offset != -1) + # if OS.is_debug_build(): print("jumped to: ", offset, " with: ", new_segment.target, " at: ", segment_index) + segment_index = offset + elif new_segment is YieldCallSegment: + if not new_segment.started: + var state = new_segment.call(locals, registry) + if state != null: + var obj = state[0] + var sgn = state[1] + is_yielding = true + _do_yield_wait(obj, sgn, new_segment) + else: + segment_index += 1 + elif new_segment is CallSegment: + new_segment.call(locals, registry) + segment_index += 1 + elif new_segment is FlagSetSegment: + new_segment.set(registry) + segment_index += 1 + elif new_segment is LabelSegment: + segment_index += 1 + if not is_eof(): + return segments[segment_index] + else: + return null + + func advance(): + if not is_yielding: + segment_index += 1 + return push_instruction() + else: + return push_instruction() + + # external jump, is fudge + func jump(target): + segment_index = labels[target] + return push_instruction() + + func is_eof(): + return segment_index >= segments.size() + + func reset(): + segment_index = -1 + + func _do_yield_wait(obj, sgn, segment): + yield(obj, sgn) + segment.finished = true + is_yielding = false + printt("Dialog VM: %s %s %s" % [obj, sgn, "completed"]) diff --git a/game.gd b/game.gd new file mode 100644 index 0000000..d6a82b0 --- /dev/null +++ b/game.gd @@ -0,0 +1,59 @@ +extends Node + +# globals, for the game +var reading_speed = 1.0 + +var game_started = false setget _on_game_started_set + +var current_registry + +# music related +onready var music_player = get_node("music_player") +onready var sfx_player = get_node("sfx_player") + +signal on_music_volume_changed(new_value) +signal on_sfx_volume_changed(new_value) +signal on_reading_speed_changed(new_value) + +signal on_settings_enter() +signal on_settings_exit() + +const Themes = {} + +# voices for characters +const brain_voice = null +const arborator_voice = null + +const DEFAULT_CHARS_PER_SECOND = 32 + +const Characters = { + BRAIN = { + name = "~brain~", + name_colour = Color(1, 1, 1, 1), + voice = brain_voice, + talking_speed = 1.0, + }, + ARBORATOR = { + name = "arborator", + name_colour = Color(1, 1, 1, 1), + voice = arborator_voice, + talking_speed = 1.0, + } +} + +func _register_functions(): + pass + +func _on_game_started_set(v): + if v and not game_started: + _on_game_start() + game_started = v + +func _on_game_start(): + + # all game state here + current_registry = {} + + +func _ready(): + pass \ No newline at end of file diff --git a/game.tscn b/game.tscn new file mode 100644 index 0000000..a50389f --- /dev/null +++ b/game.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://game.gd" type="Script" id=1] + +[node name="Game" type="Node"] + +script = ExtResource( 1 ) + +[node name="music_player" type="AudioStreamPlayer" parent="." index="0"] + +stream = null +volume_db = 0.0 +pitch_scale = 1.0 +autoplay = false +mix_target = 0 +bus = "Music" + +[node name="sfx_player" type="AudioStreamPlayer" parent="." index="1"] + +stream = null +volume_db = 0.0 +pitch_scale = 1.0 +autoplay = false +mix_target = 0 +bus = "Sfx" + + diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..4606f88 --- /dev/null +++ b/icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5fad4ab419bc3152d018bc6d58d046d7ab92a7311449066824b3201c91935e7 +size 3498 diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 0000000..0041ef8 --- /dev/null +++ b/icon.png.import @@ -0,0 +1,29 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/menus/main_menu.gd b/menus/main_menu.gd new file mode 100644 index 0000000..983abaf --- /dev/null +++ b/menus/main_menu.gd @@ -0,0 +1,26 @@ +extends Control + +onready var start_btn = get_node("split/buttons/start_btn") +onready var load_btn = get_node("split/buttons/load_btn") +onready var options_btn = get_node("split/buttons/options_btn") +onready var quit_btn = get_node("split/buttons/quit_btn") + +func _ready(): + + start_btn.connect("pressed", self, "_on_start_pressed") + load_btn.connect("pressed", self, "_on_load_pressed") + options_btn.connect("pressed", self, "_on_options_pressed") + quit_btn.connect("pressed", self, "_on_quit_pressed") + + +func _on_start_pressed(): + pass + +func _on_load_pressed(): + pass + +func _on_options_pressed(): + pass + +func _on_quit_pressed(): + get_tree().quit() \ No newline at end of file diff --git a/menus/main_menu.tscn b/menus/main_menu.tscn new file mode 100644 index 0000000..e8feaf1 --- /dev/null +++ b/menus/main_menu.tscn @@ -0,0 +1,182 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://menus/main_menu.gd" type="Script" id=1] + +[node name="main_menu" type="Control"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +script = ExtResource( 1 ) + +[node name="split" type="VSplitContainer" parent="." index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +split_offset = 0 +collapsed = false +dragger_visibility = 0 + +[node name="title" type="Label" parent="split" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 140.0 +margin_right = 1024.0 +margin_bottom = 154.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 6 +text = "godot-vn" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 +_sections_unfolded = [ "Size Flags" ] + +[node name="buttons" type="VBoxContainer" parent="split" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 306.0 +margin_right = 1024.0 +margin_bottom = 600.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 1 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 3 +alignment = 0 +_sections_unfolded = [ "Size Flags" ] + +[node name="start_btn" type="Button" parent="split/buttons" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 1024.0 +margin_bottom = 20.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 0, 0, 0, 1 ) +custom_colors/font_color_pressed = Color( 0, 0, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "start game" +flat = true +align = 1 + +[node name="load_btn" type="Button" parent="split/buttons" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 24.0 +margin_right = 1024.0 +margin_bottom = 44.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 0, 0, 0, 1 ) +custom_colors/font_color_pressed = Color( 0, 0, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "load game" +flat = true +align = 1 + +[node name="options_btn" type="Button" parent="split/buttons" index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 48.0 +margin_right = 1024.0 +margin_bottom = 68.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 0, 0, 0, 1 ) +custom_colors/font_color_pressed = Color( 0, 0, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "options" +flat = true +align = 1 + +[node name="quit_btn" type="Button" parent="split/buttons" index="3"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 72.0 +margin_right = 1024.0 +margin_bottom = 92.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 0, 0, 0, 1 ) +custom_colors/font_color_pressed = Color( 0, 0, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "quit" +flat = true +align = 1 + + diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..130b147 --- /dev/null +++ b/project.godot @@ -0,0 +1,33 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=3 + +[application] + +config/name="godot-vn" +run/main_scene="res://scene.tscn" +config/icon="res://icon.png" + +[autoload] + +SceneSwitcher="*res://scene_switcher.gd" +Game="*res://game.tscn" + +[display] + +window/size/resizable=false + +[input] + +dialog_click=[ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) + ] + +[rendering] + +environment/default_environment="res://default_env.tres" diff --git a/scene.gd b/scene.gd new file mode 100644 index 0000000..989a62d --- /dev/null +++ b/scene.gd @@ -0,0 +1,7 @@ +extends Node2D + +signal on_scene_ready + +func _ready(): + Game.game_started = true + emit_signal("on_scene_ready") \ No newline at end of file diff --git a/scene.tscn b/scene.tscn new file mode 100644 index 0000000..e549d5c --- /dev/null +++ b/scene.tscn @@ -0,0 +1,307 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://scene.gd" type="Script" id=1] +[ext_resource path="res://dialog_layer.gd" type="Script" id=2] +[ext_resource path="res://dialog.gd" type="Script" id=3] + +[sub_resource type="StyleBoxEmpty" id=1] + +content_margin_left = -1.0 +content_margin_right = -1.0 +content_margin_top = -1.0 +content_margin_bottom = -1.0 + +[node name="scene" type="Node2D" index="0"] + +script = ExtResource( 1 ) + +[node name="background" type="Sprite" parent="." index="0"] + +[node name="dialog_layer" type="CanvasLayer" parent="." index="1"] + +layer = 1 +offset = Vector2( 0, 0 ) +rotation = 0.0 +scale = Vector2( 1, 1 ) +transform = Transform2D( 1, 0, 0, 1, 0, 0 ) +script = ExtResource( 2 ) +dialog_path = "res://dialog/introduction.txt" + +[node name="dialog_box" type="Control" parent="dialog_layer" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +script = ExtResource( 3 ) + +[node name="audio_player" type="AudioStreamPlayer" parent="dialog_layer/dialog_box" index="0"] + +stream = null +volume_db = 0.0 +pitch_scale = 1.0 +autoplay = false +mix_target = 0 +bus = "Master" + +[node name="top_panel" type="HBoxContainer" parent="dialog_layer/dialog_box" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 0.0 +margin_top = 16.0 +margin_bottom = 20.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 1 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +alignment = 2 +_sections_unfolded = [ "Margin", "Size Flags", "Theme", "custom_constants" ] + +[node name="auto_btn" type="Button" parent="dialog_layer/dialog_box/top_panel" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 847.0 +margin_right = 887.0 +margin_bottom = 20.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color_disabled = Color( 0.378906, 0.378906, 0.378906, 1 ) +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 1, 0.647059, 0, 1 ) +custom_colors/font_color_pressed = Color( 1, 0.647059, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "auto" +flat = true +align = 1 +_sections_unfolded = [ "custom_constants" ] + +[node name="ffwd_btn" type="Button" parent="dialog_layer/dialog_box/top_panel" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 891.0 +margin_right = 926.0 +margin_bottom = 20.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color_disabled = Color( 0.378906, 0.378906, 0.378906, 1 ) +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 1, 0.647059, 0, 1 ) +custom_colors/font_color_pressed = Color( 1, 0.647059, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "fast" +flat = true +align = 1 + +[node name="menu_btn" type="Button" parent="dialog_layer/dialog_box/top_panel" index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 930.0 +margin_right = 1002.0 +margin_bottom = 20.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_colors/font_color_disabled = Color( 0.378906, 0.378906, 0.378906, 1 ) +custom_colors/font_color = Color( 1, 1, 1, 1 ) +custom_colors/font_color_hover = Color( 1, 0.647059, 0, 1 ) +custom_colors/font_color_pressed = Color( 1, 0.647059, 0, 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = " | menu" +flat = true +align = 1 + +[node name="spacing" type="Control" parent="dialog_layer/dialog_box/top_panel" index="3"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 1006.0 +margin_right = 1024.0 +margin_bottom = 20.0 +rect_min_size = Vector2( 18, 0 ) +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +_sections_unfolded = [ "Rect" ] + +[node name="body_container" type="PanelContainer" parent="dialog_layer/dialog_box" index="2"] + +anchor_left = 0.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_top = -100.0 +grow_vertical = 0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_styles/panel = SubResource( 1 ) +_sections_unfolded = [ "Grow Direction", "Mouse", "Size Flags", "custom_styles" ] + +[node name="bottom_panel" type="VBoxContainer" parent="dialog_layer/dialog_box/body_container" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 1024.0 +margin_bottom = 100.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +alignment = 0 +_sections_unfolded = [ "Grow Direction", "Mouse" ] + +[node name="subject_margin" type="MarginContainer" parent="dialog_layer/dialog_box/body_container/bottom_panel" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 1024.0 +margin_bottom = 14.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_constants/margin_right = 0 +custom_constants/margin_top = 0 +custom_constants/margin_left = 4 +custom_constants/margin_bottom = 0 +_sections_unfolded = [ "Mouse", "custom_constants" ] + +[node name="subject_panel" type="HBoxContainer" parent="dialog_layer/dialog_box/body_container/bottom_panel/subject_margin" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 4.0 +margin_right = 1024.0 +margin_bottom = 14.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +alignment = 0 +_sections_unfolded = [ "Mouse" ] + +[node name="subject_label" type="Label" parent="dialog_layer/dialog_box/body_container/bottom_panel/subject_margin/subject_panel" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 60.0 +margin_bottom = 14.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 4 +text = "Arborator" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 +_sections_unfolded = [ "Margin" ] + +[node name="body_margin" type="MarginContainer" parent="dialog_layer/dialog_box/body_container/bottom_panel" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 18.0 +margin_right = 1024.0 +margin_bottom = 48.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +custom_constants/margin_top = 8 +custom_constants/margin_left = 8 +custom_constants/margin_bottom = 8 +_sections_unfolded = [ "Margin", "Mouse", "custom_constants" ] + +[node name="body_label" type="Label" parent="dialog_layer/dialog_box/body_container/bottom_panel/body_margin" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 8.0 +margin_top = 8.0 +margin_right = 1024.0 +margin_bottom = 22.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 3 +size_flags_vertical = 4 +text = "Sup new kid, you skippin?" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 +_sections_unfolded = [ "Margin", "Mouse", "Rect", "Size Flags" ] + + diff --git a/scene_switcher.gd b/scene_switcher.gd new file mode 100644 index 0000000..cf32cad --- /dev/null +++ b/scene_switcher.gd @@ -0,0 +1,38 @@ +extends Node + +var current_scene + +func _ready(): + var root = get_tree().get_root() + current_scene = root.get_child(root.get_child_count() - 1) + +func goto_scene(path, from_end = false): + call_deferred("_deferred_goto_scene", path, from_end) + +func _deferred_goto_scene(path, from_end): + + # Store old name so we can pass it to next one + var current_scene_name = current_scene.get_filename() + + var old_scene = current_scene + current_scene.get_parent().remove_child(current_scene) + current_scene.queue_free() + + # Load new scene + var s = ResourceLoader.load(path) + + # Instance the new scene + current_scene = s.instance() + + if "from_scene_name" in current_scene: + if current_scene_name != current_scene.get_filename(): + current_scene.from_scene_name = current_scene_name + else: + if "from_scene_name" in old_scene: + current_scene.from_scene_name = old_scene.from_scene_name + + # Add it to the active scene, as child of root + get_tree().get_root().add_child(current_scene) + + # optional, to make it compatible with the SceneTree.change_scene() API + get_tree().set_current_scene(current_scene) diff --git a/ui/dialog_choice.tscn b/ui/dialog_choice.tscn new file mode 100644 index 0000000..09ae67a --- /dev/null +++ b/ui/dialog_choice.tscn @@ -0,0 +1,25 @@ +[gd_scene format=2] + +[node name="dialog_choice" type="Button"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 40.0 +margin_bottom = 40.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +focus_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +flat = false +align = 1 + + diff --git a/ui/dialog_choices.tscn b/ui/dialog_choices.tscn new file mode 100644 index 0000000..d2ac69f --- /dev/null +++ b/ui/dialog_choices.tscn @@ -0,0 +1,18 @@ +[gd_scene format=2] + +[node name="dialog_choices" type="Control"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 40.0 +margin_bottom = 40.0 +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 0 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 1 +size_flags_vertical = 1 + +